Destructuring parameters are evaluated in the wrong scope
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Mar 2016 08:59:01 +0000 (08:59 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Mar 2016 08:59:01 +0000 (08:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155454

Reviewed by Geoffrey Garen.

This patch makes our engine compatible with how parameter
lists are evaluated in ES6. A parameter list that contains
a rest parameter, any destructuring patterns, or default parameter values,
is classified as being non-simple. Non-simple parameter lists
must get their own scope to live in, and the variables in the
scope are under TDZ. This means that functions evaluated in the
parameter list don't have access to variables inside the function
body. Also, non-simple parameter lists get the strict-mode arguments object.

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
(JSC::BytecodeGenerator::~BytecodeGenerator):
(JSC::BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeStack):
(JSC::BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded):
* bytecompiler/BytecodeGenerator.h:
* parser/Nodes.h:
(JSC::FunctionParameters::size):
(JSC::FunctionParameters::at):
(JSC::FunctionParameters::append):
(JSC::FunctionParameters::hasDefaultParameterValues): Deleted.
* tests/es6.yaml:
* tests/stress/parameter-scoping.js: Added.
(assert):
(test):
(test.foo):
(test.):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@198206 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/tests/es6.yaml
Source/JavaScriptCore/tests/stress/parameter-scoping.js [new file with mode: 0644]
Source/JavaScriptCore/tests/stress/v8-parameter-scoping.js [new file with mode: 0644]

index 1b48cae..5bd9ffe 100644 (file)
@@ -1,3 +1,37 @@
+2016-03-15  Saam barati  <sbarati@apple.com>
+
+        Destructuring parameters are evaluated in the wrong scope
+        https://bugs.webkit.org/show_bug.cgi?id=155454
+
+        Reviewed by Geoffrey Garen.
+
+        This patch makes our engine compatible with how parameter
+        lists are evaluated in ES6. A parameter list that contains
+        a rest parameter, any destructuring patterns, or default parameter values, 
+        is classified as being non-simple. Non-simple parameter lists
+        must get their own scope to live in, and the variables in the
+        scope are under TDZ. This means that functions evaluated in the
+        parameter list don't have access to variables inside the function
+        body. Also, non-simple parameter lists get the strict-mode arguments object.
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::BytecodeGenerator):
+        (JSC::BytecodeGenerator::~BytecodeGenerator):
+        (JSC::BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeStack):
+        (JSC::BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded):
+        * bytecompiler/BytecodeGenerator.h:
+        * parser/Nodes.h:
+        (JSC::FunctionParameters::size):
+        (JSC::FunctionParameters::at):
+        (JSC::FunctionParameters::append):
+        (JSC::FunctionParameters::hasDefaultParameterValues): Deleted.
+        * tests/es6.yaml:
+        * tests/stress/parameter-scoping.js: Added.
+        (assert):
+        (test):
+        (test.foo):
+        (test.):
+
 2016-03-14  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Don't reference the properties of @Reflect directly
index 64c7fa9..0867bac 100644 (file)
@@ -225,16 +225,20 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
     functionSymbolTable->setUsesNonStrictEval(m_usesNonStrictEval);
     int symbolTableConstantIndex = addConstantValue(functionSymbolTable)->index();
 
-    Vector<Identifier> boundParameterProperties;
     FunctionParameters& parameters = *functionNode->parameters(); 
-    if (!parameters.hasDefaultParameterValues()) { 
-        // If we do have default parameters, they will be allocated in a separate scope.
-        for (size_t i = 0; i < parameters.size(); i++) {
-            auto pattern = parameters.at(i).first;
-            if (pattern->isBindingNode())
-                continue;
-            pattern->collectBoundIdentifiers(boundParameterProperties);
-        }
+    // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-functiondeclarationinstantiation
+    // This implements IsSimpleParameterList in the Ecma 2015 spec.
+    // If IsSimpleParameterList is false, we will create a strict-mode like arguments object.
+    // IsSimpleParameterList is false if the argument list contains any default parameter values,
+    // a rest parameter, or any destructuring patterns.
+    bool isSimpleParameterList = true;
+    // If we do have default parameters, destructuring parameters, or a rest parameter, our parameters will be allocated in a different scope.
+    for (size_t i = 0; i < parameters.size(); i++) {
+        std::pair<DestructuringPatternNode*, ExpressionNode*> parameter = parameters.at(i);
+        bool hasDefaultParameterValue = !!parameter.second;
+        auto pattern = parameter.first;
+        bool isSimpleParameter = !hasDefaultParameterValue && pattern->isBindingNode();
+        isSimpleParameterList &= isSimpleParameter;
     }
 
     SourceParseMode parseMode = codeBlock->parseMode();
@@ -293,6 +297,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
     m_calleeRegister.setIndex(JSStack::Callee);
 
     initializeParameters(parameters);
+    ASSERT(!(isSimpleParameterList && m_restParameter));
 
     // Before emitting a scope creation, emit a generator prologue that contains jump based on a generator's state.
     if (parseMode == SourceParseMode::GeneratorBodyMode) {
@@ -321,7 +326,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
         // We can allocate the "var" environment if we don't have default parameter expressions. If we have
         // default parameter expressions, we have to hold off on allocating the "var" environment because
         // the parent scope of the "var" environment is the parameter environment.
-        if (!parameters.hasDefaultParameterValues())
+        if (isSimpleParameterList)
             initializeVarLexicalEnvironment(symbolTableConstantIndex);
     }
 
@@ -357,13 +362,6 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
         m_argumentsRegister->ref();
     }
     
-    // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-functiondeclarationinstantiation
-    // This implements IsSimpleParameterList in the Ecma 2015 spec.
-    // If IsSimpleParameterList is false, we will create a strict-mode like arguments object.
-    // IsSimpleParameterList is false if the argument list contains any default parameter values,
-    // a rest parameter, or any destructuring patterns.
-    // FIXME: Take into account destructuring to make isSimpleParameterList false. https://bugs.webkit.org/show_bug.cgi?id=151450
-    bool isSimpleParameterList = !parameters.hasDefaultParameterValues() && !m_restParameter;
     if (needsArguments && !codeBlock->isStrictMode() && isSimpleParameterList) {
         // If we captured any formal parameter by name, then we use ScopedArguments. Otherwise we
         // use DirectArguments. With ScopedArguments, we lift all of our arguments into the
@@ -414,7 +412,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
             emitOpcode(op_create_direct_arguments);
             instructions().append(m_argumentsRegister->index());
         }
-    } else if (!parameters.hasDefaultParameterValues()) {
+    } else if (isSimpleParameterList) {
         // Create the formal parameters the normal way. Any of them could be captured, or not. If
         // captured, lift them into the scope. We can not do this if we have default parameter expressions
         // because when default parameter expressions exist, they belong in their own lexical environment
@@ -452,11 +450,6 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
         instructions().append(m_argumentsRegister->index());
     }
     
