Commit 8ee581af authored by marja's avatar marja Committed by Commit bot

Move sloppy block function hoisting logic from Parser to Scope.

This moves scope-related logic (such as looking up variables) to Scope
where it belongs, and enables PreParser to do more Scope-related
operations in the future.

BUG=

Review-Url: https://codereview.chromium.org/2301183003
Cr-Commit-Position: refs/heads/master@{#39233}
parent b11cf2e5
......@@ -1176,6 +1176,10 @@ class SloppyBlockFunctionStatement final : public Statement {
public:
Statement* statement() const { return statement_; }
void set_statement(Statement* statement) { statement_ = statement; }
VariableProxy* from() const { return from_; }
void set_from(VariableProxy* from) { from_ = from; }
VariableProxy* to() const { return to_; }
void set_to(VariableProxy* to) { to_ = to; }
Scope* scope() const { return scope_; }
SloppyBlockFunctionStatement* next() { return next_; }
void set_next(SloppyBlockFunctionStatement* next) { next_ = next; }
......@@ -1186,10 +1190,14 @@ class SloppyBlockFunctionStatement final : public Statement {
SloppyBlockFunctionStatement(Statement* statement, Scope* scope)
: Statement(kNoSourcePosition, kSloppyBlockFunctionStatement),
statement_(statement),
from_(nullptr),
to_(nullptr),
scope_(scope),
next_(nullptr) {}
Statement* statement_;
VariableProxy* from_;
VariableProxy* to_;
Scope* const scope_;
SloppyBlockFunctionStatement* next_;
};
......@@ -3149,9 +3157,9 @@ class AstNodeFactory final BASE_EMBEDDED {
return new (zone_) EmptyStatement(pos);
}
SloppyBlockFunctionStatement* NewSloppyBlockFunctionStatement(
Statement* statement, Scope* scope) {
return new (zone_) SloppyBlockFunctionStatement(statement, scope);
SloppyBlockFunctionStatement* NewSloppyBlockFunctionStatement(Scope* scope) {
return new (zone_) SloppyBlockFunctionStatement(
NewEmptyStatement(kNoSourcePosition), scope);
}
CaseClause* NewCaseClause(
......
......@@ -451,6 +451,99 @@ int Scope::num_parameters() const {
return is_declaration_scope() ? AsDeclarationScope()->num_parameters() : 0;
}
void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory,
bool* ok) {
DCHECK(is_sloppy(language_mode()));
DCHECK(is_function_scope() || is_eval_scope() || is_script_scope() ||
(is_block_scope() && outer_scope()->is_function_scope()));
DCHECK(HasSimpleParameters() || is_block_scope());
bool has_simple_parameters = HasSimpleParameters();
// For each variable which is used as a function declaration in a sloppy
// block,
SloppyBlockFunctionMap* map = 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
// or parameter,
// Check if there's a conflict with a parameter.
// This depends on the fact that functions always have a scope solely to
// hold complex parameters, and the names local to that scope are
// precisely the names of the parameters. IsDeclaredParameter(name) does
// not hold for names declared by complex parameters, nor are those
// bindings necessarily declared lexically, so we have to check for them
// explicitly. On the other hand, if there are not complex parameters,
// it is sufficient to just check IsDeclaredParameter.
if (!has_simple_parameters) {
if (outer_scope_->LookupLocal(name) != nullptr) {
continue;
}
} else {
if (IsDeclaredParameter(name)) {
continue;
}
}
bool var_created = false;
// Write in assignments to var for each block-scoped function declaration
auto delegates = static_cast<SloppyBlockFunctionStatement*>(p->value);
DeclarationScope* decl_scope = this;
while (decl_scope->is_eval_scope()) {
decl_scope = decl_scope->outer_scope()->GetDeclarationScope();
}
Scope* outer_scope = decl_scope->outer_scope();
for (SloppyBlockFunctionStatement* delegate = delegates;
delegate != nullptr; delegate = delegate->next()) {
// Check if there's a conflict with a lexical declaration
Scope* query_scope = delegate->scope()->outer_scope();
Variable* var = nullptr;
bool should_hoist = true;
// Note that we perform this loop for each delegate named 'name',
// which may duplicate work if those delegates share scopes.
// It is not sufficient to just do a Lookup on query_scope: for
// example, that does not prevent hoisting of the function in
// `{ let e; try {} catch (e) { function e(){} } }`
do {
var = query_scope->LookupLocal(name);
if (var != nullptr && IsLexicalVariableMode(var->mode())) {
should_hoist = false;
break;
}
query_scope = query_scope->outer_scope();
} while (query_scope != outer_scope);
if (!should_hoist) continue;
// Declare a var-style binding for the function in the outer scope
if (!var_created) {
var_created = true;
VariableProxy* proxy = NewUnresolved(factory, name);
Declaration* declaration =
factory->NewVariableDeclaration(proxy, this, kNoSourcePosition);
// Based on the preceding check, it doesn't matter what we pass as
// allow_harmony_restrictive_generators and
// sloppy_mode_block_scope_function_redefinition.
DeclareVariable(declaration, VAR,
Variable::DefaultInitializationFlag(VAR), false,
nullptr, ok);
DCHECK(*ok); // Based on the preceding check, this should not fail
if (!*ok) return;
}
// Create VariableProxies for creating an assignment statement
// (later). Read from the local lexical scope and write to the function
// scope.
delegate->set_to(NewUnresolved(factory, name));
delegate->set_from(delegate->scope()->NewUnresolved(factory, name));
}
}
}
void DeclarationScope::Analyze(ParseInfo* info, AnalyzeMode mode) {
DCHECK(info->literal() != NULL);
DeclarationScope* scope = info->literal()->scope();
......
......@@ -737,6 +737,10 @@ class DeclarationScope : public Scope {
sloppy_block_function_map_.Declare(zone(), name, statement);
}
// Go through sloppy_block_function_map_ and hoist those (into this scope)
// which should be hoisted.
void HoistSloppyBlockFunctions(AstNodeFactory* factory, bool* ok);
SloppyBlockFunctionMap* sloppy_block_function_map() {
return &sloppy_block_function_map_;
}
......
......@@ -121,6 +121,11 @@ class Variable final : public ZoneObject {
index_ = index;
}
static InitializationFlag DefaultInitializationFlag(VariableMode mode) {
DCHECK(IsDeclaredVariableMode(mode));
return mode == VAR ? kCreatedInitialized : kNeedsInitialization;
}
private:
Scope* scope_;
const AstRawString* name_;
......
......@@ -715,7 +715,7 @@ FunctionLiteral* Parser::DoParseProgram(ParseInfo* info) {
// pre-existing bindings should be made writable, enumerable and
// nonconfigurable if possible, whereas this code will leave attributes
// unchanged if the property already exists.
InsertSloppyBlockFunctionVarBindings(scope, nullptr, &ok);
InsertSloppyBlockFunctionVarBindings(scope, &ok);
}
if (ok) {
CheckConflictingVarDeclarations(scope, &ok);
......@@ -1365,14 +1365,10 @@ VariableProxy* Parser::NewUnresolved(const AstRawString* name) {
scanner()->location().end_pos);
}
InitializationFlag Parser::DefaultInitializationFlag(VariableMode mode) {
DCHECK(IsDeclaredVariableMode(mode));
return mode == VAR ? kCreatedInitialized : kNeedsInitialization;
}
Declaration* Parser::DeclareVariable(const AstRawString* name,
VariableMode mode, int pos, bool* ok) {
return DeclareVariable(name, mode, DefaultInitializationFlag(mode), pos, ok);
return DeclareVariable(name, mode, Variable::DefaultInitializationFlag(mode),
pos, ok);
}
Declaration* Parser::DeclareVariable(const AstRawString* name,
......@@ -1535,7 +1531,6 @@ Statement* Parser::ParseHoistableDeclaration(
Declare(declaration, DeclarationDescriptor::NORMAL, mode, kCreatedInitialized,
CHECK_OK);
if (names) names->Add(variable_name, zone());
EmptyStatement* empty = factory()->NewEmptyStatement(kNoSourcePosition);
// Async functions don't undergo sloppy mode block scoped hoisting, and don't
// allow duplicates in a block. Both are represented by the
// sloppy_block_function_map. Don't add them to the map for async functions.
......@@ -1544,12 +1539,12 @@ Statement* Parser::ParseHoistableDeclaration(
if (is_sloppy(language_mode()) && !scope()->is_declaration_scope() &&
!is_async && !(allow_harmony_restrictive_generators() && is_generator)) {
SloppyBlockFunctionStatement* delegate =
factory()->NewSloppyBlockFunctionStatement(empty, scope());
factory()->NewSloppyBlockFunctionStatement(scope());
DeclarationScope* target_scope = GetDeclarationScope();
target_scope->DeclareSloppyBlockFunction(variable_name, delegate);
return delegate;
}
return empty;
return factory()->NewEmptyStatement(kNoSourcePosition);
}
Statement* Parser::ParseClassDeclaration(ZoneList<const AstRawString*>* names,
......@@ -4112,8 +4107,7 @@ ZoneList<Statement*>* Parser::ParseEagerFunctionBody(
Block* init_block = BuildParameterInitializationBlock(parameters, CHECK_OK);
if (is_sloppy(inner_scope->language_mode())) {
InsertSloppyBlockFunctionVarBindings(inner_scope, function_scope,
CHECK_OK);
InsertSloppyBlockFunctionVarBindings(inner_scope, CHECK_OK);
}
// TODO(littledan): Merge the two rejection blocks into one
......@@ -4135,7 +4129,7 @@ ZoneList<Statement*>* Parser::ParseEagerFunctionBody(
} else {
DCHECK_EQ(inner_scope, function_scope);
if (is_sloppy(function_scope->language_mode())) {
InsertSloppyBlockFunctionVarBindings(function_scope, nullptr, CHECK_OK);
InsertSloppyBlockFunctionVarBindings(function_scope, CHECK_OK);
}
}
......@@ -4222,7 +4216,7 @@ Expression* Parser::ParseClassLiteral(const AstRawString* name,
Declaration* declaration =
factory()->NewVariableDeclaration(proxy, block_state.scope(), pos);
Declare(declaration, DeclarationDescriptor::NORMAL, CONST,
DefaultInitializationFlag(CONST), CHECK_OK);
Variable::DefaultInitializationFlag(CONST), CHECK_OK);
}
Expression* extends = nullptr;
......@@ -4419,86 +4413,20 @@ void Parser::InsertShadowingVarBindingInitializers(Block* inner_block) {
}
void Parser::InsertSloppyBlockFunctionVarBindings(DeclarationScope* scope,
Scope* complex_params_scope,
bool* ok) {
// For each variable which is used as a function declaration in a sloppy
// block,
scope->HoistSloppyBlockFunctions(factory(), CHECK_OK_VOID);
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
// or parameter,
// Check if there's a conflict with a parameter.
// This depends on the fact that functions always have a scope solely to
// hold complex parameters, and the names local to that scope are
// precisely the names of the parameters. IsDeclaredParameter(name) does
// not hold for names declared by complex parameters, nor are those
// bindings necessarily declared lexically, so we have to check for them
// explicitly. On the other hand, if there are not complex parameters,
// it is sufficient to just check IsDeclaredParameter.
if (complex_params_scope != nullptr) {
if (complex_params_scope->LookupLocal(name) != nullptr) {
continue;
}
} else {
if (scope->IsDeclaredParameter(name)) {
continue;
}
}
bool var_created = false;
// Write in assignments to var for each block-scoped function declaration
auto delegates = static_cast<SloppyBlockFunctionStatement*>(p->value);
DeclarationScope* decl_scope = scope;
while (decl_scope->is_eval_scope()) {
decl_scope = decl_scope->outer_scope()->GetDeclarationScope();
}
Scope* outer_scope = decl_scope->outer_scope();
for (SloppyBlockFunctionStatement* delegate = delegates;
delegate != nullptr; delegate = delegate->next()) {
// Check if there's a conflict with a lexical declaration
Scope* query_scope = delegate->scope()->outer_scope();
Variable* var = nullptr;
bool should_hoist = true;
// Note that we perform this loop for each delegate named 'name',
// which may duplicate work if those delegates share scopes.
// It is not sufficient to just do a Lookup on query_scope: for
// example, that does not prevent hoisting of the function in
// `{ let e; try {} catch (e) { function e(){} } }`
do {
var = query_scope->LookupLocal(name);
if (var != nullptr && IsLexicalVariableMode(var->mode())) {
should_hoist = false;
break;
}
query_scope = query_scope->outer_scope();
} while (query_scope != outer_scope);
if (!should_hoist) continue;
// Declare a var-style binding for the function in the outer scope
if (!var_created) {
var_created = true;
VariableProxy* proxy = scope->NewUnresolved(factory(), name);
Declaration* declaration =
factory()->NewVariableDeclaration(proxy, scope, kNoSourcePosition);
Declare(declaration, DeclarationDescriptor::NORMAL, VAR,
DefaultInitializationFlag(VAR), ok, scope);
DCHECK(*ok); // Based on the preceding check, this should not fail
if (!*ok) return;
if (delegate->to() == nullptr) {
continue;
}
// 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, kNoSourcePosition);
Expression* assignment = factory()->NewAssignment(
Token::ASSIGN, delegate->to(), delegate->from(), kNoSourcePosition);
Statement* statement =
factory()->NewExpressionStatement(assignment, kNoSourcePosition);
delegate->set_statement(statement);
......
......@@ -466,10 +466,8 @@ class Parser : public ParserBase<Parser> {
// Implement sloppy block-scoped functions, ES2015 Annex B 3.3
void InsertSloppyBlockFunctionVarBindings(DeclarationScope* scope,
Scope* complex_params_scope,
bool* ok);
static InitializationFlag DefaultInitializationFlag(VariableMode mode);
VariableProxy* NewUnresolved(const AstRawString* name, int begin_pos,
int end_pos = kNoSourcePosition,
Variable::Kind kind = Variable::NORMAL);
......
......@@ -146,10 +146,10 @@ void Parser::PatternRewriter::VisitVariableProxy(VariableProxy* pattern) {
parser_->scanner()->location().end_pos);
Declaration* declaration = factory()->NewVariableDeclaration(
proxy, descriptor_->scope, descriptor_->declaration_pos);
Variable* var = parser_->Declare(declaration, descriptor_->declaration_kind,
descriptor_->mode,
DefaultInitializationFlag(descriptor_->mode),
ok_, descriptor_->hoist_scope);
Variable* var = parser_->Declare(
declaration, descriptor_->declaration_kind, descriptor_->mode,
Variable::DefaultInitializationFlag(descriptor_->mode), ok_,
descriptor_->hoist_scope);
if (!*ok_) return;
DCHECK_NOT_NULL(var);
DCHECK(proxy->is_resolved());
......
......@@ -67,6 +67,24 @@
assertEquals(1, f);
})();
(function shadowingLetDoesntBindGenerator() {
let f = function *f() {
while(true) {
yield 1;
}
};
assertEquals(1, f().next().value);
{
function *f() {
while(true) {
yield 2;
}
}
assertEquals(2, f().next().value);
}
assertEquals(1, f().next().value);
})();
(function shadowingClassDoesntBind() {
class f { }
assertEquals('class f { }', f.toString());
......
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