From e8bccc2cb0d781e9396cfefc980736c8aedac29c Mon Sep 17 00:00:00 2001 From: "keuchel@chromium.org" Date: Tue, 25 Oct 2011 08:33:08 +0000 Subject: [PATCH] Block scoped const variables. This implements block scoped 'const' declared variables in harmony mode. They have a temporal dead zone semantics similar to 'let' bindings, i.e. accessing uninitialized 'const' bindings in throws a ReferenceError. As for 'let' bindings, the semantics of 'const' bindings in global scope is not correctly implemented yet. Furthermore assignments to 'const's are silently ignored. Another CL will introduce treatment of those assignments as early errors. Review URL: http://codereview.chromium.org/7992005 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9764 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/full-codegen-arm.cc | 39 ++++++--- src/ast.h | 5 +- src/contexts.cc | 13 ++- src/contexts.h | 41 ++++++--- src/full-codegen.cc | 4 +- src/hydrogen.cc | 4 +- src/ia32/full-codegen-ia32.cc | 41 ++++++--- src/messages.js | 1 + src/objects.h | 2 +- src/parser.cc | 87 ++++++++++++++----- src/parser.h | 4 +- src/preparser.cc | 29 ++++++- src/runtime.cc | 27 ++++-- src/scopeinfo.cc | 14 ++- src/scopes.cc | 11 ++- src/scopes.h | 2 +- src/token.h | 1 + src/v8globals.h | 8 +- src/variables.cc | 1 + src/variables.h | 9 ++ src/x64/full-codegen-x64.cc | 44 +++++++--- test/mjsunit/harmony/block-conflicts.js | 5 ++ test/mjsunit/harmony/block-let-declaration.js | 32 ++++++- test/mjsunit/harmony/block-let-semantics.js | 28 +++++- test/mjsunit/harmony/block-scoping.js | 54 +++++++++++- 25 files changed, 395 insertions(+), 111 deletions(-) diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 43e5c6a70..497a29546 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -269,7 +269,10 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { // constant. if (scope()->is_function_scope() && scope()->function() != NULL) { int ignored = 0; - EmitDeclaration(scope()->function(), CONST, NULL, &ignored); + VariableProxy* proxy = scope()->function(); + ASSERT(proxy->var()->mode() == CONST || + proxy->var()->mode() == CONST_HARMONY); + EmitDeclaration(proxy, proxy->var()->mode(), NULL, &ignored); } VisitDeclarations(scope()->declarations()); } @@ -718,6 +721,8 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, // need to "declare" it at runtime to make sure it actually exists in the // local context. Variable* variable = proxy->var(); + bool binding_needs_init = + mode == CONST || mode == CONST_HARMONY || mode == LET; switch (variable->location()) { case Variable::UNALLOCATED: ++(*global_count); @@ -729,7 +734,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, Comment cmnt(masm_, "[ Declaration"); VisitForAccumulatorValue(function); __ str(result_register(), StackOperand(variable)); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { Comment cmnt(masm_, "[ Declaration"); __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); __ str(ip, StackOperand(variable)); @@ -763,7 +768,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); PrepareForBailoutForId(proxy->id(), NO_REGISTERS); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { Comment cmnt(masm_, "[ Declaration"); __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); __ str(ip, ContextOperand(cp, variable->index())); @@ -775,9 +780,13 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, case Variable::LOOKUP: { Comment cmnt(masm_, "[ Declaration"); __ mov(r2, Operand(variable->name())); - // Declaration nodes are always introduced in one of three modes. - ASSERT(mode == VAR || mode == CONST || mode == LET); - PropertyAttributes attr = (mode == CONST) ? READ_ONLY : NONE; + // Declaration nodes are always introduced in one of four modes. + ASSERT(mode == VAR || + mode == CONST || + mode == CONST_HARMONY || + mode == LET); + PropertyAttributes attr = (mode == CONST || mode == CONST_HARMONY) + ? READ_ONLY : NONE; __ mov(r1, Operand(Smi::FromInt(attr))); // Push initial value, if any. // Note: For variables we must not push an initial value (such as @@ -787,7 +796,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, __ Push(cp, r2, r1); // Push initial value for function declaration. VisitForStackValue(function); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { __ LoadRoot(r0, Heap::kTheHoleValueRootIndex); __ Push(cp, r2, r1, r0); } else { @@ -1242,11 +1251,12 @@ void FullCodeGenerator::EmitDynamicLookupFastCase(Variable* var, Variable* local = var->local_if_not_shadowed(); __ ldr(r0, ContextSlotOperandCheckExtensions(local, slow)); if (local->mode() == CONST || + local->mode() == CONST_HARMONY || local->mode() == LET) { __ CompareRoot(r0, Heap::kTheHoleValueRootIndex); if (local->mode() == CONST) { __ LoadRoot(r0, Heap::kUndefinedValueRootIndex, eq); - } else { // LET + } else { // LET || CONST_HARMONY __ b(ne, done); __ mov(r0, Operand(var->name())); __ push(r0); @@ -1284,13 +1294,15 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { Comment cmnt(masm_, var->IsContextSlot() ? "Context variable" : "Stack variable"); - if (var->mode() != LET && var->mode() != CONST) { + if (!var->binding_needs_init()) { context()->Plug(var); } else { // Let and const need a read barrier. GetVar(r0, var); __ CompareRoot(r0, Heap::kTheHoleValueRootIndex); - if (var->mode() == LET) { + if (var->mode() == LET || var->mode() == CONST_HARMONY) { + // Throw a reference error when using an uninitialized let/const + // binding in harmony mode. Label done; __ b(ne, &done); __ mov(r0, Operand(var->name())); @@ -1298,6 +1310,8 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { __ CallRuntime(Runtime::kThrowReferenceError, 1); __ bind(&done); } else { + // Uninitalized const bindings outside of harmony mode are unholed. + ASSERT(var->mode() == CONST); __ LoadRoot(r0, Heap::kUndefinedValueRootIndex, eq); } context()->Plug(r0); @@ -1965,8 +1979,9 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } } - } else if (var->mode() != CONST) { - // Assignment to var or initializing assignment to let. + } else if (!var->is_const_mode() || op == Token::INIT_CONST_HARMONY) { + // Assignment to var or initializing assignment to let/const + // in harmony mode. if (var->IsStackAllocated() || var->IsContextSlot()) { MemOperand location = VarOperand(var, r1); if (FLAG_debug_code && op == Token::INIT_LET) { diff --git a/src/ast.h b/src/ast.h index e5aa57ea0..3de00ef5d 100644 --- a/src/ast.h +++ b/src/ast.h @@ -405,7 +405,10 @@ class Declaration: public AstNode { mode_(mode), fun_(fun), scope_(scope) { - ASSERT(mode == VAR || mode == CONST || mode == LET); + ASSERT(mode == VAR || + mode == CONST || + mode == CONST_HARMONY || + mode == LET); // At the moment there are no "const functions"'s in JavaScript... ASSERT(fun == NULL || mode == VAR || mode == LET); } diff --git a/src/contexts.cc b/src/contexts.cc index 04e58e578..b25ffac93 100644 --- a/src/contexts.cc +++ b/src/contexts.cc @@ -174,6 +174,10 @@ Handle Context::Lookup(Handle name, *attributes = READ_ONLY; *binding_flags = IMMUTABLE_CHECK_INITIALIZED; break; + case CONST_HARMONY: + *attributes = READ_ONLY; + *binding_flags = IMMUTABLE_CHECK_INITIALIZED_HARMONY; + break; case DYNAMIC: case DYNAMIC_GLOBAL: case DYNAMIC_LOCAL: @@ -187,7 +191,8 @@ Handle Context::Lookup(Handle name, // Check the slot corresponding to the intermediate context holding // only the function name variable. if (follow_context_chain && context->IsFunctionContext()) { - int function_index = scope_info->FunctionContextSlotIndex(*name); + VariableMode mode; + int function_index = scope_info->FunctionContextSlotIndex(*name, &mode); if (function_index >= 0) { if (FLAG_trace_contexts) { PrintF("=> found intermediate function in context slot %d\n", @@ -195,7 +200,9 @@ Handle Context::Lookup(Handle name, } *index = function_index; *attributes = READ_ONLY; - *binding_flags = IMMUTABLE_IS_INITIALIZED; + ASSERT(mode == CONST || mode == CONST_HARMONY); + *binding_flags = (mode == CONST) + ? IMMUTABLE_IS_INITIALIZED : IMMUTABLE_IS_INITIALIZED_HARMONY; return context; } } @@ -255,7 +262,7 @@ bool Context::GlobalIfNotShadowedByEval(Handle name) { if (param_index >= 0) return false; // Check context only holding the function name variable. - index = scope_info->FunctionContextSlotIndex(*name); + index = scope_info->FunctionContextSlotIndex(*name, NULL); if (index >= 0) return false; context = context->previous(); } diff --git a/src/contexts.h b/src/contexts.h index c30fe6e9a..7021ff8fb 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -46,24 +46,43 @@ enum ContextLookupFlags { // ES5 10.2 defines lexical environments with mutable and immutable bindings. // Immutable bindings have two states, initialized and uninitialized, and -// their state is changed by the InitializeImmutableBinding method. +// their state is changed by the InitializeImmutableBinding method. The +// BindingFlags enum represents information if a binding has definitely been +// initialized. A mutable binding does not need to be checked and thus has +// the BindingFlag MUTABLE_IS_INITIALIZED. +// +// There are two possibilities for immutable bindings +// * 'const' declared variables. They are initialized when evaluating the +// corresponding declaration statement. They need to be checked for being +// initialized and thus get the flag IMMUTABLE_CHECK_INITIALIZED. +// * The function name of a named function literal. The binding is immediately +// initialized when entering the function and thus does not need to be +// checked. it gets the BindingFlag IMMUTABLE_IS_INITIALIZED. +// Accessing an uninitialized binding produces the undefined value. // // The harmony proposal for block scoped bindings also introduces the -// uninitialized state for mutable bindings. A 'let' declared variable -// is a mutable binding that is created uninitalized upon activation of its -// lexical environment and it is initialized when evaluating its declaration -// statement. Var declared variables are mutable bindings that are -// immediately initialized upon creation. The BindingFlags enum represents -// information if a binding has definitely been initialized. 'const' declared -// variables are created as uninitialized immutable bindings. - -// In harmony mode accessing an uninitialized binding produces a reference -// error. +// uninitialized state for mutable bindings. +// * A 'let' declared variable. They are initialized when evaluating the +// corresponding declaration statement. They need to be checked for being +// initialized and thus get the flag MUTABLE_CHECK_INITIALIZED. +// * A 'var' declared variable. It is initialized immediately upon creation +// and thus doesn't need to be checked. It gets the flag +// MUTABLE_IS_INITIALIZED. +// * Catch bound variables, function parameters and variables introduced by +// function declarations are initialized immediately and do not need to be +// checked. Thus they get the flag MUTABLE_IS_INITIALIZED. +// Immutable bindings in harmony mode get the _HARMONY flag variants. Accessing +// an uninitialized binding produces a reference error. +// +// In V8 uninitialized bindings are set to the hole value upon creation and set +// to a different value upon initialization. enum BindingFlags { MUTABLE_IS_INITIALIZED, MUTABLE_CHECK_INITIALIZED, IMMUTABLE_IS_INITIALIZED, IMMUTABLE_CHECK_INITIALIZED, + IMMUTABLE_IS_INITIALIZED_HARMONY, + IMMUTABLE_CHECK_INITIALIZED_HARMONY, MISSING_BINDING }; diff --git a/src/full-codegen.cc b/src/full-codegen.cc index efcd3461d..27c509f77 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -521,8 +521,8 @@ void FullCodeGenerator::VisitDeclarations( if (var->IsUnallocated()) { array->set(j++, *(var->name())); if (decl->fun() == NULL) { - if (var->mode() == CONST) { - // In case this is const property use the hole. + if (var->binding_needs_init()) { + // In case this binding needs initialization use the hole. array->set_the_hole(j++); } else { array->set_undefined(j++); diff --git a/src/hydrogen.cc b/src/hydrogen.cc index d45c4ee77..6c33609d1 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -6048,7 +6048,9 @@ void HGraphBuilder::VisitDeclaration(Declaration* decl) { void HGraphBuilder::HandleDeclaration(VariableProxy* proxy, VariableMode mode, FunctionLiteral* function) { - if (mode == LET) return Bailout("unsupported let declaration"); + if (mode == LET || mode == CONST_HARMONY) { + return Bailout("unsupported harmony declaration"); + } Variable* var = proxy->var(); switch (var->location()) { case Variable::UNALLOCATED: diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index dd697bc98..de5dc06eb 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -266,7 +266,10 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { // constant. if (scope()->is_function_scope() && scope()->function() != NULL) { int ignored = 0; - EmitDeclaration(scope()->function(), CONST, NULL, &ignored); + VariableProxy* proxy = scope()->function(); + ASSERT(proxy->var()->mode() == CONST || + proxy->var()->mode() == CONST_HARMONY); + EmitDeclaration(proxy, proxy->var()->mode(), NULL, &ignored); } VisitDeclarations(scope()->declarations()); } @@ -711,6 +714,8 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, // need to "declare" it at runtime to make sure it actually exists in the // local context. Variable* variable = proxy->var(); + bool binding_needs_init = + mode == CONST || mode == CONST_HARMONY || mode == LET; switch (variable->location()) { case Variable::UNALLOCATED: ++(*global_count); @@ -722,7 +727,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, Comment cmnt(masm_, "[ Declaration"); VisitForAccumulatorValue(function); __ mov(StackOperand(variable), result_register()); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { Comment cmnt(masm_, "[ Declaration"); __ mov(StackOperand(variable), Immediate(isolate()->factory()->the_hole_value())); @@ -754,7 +759,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); PrepareForBailoutForId(proxy->id(), NO_REGISTERS); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { Comment cmnt(masm_, "[ Declaration"); __ mov(ContextOperand(esi, variable->index()), Immediate(isolate()->factory()->the_hole_value())); @@ -767,9 +772,13 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, Comment cmnt(masm_, "[ Declaration"); __ push(esi); __ push(Immediate(variable->name())); - // Declaration nodes are always introduced in one of three modes. - ASSERT(mode == VAR || mode == CONST || mode == LET); - PropertyAttributes attr = (mode == CONST) ? READ_ONLY : NONE; + // Declaration nodes are always introduced in one of four modes. + ASSERT(mode == VAR || + mode == CONST || + mode == CONST_HARMONY || + mode == LET); + PropertyAttributes attr = (mode == CONST || mode == CONST_HARMONY) + ? READ_ONLY : NONE; __ push(Immediate(Smi::FromInt(attr))); // Push initial value, if any. // Note: For variables we must not push an initial value (such as @@ -778,7 +787,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, increment_stack_height(3); if (function != NULL) { VisitForStackValue(function); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { __ push(Immediate(isolate()->factory()->the_hole_value())); increment_stack_height(); } else { @@ -1226,12 +1235,13 @@ void FullCodeGenerator::EmitDynamicLookupFastCase(Variable* var, Variable* local = var->local_if_not_shadowed(); __ mov(eax, ContextSlotOperandCheckExtensions(local, slow)); if (local->mode() == CONST || + local->mode() == CONST_HARMONY || local->mode() == LET) { __ cmp(eax, isolate()->factory()->the_hole_value()); __ j(not_equal, done); if (local->mode() == CONST) { __ mov(eax, isolate()->factory()->undefined_value()); - } else { // LET + } else { // LET || CONST_HARMONY __ push(Immediate(var->name())); __ CallRuntime(Runtime::kThrowReferenceError, 1); } @@ -1267,7 +1277,7 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { Comment cmnt(masm_, var->IsContextSlot() ? "Context variable" : "Stack variable"); - if (var->mode() != LET && var->mode() != CONST) { + if (!var->binding_needs_init()) { context()->Plug(var); } else { // Let and const need a read barrier. @@ -1275,10 +1285,14 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { GetVar(eax, var); __ cmp(eax, isolate()->factory()->the_hole_value()); __ j(not_equal, &done, Label::kNear); - if (var->mode() == LET) { + if (var->mode() == LET || var->mode() == CONST_HARMONY) { + // Throw a reference error when using an uninitialized let/const + // binding in harmony mode. __ push(Immediate(var->name())); __ CallRuntime(Runtime::kThrowReferenceError, 1); - } else { // CONST + } else { + // Uninitalized const bindings outside of harmony mode are unholed. + ASSERT(var->mode() == CONST); __ mov(eax, isolate()->factory()->undefined_value()); } __ bind(&done); @@ -1961,8 +1975,9 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } } - } else if (var->mode() != CONST) { - // Assignment to var or initializing assignment to let. + } else if (!var->is_const_mode() || op == Token::INIT_CONST_HARMONY) { + // Assignment to var or initializing assignment to let/const + // in harmony mode. if (var->IsStackAllocated() || var->IsContextSlot()) { MemOperand location = VarOperand(var, ecx); if (FLAG_debug_code && op == Token::INIT_LET) { diff --git a/src/messages.js b/src/messages.js index 3413ce578..4a81e95b6 100644 --- a/src/messages.js +++ b/src/messages.js @@ -241,6 +241,7 @@ function FormatMessage(message) { "strict_poison_pill", ["'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them"], "strict_caller", ["Illegal access to a strict mode caller function."], "unprotected_let", ["Illegal let declaration in unprotected statement context."], + "unprotected_const", ["Illegal const declaration in unprotected statement context."], "cant_prevent_ext_external_array_elements", ["Cannot prevent extension of an object with external array elements"], "redef_external_array_element", ["Cannot redefine a property of an object with external array elements"], ]; diff --git a/src/objects.h b/src/objects.h index 6ee527d31..40c40660d 100644 --- a/src/objects.h +++ b/src/objects.h @@ -3113,7 +3113,7 @@ class SerializedScopeInfo : public FixedArray { // function context slot index if the function name is present (named // function expressions, only), otherwise returns a value < 0. The name // must be a symbol (canonicalized). - int FunctionContextSlotIndex(String* name); + int FunctionContextSlotIndex(String* name, VariableMode* mode); static Handle Create(Scope* scope); diff --git a/src/parser.cc b/src/parser.cc index d4e507e20..3c6c4ba1e 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -1138,14 +1138,14 @@ Statement* Parser::ParseSourceElement(ZoneStringList* labels, // In harmony mode we allow additionally the following productions // SourceElement: // LetDeclaration + // ConstDeclaration if (peek() == Token::FUNCTION) { return ParseFunctionDeclaration(ok); - } else if (peek() == Token::LET) { + } else if (peek() == Token::LET || peek() == Token::CONST) { return ParseVariableStatement(kSourceElement, ok); - } else { - return ParseStatement(labels, ok); } + return ParseStatement(labels, ok); } @@ -1363,6 +1363,10 @@ VariableProxy* Parser::Declare(Handle name, // If we are inside a function, a declaration of a var/const variable is a // truly local variable, and the scope of the variable is always the function // scope. + // Let/const variables in harmony mode are always added to the immediately + // enclosing scope. + Scope* declaration_scope = (mode == LET || mode == CONST_HARMONY) + ? top_scope_ : top_scope_->DeclarationScope(); // If a function scope exists, then we can statically declare this // variable and also set its mode. In any case, a Declaration node @@ -1372,9 +1376,8 @@ VariableProxy* Parser::Declare(Handle name, // to the calling function context. // Similarly, strict mode eval scope does not leak variable declarations to // the caller's scope so we declare all locals, too. - - Scope* declaration_scope = mode == LET ? top_scope_ - : top_scope_->DeclarationScope(); + // Also for block scoped let/const bindings the variable can be + // statically declared. if (declaration_scope->is_function_scope() || declaration_scope->is_strict_mode_eval_scope() || declaration_scope->is_block_scope()) { @@ -1399,6 +1402,7 @@ VariableProxy* Parser::Declare(Handle name, // We only have vars, consts and lets in declarations. ASSERT(var->mode() == VAR || var->mode() == CONST || + var->mode() == CONST_HARMONY || var->mode() == LET); if (harmony_scoping_) { // In harmony mode we treat re-declarations as early errors. See @@ -1410,8 +1414,8 @@ VariableProxy* Parser::Declare(Handle name, *ok = false; return NULL; } - const char* type = (var->mode() == VAR) ? "var" : - (var->mode() == CONST) ? "const" : "let"; + const char* type = (var->mode() == VAR) + ? "var" : var->is_const_mode() ? "const" : "let"; Handle type_string = isolate()->factory()->NewStringFromUtf8(CStrVector(type), TENURED); Expression* expression = @@ -1444,7 +1448,8 @@ VariableProxy* Parser::Declare(Handle name, new(zone()) Declaration(proxy, mode, fun, top_scope_)); // For global const variables we bind the proxy to a variable. - if (mode == CONST && declaration_scope->is_global_scope()) { + if ((mode == CONST || mode == CONST_HARMONY) && + declaration_scope->is_global_scope()) { ASSERT(resolve); // should be set by all callers Variable::Kind kind = Variable::NORMAL; var = new(zone()) Variable(declaration_scope, name, CONST, true, kind); @@ -1651,8 +1656,18 @@ Block* Parser::ParseVariableDeclarations( Handle* out, bool* ok) { // VariableDeclarations :: - // ('var' | 'const') (Identifier ('=' AssignmentExpression)?)+[','] - + // ('var' | 'const' | 'let') (Identifier ('=' AssignmentExpression)?)+[','] + // + // The ES6 Draft Rev3 specifies the following grammar for const declarations + // + // ConstDeclaration :: + // const ConstBinding (',' ConstBinding)* ';' + // ConstBinding :: + // Identifier '=' AssignmentExpression + // + // TODO(ES6): + // ConstBinding :: + // BindingPattern '=' AssignmentExpression VariableMode mode = VAR; // True if the binding needs initialization. 'let' and 'const' declared // bindings are created uninitialized by their declaration nodes and @@ -1665,19 +1680,32 @@ Block* Parser::ParseVariableDeclarations( Consume(Token::VAR); } else if (peek() == Token::CONST) { Consume(Token::CONST); - if (top_scope_->is_strict_mode()) { + if (harmony_scoping_) { + if (var_context != kSourceElement && + var_context != kForStatement) { + // In harmony mode 'const' declarations are only allowed in source + // element positions. + ReportMessage("unprotected_const", Vector::empty()); + *ok = false; + return NULL; + } + mode = CONST_HARMONY; + init_op = Token::INIT_CONST_HARMONY; + } else if (top_scope_->is_strict_mode()) { ReportMessage("strict_const", Vector::empty()); *ok = false; return NULL; + } else { + mode = CONST; + init_op = Token::INIT_CONST; } - mode = CONST; is_const = true; needs_init = true; - init_op = Token::INIT_CONST; } else if (peek() == Token::LET) { Consume(Token::LET); if (var_context != kSourceElement && var_context != kForStatement) { + // Let declarations are only allowed in source element positions. ASSERT(var_context == kStatement); ReportMessage("unprotected_let", Vector::empty()); *ok = false; @@ -1690,7 +1718,7 @@ Block* Parser::ParseVariableDeclarations( UNREACHABLE(); // by current callers } - Scope* declaration_scope = (mode == LET) + Scope* declaration_scope = (mode == LET || mode == CONST_HARMONY) ? top_scope_ : top_scope_->DeclarationScope(); // The scope of a var/const declared variable anywhere inside a function // is the entire function (ECMA-262, 3rd, 10.1.3, and 12.2). Thus we can @@ -1735,8 +1763,10 @@ Block* Parser::ParseVariableDeclarations( // If we have a const declaration, in an inner scope, the proxy is always // bound to the declared variable (independent of possibly surrounding with // statements). - Declare(name, mode, NULL, is_const /* always bound for CONST! */, - CHECK_OK); + // 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. + Declare(name, mode, NULL, mode != VAR, CHECK_OK); nvars++; if (declaration_scope->num_var_or_const() > kMaxNumFunctionLocals) { ReportMessageAt(scanner().location(), "too_many_variables", @@ -1775,7 +1805,8 @@ Block* Parser::ParseVariableDeclarations( Scope* initialization_scope = is_const ? declaration_scope : top_scope_; Expression* value = NULL; int position = -1; - if (peek() == Token::ASSIGN) { + // Harmony consts have non-optional initializers. + if (peek() == Token::ASSIGN || mode == CONST_HARMONY) { Expect(Token::ASSIGN, CHECK_OK); position = scanner().location().beg_pos; value = ParseAssignmentExpression(var_context != kForStatement, CHECK_OK); @@ -1814,7 +1845,6 @@ Block* Parser::ParseVariableDeclarations( // declaration statement has been executed. This is important in // browsers where the global object (window) has lots of // properties defined in prototype objects. - if (initialization_scope->is_global_scope()) { // Compute the arguments for the runtime call. ZoneList* arguments = new(zone()) ZoneList(3); @@ -1876,9 +1906,9 @@ Block* Parser::ParseVariableDeclarations( // dynamically looked-up variables and constants (the start context // for constant lookups is always the function context, while it is // the top context for var declared variables). Sigh... - // For 'let' declared variables the initialization is in the same scope - // as the declaration. Thus dynamic lookups are unnecessary even if the - // block scope is inside a with. + // For 'let' and 'const' declared variables in harmony mode the + // initialization is in the same scope as the declaration. Thus dynamic + // lookups are unnecessary even if the block scope is inside a with. if (value != NULL) { VariableProxy* proxy = initialization_scope->NewUnresolved(name); Assignment* assignment = @@ -3927,12 +3957,21 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle function_name, // future we can change the AST to only refer to VariableProxies // instead of Variables and Proxis as is the case now. if (type == FunctionLiteral::NAMED_EXPRESSION) { - Variable* fvar = top_scope_->DeclareFunctionVar(function_name); + VariableMode fvar_mode; + Token::Value fvar_init_op; + if (harmony_scoping_) { + fvar_mode = CONST_HARMONY; + fvar_init_op = Token::INIT_CONST_HARMONY; + } else { + fvar_mode = CONST; + fvar_init_op = Token::INIT_CONST; + } + Variable* fvar = top_scope_->DeclareFunctionVar(function_name, fvar_mode); VariableProxy* fproxy = top_scope_->NewUnresolved(function_name); fproxy->BindTo(fvar); body->Add(new(zone()) ExpressionStatement( new(zone()) Assignment(isolate(), - Token::INIT_CONST, + fvar_init_op, fproxy, new(zone()) ThisFunction(isolate()), RelocInfo::kNoPosition))); diff --git a/src/parser.h b/src/parser.h index 5c53cf8c8..268b09474 100644 --- a/src/parser.h +++ b/src/parser.h @@ -500,7 +500,6 @@ class Parser { Statement* ParseFunctionDeclaration(bool* ok); Statement* ParseNativeDeclaration(bool* ok); Block* ParseBlock(ZoneStringList* labels, bool* ok); - Block* ParseScopedBlock(ZoneStringList* labels, bool* ok); Block* ParseVariableStatement(VariableDeclarationContext var_context, bool* ok); Block* ParseVariableDeclarations(VariableDeclarationContext var_context, @@ -524,6 +523,9 @@ class Parser { TryStatement* ParseTryStatement(bool* ok); DebuggerStatement* ParseDebuggerStatement(bool* ok); + // Support for hamony block scoped bindings. + Block* ParseScopedBlock(ZoneStringList* labels, bool* ok); + Expression* ParseExpression(bool accept_IN, bool* ok); Expression* ParseAssignmentExpression(bool accept_IN, bool* ok); Expression* ParseConditionalExpression(bool accept_IN, bool* ok); diff --git a/src/preparser.cc b/src/preparser.cc index 492ae7b21..3313658ef 100644 --- a/src/preparser.cc +++ b/src/preparser.cc @@ -125,11 +125,13 @@ PreParser::Statement PreParser::ParseSourceElement(bool* ok) { // In harmony mode we allow additionally the following productions // SourceElement: // LetDeclaration + // ConstDeclaration switch (peek()) { case i::Token::FUNCTION: return ParseFunctionDeclaration(ok); case i::Token::LET: + case i::Token::CONST: return ParseVariableStatement(kSourceElement, ok); default: return ParseStatement(ok); @@ -331,11 +333,32 @@ PreParser::Statement PreParser::ParseVariableDeclarations( bool* ok) { // VariableDeclarations :: // ('var' | 'const') (Identifier ('=' AssignmentExpression)?)+[','] - + // + // The ES6 Draft Rev3 specifies the following grammar for const declarations + // + // ConstDeclaration :: + // const ConstBinding (',' ConstBinding)* ';' + // ConstBinding :: + // Identifier '=' AssignmentExpression + // + // TODO(ES6): + // ConstBinding :: + // BindingPattern '=' AssignmentExpression + bool require_initializer = false; if (peek() == i::Token::VAR) { Consume(i::Token::VAR); } else if (peek() == i::Token::CONST) { - if (strict_mode()) { + if (harmony_scoping_) { + if (var_context != kSourceElement && + var_context != kForStatement) { + i::Scanner::Location location = scanner_->peek_location(); + ReportMessageAt(location.beg_pos, location.end_pos, + "unprotected_const", NULL); + *ok = false; + return Statement::Default(); + } + require_initializer = true; + } else if (strict_mode()) { i::Scanner::Location location = scanner_->peek_location(); ReportMessageAt(location, "strict_const", NULL); *ok = false; @@ -374,7 +397,7 @@ PreParser::Statement PreParser::ParseVariableDeclarations( return Statement::Default(); } nvars++; - if (peek() == i::Token::ASSIGN) { + if (peek() == i::Token::ASSIGN || require_initializer) { Expect(i::Token::ASSIGN, CHECK_OK); ParseAssignmentExpression(var_context != kForStatement, CHECK_OK); if (decl_props != NULL) *decl_props = kHasInitializers; diff --git a/src/runtime.cc b/src/runtime.cc index 95ced25f9..f1de96c0d 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -8865,13 +8865,26 @@ static ObjectPair LoadContextSlotHelper(Arguments args, Handle receiver = isolate->factory()->the_hole_value(); Object* value = Context::cast(*holder)->get(index); // Check for uninitialized bindings. - if (binding_flags == MUTABLE_CHECK_INITIALIZED && value->IsTheHole()) { - Handle reference_error = - isolate->factory()->NewReferenceError("not_defined", - HandleVector(&name, 1)); - return MakePair(isolate->Throw(*reference_error), NULL); - } else { - return MakePair(Unhole(isolate->heap(), value, attributes), *receiver); + switch (binding_flags) { + case MUTABLE_CHECK_INITIALIZED: + case IMMUTABLE_CHECK_INITIALIZED_HARMONY: + if (value->IsTheHole()) { + Handle reference_error = + isolate->factory()->NewReferenceError("not_defined", + HandleVector(&name, 1)); + return MakePair(isolate->Throw(*reference_error), NULL); + } + // FALLTHROUGH + case MUTABLE_IS_INITIALIZED: + case IMMUTABLE_IS_INITIALIZED: + case IMMUTABLE_IS_INITIALIZED_HARMONY: + ASSERT(!value->IsTheHole()); + return MakePair(value, *receiver); + case IMMUTABLE_CHECK_INITIALIZED: + return MakePair(Unhole(isolate->heap(), value, attributes), *receiver); + case MISSING_BINDING: + UNREACHABLE(); + return MakePair(NULL, NULL); } } diff --git a/src/scopeinfo.cc b/src/scopeinfo.cc index 47b541ea4..8ea5f1e73 100644 --- a/src/scopeinfo.cc +++ b/src/scopeinfo.cc @@ -139,7 +139,7 @@ ScopeInfo::ScopeInfo(Scope* scope) ASSERT(proxy->var()->index() - Context::MIN_CONTEXT_SLOTS == context_modes_.length()); context_slots_.Add(FACTORY->empty_symbol()); - context_modes_.Add(INTERNAL); + context_modes_.Add(proxy->var()->mode()); } } } @@ -540,16 +540,24 @@ int SerializedScopeInfo::ParameterIndex(String* name) { } -int SerializedScopeInfo::FunctionContextSlotIndex(String* name) { +int SerializedScopeInfo::FunctionContextSlotIndex(String* name, + VariableMode* mode) { ASSERT(name->IsSymbol()); if (length() > 0) { Object** p = data_start(); if (*p == name) { p = ContextEntriesAddr(); int number_of_context_slots; - ReadInt(p, &number_of_context_slots); + p = ReadInt(p, &number_of_context_slots); ASSERT(number_of_context_slots != 0); // The function context slot is the last entry. + if (mode != NULL) { + // Seek to context slot entry. + p += (number_of_context_slots - 1) * 2; + // Seek to mode. + ++p; + ReadInt(p, mode); + } return number_of_context_slots + Context::MIN_CONTEXT_SLOTS - 1; } } diff --git a/src/scopes.cc b/src/scopes.cc index 5e49d8013..3167c4d09 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -379,7 +379,7 @@ Variable* Scope::LocalLookup(Handle name) { index = scope_info_->ParameterIndex(*name); if (index < 0) { // Check the function name. - index = scope_info_->FunctionContextSlotIndex(*name); + index = scope_info_->FunctionContextSlotIndex(*name, NULL); if (index < 0) return NULL; } } @@ -402,10 +402,10 @@ Variable* Scope::Lookup(Handle name) { } -Variable* Scope::DeclareFunctionVar(Handle name) { +Variable* Scope::DeclareFunctionVar(Handle name, VariableMode mode) { ASSERT(is_function_scope() && function_ == NULL); Variable* function_var = - new Variable(this, name, CONST, true, Variable::NORMAL); + new Variable(this, name, mode, true, Variable::NORMAL); function_ = new(isolate_->zone()) VariableProxy(isolate_, function_var); return function_var; } @@ -425,7 +425,10 @@ Variable* Scope::DeclareLocal(Handle name, VariableMode mode) { // This function handles VAR and CONST modes. DYNAMIC variables are // introduces during variable allocation, INTERNAL variables are allocated // explicitly, and TEMPORARY variables are allocated via NewTemporary(). - ASSERT(mode == VAR || mode == CONST || mode == LET); + ASSERT(mode == VAR || + mode == CONST || + mode == CONST_HARMONY || + mode == LET); ++num_var_or_const_; return variables_.Declare(this, name, mode, true, Variable::NORMAL); } diff --git a/src/scopes.h b/src/scopes.h index 922d8d131..a1418874e 100644 --- a/src/scopes.h +++ b/src/scopes.h @@ -122,7 +122,7 @@ class Scope: public ZoneObject { // Declare the function variable for a function literal. This variable // is in an intermediate scope between this function scope and the the // outer scope. Only possible for function scopes; at most one variable. - Variable* DeclareFunctionVar(Handle name); + Variable* DeclareFunctionVar(Handle name, VariableMode mode); // Declare a parameter in this scope. When there are duplicated // parameters the rightmost one 'wins'. However, the implementation diff --git a/src/token.h b/src/token.h index de4972dd7..7a2156c95 100644 --- a/src/token.h +++ b/src/token.h @@ -73,6 +73,7 @@ namespace internal { T(INIT_VAR, "=init_var", 2) /* AST-use only. */ \ T(INIT_LET, "=init_let", 2) /* AST-use only. */ \ T(INIT_CONST, "=init_const", 2) /* AST-use only. */ \ + T(INIT_CONST_HARMONY, "=init_const_harmony", 2) /* AST-use only. */ \ T(ASSIGN, "=", 2) \ T(ASSIGN_BIT_OR, "|=", 2) \ T(ASSIGN_BIT_XOR, "^=", 2) \ diff --git a/src/v8globals.h b/src/v8globals.h index 0601e8aff..f4703ff09 100644 --- a/src/v8globals.h +++ b/src/v8globals.h @@ -531,11 +531,13 @@ const uint64_t kLastNonNaNInt64 = enum VariableMode { // User declared variables: - VAR, // declared via 'var', and 'function' declarations + VAR, // declared via 'var', and 'function' declarations - CONST, // declared via 'const' declarations + CONST, // declared via 'const' declarations - LET, // declared via 'let' declarations + CONST_HARMONY, // declared via 'const' declarations in harmony mode + + LET, // declared via 'let' declarations // Variables introduced by the compiler: DYNAMIC, // always require dynamic lookup (we don't know diff --git a/src/variables.cc b/src/variables.cc index 076cdc0a4..d85e1b270 100644 --- a/src/variables.cc +++ b/src/variables.cc @@ -41,6 +41,7 @@ const char* Variable::Mode2String(VariableMode mode) { switch (mode) { case VAR: return "VAR"; case CONST: return "CONST"; + case CONST_HARMONY: return "CONST"; case LET: return "LET"; case DYNAMIC: return "DYNAMIC"; case DYNAMIC_GLOBAL: return "DYNAMIC_GLOBAL"; diff --git a/src/variables.h b/src/variables.h index c5fef9ac6..8b2d86956 100644 --- a/src/variables.h +++ b/src/variables.h @@ -118,6 +118,15 @@ class Variable: public ZoneObject { mode_ == DYNAMIC_GLOBAL || mode_ == DYNAMIC_LOCAL); } + bool is_const_mode() const { + return (mode_ == CONST || + mode_ == CONST_HARMONY); + } + bool binding_needs_init() const { + return (mode_ == LET || + mode_ == CONST || + mode_ == CONST_HARMONY); + } bool is_global() const; bool is_this() const { return kind_ == THIS; } diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index afff92787..bf640dbcb 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -254,7 +254,10 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { // constant. if (scope()->is_function_scope() && scope()->function() != NULL) { int ignored = 0; - EmitDeclaration(scope()->function(), CONST, NULL, &ignored); + VariableProxy* proxy = scope()->function(); + ASSERT(proxy->var()->mode() == CONST || + proxy->var()->mode() == CONST_HARMONY); + EmitDeclaration(proxy, proxy->var()->mode(), NULL, &ignored); } VisitDeclarations(scope()->declarations()); } @@ -684,6 +687,8 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, // need to "declare" it at runtime to make sure it actually exists in the // local context. Variable* variable = proxy->var(); + bool binding_needs_init = + mode == CONST || mode == CONST_HARMONY || mode == LET; switch (variable->location()) { case Variable::UNALLOCATED: ++(*global_count); @@ -695,7 +700,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, Comment cmnt(masm_, "[ Declaration"); VisitForAccumulatorValue(function); __ movq(StackOperand(variable), result_register()); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { Comment cmnt(masm_, "[ Declaration"); __ LoadRoot(kScratchRegister, Heap::kTheHoleValueRootIndex); __ movq(StackOperand(variable), kScratchRegister); @@ -728,7 +733,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); PrepareForBailoutForId(proxy->id(), NO_REGISTERS); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { Comment cmnt(masm_, "[ Declaration"); __ LoadRoot(kScratchRegister, Heap::kTheHoleValueRootIndex); __ movq(ContextOperand(rsi, variable->index()), kScratchRegister); @@ -741,9 +746,13 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, Comment cmnt(masm_, "[ Declaration"); __ push(rsi); __ Push(variable->name()); - // Declaration nodes are always introduced in one of three modes. - ASSERT(mode == VAR || mode == CONST || mode == LET); - PropertyAttributes attr = (mode == CONST) ? READ_ONLY : NONE; + // Declaration nodes are always introduced in one of four modes. + ASSERT(mode == VAR || + mode == CONST || + mode == CONST_HARMONY || + mode == LET); + PropertyAttributes attr = + (mode == CONST || mode == CONST_HARMONY) ? READ_ONLY : NONE; __ Push(Smi::FromInt(attr)); // Push initial value, if any. // Note: For variables we must not push an initial value (such as @@ -751,7 +760,7 @@ void FullCodeGenerator::EmitDeclaration(VariableProxy* proxy, // must not destroy the current value. if (function != NULL) { VisitForStackValue(function); - } else if (mode == CONST || mode == LET) { + } else if (binding_needs_init) { __ PushRoot(Heap::kTheHoleValueRootIndex); } else { __ Push(Smi::FromInt(0)); // Indicates no initial value. @@ -1201,12 +1210,14 @@ void FullCodeGenerator::EmitDynamicLookupFastCase(Variable* var, } else if (var->mode() == DYNAMIC_LOCAL) { Variable* local = var->local_if_not_shadowed(); __ movq(rax, ContextSlotOperandCheckExtensions(local, slow)); - if (local->mode() == CONST || local->mode() == LET) { + if (local->mode() == CONST || + local->mode() == CONST_HARMONY || + local->mode() == LET) { __ CompareRoot(rax, Heap::kTheHoleValueRootIndex); __ j(not_equal, done); if (local->mode() == CONST) { __ LoadRoot(rax, Heap::kUndefinedValueRootIndex); - } else { // LET + } else { // LET || CONST_HARMONY __ Push(var->name()); __ CallRuntime(Runtime::kThrowReferenceError, 1); } @@ -1240,7 +1251,7 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { case Variable::LOCAL: case Variable::CONTEXT: { Comment cmnt(masm_, var->IsContextSlot() ? "Context slot" : "Stack slot"); - if (var->mode() != LET && var->mode() != CONST) { + if (!var->binding_needs_init()) { context()->Plug(var); } else { // Let and const need a read barrier. @@ -1248,10 +1259,14 @@ void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { GetVar(rax, var); __ CompareRoot(rax, Heap::kTheHoleValueRootIndex); __ j(not_equal, &done, Label::kNear); - if (var->mode() == LET) { + if (var->mode() == LET || var->mode() == CONST_HARMONY) { + // Throw a reference error when using an uninitialized let/const + // binding in harmony mode. __ Push(var->name()); __ CallRuntime(Runtime::kThrowReferenceError, 1); - } else { // CONST + } else { + // Uninitalized const bindings outside of harmony mode are unholed. + ASSERT(var->mode() == CONST); __ LoadRoot(rax, Heap::kUndefinedValueRootIndex); } __ bind(&done); @@ -1873,8 +1888,9 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } } - } else if (var->mode() != CONST) { - // Assignment to var or initializing assignment to let. + } else if (!var->is_const_mode() || op == Token::INIT_CONST_HARMONY) { + // Assignment to var or initializing assignment to let/const + // in harmony mode. if (var->IsStackAllocated() || var->IsContextSlot()) { MemOperand location = VarOperand(var, rcx); if (FLAG_debug_code && op == Token::INIT_LET) { diff --git a/test/mjsunit/harmony/block-conflicts.js b/test/mjsunit/harmony/block-conflicts.js index 8b171f171..e27d6a1d3 100644 --- a/test/mjsunit/harmony/block-conflicts.js +++ b/test/mjsunit/harmony/block-conflicts.js @@ -80,6 +80,11 @@ var letbinds = [ "let x", "let x = function() {}", "let x, y", "let y, x", + "const x = 0", + "const x = undefined", + "const x = function() {}", + "const x = 2, y = 3", + "const y = 4, x = 5", ]; var varbinds = [ "var x", "var x = 0", diff --git a/test/mjsunit/harmony/block-let-declaration.js b/test/mjsunit/harmony/block-let-declaration.js index e43e62b04..a1acc28da 100644 --- a/test/mjsunit/harmony/block-let-declaration.js +++ b/test/mjsunit/harmony/block-let-declaration.js @@ -32,15 +32,18 @@ // Global let x; let y = 2; +const z = 4; // Block local { let y; let x = 3; + const z = 5; } assertEquals(undefined, x); assertEquals(2,y); +assertEquals(4,z); if (true) { let y; @@ -58,7 +61,7 @@ function TestLocalDoesNotThrow(str) { assertDoesNotThrow("(function(){" + str + "})()"); } -// Test let declarations statement positions. +// Test let declarations in statement positions. TestLocalThrows("if (true) let x;", SyntaxError); TestLocalThrows("if (true) {} else let x;", SyntaxError); TestLocalThrows("do let x; while (false)", SyntaxError); @@ -68,7 +71,32 @@ TestLocalThrows("for (;false;) let x;", SyntaxError); TestLocalThrows("switch (true) { case true: let x; }", SyntaxError); TestLocalThrows("switch (true) { default: let x; }", SyntaxError); -// Test var declarations statement positions. +// Test const declarations with initialisers in statement positions. +TestLocalThrows("if (true) const x = 1;", SyntaxError); +TestLocalThrows("if (true) {} else const x = 1;", SyntaxError); +TestLocalThrows("do const x = 1; while (false)", SyntaxError); +TestLocalThrows("while (false) const x = 1;", SyntaxError); +TestLocalThrows("label: const x = 1;", SyntaxError); +TestLocalThrows("for (;false;) const x = 1;", SyntaxError); +TestLocalThrows("switch (true) { case true: const x = 1; }", SyntaxError); +TestLocalThrows("switch (true) { default: const x = 1; }", SyntaxError); + +// Test const declarations without initialisers. +TestLocalThrows("const x;", SyntaxError); +TestLocalThrows("const x = 1, y;", SyntaxError); +TestLocalThrows("const x, y = 1;", SyntaxError); + +// Test const declarations without initialisers in statement positions. +TestLocalThrows("if (true) const x;", SyntaxError); +TestLocalThrows("if (true) {} else const x;", SyntaxError); +TestLocalThrows("do const x; while (false)", SyntaxError); +TestLocalThrows("while (false) const x;", SyntaxError); +TestLocalThrows("label: const x;", SyntaxError); +TestLocalThrows("for (;false;) const x;", SyntaxError); +TestLocalThrows("switch (true) { case true: const x; }", SyntaxError); +TestLocalThrows("switch (true) { default: const x; }", SyntaxError); + +// Test var declarations in statement positions. TestLocalDoesNotThrow("if (true) var x;"); TestLocalDoesNotThrow("if (true) {} else var x;"); TestLocalDoesNotThrow("do var x; while (false)"); diff --git a/test/mjsunit/harmony/block-let-semantics.js b/test/mjsunit/harmony/block-let-semantics.js index 94020a4ca..f45b72ff0 100644 --- a/test/mjsunit/harmony/block-let-semantics.js +++ b/test/mjsunit/harmony/block-let-semantics.js @@ -61,6 +61,7 @@ TestAll('let x = x + 1'); TestAll('let x = x += 1'); TestAll('let x = x++'); TestAll('let x = ++x'); +TestAll('const x = x + 1'); // Use before initialization in prior statement. TestAll('x + 1; let x;'); @@ -68,18 +69,21 @@ TestAll('x = 1; let x;'); TestAll('x += 1; let x;'); TestAll('++x; let x;'); TestAll('x++; let x;'); +TestAll('let y = x; const x = 1;'); TestAll('f(); let x; function f() { return x + 1; }'); TestAll('f(); let x; function f() { x = 1; }'); TestAll('f(); let x; function f() { x += 1; }'); TestAll('f(); let x; function f() { ++x; }'); TestAll('f(); let x; function f() { x++; }'); +TestAll('f(); const x = 1; function f() { return x; }'); TestAll('f()(); let x; function f() { return function() { return x + 1; } }'); TestAll('f()(); let x; function f() { return function() { x = 1; } }'); TestAll('f()(); let x; function f() { return function() { x += 1; } }'); TestAll('f()(); let x; function f() { return function() { ++x; } }'); TestAll('f()(); let x; function f() { return function() { x++; } }'); +TestAll('f()(); const x = 1; function f() { return function() { return x; } }'); // Use before initialization with a dynamic lookup. TestAll('eval("x + 1;"); let x;'); @@ -87,6 +91,7 @@ TestAll('eval("x = 1;"); let x;'); TestAll('eval("x += 1;"); let x;'); TestAll('eval("++x;"); let x;'); TestAll('eval("x++;"); let x;'); +TestAll('eval("x"); const x = 1;'); // Use before initialization with check for eval-shadowed bindings. TestAll('function f() { eval("var y = 2;"); x + 1; }; f(); let x;'); @@ -139,10 +144,31 @@ function f2() { function h() { return b + c; } - let b = 3; + let c = 3; } assertEquals(5, n()); + + { + o = i; + function i() { + return d; + } + let d = 4; + } + assertEquals(4, o()); + + try { + throw 5; + } catch(e) { + p = j; + function j() { + return e + f; + } + let f = 6; + } + assertEquals(11, p()); } +f2(); // Test that resolution of let bound variables works with scopes that call eval. function outer() { diff --git a/test/mjsunit/harmony/block-scoping.js b/test/mjsunit/harmony/block-scoping.js index c70b3b6ea..0d0526afa 100644 --- a/test/mjsunit/harmony/block-scoping.js +++ b/test/mjsunit/harmony/block-scoping.js @@ -44,12 +44,16 @@ f1(); function f2(one) { var x = one + 1; let y = one + 2; + const u = one + 4; { let z = one + 3; + const v = one + 5; assertEquals(1, eval('one')); assertEquals(2, eval('x')); assertEquals(3, eval('y')); assertEquals(4, eval('z')); + assertEquals(5, eval('u')); + assertEquals(6, eval('v')); } } f2(1); @@ -59,12 +63,17 @@ f2(1); function f3(one) { var x = one + 1; let y = one + 2; + const u = one + 4; { let z = one + 3; + const v = one + 5; assertEquals(1, one); assertEquals(2, x); assertEquals(3, y); assertEquals(4, z); + assertEquals(5, u); + assertEquals(6, v); + } } f3(1); @@ -74,13 +83,17 @@ f3(1); function f4(one) { var x = one + 1; let y = one + 2; + const u = one + 4; { let z = one + 3; + const v = one + 5; function f() { assertEquals(1, eval('one')); assertEquals(2, eval('x')); assertEquals(3, eval('y')); assertEquals(4, eval('z')); + assertEquals(5, eval('u')); + assertEquals(6, eval('v')); }; } } @@ -91,13 +104,17 @@ f4(1); function f5(one) { var x = one + 1; let y = one + 2; + const u = one + 4; { let z = one + 3; + const v = one + 5; function f() { assertEquals(1, one); assertEquals(2, x); assertEquals(3, y); assertEquals(4, z); + assertEquals(5, u); + assertEquals(6, v); }; } } @@ -107,8 +124,10 @@ f5(1); // Return from block. function f6() { let x = 1; + const u = 3; { let y = 2; + const v = 4; return x + y; } } @@ -120,13 +139,26 @@ function f7(a) { let b = 1; var c = 1; var d = 1; - { // let variables shadowing argument, let and var variables + const e = 1; + { // let variables shadowing argument, let, const and var variables let a = 2; let b = 2; let c = 2; + let e = 2; + assertEquals(2,a); + assertEquals(2,b); + assertEquals(2,c); + assertEquals(2,e); + } + { // const variables shadowing argument, let, const and var variables + const a = 2; + const b = 2; + const c = 2; + const e = 2; assertEquals(2,a); assertEquals(2,b); assertEquals(2,c); + assertEquals(2,e); } try { throw 'stuff1'; @@ -156,6 +188,12 @@ function f7(a) { } catch (c) { // catch variable shadowing var variable assertEquals('stuff3',c); + { + // const variable shadowing catch variable + const c = 3; + assertEquals(3,c); + } + assertEquals('stuff3',c); try { throw 'stuff4'; } catch(c) { @@ -178,14 +216,16 @@ function f7(a) { c = 2; } assertEquals(1,c); - (function(a,b,c) { - // arguments shadowing argument, let and var variable + (function(a,b,c,e) { + // arguments shadowing argument, let, const and var variable a = 2; b = 2; c = 2; + e = 2; assertEquals(2,a); assertEquals(2,b); assertEquals(2,c); + assertEquals(2,e); // var variable shadowing var variable var d = 2; })(1,1); @@ -193,24 +233,30 @@ function f7(a) { assertEquals(1,b); assertEquals(1,c); assertEquals(1,d); + assertEquals(1,e); } f7(1); -// Ensure let variables are block local and var variables function local. +// Ensure let and const variables are block local +// and var variables function local. function f8() { var let_accessors = []; var var_accessors = []; + var const_accessors = []; for (var i = 0; i < 10; i++) { let x = i; var y = i; + const z = i; let_accessors[i] = function() { return x; } var_accessors[i] = function() { return y; } + const_accessors[i] = function() { return z; } } for (var j = 0; j < 10; j++) { y = j + 10; assertEquals(j, let_accessors[j]()); assertEquals(y, var_accessors[j]()); + assertEquals(j, const_accessors[j]()); } } f8(); -- 2.34.1