-    // Now declare all variables.
-    for (const Identifier& ident : boundParameterProperties) {
-        ASSERT(!parameters.hasDefaultParameterValues());
-        createVariable(ident, varKind(ident.impl()), functionSymbolTable);
-    }
     for (FunctionMetadataNode* function : functionNode->functionStack()) {
         const Identifier& ident = function->ident();
         createVariable(ident, varKind(ident.impl()), functionSymbolTable);
@@ -567,7 +560,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
     // All "addVar()"s needs to happen before "initializeDefaultParameterValuesAndSetupFunctionScopeStack()" is called
     // because a function's default parameter ExpressionNodes will use temporary registers.
     pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize);
-    initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, functionNode, functionSymbolTable, symbolTableConstantIndex, captures);
+    initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures);
     
     // Loading |this| inside an arrow function must be done after initializeDefaultParameterValuesAndSetupFunctionScopeStack()
     // because that function sets up the SymbolTable stack and emitLoadThisFromArrowFunctionLexicalEnvironment()
@@ -793,11 +786,11 @@ BytecodeGenerator::~BytecodeGenerator()
 }
 
 void BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeStack(
-    FunctionParameters& parameters, FunctionNode* functionNode, SymbolTable* functionSymbolTable, 
+    FunctionParameters& parameters, bool isSimpleParameterList, FunctionNode* functionNode, SymbolTable* functionSymbolTable, 
     int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures)
 {
     Vector<std::pair<Identifier, RefPtr<RegisterID>>> valuesToMoveIntoVars;
-    if (parameters.hasDefaultParameterValues()) {
+    if (!isSimpleParameterList) {
         // Refer to the ES6 spec section 9.2.12: http://www.ecma-international.org/ecma-262/6.0/index.html#sec-functiondeclarationinstantiation
         // This implements step 21.
         VariableEnvironment environment;
@@ -866,24 +859,11 @@ void BytecodeGenerator::initializeDefaultParameterValuesAndSetupFunctionScopeSta
 
     // This completes step 28 of section 9.2.12.
     for (unsigned i = 0; i < valuesToMoveIntoVars.size(); i++) {
-        ASSERT(parameters.hasDefaultParameterValues());
+        ASSERT(!isSimpleParameterList);
         Variable var = variable(valuesToMoveIntoVars[i].first);
         RegisterID* scope = emitResolveScope(nullptr, var);
         emitPutToScope(scope, var, valuesToMoveIntoVars[i].second.get(), DoNotThrowIfNotFound, NotInitialization);
     }
-
-    if (!parameters.hasDefaultParameterValues()) {
-        ASSERT(!valuesToMoveIntoVars.size());
-        // Initialize destructuring parameters the old way as if we don't have any default parameter values.
-        // If we have default parameter values, we handle this case above.
-        for (unsigned i = 0; i < parameters.size(); i++) {
-            DestructuringPatternNode* pattern = parameters.at(i).first;
-            if (!pattern->isBindingNode() && !pattern->isRestParameter()) {
-                RefPtr<RegisterID> parameterValue = &registerFor(virtualRegisterForArgument(1 + i));
-                pattern->bindValue(*this, parameterValue.get());
-            }
-        }
-    }
 }
 
 void BytecodeGenerator::initializeArrowFunctionContextScopeIfNeeded(SymbolTable* symbolTable)
index dbfd77c..3dc888e 100644 (file)
@@ -835,7 +835,7 @@ namespace JSC {
 
         void initializeParameters(FunctionParameters&);
         void initializeVarLexicalEnvironment(int symbolTableConstantIndex);
-        void initializeDefaultParameterValuesAndSetupFunctionScopeStack(FunctionParameters&, FunctionNode*, SymbolTable*, int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures);
+        void initializeDefaultParameterValuesAndSetupFunctionScopeStack(FunctionParameters&, bool isSimpleParameterList, FunctionNode*, SymbolTable*, int symbolTableConstantIndex, const std::function<bool (UniquedStringImpl*)>& captures);
         void initializeArrowFunctionContextScopeIfNeeded(SymbolTable* = nullptr);
 
     public:
index 07f672c..58f8334 100644 (file)
@@ -1818,19 +1818,15 @@ namespace JSC {
         FunctionParameters();
         ALWAYS_INLINE unsigned size() const { return m_patterns.size(); }
         ALWAYS_INLINE std::pair<DestructuringPatternNode*, ExpressionNode*> at(unsigned index) { return m_patterns[index]; }
-        bool hasDefaultParameterValues() const { return m_hasDefaultParameterValues; }
         ALWAYS_INLINE void append(DestructuringPatternNode* pattern, ExpressionNode* defaultValue) 
         { 
             ASSERT(pattern); 
             m_patterns.append(std::make_pair(pattern, defaultValue));
-            if (defaultValue)
-                m_hasDefaultParameterValues = true;
         }
 
     private:
 
         Vector<std::pair<DestructuringPatternNode*, ExpressionNode*>, 3> m_patterns;
-        bool m_hasDefaultParameterValues { false };
     };
 
     class FunctionMetadataNode final : public Node, public ParserArenaDeletable {
index e0a2f5b..e302416 100644 (file)
 - path: es6/destructuring_computed_properties.js
   cmd: runES6 :normal
 - path: es6/destructuring_defaults_in_parameters_separate_scope.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/destructuring_iterator_closing.js
   cmd: runES6 :fail
 - path: es6/destructuring_nested_rest.js
diff --git a/Source/JavaScriptCore/tests/stress/parameter-scoping.js b/Source/JavaScriptCore/tests/stress/parameter-scoping.js
new file mode 100644 (file)
index 0000000..0fb5deb
--- /dev/null
@@ -0,0 +1,236 @@
+function assert(b) {
+    if (!b)
+        throw new Error("bad");
+}
+
+function test(f) {
+    for (let i = 0; i < 1000; i++)
+        f();
+}
+
+test(function() {
+    function foo(a, b) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 2);
+        arguments[0] = "hello";
+        arguments[1] = "world";
+        assert(a === "hello");
+        assert(b === "world");
+    }
+    foo(null, null);
+});
+
+test(function() {
+    function foo(a, b) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 2);
+        a = "hello";
+        b = "world";
+        assert(arguments[0] === "hello");
+        assert(arguments[1] === "world");
+    }
+    foo(null, null);
+});
+
+test(function() {
+    function foo(a, b, ...rest) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 2);
+        arguments[0] = "hello";
+        arguments[1] = "world";
+        assert(a === null);
+        assert(b === null);
+    }
+    foo(null, null);
+});
+
+test(function() {
+    function foo(a, b, ...rest) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 2);
+        a = "hello";
+        b = "world";
+        assert(arguments[0] === null);
+        assert(arguments[1] === null);
+    }
+    foo(null, null);
+});
+
+test(function() {
+    function foo(a, b, {destructure}) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 3);
+        arguments[0] = "hello";
+        arguments[1] = "world";
+        assert(a === null);
+        assert(b === null);
+    }
+    foo(null, null, {});
+});
+
+test(function() {
+    function foo(a, b, {destructure}) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 3);
+        a = "hello";
+        b = "world";
+        assert(arguments[0] === null);
+        assert(arguments[1] === null);
+    }
+    foo(null, null, {});
+});
+
+test(function() {
+    function foo(a, b, defaultParam = 20) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 3);
+        arguments[0] = "hello";
+        arguments[1] = "world";
+        assert(a === null);
+        assert(b === null);
+    }
+    foo(null, null, {});
+});
+
+test(function() {
+    function foo(a, b, defaultParam = 20) {
+        assert(arguments[0] === a);
+        assert(arguments[1] === b);
+        assert(arguments.length === 3);
+        a = "hello";
+        b = "world";
+        assert(arguments[0] === null);
+        assert(arguments[1] === null);
+    }
+    foo(null, null, {});
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, {foo = b}) {
+        assert(foo === b);
+        assert(foo === obj);
+    }
+    foo(null, obj, {});
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, {foo = b}) {
+        assert(foo === b);
+        assert(foo === obj);
+        function capB() { return b; }
+    }
+    foo(null, obj, {});
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, {foo = b}) {
+        assert(foo === 25);
+    }
+    foo(null, obj, {foo: 25});
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, {foo = function() { return b; }}) {
+        assert(foo() === b);
+        assert(foo() === obj);
+        return foo;
+    }
+    let result = foo(null, obj, {});
+    assert(result() === obj);
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, [foo = function() { return b; }]) {
+        assert(foo() === b);
+        assert(foo() === obj);
+        return foo;
+    }
+    let result = foo(null, obj, [undefined]);
+    assert(result() === obj);
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, [foo = function() { return e; }], {d = foo()}, e) { }
+    foo(null, obj, [], {d:null}, 20);
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, [foo = function() { return e; }], {d = foo()}, e) { }
+    try {
+        foo(null, obj, [], {}, 20);
+    } catch(e) {
+        assert(e.toString() === "ReferenceError: Cannot access uninitialized variable.");
+    }
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, [foo = function() { return e; }], e, {d = foo()}) { 
+        return d;
+    }
+    assert(foo(null, obj, [], 20, {}) === 20);
+});
+
+test(function() {
+    let obj = {}
+    function foo(a, b, [foo = function() { return e; }], e, {d = foo()}) { 
+        var d;
+        assert(d === 20);
+        return d;
+    }
+    assert(foo(null, obj, [], 20, {}) === 20);
+});
+
+test(function() {
+    function foo(b, {a = function() { return b; }}) { 
+        var b = 25;
+        assert(b === 25);
+        assert(a() === 20);
+    }
+    foo(20, {});
+});
+
+test(function() {
+    function foo(b, {a = function() { return typeof inner; }}) { 
+        let inner = 25;
+        assert(inner === 25);
+        assert(a() === "undefined");
+    }
+    foo(20, {});
+});
+
+test(function() {
+    let obj = {};
+    let inner = obj;
+    function foo(b, {a = function() { return inner; }}) { 
+        let inner = 25;
+        assert(inner === 25);
+        assert(a() === obj);
+    }
+    foo(20, {});
+});
+
+test(function() {
+    let obj = {};
+    let inner = obj;
+    let foo = (b, {a = function() { return inner; }}) => {
+        let inner = 25;
+        assert(inner === 25);
+        assert(a() === obj);
+    }
+    foo(20, {});
+});
diff --git a/Source/JavaScriptCore/tests/stress/v8-parameter-scoping.js b/Source/JavaScriptCore/tests/stress/v8-parameter-scoping.js
new file mode 100644 (file)
index 0000000..46b0eb6
--- /dev/null
@@ -0,0 +1,103 @@
+//  Copyright 2014, the V8 project authors. All rights reserved.
+//  Redistribution and use in source and binary forms, with or without
+//  modification, are permitted provided that the following conditions are
+//  met:
+//  
+//      * Redistributions of source code must retain the above copyright
+//        notice, this list of conditions and the following disclaimer.
+//      * Redistributions in binary form must reproduce the above
+//        copyright notice, this list of conditions and the following
+//        disclaimer in the documentation and/or other materials provided
+//        with the distribution.
+//      * Neither the name of Google Inc. nor the names of its
+//        contributors may be used to endorse or promote products derived
+//        from this software without specific prior written permission.
+//  
+//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+//  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+//  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+//  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+//  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+function assert(b) {
+    if (!b)
+        throw new Error("bad");
+}
+
+function assertEquals(a, b) {
+    assert(a === b);
+}
+
+function assertTrue(a) {
+    assert(a === true);
+}
+
+;(function testExpressionTypes() {
+    "use strict";
+    ((x, y = x) => assertEquals(42, y))(42);
+
+    ((x, y = (x)) => assertEquals(42, y))(42);
+    ((x, y = `${x}`) => assertEquals("42", y))(42);
+    ((x, y = x = x + 1) => assertEquals(43, y))(42);
+    ((x, y = x()) => assertEquals(42, y))(() => 42);
+    ((x, y = new x()) => assertEquals(42, y.z))(function() { this.z = 42 });
+    ((x, y = -x) => assertEquals(-42, y))(42);
+    ((x, y = ++x) => assertEquals(43, y))(42);
+    ((x, y = x === 42) => assertTrue(y))(42);
+    ((x, y = (x == 42 ? x : 0)) => assertEquals(42, y))(42);
+
+    ((x, y = function() { return x }) => assertEquals(42, y()))(42);
+    ((x, y = () => x) => assertEquals(42, y()))(42);
+
+    // Literals
+    ((x, y = {z: x}) => assertEquals(42, y.z))(42);
+    ((x, y = {[x]: x}) => assertEquals(42, y[42]))(42);
+    ((x, y = [x]) => assertEquals(42, y[0]))(42);
+    ((x, y = [...x]) => assertEquals(42, y[0]))([42]);
+
+    ((x, y = class {
+        static [x]() { return x }
+    }) => assertEquals(42, y[42]()))(42);
+    ((x, y = (new class {
+        z() { return x }
+    })) => assertEquals(42, y.z()))(42);
+
+    ((x, y = (new class Y {
+        static [x]() { return x }
+        z() { return Y[42]() }
+    })) => assertEquals(42, y.z()))(42);
+
+    ((x, y = (new class {
+        constructor() { this.z = x }
+    })) => assertEquals(42, y.z))(42);
+    ((x, y = (new class Y {
+        constructor() { this.z = x }
+    })) => assertEquals(42, y.z))(42);
+
+    ((x, y = (new class extends x {
+    })) => assertEquals(42, y.z()))(class { z() { return 42 } });
+
+    // Defaults inside destructuring
+    ((x, {y = x}) => assertEquals(42, y))(42, {});
+    ((x, [y = x]) => assertEquals(42, y))(42, []);
+})();
+
+;(function testMultiScopeCapture() {
+    "use strict";
+    var x = 1;
+    {
+        let y = 2;
+        ((x, y, a = x, b = y) => {
+            assertEquals(3, x);
+            assertEquals(3, a);
+            assertEquals(4, y);
+            assertEquals(4, b);
+        })(3, 4);
+    }
+})();