Commit e5ff10d7 authored by littledan's avatar littledan Committed by Commit bot

Implement sloppy-mode block-defined functions (Annex B 3.3)

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}
parent 4962bbb1
......@@ -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()));
......
......@@ -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) {}
......
......@@ -113,6 +113,13 @@ void AstNumberingVisitor::VisitEmptyStatement(EmptyStatement* node) {
}
void AstNumberingVisitor::VisitSloppyBlockFunctionStatement(
SloppyBlockFunctionStatement* node) {
IncrementNodeCount();
Visit(node->statement());
}
void AstNumberingVisitor::VisitContinueStatement(ContinueStatement* node) {
IncrementNodeCount();
}
......
......@@ -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);
......
......@@ -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());
......
......@@ -202,6 +202,12 @@ void ALAA::VisitCaseClause(CaseClause* cc) {
}
void ALAA::VisitSloppyBlockFunctionStatement(
SloppyBlockFunctionStatement* stmt) {
Visit(stmt->statement());
}
// ---------------------------------------------------------------------------
// -- Interesting nodes-------------------------------------------------------
// ---------------------------------------------------------------------------
......
......@@ -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()) |
......
......@@ -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);
......
......@@ -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();
}
......
......@@ -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
......
......@@ -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,
......
......@@ -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)
......
......@@ -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());
......
......@@ -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) {}
......
......@@ -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),
......
......@@ -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_;
......
......@@ -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(); }
......
......@@ -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() &&
......
......@@ -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() {
......
// 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);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment