Commit ab7d5f96 authored by caitpotter88's avatar caitpotter88 Committed by Commit bot

[parser] report better errors for multiple ForBindings in ForIn/Of loops

Instead of "unexpected token" errors, report something meatier and more actionable.

BUG=
R=marja@chromium.org, arv@chromium.org
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#27677}
parent 6244bbcd
...@@ -199,7 +199,8 @@ var kMessages = { ...@@ -199,7 +199,8 @@ var kMessages = {
constructor_noncallable: ["Class constructors cannot be invoked without 'new'"], constructor_noncallable: ["Class constructors cannot be invoked without 'new'"],
array_not_subclassable: ["Subclassing Arrays is not currently supported."], array_not_subclassable: ["Subclassing Arrays is not currently supported."],
for_in_loop_initializer: ["for-in loop variable declaration may not have an initializer."], for_in_loop_initializer: ["for-in loop variable declaration may not have an initializer."],
for_of_loop_initializer: ["for-of loop variable declaration may not have an initializer."] for_of_loop_initializer: ["for-of loop variable declaration may not have an initializer."],
for_inof_loop_multi_bindings: ["Invalid left-hand side in ", "%0", " loop: Must have a single binding."]
}; };
......
...@@ -2217,8 +2217,8 @@ Block* Parser::ParseVariableStatement(VariableDeclarationContext var_context, ...@@ -2217,8 +2217,8 @@ Block* Parser::ParseVariableStatement(VariableDeclarationContext var_context,
// VariableDeclarations ';' // VariableDeclarations ';'
const AstRawString* ignore; const AstRawString* ignore;
Block* result = Block* result = ParseVariableDeclarations(
ParseVariableDeclarations(var_context, names, &ignore, nullptr, CHECK_OK); var_context, nullptr, names, &ignore, nullptr, nullptr, CHECK_OK);
ExpectSemicolon(CHECK_OK); ExpectSemicolon(CHECK_OK);
return result; return result;
} }
...@@ -2230,9 +2230,10 @@ Block* Parser::ParseVariableStatement(VariableDeclarationContext var_context, ...@@ -2230,9 +2230,10 @@ Block* Parser::ParseVariableStatement(VariableDeclarationContext var_context,
// to initialize it properly. This mechanism is used for the parsing // to initialize it properly. This mechanism is used for the parsing
// of 'for-in' loops. // of 'for-in' loops.
Block* Parser::ParseVariableDeclarations( Block* Parser::ParseVariableDeclarations(
VariableDeclarationContext var_context, VariableDeclarationContext var_context, int* num_decl,
ZoneList<const AstRawString*>* names, const AstRawString** out, ZoneList<const AstRawString*>* names, const AstRawString** out,
Scanner::Location* first_initializer_loc, bool* ok) { Scanner::Location* first_initializer_loc, Scanner::Location* bindings_loc,
bool* ok) {
// VariableDeclarations :: // VariableDeclarations ::
// ('var' | 'const' | 'let') (Identifier ('=' AssignmentExpression)?)+[','] // ('var' | 'const' | 'let') (Identifier ('=' AssignmentExpression)?)+[',']
// //
...@@ -2304,7 +2305,9 @@ Block* Parser::ParseVariableDeclarations( ...@@ -2304,7 +2305,9 @@ Block* Parser::ParseVariableDeclarations(
// Create new block with one expected declaration. // Create new block with one expected declaration.
Block* block = factory()->NewBlock(NULL, 1, true, pos); Block* block = factory()->NewBlock(NULL, 1, true, pos);
int nvars = 0; // the number of variables declared int nvars = 0; // the number of variables declared
int bindings_start = peek_position();
const AstRawString* name = NULL; const AstRawString* name = NULL;
const AstRawString* first_name = NULL;
bool is_for_iteration_variable; bool is_for_iteration_variable;
do { do {
if (fni_ != NULL) fni_->Enter(); if (fni_ != NULL) fni_->Enter();
...@@ -2312,6 +2315,7 @@ Block* Parser::ParseVariableDeclarations( ...@@ -2312,6 +2315,7 @@ Block* Parser::ParseVariableDeclarations(
// Parse variable name. // Parse variable name.
if (nvars > 0) Consume(Token::COMMA); if (nvars > 0) Consume(Token::COMMA);
name = ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK); name = ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK);
if (!first_name) first_name = name;
Scanner::Location variable_loc = scanner()->location(); Scanner::Location variable_loc = scanner()->location();
if (fni_ != NULL) fni_->PushVariableName(name); if (fni_ != NULL) fni_->PushVariableName(name);
...@@ -2519,12 +2523,14 @@ Block* Parser::ParseVariableDeclarations( ...@@ -2519,12 +2523,14 @@ Block* Parser::ParseVariableDeclarations(
if (fni_ != NULL) fni_->Leave(); if (fni_ != NULL) fni_->Leave();
} while (peek() == Token::COMMA); } while (peek() == Token::COMMA);
// If there was a single non-const declaration, return it in the output if (bindings_loc) {
// parameter for possible use by for/in. *bindings_loc =
if (nvars == 1 && (!is_const || is_for_iteration_variable)) { Scanner::Location(bindings_start, scanner()->location().end_pos);
*out = name;
} }
if (num_decl) *num_decl = nvars;
*out = first_name;
return block; return block;
} }
...@@ -3368,15 +3374,27 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels, ...@@ -3368,15 +3374,27 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
(peek() == Token::CONST && is_sloppy(language_mode()))) { (peek() == Token::CONST && is_sloppy(language_mode()))) {
const AstRawString* name = NULL; const AstRawString* name = NULL;
Scanner::Location first_initializer_loc = Scanner::Location::invalid(); Scanner::Location first_initializer_loc = Scanner::Location::invalid();
Scanner::Location bindings_loc = Scanner::Location::invalid();
int num_decl;
Block* variable_statement = ParseVariableDeclarations( Block* variable_statement = ParseVariableDeclarations(
kForStatement, nullptr, &name, &first_initializer_loc, CHECK_OK); kForStatement, &num_decl, nullptr, &name, &first_initializer_loc,
&bindings_loc, CHECK_OK);
bool accept_IN = num_decl >= 1;
bool accept_OF = true; bool accept_OF = true;
ForEachStatement::VisitMode mode; ForEachStatement::VisitMode mode;
int each_beg_pos = scanner()->location().beg_pos; int each_beg_pos = scanner()->location().beg_pos;
int each_end_pos = scanner()->location().end_pos; int each_end_pos = scanner()->location().end_pos;
if (name != NULL && CheckInOrOf(accept_OF, &mode, ok)) { if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) {
if (!*ok) return nullptr; if (!*ok) return nullptr;
if (num_decl != 1) {
const char* loop_type =
mode == ForEachStatement::ITERATE ? "for-of" : "for-in";
ParserTraits::ReportMessageAt(
bindings_loc, "for_inof_loop_multi_bindings", loop_type);
*ok = false;
return nullptr;
}
if (first_initializer_loc.IsValid() && if (first_initializer_loc.IsValid() &&
(is_strict(language_mode()) || mode == ForEachStatement::ITERATE)) { (is_strict(language_mode()) || mode == ForEachStatement::ITERATE)) {
if (mode == ForEachStatement::ITERATE) { if (mode == ForEachStatement::ITERATE) {
...@@ -3417,10 +3435,12 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels, ...@@ -3417,10 +3435,12 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
is_const = peek() == Token::CONST; is_const = peek() == Token::CONST;
const AstRawString* name = NULL; const AstRawString* name = NULL;
Scanner::Location first_initializer_loc = Scanner::Location::invalid(); Scanner::Location first_initializer_loc = Scanner::Location::invalid();
Block* variable_statement = Scanner::Location bindings_loc = Scanner::Location::invalid();
ParseVariableDeclarations(kForStatement, &lexical_bindings, &name, int num_decl;
&first_initializer_loc, CHECK_OK); Block* variable_statement = ParseVariableDeclarations(
bool accept_IN = name != NULL; kForStatement, &num_decl, &lexical_bindings, &name,
&first_initializer_loc, &bindings_loc, CHECK_OK);
bool accept_IN = num_decl >= 1;
bool accept_OF = true; bool accept_OF = true;
ForEachStatement::VisitMode mode; ForEachStatement::VisitMode mode;
int each_beg_pos = scanner()->location().beg_pos; int each_beg_pos = scanner()->location().beg_pos;
...@@ -3428,6 +3448,14 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels, ...@@ -3428,6 +3448,14 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) { if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) {
if (!*ok) return nullptr; if (!*ok) return nullptr;
if (num_decl != 1) {
const char* loop_type =
mode == ForEachStatement::ITERATE ? "for-of" : "for-in";
ParserTraits::ReportMessageAt(
bindings_loc, "for_inof_loop_multi_bindings", loop_type);
*ok = false;
return nullptr;
}
if (first_initializer_loc.IsValid() && if (first_initializer_loc.IsValid() &&
(is_strict(language_mode()) || mode == ForEachStatement::ITERATE)) { (is_strict(language_mode()) || mode == ForEachStatement::ITERATE)) {
if (mode == ForEachStatement::ITERATE) { if (mode == ForEachStatement::ITERATE) {
......
...@@ -909,10 +909,11 @@ class Parser : public ParserBase<ParserTraits> { ...@@ -909,10 +909,11 @@ class Parser : public ParserBase<ParserTraits> {
ZoneList<const AstRawString*>* names, ZoneList<const AstRawString*>* names,
bool* ok); bool* ok);
Block* ParseVariableDeclarations(VariableDeclarationContext var_context, Block* ParseVariableDeclarations(VariableDeclarationContext var_context,
int* num_decl,
ZoneList<const AstRawString*>* names, ZoneList<const AstRawString*>* names,
const AstRawString** out, const AstRawString** out,
Scanner::Location* first_initializer_loc, Scanner::Location* first_initializer_loc,
bool* ok); Scanner::Location* bindings_loc, bool* ok);
Statement* ParseExpressionOrLabelledStatement( Statement* ParseExpressionOrLabelledStatement(
ZoneList<const AstRawString*>* labels, bool* ok); ZoneList<const AstRawString*>* labels, bool* ok);
IfStatement* ParseIfStatement(ZoneList<const AstRawString*>* labels, IfStatement* ParseIfStatement(ZoneList<const AstRawString*>* labels,
......
...@@ -410,8 +410,8 @@ PreParser::Statement PreParser::ParseVariableStatement( ...@@ -410,8 +410,8 @@ PreParser::Statement PreParser::ParseVariableStatement(
// VariableStatement :: // VariableStatement ::
// VariableDeclarations ';' // VariableDeclarations ';'
Statement result = Statement result = ParseVariableDeclarations(var_context, nullptr, nullptr,
ParseVariableDeclarations(var_context, nullptr, nullptr, CHECK_OK); nullptr, CHECK_OK);
ExpectSemicolon(CHECK_OK); ExpectSemicolon(CHECK_OK);
return result; return result;
} }
...@@ -424,7 +424,8 @@ PreParser::Statement PreParser::ParseVariableStatement( ...@@ -424,7 +424,8 @@ PreParser::Statement PreParser::ParseVariableStatement(
// of 'for-in' loops. // of 'for-in' loops.
PreParser::Statement PreParser::ParseVariableDeclarations( PreParser::Statement PreParser::ParseVariableDeclarations(
VariableDeclarationContext var_context, int* num_decl, VariableDeclarationContext var_context, int* num_decl,
Scanner::Location* first_initializer_loc, bool* ok) { Scanner::Location* first_initializer_loc, Scanner::Location* bindings_loc,
bool* ok) {
// VariableDeclarations :: // VariableDeclarations ::
// ('var' | 'const') (Identifier ('=' AssignmentExpression)?)+[','] // ('var' | 'const') (Identifier ('=' AssignmentExpression)?)+[',']
// //
...@@ -478,6 +479,7 @@ PreParser::Statement PreParser::ParseVariableDeclarations( ...@@ -478,6 +479,7 @@ PreParser::Statement PreParser::ParseVariableDeclarations(
// of a let declared variable is the scope of the immediately enclosing // of a let declared variable is the scope of the immediately enclosing
// block. // block.
int nvars = 0; // the number of variables declared int nvars = 0; // the number of variables declared
int bindings_start = peek_position();
do { do {
// Parse variable name. // Parse variable name.
if (nvars > 0) Consume(Token::COMMA); if (nvars > 0) Consume(Token::COMMA);
...@@ -497,6 +499,11 @@ PreParser::Statement PreParser::ParseVariableDeclarations( ...@@ -497,6 +499,11 @@ PreParser::Statement PreParser::ParseVariableDeclarations(
} }
} while (peek() == Token::COMMA); } while (peek() == Token::COMMA);
if (bindings_loc) {
*bindings_loc =
Scanner::Location(bindings_start, scanner()->location().end_pos);
}
if (num_decl != NULL) *num_decl = nvars; if (num_decl != NULL) *num_decl = nvars;
return Statement::Default(); return Statement::Default();
} }
...@@ -732,17 +739,24 @@ PreParser::Statement PreParser::ParseForStatement(bool* ok) { ...@@ -732,17 +739,24 @@ PreParser::Statement PreParser::ParseForStatement(bool* ok) {
ForEachStatement::VisitMode mode; ForEachStatement::VisitMode mode;
if (peek() == Token::VAR || peek() == Token::CONST || if (peek() == Token::VAR || peek() == Token::CONST ||
(peek() == Token::LET && is_strict(language_mode()))) { (peek() == Token::LET && is_strict(language_mode()))) {
bool is_lexical = peek() == Token::LET ||
(peek() == Token::CONST && is_strict(language_mode()));
int decl_count; int decl_count;
Scanner::Location first_initializer_loc = Scanner::Location::invalid(); Scanner::Location first_initializer_loc = Scanner::Location::invalid();
Scanner::Location bindings_loc = Scanner::Location::invalid();
ParseVariableDeclarations(kForStatement, &decl_count, ParseVariableDeclarations(kForStatement, &decl_count,
&first_initializer_loc, CHECK_OK); &first_initializer_loc, &bindings_loc,
bool has_initializers = first_initializer_loc.IsValid(); CHECK_OK);
bool accept_IN = decl_count == 1 && !(is_lexical && has_initializers); bool accept_IN = decl_count >= 1;
bool accept_OF = true; bool accept_OF = true;
if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) { if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) {
if (!*ok) return Statement::Default(); if (!*ok) return Statement::Default();
if (decl_count != 1) {
const char* loop_type =
mode == ForEachStatement::ITERATE ? "for-of" : "for-in";
PreParserTraits::ReportMessageAt(
bindings_loc, "for_inof_loop_multi_bindings", loop_type);
*ok = false;
return Statement::Default();
}
if (first_initializer_loc.IsValid() && if (first_initializer_loc.IsValid() &&
(is_strict(language_mode()) || mode == ForEachStatement::ITERATE)) { (is_strict(language_mode()) || mode == ForEachStatement::ITERATE)) {
if (mode == ForEachStatement::ITERATE) { if (mode == ForEachStatement::ITERATE) {
......
...@@ -1587,6 +1587,7 @@ class PreParser : public ParserBase<PreParserTraits> { ...@@ -1587,6 +1587,7 @@ class PreParser : public ParserBase<PreParserTraits> {
Statement ParseVariableDeclarations(VariableDeclarationContext var_context, Statement ParseVariableDeclarations(VariableDeclarationContext var_context,
int* num_decl, int* num_decl,
Scanner::Location* first_initializer_loc, Scanner::Location* first_initializer_loc,
Scanner::Location* bindings_loc,
bool* ok); bool* ok);
Statement ParseExpressionOrLabelledStatement(bool* ok); Statement ParseExpressionOrLabelledStatement(bool* ok);
Statement ParseIfStatement(bool* ok); Statement ParseIfStatement(bool* ok);
......
...@@ -4628,6 +4628,159 @@ TEST(ConstParsingInForInError) { ...@@ -4628,6 +4628,159 @@ TEST(ConstParsingInForInError) {
} }
TEST(InitializedDeclarationsInStrictForInError) {
const char* context_data[][2] = {{"'use strict';", ""},
{"function foo(){ 'use strict';", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var i = 1 in {}) {}",
"for (var i = void 0 in [1, 2, 3]) {}",
"for (let i = 1 in {}) {}",
"for (let i = void 0 in [1, 2, 3]) {}",
"for (const i = 1 in {}) {}",
"for (const i = void 0 in [1, 2, 3]) {}",
NULL};
RunParserSyncTest(context_data, data, kError);
}
TEST(InitializedDeclarationsInStrictForOfError) {
const char* context_data[][2] = {{"'use strict';", ""},
{"function foo(){ 'use strict';", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var i = 1 of {}) {}",
"for (var i = void 0 of [1, 2, 3]) {}",
"for (let i = 1 of {}) {}",
"for (let i = void 0 of [1, 2, 3]) {}",
"for (const i = 1 of {}) {}",
"for (const i = void 0 of [1, 2, 3]) {}",
NULL};
RunParserSyncTest(context_data, data, kError);
}
TEST(InitializedDeclarationsInSloppyForInError) {
const char* context_data[][2] = {{"", ""},
{"function foo(){", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var i = 1 in {}) {}",
"for (var i = void 0 in [1, 2, 3]) {}",
NULL};
// TODO(caitp): This should be an error in sloppy mode.
RunParserSyncTest(context_data, data, kSuccess);
}
TEST(InitializedDeclarationsInSloppyForOfError) {
const char* context_data[][2] = {{"", ""},
{"function foo(){", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var i = 1 of {}) {}",
"for (var i = void 0 of [1, 2, 3]) {}",
NULL};
RunParserSyncTest(context_data, data, kError);
}
TEST(ForInMultipleDeclarationsError) {
const char* context_data[][2] = {{"", ""},
{"function foo(){", "}"},
{"'use strict';", ""},
{"function foo(){ 'use strict';", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var i, j in {}) {}",
"for (var i, j in [1, 2, 3]) {}",
"for (var i, j = 1 in {}) {}",
"for (var i, j = void 0 in [1, 2, 3]) {}",
"for (let i, j in {}) {}",
"for (let i, j in [1, 2, 3]) {}",
"for (let i, j = 1 in {}) {}",
"for (let i, j = void 0 in [1, 2, 3]) {}",
"for (const i, j in {}) {}",
"for (const i, j in [1, 2, 3]) {}",
"for (const i, j = 1 in {}) {}",
"for (const i, j = void 0 in [1, 2, 3]) {}",
NULL};
static const ParserFlag always_flags[] = {kAllowHarmonySloppy};
RunParserSyncTest(context_data, data, kError, nullptr, 0, always_flags,
arraysize(always_flags));
}
TEST(ForOfMultipleDeclarationsError) {
const char* context_data[][2] = {{"", ""},
{"function foo(){", "}"},
{"'use strict';", ""},
{"function foo(){ 'use strict';", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var i, j of {}) {}",
"for (var i, j of [1, 2, 3]) {}",
"for (var i, j = 1 of {}) {}",
"for (var i, j = void 0 of [1, 2, 3]) {}",
"for (let i, j of {}) {}",
"for (let i, j of [1, 2, 3]) {}",
"for (let i, j = 1 of {}) {}",
"for (let i, j = void 0 of [1, 2, 3]) {}",
"for (const i, j of {}) {}",
"for (const i, j of [1, 2, 3]) {}",
"for (const i, j = 1 of {}) {}",
"for (const i, j = void 0 of [1, 2, 3]) {}",
NULL};
static const ParserFlag always_flags[] = {kAllowHarmonySloppy};
RunParserSyncTest(context_data, data, kError, nullptr, 0, always_flags,
arraysize(always_flags));
}
TEST(ForInNoDeclarationsError) {
const char* context_data[][2] = {{"", ""},
{"function foo(){", "}"},
{"'use strict';", ""},
{"function foo(){ 'use strict';", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var in {}) {}",
"for (const in {}) {}",
NULL};
static const ParserFlag always_flags[] = {kAllowHarmonySloppy};
RunParserSyncTest(context_data, data, kError, nullptr, 0, always_flags,
arraysize(always_flags));
}
TEST(ForOfNoDeclarationsError) {
const char* context_data[][2] = {{"", ""},
{"function foo(){", "}"},
{"'use strict';", ""},
{"function foo(){ 'use strict';", "}"},
{NULL, NULL}};
const char* data[] = {
"for (var of [1, 2, 3]) {}",
"for (const of [1, 2, 3]) {}",
NULL};
static const ParserFlag always_flags[] = {kAllowHarmonySloppy};
RunParserSyncTest(context_data, data, kError, nullptr, 0, always_flags,
arraysize(always_flags));
}
TEST(InvalidUnicodeEscapes) { TEST(InvalidUnicodeEscapes) {
const char* context_data[][2] = {{"", ""}, const char* context_data[][2] = {{"", ""},
{"'use strict';", ""}, {"'use strict';", ""},
......
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