From b54f7d3c464e22ae7240f3bb284d8a5d0e52b9be Mon Sep 17 00:00:00 2001 From: "dslomov@chromium.org" Date: Thu, 23 Oct 2014 11:18:50 +0000 Subject: [PATCH] harmony-scoping: Allow 'const' iteration variables in strict mode. R=rossberg@chromium.org BUG=v8:2506 LOG=N Review URL: https://codereview.chromium.org/671913002 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24834 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/parser.cc | 30 +++++++++----- src/preparser.cc | 13 ++++-- src/preparser.h | 8 +++- test/cctest/test-parsing.cc | 39 ++++++++++++++++++ test/mjsunit/regress/regress-2506.js | 78 ++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 test/mjsunit/regress/regress-2506.js diff --git a/src/parser.cc b/src/parser.cc index bec0a4b..b63d76d 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -2156,6 +2156,7 @@ Block* Parser::ParseVariableDeclarations( Block* block = factory()->NewBlock(NULL, 1, true, pos); int nvars = 0; // the number of variables declared const AstRawString* name = NULL; + bool is_for_iteration_variable; do { if (fni_ != NULL) fni_->Enter(); @@ -2179,6 +2180,13 @@ Block* Parser::ParseVariableDeclarations( // For let/const declarations in harmony mode, we can also immediately // pre-resolve the proxy because it resides in the same scope as the // declaration. + is_for_iteration_variable = + var_context == kForStatement && + (peek() == Token::IN || PeekContextualKeyword(CStrVector("of"))); + if (is_for_iteration_variable && mode == CONST) { + needs_init = false; + } + Interface* interface = is_const ? Interface::NewConst() : Interface::NewValue(); VariableProxy* proxy = NewUnresolved(name, mode, interface); @@ -2224,7 +2232,8 @@ Block* Parser::ParseVariableDeclarations( Expression* value = NULL; int pos = -1; // Harmony consts have non-optional initializers. - if (peek() == Token::ASSIGN || mode == CONST) { + if (peek() == Token::ASSIGN || + (mode == CONST && !is_for_iteration_variable)) { Expect(Token::ASSIGN, CHECK_OK); pos = position(); value = ParseAssignmentExpression(var_context != kForStatement, CHECK_OK); @@ -2357,7 +2366,7 @@ Block* Parser::ParseVariableDeclarations( // If there was a single non-const declaration, return it in the output // parameter for possible use by for/in. - if (nvars == 1 && !is_const) { + if (nvars == 1 && (!is_const || is_for_iteration_variable)) { *out = name; } @@ -3094,7 +3103,8 @@ Statement* Parser::ParseForStatement(ZoneList* labels, Expect(Token::LPAREN, CHECK_OK); for_scope->set_start_position(scanner()->location().beg_pos); if (peek() != Token::SEMICOLON) { - if (peek() == Token::VAR || peek() == Token::CONST) { + if (peek() == Token::VAR || + (peek() == Token::CONST && strict_mode() == SLOPPY)) { bool is_const = peek() == Token::CONST; const AstRawString* name = NULL; VariableDeclarationProperties decl_props = kHasNoInitializers; @@ -3131,8 +3141,10 @@ Statement* Parser::ParseForStatement(ZoneList* labels, } else { init = variable_statement; } - } else if (peek() == Token::LET && strict_mode() == STRICT) { + } else if ((peek() == Token::LET || peek() == Token::CONST) && + strict_mode() == STRICT) { DCHECK(allow_harmony_scoping()); + bool is_const = peek() == Token::CONST; const AstRawString* name = NULL; VariableDeclarationProperties decl_props = kHasNoInitializers; Block* variable_statement = @@ -3145,13 +3157,13 @@ Statement* Parser::ParseForStatement(ZoneList* labels, if (accept_IN && CheckInOrOf(accept_OF, &mode)) { // Rewrite a for-in statement of the form // - // for (let x in e) b + // for (let/const x in e) b // // into // // // for (x' in e) { - // let x; + // let/const x; // x = x'; // b; // } @@ -3171,13 +3183,13 @@ Statement* Parser::ParseForStatement(ZoneList* labels, scope_ = for_scope; Expect(Token::RPAREN, CHECK_OK); - VariableProxy* each = - scope_->NewUnresolved(factory(), name, Interface::NewValue()); + VariableProxy* each = scope_->NewUnresolved(factory(), name); Statement* body = ParseStatement(NULL, CHECK_OK); Block* body_block = factory()->NewBlock(NULL, 3, false, RelocInfo::kNoPosition); + Token::Value init_op = is_const ? Token::INIT_CONST : Token::ASSIGN; Assignment* assignment = factory()->NewAssignment( - Token::ASSIGN, each, temp_proxy, RelocInfo::kNoPosition); + init_op, each, temp_proxy, RelocInfo::kNoPosition); Statement* assignment_statement = factory()->NewExpressionStatement( assignment, RelocInfo::kNoPosition); body_block->AddStatement(variable_statement, zone()); diff --git a/src/preparser.cc b/src/preparser.cc index 987900a..316f129 100644 --- a/src/preparser.cc +++ b/src/preparser.cc @@ -409,6 +409,7 @@ PreParser::Statement PreParser::ParseVariableDeclarations( // ConstBinding :: // BindingPattern '=' AssignmentExpression bool require_initializer = false; + bool is_strict_const = false; if (peek() == Token::VAR) { Consume(Token::VAR); } else if (peek() == Token::CONST) { @@ -430,7 +431,8 @@ PreParser::Statement PreParser::ParseVariableDeclarations( *ok = false; return Statement::Default(); } - require_initializer = true; + is_strict_const = true; + require_initializer = var_context != kForStatement; } else { Scanner::Location location = scanner()->peek_location(); ReportMessageAt(location, "strict_const"); @@ -460,7 +462,9 @@ PreParser::Statement PreParser::ParseVariableDeclarations( if (nvars > 0) Consume(Token::COMMA); ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK); nvars++; - if (peek() == Token::ASSIGN || require_initializer) { + if (peek() == Token::ASSIGN || require_initializer || + // require initializers for multiple consts. + (is_strict_const && peek() == Token::COMMA)) { Expect(Token::ASSIGN, CHECK_OK); ParseAssignmentExpression(var_context != kForStatement, CHECK_OK); if (decl_props != NULL) *decl_props = kHasInitializers; @@ -678,13 +682,14 @@ PreParser::Statement PreParser::ParseForStatement(bool* ok) { if (peek() != Token::SEMICOLON) { if (peek() == Token::VAR || peek() == Token::CONST || (peek() == Token::LET && strict_mode() == STRICT)) { - bool is_let = peek() == Token::LET; + bool is_lexical = peek() == Token::LET || + (peek() == Token::CONST && strict_mode() == STRICT); int decl_count; VariableDeclarationProperties decl_props = kHasNoInitializers; ParseVariableDeclarations( kForStatement, &decl_props, &decl_count, CHECK_OK); bool has_initializers = decl_props == kHasInitializers; - bool accept_IN = decl_count == 1 && !(is_let && has_initializers); + bool accept_IN = decl_count == 1 && !(is_lexical && has_initializers); bool accept_OF = !has_initializers; if (accept_IN && CheckInOrOf(accept_OF)) { ParseExpression(true, CHECK_OK); diff --git a/src/preparser.h b/src/preparser.h index fd82651..a53abfe 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -344,14 +344,18 @@ class ParserBase : public Traits { } bool CheckContextualKeyword(Vector keyword) { - if (peek() == Token::IDENTIFIER && - scanner()->is_next_contextual_keyword(keyword)) { + if (PeekContextualKeyword(keyword)) { Consume(Token::IDENTIFIER); return true; } return false; } + bool PeekContextualKeyword(Vector keyword) { + return peek() == Token::IDENTIFIER && + scanner()->is_next_contextual_keyword(keyword); + } + void ExpectContextualKeyword(Vector keyword, bool* ok) { Expect(Token::IDENTIFIER, ok); if (!*ok) return; diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index 700d104..7ab4722 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -4205,3 +4205,42 @@ TEST(ObjectLiteralPropertyShorthandYieldInGeneratorError) { RunParserSyncTest(context_data, name_data, kError, NULL, 0, always_flags, arraysize(always_flags)); } + + +TEST(ConstParsingInForIn) { + const char* context_data[][2] = {{"'use strict';", ""}, + {"function foo(){ 'use strict';", "}"}, + {NULL, NULL}}; + + const char* data[] = { + "for(const x = 1; ; ) {}", + "for(const x = 1, y = 2;;){}", + "for(const x in [1,2,3]) {}", + "for(const x of [1,2,3]) {}", + NULL}; + static const ParserFlag always_flags[] = {kAllowHarmonyScoping}; + RunParserSyncTest(context_data, data, kSuccess, NULL, 0, always_flags, + arraysize(always_flags)); +} + + +TEST(ConstParsingInForInError) { + const char* context_data[][2] = {{"'use strict';", ""}, + {"function foo(){ 'use strict';", "}"}, + {NULL, NULL}}; + + const char* data[] = { + "for(const x,y = 1; ; ) {}", + "for(const x = 4 in [1,2,3]) {}", + "for(const x = 4, y in [1,2,3]) {}", + "for(const x = 4 of [1,2,3]) {}", + "for(const x = 4, y of [1,2,3]) {}", + "for(const x = 1, y = 2 in []) {}", + "for(const x,y in []) {}", + "for(const x = 1, y = 2 of []) {}", + "for(const x,y of []) {}", + NULL}; + static const ParserFlag always_flags[] = {kAllowHarmonyScoping}; + RunParserSyncTest(context_data, data, kError, NULL, 0, always_flags, + arraysize(always_flags)); +} diff --git a/test/mjsunit/regress/regress-2506.js b/test/mjsunit/regress/regress-2506.js new file mode 100644 index 0000000..e6b37d3 --- /dev/null +++ b/test/mjsunit/regress/regress-2506.js @@ -0,0 +1,78 @@ +// Copyright 2014 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: --harmony-scoping + +'use strict'; + +// Top-level code +let s = 0; +let f = [undefined, undefined, undefined] +for (const x of [1,2,3]) { + s += x; + f[x-1] = function() { return x; } +} +assertEquals(6, s); +assertEquals(1, f[0]()); +assertEquals(2, f[1]()); +assertEquals(3, f[2]()); + +let x = 1; +s = 0; +for (const x of [x, x+1, x+2]) { + s += x; +} +assertEquals(6, s); + +s = 0; +var q = 1; +for (const q of [q, q+1, q+2]) { + s += q; +} +assertEquals(6, s); + +let z = 1; +s = 0; +for (const x = 1; z < 2; z++) { + s += x + z; +} +assertEquals(2, s); + + +s = ""; +for (const x in [1,2,3]) { + s += x; +} +assertEquals("012", s); + +assertThrows(function() { for(const x in [1,2,3]) { x++ } }, SyntaxError); + +// Function scope +(function() { + let s = 0; + for (const x of [1,2,3]) { + s += x; + } + assertEquals(6, s); + + let x = 1; + s = 0; + for (const x of [x, x+1, x+2]) { + s += x; + } + assertEquals(6, s); + + s = 0; + var q = 1; + for (const q of [q, q+1, q+2]) { + s += q; + } + assertEquals(6, s); + + s = ""; + for (const x in [1,2,3]) { + s += x; + } + assertEquals("012", s); +}()); -- 2.7.4