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()));
void AstLiteralReindexer::VisitEmptyStatement(EmptyStatement* node) {}
+void AstLiteralReindexer::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* node) {
+ Visit(node->statement());
+}
+
+
void AstLiteralReindexer::VisitContinueStatement(ContinueStatement* node) {}
}
+void AstNumberingVisitor::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* node) {
+ IncrementNodeCount();
+ Visit(node->statement());
+}
+
+
void AstNumberingVisitor::VisitContinueStatement(ContinueStatement* node) {
IncrementNodeCount();
}
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) \
};
+// 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)
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);
}
+void AstGraphBuilder::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* stmt) {
+ Visit(stmt->statement());
+}
+
+
void AstGraphBuilder::VisitIfStatement(IfStatement* stmt) {
IfBuilder compare_if(this);
VisitForTest(stmt->condition());
}
+void ALAA::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* stmt) {
+ Visit(stmt->statement());
+}
+
+
// ---------------------------------------------------------------------------
// -- Interesting nodes-------------------------------------------------------
// ---------------------------------------------------------------------------
}
+void FullCodeGenerator::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* declaration) {
+ Visit(declaration->statement());
+}
+
+
int FullCodeGenerator::DeclareGlobalsFlags() {
DCHECK(DeclareGlobalsLanguageMode::is_valid(language_mode()));
return DeclareGlobalsEvalFlag::encode(is_eval()) |
}
+void HOptimizedGraphBuilder::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* stmt) {
+ Visit(stmt->statement());
+}
+
+
void HOptimizedGraphBuilder::VisitIfStatement(IfStatement* stmt) {
DCHECK(!HasStackOverflow());
DCHECK(current_block() != NULL);
void BytecodeGenerator::VisitIfStatement(IfStatement* stmt) { UNIMPLEMENTED(); }
+void BytecodeGenerator::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* stmt) {
+ Visit(stmt->statement());
+}
+
+
void BytecodeGenerator::VisitContinueStatement(ContinueStatement* stmt) {
UNIMPLEMENTED();
}
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;
}
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);
}
}
+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
// 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,
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)
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());
}
+void PrettyPrinter::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* node) {
+ Visit(node->statement());
+}
+
+
void PrettyPrinter::VisitIfStatement(IfStatement* node) {
Print("if (");
Visit(node->condition());
}
+void AstPrinter::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* node) {
+ Visit(node->statement());
+}
+
+
void AstPrinter::VisitIfStatement(IfStatement* node) {
IndentedScope indent(this, "IF");
PrintIndentedVisit("CONDITION", node->condition());
}
+void Processor::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* node) {
+ Visit(node->statement());
+}
+
+
// Do nothing:
void Processor::VisitVariableDeclaration(VariableDeclaration* node) {}
void Processor::VisitFunctionDeclaration(FunctionDeclaration* node) {}
}
+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
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),
unresolved_(16, zone),
decls_(4, zone),
module_descriptor_(NULL),
+ sloppy_block_function_map_(zone),
already_resolved_(true),
ast_value_factory_(value_factory),
zone_(zone),
unresolved_(0, zone),
decls_(0, zone),
module_descriptor_(NULL),
+ sloppy_block_function_map_(zone),
already_resolved_(true),
ast_value_factory_(value_factory),
zone_(zone),
};
+// 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
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,
// 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_;
void AsmTyper::VisitEmptyStatement(EmptyStatement* stmt) {}
+void AsmTyper::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* stmt) {
+ Visit(stmt->statement());
+}
+
+
void AsmTyper::VisitEmptyParentheses(EmptyParentheses* expr) { UNREACHABLE(); }
}
+void AstTyper::VisitSloppyBlockFunctionStatement(
+ SloppyBlockFunctionStatement* stmt) {
+ Visit(stmt->statement());
+}
+
+
void AstTyper::VisitIfStatement(IfStatement* stmt) {
// Collect type feedback.
if (!stmt->condition()->ToBooleanIsTrue() &&
}
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() {
--- /dev/null
+// 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);