// 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());
}
// 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);
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));
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()));
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
__ 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 {
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);
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()));
__ 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);
}
}
- } 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) {
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);
}
*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:
// 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",
}
*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;
}
}
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();
}
// 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
};
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++);
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:
// 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());
}
// 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);
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()));
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()));
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
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 {
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);
}
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(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);
}
}
- } 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) {
"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"],
];
// 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<SerializedScopeInfo> Create(Scope* scope);
// 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);
}
// 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
// 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()) {
// 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
*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<String> type_string =
isolate()->factory()->NewStringFromUtf8(CStrVector(type), TENURED);
Expression* expression =
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);
Handle<String>* 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
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<const char*>::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<const char*>::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<const char*>::empty());
*ok = false;
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
// 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",
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);
// 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<Expression*>* arguments = new(zone()) ZoneList<Expression*>(3);
// 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 =
// 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)));
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,
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);
// 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);
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;
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;
Handle<Object> 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<Object> 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<Object> 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);
}
}
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());
}
}
}
}
-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;
}
}
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;
}
}
}
-Variable* Scope::DeclareFunctionVar(Handle<String> name) {
+Variable* Scope::DeclareFunctionVar(Handle<String> 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;
}
// 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);
}
// 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<String> name);
+ Variable* DeclareFunctionVar(Handle<String> name, VariableMode mode);
// Declare a parameter in this scope. When there are duplicated
// parameters the rightmost one 'wins'. However, the implementation
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) \
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
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";
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; }
// 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());
}
// 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);
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);
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);
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
// 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.
} 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);
}
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.
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);
}
}
- } 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) {
"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",
// 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;
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);
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)");
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;');
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;');
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;');
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() {
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);
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);
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'));
};
}
}
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);
};
}
}
// Return from block.
function f6() {
let x = 1;
+ const u = 3;
{
let y = 2;
+ const v = 4;
return x + y;
}
}
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';
} 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) {
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);
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();