Implement sloppy-mode block-defined functions (Annex B 3.3)
authorlittledan <littledan@chromium.org>
Mon, 21 Sep 2015 04:30:50 +0000 (21:30 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 21 Sep 2015 04:31:09 +0000 (04:31 +0000)
ES2015 specifies very particular semantics for functions defined in blocks.
In strict mode, it is simply a lexical binding scoped to that block. In sloppy
mode, in addition to that lexical binding, there is a var-style binding in
the outer scope, which is overwritten with the local binding when the function
declaration is evaluated, *as long as* introducing ths var binding would not
create a var/let conflict in the outer scope.

This patch implements the semantics by introducing a DelegateStatement, which
is initially filled in with the EmptyStatement and overwritten with the
assignment when the scope is closed out and it can be checked that there is
no conflict.

This patch is tested with a new mjsunit test, and I tried staging it and running
test262, finding that the tests that we have disabled due to lack of Annex B
support now pass.

R=adamk,rossberg
LOG=Y
BUG=v8:4285

Review URL: https://codereview.chromium.org/1332873003

Cr-Commit-Position: refs/heads/master@{#30842}

20 files changed:
src/ast-expression-visitor.cc
src/ast-literal-reindexer.cc
src/ast-numbering.cc
src/ast.h
src/compiler/ast-graph-builder.cc
src/compiler/ast-loop-assignment-analyzer.cc
src/full-codegen/full-codegen.cc
src/hydrogen.cc
src/interpreter/bytecode-generator.cc
src/parser.cc
src/parser.h
src/pattern-rewriter.cc
src/prettyprinter.cc
src/rewriter.cc
src/scopes.cc
src/scopes.h
src/typing-asm.cc
src/typing.cc
test/mjsunit/harmony/block-let-semantics-sloppy.js
test/mjsunit/harmony/block-sloppy-function.js [new file with mode: 0644]

index 014f56de5335f5d4040cc3546efe66db50495565..782d4bbca6405d6068b03a47705f689168be355e 100644 (file)
@@ -79,6 +79,12 @@ void AstExpressionVisitor::VisitExpressionStatement(ExpressionStatement* stmt) {
 void AstExpressionVisitor::VisitEmptyStatement(EmptyStatement* stmt) {}
 
 
+void AstExpressionVisitor::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  RECURSE(Visit(stmt->statement()));
+}
+
+
 void AstExpressionVisitor::VisitIfStatement(IfStatement* stmt) {
   RECURSE(Visit(stmt->condition()));
   RECURSE(Visit(stmt->then_statement()));
index d9a6ed1c41d141ce3c92d50c957120bf16ad82e0..e5729c7818a2115edfe21bb974345411041d96aa 100644 (file)
@@ -24,6 +24,12 @@ void AstLiteralReindexer::VisitExportDeclaration(ExportDeclaration* node) {
 void AstLiteralReindexer::VisitEmptyStatement(EmptyStatement* node) {}
 
 
+void AstLiteralReindexer::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* node) {
+  Visit(node->statement());
+}
+
+
 void AstLiteralReindexer::VisitContinueStatement(ContinueStatement* node) {}
 
 
index b36e1ff7e1d0d2c4249bd9a6b96a2fc20d03b9ed..a89b5fb60a298d674be4a9865570da38f3744641 100644 (file)
@@ -113,6 +113,13 @@ void AstNumberingVisitor::VisitEmptyStatement(EmptyStatement* node) {
 }
 
 
+void AstNumberingVisitor::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* node) {
+  IncrementNodeCount();
+  Visit(node->statement());
+}
+
+
 void AstNumberingVisitor::VisitContinueStatement(ContinueStatement* node) {
   IncrementNodeCount();
 }
index af82541ec668e60bcd4098da1c10ea39d4c9af5f..2f54443e889768ad29e8e540641dd6c7b5ece14a 100644 (file)
--- a/src/ast.h
+++ b/src/ast.h
@@ -44,23 +44,24 @@ namespace internal {
   V(ImportDeclaration)           \
   V(ExportDeclaration)
 
-#define STATEMENT_NODE_LIST(V)                  \
-  V(Block)                                      \
-  V(ExpressionStatement)                        \
-  V(EmptyStatement)                             \
-  V(IfStatement)                                \
-  V(ContinueStatement)                          \
-  V(BreakStatement)                             \
-  V(ReturnStatement)                            \
-  V(WithStatement)                              \
-  V(SwitchStatement)                            \
-  V(DoWhileStatement)                           \
-  V(WhileStatement)                             \
-  V(ForStatement)                               \
-  V(ForInStatement)                             \
-  V(ForOfStatement)                             \
-  V(TryCatchStatement)                          \
-  V(TryFinallyStatement)                        \
+#define STATEMENT_NODE_LIST(V)    \
+  V(Block)                        \
+  V(ExpressionStatement)          \
+  V(EmptyStatement)               \
+  V(SloppyBlockFunctionStatement) \
+  V(IfStatement)                  \
+  V(ContinueStatement)            \
+  V(BreakStatement)               \
+  V(ReturnStatement)              \
+  V(WithStatement)                \
+  V(SwitchStatement)              \
+  V(DoWhileStatement)             \
+  V(WhileStatement)               \
+  V(ForStatement)                 \
+  V(ForInStatement)               \
+  V(ForOfStatement)               \
+  V(TryCatchStatement)            \
+  V(TryFinallyStatement)          \
   V(DebuggerStatement)
 
 #define EXPRESSION_NODE_LIST(V) \
@@ -1268,6 +1269,29 @@ class EmptyStatement final : public Statement {
 };
 
 
+// Delegates to another statement, which may be overwritten.
+// This was introduced to implement ES2015 Annex B3.3 for conditionally making
+// sloppy-mode block-scoped functions have a var binding, which is changed
+// from one statement to another during parsing.
+class SloppyBlockFunctionStatement final : public Statement {
+ public:
+  DECLARE_NODE_TYPE(SloppyBlockFunctionStatement)
+
+  Statement* statement() const { return statement_; }
+  void set_statement(Statement* statement) { statement_ = statement; }
+  Scope* scope() const { return scope_; }
+
+ private:
+  SloppyBlockFunctionStatement(Zone* zone, Statement* statement, Scope* scope)
+      : Statement(zone, RelocInfo::kNoPosition),
+        statement_(statement),
+        scope_(scope) {}
+
+  Statement* statement_;
+  Scope* const scope_;
+};
+
+
 class Literal final : public Expression {
  public:
   DECLARE_NODE_TYPE(Literal)
@@ -3400,6 +3424,12 @@ class AstNodeFactory final BASE_EMBEDDED {
     return new (local_zone_) EmptyStatement(local_zone_, pos);
   }
 
+  SloppyBlockFunctionStatement* NewSloppyBlockFunctionStatement(
+      Statement* statement, Scope* scope) {
+    return new (local_zone_)
+        SloppyBlockFunctionStatement(local_zone_, statement, scope);
+  }
+
   CaseClause* NewCaseClause(
       Expression* label, ZoneList<Statement*>* statements, int pos) {
     return new (local_zone_) CaseClause(local_zone_, label, statements, pos);
index 48600b7553b957045a62898aac469e886d38e375..963802eb666f45fbfbffe5d31ffa019a4e99996f 100644 (file)
@@ -1158,6 +1158,12 @@ void AstGraphBuilder::VisitEmptyStatement(EmptyStatement* stmt) {
 }
 
 
+void AstGraphBuilder::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  Visit(stmt->statement());
+}
+
+
 void AstGraphBuilder::VisitIfStatement(IfStatement* stmt) {
   IfBuilder compare_if(this);
   VisitForTest(stmt->condition());
index b73cbb35f108ff49bb9e0b7d25464a2088d63ee6..d9ec109e40ecbaebdaf86b69169c10cc1e818367 100644 (file)
@@ -202,6 +202,12 @@ void ALAA::VisitCaseClause(CaseClause* cc) {
 }
 
 
+void ALAA::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  Visit(stmt->statement());
+}
+
+
 // ---------------------------------------------------------------------------
 // -- Interesting nodes-------------------------------------------------------
 // ---------------------------------------------------------------------------
index ea53844fbfc3dc9eb89dd3296cd8c72e3dc8e04e..69237b10aa2c8bb575e0a6897a5e210d877b0279 100644 (file)
@@ -450,6 +450,12 @@ void FullCodeGenerator::VisitVariableProxy(VariableProxy* expr) {
 }
 
 
+void FullCodeGenerator::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* declaration) {
+  Visit(declaration->statement());
+}
+
+
 int FullCodeGenerator::DeclareGlobalsFlags() {
   DCHECK(DeclareGlobalsLanguageMode::is_valid(language_mode()));
   return DeclareGlobalsEvalFlag::encode(is_eval()) |
index 1372ae457e71fc6ae7c7b68cb9c1585636eee545..31c42c74e65ee7475ff990316726c969d36b1607 100644 (file)
@@ -4789,6 +4789,12 @@ void HOptimizedGraphBuilder::VisitEmptyStatement(EmptyStatement* stmt) {
 }
 
 
+void HOptimizedGraphBuilder::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  Visit(stmt->statement());
+}
+
+
 void HOptimizedGraphBuilder::VisitIfStatement(IfStatement* stmt) {
   DCHECK(!HasStackOverflow());
   DCHECK(current_block() != NULL);
index 2a330ca87059de709f9d3ab3c91039b6738ee064..e40a5b3513eeeb97b9e8c7cda8299136cd427cb5 100644 (file)
@@ -121,6 +121,12 @@ void BytecodeGenerator::VisitEmptyStatement(EmptyStatement* stmt) {
 void BytecodeGenerator::VisitIfStatement(IfStatement* stmt) { UNIMPLEMENTED(); }
 
 
+void BytecodeGenerator::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  Visit(stmt->statement());
+}
+
+
 void BytecodeGenerator::VisitContinueStatement(ContinueStatement* stmt) {
   UNIMPLEMENTED();
 }
index 8d180f06a643abb27dbc00ed45474ffc24974fa8..e6fb69edfefd479f97c18dd6da5f31f732047455 100644 (file)
@@ -2259,7 +2259,16 @@ Statement* Parser::ParseFunctionDeclaration(
       factory()->NewFunctionDeclaration(proxy, mode, fun, scope_, pos);
   Declare(declaration, DeclarationDescriptor::NORMAL, true, CHECK_OK);
   if (names) names->Add(name, zone());
-  return factory()->NewEmptyStatement(RelocInfo::kNoPosition);
+  EmptyStatement* empty = factory()->NewEmptyStatement(RelocInfo::kNoPosition);
+  if (is_sloppy(language_mode()) && allow_harmony_sloppy_function() &&
+      !scope_->is_declaration_scope()) {
+    SloppyBlockFunctionStatement* delegate =
+        factory()->NewSloppyBlockFunctionStatement(empty, scope_);
+    scope_->DeclarationScope()->sloppy_block_function_map()->Declare(name,
+                                                                     delegate);
+    return delegate;
+  }
+  return empty;
 }
 
 
@@ -4278,6 +4287,9 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
       CheckStrictOctalLiteral(scope->start_position(), scope->end_position(),
                               CHECK_OK);
     }
+    if (is_sloppy(language_mode) && allow_harmony_sloppy_function()) {
+      InsertSloppyBlockFunctionVarBindings(scope, CHECK_OK);
+    }
     if (is_strict(language_mode) || allow_harmony_sloppy()) {
       CheckConflictingVarDeclarations(scope, CHECK_OK);
     }
@@ -4940,6 +4952,41 @@ void Parser::CheckConflictingVarDeclarations(Scope* scope, bool* ok) {
 }
 
 
+void Parser::InsertSloppyBlockFunctionVarBindings(Scope* scope, bool* ok) {
+  // For each variable which is used as a function declaration in a sloppy
+  // block,
+  DCHECK(scope->is_declaration_scope());
+  SloppyBlockFunctionMap* map = scope->sloppy_block_function_map();
+  for (ZoneHashMap::Entry* p = map->Start(); p != nullptr; p = map->Next(p)) {
+    AstRawString* name = static_cast<AstRawString*>(p->key);
+    // If the variable wouldn't conflict with a lexical declaration,
+    Variable* var = scope->LookupLocal(name);
+    if (var == nullptr || !IsLexicalVariableMode(var->mode())) {
+      // Declare a var-style binding for the function in the outer scope
+      VariableProxy* proxy = scope->NewUnresolved(factory(), name);
+      Declaration* declaration = factory()->NewVariableDeclaration(
+          proxy, VAR, scope, RelocInfo::kNoPosition);
+      Declare(declaration, DeclarationDescriptor::NORMAL, true, ok, scope);
+      DCHECK(ok);  // Based on the preceding check, this should not fail
+      if (!ok) return;
+
+      // Write in assignments to var for each block-scoped function declaration
+      auto delegates = static_cast<SloppyBlockFunctionMap::Vector*>(p->value);
+      for (SloppyBlockFunctionStatement* delegate : *delegates) {
+        // Read from the local lexical scope and write to the function scope
+        VariableProxy* to = scope->NewUnresolved(factory(), name);
+        VariableProxy* from = delegate->scope()->NewUnresolved(factory(), name);
+        Expression* assignment = factory()->NewAssignment(
+            Token::ASSIGN, to, from, RelocInfo::kNoPosition);
+        Statement* statement = factory()->NewExpressionStatement(
+            assignment, RelocInfo::kNoPosition);
+        delegate->set_statement(statement);
+      }
+    }
+  }
+}
+
+
 // ----------------------------------------------------------------------------
 // Parser support
 
index 29dbb099547fe88073c659017b44ab8bb796cbfa..cf4cdad66bba7d1cf5c61021f65cedf73a02201e 100644 (file)
@@ -1134,6 +1134,9 @@ class Parser : public ParserBase<ParserTraits> {
   // hoisted over such a scope.
   void CheckConflictingVarDeclarations(Scope* scope, bool* ok);
 
+  // Implement sloppy block-scoped functions, ES2015 Annex B 3.3
+  void InsertSloppyBlockFunctionVarBindings(Scope* scope, bool* ok);
+
   // Parser support
   VariableProxy* NewUnresolved(const AstRawString* name, VariableMode mode);
   Variable* Declare(Declaration* declaration,
index f7e5b434664ce5905effdbee891e44859ae77aea..e4c602aa4888b814238f5dbb17e1e1db58937b84 100644 (file)
@@ -395,6 +395,7 @@ NOT_A_PATTERN(CountOperation)
 NOT_A_PATTERN(DebuggerStatement)
 NOT_A_PATTERN(DoWhileStatement)
 NOT_A_PATTERN(EmptyStatement)
+NOT_A_PATTERN(SloppyBlockFunctionStatement)
 NOT_A_PATTERN(ExportDeclaration)
 NOT_A_PATTERN(ExpressionStatement)
 NOT_A_PATTERN(ForInStatement)
index ac123bd10feb52e22380c68446a4fa49e4311bf9..37110e27b3484b909b037874f7534e10b5a9bc3b 100644 (file)
@@ -114,6 +114,12 @@ void CallPrinter::VisitExpressionStatement(ExpressionStatement* node) {
 void CallPrinter::VisitEmptyStatement(EmptyStatement* node) {}
 
 
+void CallPrinter::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* node) {
+  Find(node->statement());
+}
+
+
 void CallPrinter::VisitIfStatement(IfStatement* node) {
   Find(node->condition());
   Find(node->then_statement());
@@ -499,6 +505,12 @@ void PrettyPrinter::VisitEmptyStatement(EmptyStatement* node) {
 }
 
 
+void PrettyPrinter::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* node) {
+  Visit(node->statement());
+}
+
+
 void PrettyPrinter::VisitIfStatement(IfStatement* node) {
   Print("if (");
   Visit(node->condition());
@@ -1216,6 +1228,12 @@ void AstPrinter::VisitEmptyStatement(EmptyStatement* node) {
 }
 
 
+void AstPrinter::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* node) {
+  Visit(node->statement());
+}
+
+
 void AstPrinter::VisitIfStatement(IfStatement* node) {
   IndentedScope indent(this, "IF");
   PrintIndentedVisit("CONDITION", node->condition());
index f7b0ce005e6c7eb9c5032419b5cea8fe83c75e1c..d88e1199f82a697bed4f622ab6620970756ec881 100644 (file)
@@ -190,6 +190,12 @@ void Processor::VisitWithStatement(WithStatement* node) {
 }
 
 
+void Processor::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* node) {
+  Visit(node->statement());
+}
+
+
 // Do nothing:
 void Processor::VisitVariableDeclaration(VariableDeclaration* node) {}
 void Processor::VisitFunctionDeclaration(FunctionDeclaration* node) {}
index 963d539afb96e664e8b2f063a14f7e85ee03048f..e9c04c9776810203b2c849317444dd07037fd62f 100644 (file)
@@ -66,6 +66,27 @@ Variable* VariableMap::Lookup(const AstRawString* name) {
 }
 
 
+SloppyBlockFunctionMap::SloppyBlockFunctionMap(Zone* zone)
+    : ZoneHashMap(ZoneHashMap::PointersMatch, 8, ZoneAllocationPolicy(zone)),
+      zone_(zone) {}
+SloppyBlockFunctionMap::~SloppyBlockFunctionMap() {}
+
+
+void SloppyBlockFunctionMap::Declare(const AstRawString* name,
+                                     SloppyBlockFunctionStatement* stmt) {
+  // AstRawStrings are unambiguous, i.e., the same string is always represented
+  // by the same AstRawString*.
+  Entry* p =
+      ZoneHashMap::LookupOrInsert(const_cast<AstRawString*>(name), name->hash(),
+                                  ZoneAllocationPolicy(zone_));
+  if (p->value == nullptr) {
+    p->value = new (zone_->New(sizeof(Vector))) Vector(zone_);
+  }
+  Vector* delegates = static_cast<Vector*>(p->value);
+  delegates->push_back(stmt);
+}
+
+
 // ----------------------------------------------------------------------------
 // Implementation of Scope
 
@@ -79,6 +100,7 @@ Scope::Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type,
       decls_(4, zone),
       module_descriptor_(
           scope_type == MODULE_SCOPE ? ModuleDescriptor::New(zone) : NULL),
+      sloppy_block_function_map_(zone),
       already_resolved_(false),
       ast_value_factory_(ast_value_factory),
       zone_(zone),
@@ -100,6 +122,7 @@ Scope::Scope(Zone* zone, Scope* inner_scope, ScopeType scope_type,
       unresolved_(16, zone),
       decls_(4, zone),
       module_descriptor_(NULL),
+      sloppy_block_function_map_(zone),
       already_resolved_(true),
       ast_value_factory_(value_factory),
       zone_(zone),
@@ -125,6 +148,7 @@ Scope::Scope(Zone* zone, Scope* inner_scope,
       unresolved_(0, zone),
       decls_(0, zone),
       module_descriptor_(NULL),
+      sloppy_block_function_map_(zone),
       already_resolved_(true),
       ast_value_factory_(value_factory),
       zone_(zone),
index 4b8fe4997b46a151439be29e70f9d30de261c240..d9aac2f7e5ff96b2616e8ccaff81391ddeabb277 100644 (file)
@@ -57,6 +57,23 @@ class DynamicScopePart : public ZoneObject {
 };
 
 
+// Sloppy block-scoped function declarations to var-bind
+class SloppyBlockFunctionMap : public ZoneHashMap {
+ public:
+  explicit SloppyBlockFunctionMap(Zone* zone);
+
+  virtual ~SloppyBlockFunctionMap();
+
+  void Declare(const AstRawString* name,
+               SloppyBlockFunctionStatement* statement);
+
+  typedef ZoneVector<SloppyBlockFunctionStatement*> Vector;
+
+ private:
+  Zone* zone_;
+};
+
+
 // Global invariants after AST construction: Each reference (i.e. identifier)
 // to a JavaScript variable (including global properties) is represented by a
 // VariableProxy node. Immediately after AST construction and before variable
@@ -544,6 +561,10 @@ class Scope: public ZoneObject {
     return params_.Contains(variables_.Lookup(name));
   }
 
+  SloppyBlockFunctionMap* sloppy_block_function_map() {
+    return &sloppy_block_function_map_;
+  }
+
   // Error handling.
   void ReportMessage(int start_position, int end_position,
                      MessageTemplate::Template message,
@@ -602,6 +623,9 @@ class Scope: public ZoneObject {
   // Module descriptor; module scopes only.
   ModuleDescriptor* module_descriptor_;
 
+  // Map of function names to lists of functions defined in sloppy blocks
+  SloppyBlockFunctionMap sloppy_block_function_map_;
+
   // Illegal redeclaration.
   Expression* illegal_redecl_;
 
index a8d0fbc738a8ea4d1abb4dea41f3ba1e0a8f1817..f7688964a573714f28a8cf6be741128d21b1e287 100644 (file)
@@ -266,6 +266,12 @@ void AsmTyper::VisitExpressionStatement(ExpressionStatement* stmt) {
 void AsmTyper::VisitEmptyStatement(EmptyStatement* stmt) {}
 
 
+void AsmTyper::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  Visit(stmt->statement());
+}
+
+
 void AsmTyper::VisitEmptyParentheses(EmptyParentheses* expr) { UNREACHABLE(); }
 
 
index c390956e19dcc227d80324a042f582d4f726b884..bd5114e89a4108dd3db0a55504b69fbc68040d2b 100644 (file)
@@ -136,6 +136,12 @@ void AstTyper::VisitEmptyStatement(EmptyStatement* stmt) {
 }
 
 
+void AstTyper::VisitSloppyBlockFunctionStatement(
+    SloppyBlockFunctionStatement* stmt) {
+  Visit(stmt->statement());
+}
+
+
 void AstTyper::VisitIfStatement(IfStatement* stmt) {
   // Collect type feedback.
   if (!stmt->condition()->ToBooleanIsTrue() &&
index 3d529fc36d0a0a35745451555491e679ae881bc6..8ec1eeacd09b1877f926ef8beacf60a72e3a1b7a 100644 (file)
@@ -127,8 +127,9 @@ function f() {
 }
 f();
 
-// Test that a function declaration introduces a block scoped variable.
-TestAll('{ function k() { return 0; } }; k(); ');
+// Test that a function declaration introduces a block scoped variable
+// and no function hoisting if there is a conflict.
+TestFunctionLocal('{ function k() { return 0; } }; k(); let k;');
 
 // Test that a function declaration sees the scope it resides in.
 function f2() {
diff --git a/test/mjsunit/harmony/block-sloppy-function.js b/test/mjsunit/harmony/block-sloppy-function.js
new file mode 100644 (file)
index 0000000..a17a4c0
--- /dev/null
@@ -0,0 +1,203 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --no-legacy-const --harmony-sloppy --harmony-sloppy-let
+// Flags: --harmony-sloppy-function --harmony-destructuring
+// Flags: --harmony-rest-parameters
+
+// Test Annex B 3.3 semantics for functions declared in blocks in sloppy mode.
+// http://www.ecma-international.org/ecma-262/6.0/#sec-block-level-function-declarations-web-legacy-compatibility-semantics
+
+(function overridingLocalFunction() {
+  var x = [];
+  assertEquals('function', typeof f);
+  function f() {
+    x.push(1);
+  }
+  f();
+  {
+    f();
+    function f() {
+      x.push(2);
+    }
+    f();
+  }
+  f();
+  {
+    f();
+    function f() {
+      x.push(3);
+    }
+    f();
+  }
+  f();
+  assertArrayEquals([1, 2, 2, 2, 3, 3, 3], x);
+})();
+
+(function newFunctionBinding() {
+  var x = [];
+  assertEquals('undefined', typeof f);
+  {
+    f();
+    function f() {
+      x.push(2);
+    }
+    f();
+  }
+  f();
+  {
+    f();
+    function f() {
+      x.push(3);
+    }
+    f();
+  }
+  f();
+  assertArrayEquals([2, 2, 2, 3, 3, 3], x);
+})();
+
+(function shadowingLetDoesntBind() {
+  let f = 1;
+  assertEquals(1, f);
+  {
+    let y = 3;
+    function f() {
+      y = 2;
+    }
+    f();
+    assertEquals(2, y);
+  }
+  assertEquals(1, f);
+})();
+
+(function shadowingClassDoesntBind() {
+  class f { }
+  assertEquals('class f { }', f.toString());
+  {
+    let y = 3;
+    function f() {
+      y = 2;
+    }
+    f();
+    assertEquals(2, y);
+  }
+  assertEquals('class f { }', f.toString());
+})();
+
+(function shadowingConstDoesntBind() {
+  const f = 1;
+  assertEquals(1, f);
+  {
+    let y = 3;
+    function f() {
+      y = 2;
+    }
+    f();
+    assertEquals(2, y);
+  }
+  assertEquals(1, f);
+})();
+
+(function shadowingVarBinds() {
+  var f = 1;
+  assertEquals(1, f);
+  {
+    let y = 3;
+    function f() {
+      y = 2;
+    }
+    f();
+    assertEquals(2, y);
+  }
+  assertEquals('function', typeof f);
+})();
+
+(function conditional() {
+  if (true) {
+    function f() { return 1; }
+  } else {
+    function f() { return 2; }
+  }
+  assertEquals(1, f());
+
+  if (false) {
+    function g() { return 1; }
+  } else {
+    function g() { return 2; }
+  }
+  assertEquals(2, g());
+})();
+
+(function skipExecution() {
+  {
+    function f() { return 1; }
+  }
+  assertEquals(1, f());
+  {
+    function f() { return 2; }
+  }
+  assertEquals(2, f());
+  L: {
+    assertEquals(3, f());
+    break L;
+    function f() { return 3; }
+  }
+  assertEquals(2, f());
+})();
+
+// Test that hoisting from blocks doesn't happen in global scope
+function globalUnhoisted() { return 0; }
+{
+  function globalUnhoisted() { return 1; }
+}
+assertEquals(0, globalUnhoisted());
+
+// Test that shadowing arguments is fine
+(function shadowArguments(x) {
+  assertArrayEquals([1], arguments);
+  {
+    assertEquals('function', typeof arguments);
+    function arguments() {}
+    assertEquals('function', typeof arguments);
+  }
+  assertEquals('function', typeof arguments);
+})(1);
+
+// Shadow function parameter
+(function shadowParameter(x) {
+  assertEquals(1, x);
+  {
+    function x() {}
+  }
+  assertEquals('function', typeof x);
+})(1);
+
+// Shadow function parameter
+(function shadowDefaultParameter(x = 0) {
+  assertEquals(1, x);
+  {
+    function x() {}
+  }
+  // TODO(littledan): Once destructured parameters are no longer
+  // let-bound, enable this assertion. This is the core of the test.
+  // assertEquals('function', typeof x);
+})(1);
+
+(function shadowRestParameter(...x) {
+  assertArrayEquals([1], x);
+  {
+    function x() {}
+  }
+  // TODO(littledan): Once destructured parameters are no longer
+  // let-bound, enable this assertion. This is the core of the test.
+  // assertEquals('function', typeof x);
+})(1);
+
+assertThrows(function notInDefaultScope(x = y) {
+  {
+    function y() {}
+  }
+  assertEquals('function', typeof y);
+  assertEquals(x, undefined);
+}, ReferenceError);