From: arv Date: Wed, 17 Dec 2014 18:38:38 +0000 (-0800) Subject: ES6 computed property names X-Git-Tag: upstream/4.7.83~5172 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=cc568d1b7ae7ff9f870f21aaad6698e2cd6cd9d2;p=platform%2Fupstream%2Fv8.git ES6 computed property names This adds support for computed property names, under the flag --harmony-computed-property-names, for both object literals and classes. This is a revert of the revert, a76419f0f497db156eb1010e9b4be398951280cb. This changes to do an early bailout in HOptimizedGraphBuilder::VisitObjectLiteral instead of doing that in the later loop. BUG=v8:3754 LOG=Y TBR=dslomov@chromium.org Review URL: https://codereview.chromium.org/792233008 Cr-Commit-Position: refs/heads/master@{#25868} --- diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 3dc5420..24ccf09 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -1686,11 +1686,13 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { expr->CalculateEmitStore(zone()); AccessorTable accessor_table(zone()); - for (int i = 0; i < expr->properties()->length(); i++) { - ObjectLiteral::Property* property = expr->properties()->at(i); + int property_index = 0; + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + if (property->is_computed_name()) break; if (property->IsCompileTimeValue()) continue; - Literal* key = property->key(); + Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); if (!result_saved) { __ push(r0); // Save result on stack @@ -1778,6 +1780,67 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5); } + // Object literals have two parts. The "static" part on the left contains no + // computed property names, and so we can compute its map ahead of time; see + // runtime.cc::CreateObjectLiteralBoilerplate. The second "dynamic" part + // starts with the first computed property name, and continues with all + // properties to its right. All the code from above initializes the static + // component of the object literal, and arranges for the map of the result to + // reflect the static order in which the keys appear. For the dynamic + // properties, we compile them into a series of "SetOwnProperty" runtime + // calls. This will preserve insertion order. + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + + Expression* value = property->value(); + if (!result_saved) { + __ push(r0); // Save result on the stack + result_saved = true; + } + + __ ldr(r0, MemOperand(sp)); // Duplicate receiver. + __ push(r0); + + if (property->kind() == ObjectLiteral::Property::PROTOTYPE) { + DCHECK(!property->is_computed_name()); + VisitForStackValue(value); + if (property->emit_store()) { + __ CallRuntime(Runtime::kInternalSetPrototype, 2); + } else { + __ Drop(2); + } + } else { + EmitPropertyKey(property); + VisitForStackValue(value); + + switch (property->kind()) { + case ObjectLiteral::Property::CONSTANT: + case ObjectLiteral::Property::MATERIALIZED_LITERAL: + case ObjectLiteral::Property::COMPUTED: + if (property->emit_store()) { + __ mov(r0, Operand(Smi::FromInt(NONE))); + __ push(r0); + __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4); + } else { + __ Drop(3); + } + break; + + case ObjectLiteral::Property::PROTOTYPE: + UNREACHABLE(); + break; + + case ObjectLiteral::Property::GETTER: + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); + break; + + case ObjectLiteral::Property::SETTER: + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); + break; + } + } + } + if (expr->has_function()) { DCHECK(result_saved); __ ldr(r0, MemOperand(sp)); @@ -2478,9 +2541,7 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { for (int i = 0; i < lit->properties()->length(); i++) { ObjectLiteral::Property* property = lit->properties()->at(i); - Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); - DCHECK(key != NULL); if (property->is_static()) { __ ldr(scratch, MemOperand(sp, kPointerSize)); // constructor @@ -2488,7 +2549,7 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { __ ldr(scratch, MemOperand(sp, 0)); // prototype } __ push(scratch); - VisitForStackValue(key); + EmitPropertyKey(property); VisitForStackValue(value); EmitSetHomeObjectIfNeeded(value, 2); @@ -2501,11 +2562,11 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { break; case ObjectLiteral::Property::GETTER: - __ CallRuntime(Runtime::kDefineClassGetter, 3); + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); break; case ObjectLiteral::Property::SETTER: - __ CallRuntime(Runtime::kDefineClassSetter, 3); + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); break; default: diff --git a/src/arm64/full-codegen-arm64.cc b/src/arm64/full-codegen-arm64.cc index 0d3d34b..67267a6 100644 --- a/src/arm64/full-codegen-arm64.cc +++ b/src/arm64/full-codegen-arm64.cc @@ -1667,11 +1667,13 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { expr->CalculateEmitStore(zone()); AccessorTable accessor_table(zone()); - for (int i = 0; i < expr->properties()->length(); i++) { - ObjectLiteral::Property* property = expr->properties()->at(i); + int property_index = 0; + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + if (property->is_computed_name()) break; if (property->IsCompileTimeValue()) continue; - Literal* key = property->key(); + Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); if (!result_saved) { __ Push(x0); // Save result on stack @@ -1759,6 +1761,67 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5); } + // Object literals have two parts. The "static" part on the left contains no + // computed property names, and so we can compute its map ahead of time; see + // runtime.cc::CreateObjectLiteralBoilerplate. The second "dynamic" part + // starts with the first computed property name, and continues with all + // properties to its right. All the code from above initializes the static + // component of the object literal, and arranges for the map of the result to + // reflect the static order in which the keys appear. For the dynamic + // properties, we compile them into a series of "SetOwnProperty" runtime + // calls. This will preserve insertion order. + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + + Expression* value = property->value(); + if (!result_saved) { + __ Push(x0); // Save result on stack + result_saved = true; + } + + __ Peek(x10, 0); // Duplicate receiver. + __ Push(x10); + + if (property->kind() == ObjectLiteral::Property::PROTOTYPE) { + DCHECK(!property->is_computed_name()); + VisitForStackValue(value); + if (property->emit_store()) { + __ CallRuntime(Runtime::kInternalSetPrototype, 2); + } else { + __ Drop(2); + } + } else { + EmitPropertyKey(property); + VisitForStackValue(value); + + switch (property->kind()) { + case ObjectLiteral::Property::CONSTANT: + case ObjectLiteral::Property::MATERIALIZED_LITERAL: + case ObjectLiteral::Property::COMPUTED: + if (property->emit_store()) { + __ Mov(x0, Smi::FromInt(NONE)); + __ Push(x0); + __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4); + } else { + __ Drop(3); + } + break; + + case ObjectLiteral::Property::PROTOTYPE: + UNREACHABLE(); + break; + + case ObjectLiteral::Property::GETTER: + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); + break; + + case ObjectLiteral::Property::SETTER: + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); + break; + } + } + } + if (expr->has_function()) { DCHECK(result_saved); __ Peek(x0, 0); @@ -2175,9 +2238,7 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { for (int i = 0; i < lit->properties()->length(); i++) { ObjectLiteral::Property* property = lit->properties()->at(i); - Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); - DCHECK(key != NULL); if (property->is_static()) { __ Peek(scratch, kPointerSize); // constructor @@ -2185,7 +2246,7 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { __ Peek(scratch, 0); // prototype } __ Push(scratch); - VisitForStackValue(key); + EmitPropertyKey(property); VisitForStackValue(value); EmitSetHomeObjectIfNeeded(value, 2); @@ -2198,11 +2259,11 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { break; case ObjectLiteral::Property::GETTER: - __ CallRuntime(Runtime::kDefineClassGetter, 3); + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); break; case ObjectLiteral::Property::SETTER: - __ CallRuntime(Runtime::kDefineClassSetter, 3); + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); break; default: diff --git a/src/ast-numbering.cc b/src/ast-numbering.cc index c2dd70e..8b1c2ec 100644 --- a/src/ast-numbering.cc +++ b/src/ast-numbering.cc @@ -476,6 +476,7 @@ void AstNumberingVisitor::VisitObjectLiteral(ObjectLiteral* node) { void AstNumberingVisitor::VisitObjectLiteralProperty( ObjectLiteralProperty* node) { + if (node->is_computed_name()) DisableTurbofan(kComputedPropertyName); Visit(node->key()); Visit(node->value()); } diff --git a/src/ast.cc b/src/ast.cc index 6329371..298cd23 100644 --- a/src/ast.cc +++ b/src/ast.cc @@ -185,13 +185,17 @@ void FunctionLiteral::InitializeSharedInfo( ObjectLiteralProperty::ObjectLiteralProperty(Zone* zone, AstValueFactory* ast_value_factory, - Literal* key, Expression* value, - bool is_static) { - emit_store_ = true; - key_ = key; - value_ = value; - is_static_ = is_static; - if (key->raw_value()->EqualsString(ast_value_factory->proto_string())) { + Expression* key, Expression* value, + bool is_static, + bool is_computed_name) + : key_(key), + value_(value), + emit_store_(true), + is_static_(is_static), + is_computed_name_(is_computed_name) { + if (!is_computed_name && + key->AsLiteral()->raw_value()->EqualsString( + ast_value_factory->proto_string())) { kind_ = PROTOTYPE; } else if (value_->AsMaterializedLiteral() != NULL) { kind_ = MATERIALIZED_LITERAL; @@ -204,13 +208,16 @@ ObjectLiteralProperty::ObjectLiteralProperty(Zone* zone, ObjectLiteralProperty::ObjectLiteralProperty(Zone* zone, bool is_getter, + Expression* key, FunctionLiteral* value, - bool is_static) { - emit_store_ = true; - value_ = value; - kind_ = is_getter ? GETTER : SETTER; - is_static_ = is_static; -} + bool is_static, + bool is_computed_name) + : key_(key), + value_(value), + kind_(is_getter ? GETTER : SETTER), + emit_store_(true), + is_static_(is_static), + is_computed_name_(is_computed_name) {} bool ObjectLiteral::Property::IsCompileTimeValue() { @@ -237,10 +244,11 @@ void ObjectLiteral::CalculateEmitStore(Zone* zone) { allocator); for (int i = properties()->length() - 1; i >= 0; i--) { ObjectLiteral::Property* property = properties()->at(i); - Literal* literal = property->key(); + if (property->is_computed_name()) continue; + Literal* literal = property->key()->AsLiteral(); if (literal->value()->IsNull()) continue; uint32_t hash = literal->Hash(); - // If the key of a computed property is in the table, do not emit + // If the key of a computed property value is in the table, do not emit // a store for the property later. if ((property->kind() == ObjectLiteral::Property::MATERIALIZED_LITERAL || property->kind() == ObjectLiteral::Property::COMPUTED) && @@ -279,6 +287,13 @@ void ObjectLiteral::BuildConstantProperties(Isolate* isolate) { is_simple = false; continue; } + + if (position == boilerplate_properties_ * 2) { + DCHECK(property->is_computed_name()); + break; + } + DCHECK(!property->is_computed_name()); + MaterializedLiteral* m_literal = property->value()->AsMaterializedLiteral(); if (m_literal != NULL) { m_literal->BuildConstants(isolate); @@ -288,7 +303,7 @@ void ObjectLiteral::BuildConstantProperties(Isolate* isolate) { // Add CONSTANT and COMPUTED properties to boilerplate. Use undefined // value for COMPUTED properties, the real value is filled in at // runtime. The enumeration order is maintained. - Handle key = property->key()->value(); + Handle key = property->key()->AsLiteral()->value(); Handle value = GetBoilerplateValue(property->value(), isolate); // Ensure objects that may, at any point in time, contain fields with double @@ -640,7 +655,8 @@ void CallNew::RecordTypeFeedback(TypeFeedbackOracle* oracle) { void ObjectLiteral::Property::RecordTypeFeedback(TypeFeedbackOracle* oracle) { - TypeFeedbackId id = key()->LiteralFeedbackId(); + DCHECK(!is_computed_name()); + TypeFeedbackId id = key()->AsLiteral()->LiteralFeedbackId(); SmallMapList maps; oracle->CollectReceiverTypes(id, &maps); receiver_type_ = maps.length() == 1 ? maps.at(0) diff --git a/src/ast.h b/src/ast.h index 43bde6a..8579ff4 100644 --- a/src/ast.h +++ b/src/ast.h @@ -1467,10 +1467,7 @@ class ObjectLiteralProperty FINAL : public ZoneObject { PROTOTYPE // Property is __proto__. }; - ObjectLiteralProperty(Zone* zone, AstValueFactory* ast_value_factory, - Literal* key, Expression* value, bool is_static); - - Literal* key() { return key_; } + Expression* key() { return key_; } Expression* value() { return value_; } Kind kind() { return kind_; } @@ -1485,20 +1482,26 @@ class ObjectLiteralProperty FINAL : public ZoneObject { bool emit_store(); bool is_static() const { return is_static_; } + bool is_computed_name() const { return is_computed_name_; } protected: friend class AstNodeFactory; - ObjectLiteralProperty(Zone* zone, bool is_getter, FunctionLiteral* value, - bool is_static); - void set_key(Literal* key) { key_ = key; } + ObjectLiteralProperty(Zone* zone, AstValueFactory* ast_value_factory, + Expression* key, Expression* value, bool is_static, + bool is_computed_name); + + ObjectLiteralProperty(Zone* zone, bool is_getter, Expression* key, + FunctionLiteral* value, bool is_static, + bool is_computed_name); private: - Literal* key_; + Expression* key_; Expression* value_; Kind kind_; bool emit_store_; bool is_static_; + bool is_computed_name_; Handle receiver_type_; }; @@ -3377,20 +3380,21 @@ class AstNodeFactory FINAL BASE_EMBEDDED { boilerplate_properties, has_function, pos); } - ObjectLiteral::Property* NewObjectLiteralProperty(Literal* key, + ObjectLiteral::Property* NewObjectLiteralProperty(Expression* key, Expression* value, - bool is_static) { - return new (zone_) ObjectLiteral::Property(zone_, ast_value_factory_, key, - value, is_static); + bool is_static, + bool is_computed_name) { + return new (zone_) ObjectLiteral::Property( + zone_, ast_value_factory_, key, value, is_static, is_computed_name); } ObjectLiteral::Property* NewObjectLiteralProperty(bool is_getter, + Expression* key, FunctionLiteral* value, - int pos, bool is_static) { - ObjectLiteral::Property* prop = - new (zone_) ObjectLiteral::Property(zone_, is_getter, value, is_static); - prop->set_key(NewStringLiteral(value->raw_name(), pos)); - return prop; + int pos, bool is_static, + bool is_computed_name) { + return new (zone_) ObjectLiteral::Property(zone_, is_getter, key, value, + is_static, is_computed_name); } RegExpLiteral* NewRegExpLiteral(const AstRawString* pattern, diff --git a/src/bailout-reason.h b/src/bailout-reason.h index 7287d62..1c65fb9 100644 --- a/src/bailout-reason.h +++ b/src/bailout-reason.h @@ -47,6 +47,7 @@ namespace internal { V(kCodeGenerationFailed, "Code generation failed") \ V(kCodeObjectNotProperlyPatched, "Code object not properly patched") \ V(kCompoundAssignmentToLookupSlot, "Compound assignment to lookup slot") \ + V(kComputedPropertyName, "Computed property name") \ V(kContextAllocatedArguments, "Context-allocated arguments") \ V(kCopyBuffersOverlap, "Copy buffers overlap") \ V(kCouldNotGenerateZero, "Could not generate +0.0") \ diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 7105eb2..d4fc6cd 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1596,6 +1596,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_tostring) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_templates) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_sloppy) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_unicode) +EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_computed_property_names) void Genesis::InstallNativeFunctions_harmony_proxies() { @@ -1626,6 +1627,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_proxies) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_templates) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_sloppy) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_unicode) +EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_computed_property_names) void Genesis::InitializeGlobal_harmony_regexps() { Handle builtins(native_context()->builtins()); @@ -2180,6 +2182,7 @@ bool Genesis::InstallExperimentalNatives() { "native harmony-templates.js", NULL}; static const char* harmony_sloppy_natives[] = {NULL}; static const char* harmony_unicode_natives[] = {NULL}; + static const char* harmony_computed_property_names_natives[] = {NULL}; for (int i = ExperimentalNatives::GetDebuggerCount(); i < ExperimentalNatives::GetBuiltinsCount(); i++) { diff --git a/src/builtins.h b/src/builtins.h index 13a4b80..b776f73 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -165,35 +165,36 @@ enum BuiltinExtraArguments { DEBUG_BREAK) // Define list of builtins implemented in JavaScript. -#define BUILTINS_LIST_JS(V) \ - V(EQUALS, 1) \ - V(STRICT_EQUALS, 1) \ - V(COMPARE, 2) \ - V(ADD, 1) \ - V(SUB, 1) \ - V(MUL, 1) \ - V(DIV, 1) \ - V(MOD, 1) \ - V(BIT_OR, 1) \ - V(BIT_AND, 1) \ - V(BIT_XOR, 1) \ - V(SHL, 1) \ - V(SAR, 1) \ - V(SHR, 1) \ - V(DELETE, 2) \ - V(IN, 1) \ - V(INSTANCE_OF, 1) \ - V(FILTER_KEY, 1) \ - V(CALL_NON_FUNCTION, 0) \ - V(CALL_NON_FUNCTION_AS_CONSTRUCTOR, 0) \ +#define BUILTINS_LIST_JS(V) \ + V(EQUALS, 1) \ + V(STRICT_EQUALS, 1) \ + V(COMPARE, 2) \ + V(ADD, 1) \ + V(SUB, 1) \ + V(MUL, 1) \ + V(DIV, 1) \ + V(MOD, 1) \ + V(BIT_OR, 1) \ + V(BIT_AND, 1) \ + V(BIT_XOR, 1) \ + V(SHL, 1) \ + V(SAR, 1) \ + V(SHR, 1) \ + V(DELETE, 2) \ + V(IN, 1) \ + V(INSTANCE_OF, 1) \ + V(FILTER_KEY, 1) \ + V(CALL_NON_FUNCTION, 0) \ + V(CALL_NON_FUNCTION_AS_CONSTRUCTOR, 0) \ V(CALL_FUNCTION_PROXY, 1) \ V(CALL_FUNCTION_PROXY_AS_CONSTRUCTOR, 1) \ - V(TO_OBJECT, 0) \ - V(TO_NUMBER, 0) \ - V(TO_STRING, 0) \ - V(STRING_ADD_LEFT, 1) \ - V(STRING_ADD_RIGHT, 1) \ - V(APPLY_PREPARE, 1) \ + V(TO_OBJECT, 0) \ + V(TO_NUMBER, 0) \ + V(TO_STRING, 0) \ + V(TO_NAME, 0) \ + V(STRING_ADD_LEFT, 1) \ + V(STRING_ADD_RIGHT, 1) \ + V(APPLY_PREPARE, 1) \ V(STACK_OVERFLOW, 1) class BuiltinFunctionTable; diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index cde5e71..1c53873 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -910,7 +910,7 @@ void AstGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) { ObjectLiteral::Property* property = expr->properties()->at(i); if (property->IsCompileTimeValue()) continue; - Literal* key = property->key(); + Literal* key = property->key()->AsLiteral(); switch (property->kind()) { case ObjectLiteral::Property::CONSTANT: UNREACHABLE(); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index cb95aaf..9c50931 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -172,15 +172,16 @@ DEFINE_IMPLICATION(harmony, es_staging) DEFINE_IMPLICATION(es_staging, harmony) // Features that are still work in progress (behind individual flags). -#define HARMONY_INPROGRESS(V) \ - V(harmony_modules, "harmony modules (implies block scoping)") \ - V(harmony_arrays, "harmony array methods") \ - V(harmony_array_includes, "harmony Array.prototype.includes") \ - V(harmony_regexps, "harmony regular expression extensions") \ - V(harmony_arrow_functions, "harmony arrow functions") \ - V(harmony_proxies, "harmony proxies") \ - V(harmony_sloppy, "harmony features in sloppy mode") \ - V(harmony_unicode, "harmony unicode escapes") +#define HARMONY_INPROGRESS(V) \ + V(harmony_modules, "harmony modules (implies block scoping)") \ + V(harmony_arrays, "harmony array methods") \ + V(harmony_array_includes, "harmony Array.prototype.includes") \ + V(harmony_regexps, "harmony regular expression extensions") \ + V(harmony_arrow_functions, "harmony arrow functions") \ + V(harmony_proxies, "harmony proxies") \ + V(harmony_sloppy, "harmony features in sloppy mode") \ + V(harmony_unicode, "harmony unicode escapes") \ + V(harmony_computed_property_names, "harmony computed property names") // Features that are complete (but still behind --harmony/es-staging flag). #define HARMONY_STAGED(V) \ diff --git a/src/full-codegen.cc b/src/full-codegen.cc index cb8f4aa..ec674ff 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -1211,6 +1211,13 @@ void FullCodeGenerator::EmitUnwindBeforeReturn() { } +void FullCodeGenerator::EmitPropertyKey(ObjectLiteralProperty* property) { + VisitForStackValue(property->key()); + __ InvokeBuiltin(Builtins::TO_NAME, CALL_FUNCTION); + __ Push(result_register()); +} + + void FullCodeGenerator::VisitReturnStatement(ReturnStatement* stmt) { Comment cmnt(masm_, "[ ReturnStatement"); SetStatementPosition(stmt); diff --git a/src/full-codegen.h b/src/full-codegen.h index 1439942..f870cae 100644 --- a/src/full-codegen.h +++ b/src/full-codegen.h @@ -568,6 +568,9 @@ class FullCodeGenerator: public AstVisitor { // in the accumulator after installing all the properties. void EmitClassDefineProperties(ClassLiteral* lit); + // Pushes the property key as a Name on the stack. + void EmitPropertyKey(ObjectLiteralProperty* property); + // Apply the compound assignment operator. Expects the left operand on top // of the stack and the right one in the accumulator. void EmitBinaryOp(BinaryOperation* expr, diff --git a/src/hydrogen.cc b/src/hydrogen.cc index 987d60b..9f4075e 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -5552,6 +5552,13 @@ void HOptimizedGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) { DCHECK(!HasStackOverflow()); DCHECK(current_block() != NULL); DCHECK(current_block()->HasPredecessor()); + + for (int i = 0; i < expr->properties()->length(); i++) { + if (expr->properties()->at(i)->is_computed_name()) { + return Bailout(kComputedPropertyName); + } + } + expr->BuildConstantProperties(isolate()); Handle closure = function_state()->compilation_info()->closure(); HInstruction* literal; @@ -5607,9 +5614,10 @@ void HOptimizedGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) { for (int i = 0; i < expr->properties()->length(); i++) { ObjectLiteral::Property* property = expr->properties()->at(i); + DCHECK(!property->is_computed_name()); if (property->IsCompileTimeValue()) continue; - Literal* key = property->key(); + Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); switch (property->kind()) { @@ -5635,7 +5643,7 @@ void HOptimizedGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) { } Handle map = property->GetReceiverType(); - Handle name = property->key()->AsPropertyName(); + Handle name = key->AsPropertyName(); HInstruction* store; if (map.is_null()) { // If we don't know the monomorphic type, do a generic store. diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index 1ba4095..45627ea 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -1617,11 +1617,13 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { expr->CalculateEmitStore(zone()); AccessorTable accessor_table(zone()); - for (int i = 0; i < expr->properties()->length(); i++) { - ObjectLiteral::Property* property = expr->properties()->at(i); + int property_index = 0; + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + if (property->is_computed_name()) break; if (property->IsCompileTimeValue()) continue; - Literal* key = property->key(); + Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); if (!result_saved) { __ push(eax); // Save result on the stack @@ -1701,6 +1703,65 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5); } + // Object literals have two parts. The "static" part on the left contains no + // computed property names, and so we can compute its map ahead of time; see + // runtime.cc::CreateObjectLiteralBoilerplate. The second "dynamic" part + // starts with the first computed property name, and continues with all + // properties to its right. All the code from above initializes the static + // component of the object literal, and arranges for the map of the result to + // reflect the static order in which the keys appear. For the dynamic + // properties, we compile them into a series of "SetOwnProperty" runtime + // calls. This will preserve insertion order. + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + + Expression* value = property->value(); + if (!result_saved) { + __ push(eax); // Save result on the stack + result_saved = true; + } + + __ push(Operand(esp, 0)); // Duplicate receiver. + + if (property->kind() == ObjectLiteral::Property::PROTOTYPE) { + DCHECK(!property->is_computed_name()); + VisitForStackValue(value); + if (property->emit_store()) { + __ CallRuntime(Runtime::kInternalSetPrototype, 2); + } else { + __ Drop(2); + } + } else { + EmitPropertyKey(property); + VisitForStackValue(value); + + switch (property->kind()) { + case ObjectLiteral::Property::CONSTANT: + case ObjectLiteral::Property::MATERIALIZED_LITERAL: + case ObjectLiteral::Property::COMPUTED: + if (property->emit_store()) { + __ push(Immediate(Smi::FromInt(NONE))); + __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4); + } else { + __ Drop(3); + } + break; + + case ObjectLiteral::Property::PROTOTYPE: + UNREACHABLE(); + break; + + case ObjectLiteral::Property::GETTER: + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); + break; + + case ObjectLiteral::Property::SETTER: + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); + break; + } + } + } + if (expr->has_function()) { DCHECK(result_saved); __ push(Operand(esp, 0)); @@ -2394,16 +2455,14 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { for (int i = 0; i < lit->properties()->length(); i++) { ObjectLiteral::Property* property = lit->properties()->at(i); - Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); - DCHECK(key != NULL); if (property->is_static()) { __ push(Operand(esp, kPointerSize)); // constructor } else { __ push(Operand(esp, 0)); // prototype } - VisitForStackValue(key); + EmitPropertyKey(property); VisitForStackValue(value); EmitSetHomeObjectIfNeeded(value, 2); @@ -2416,11 +2475,11 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { break; case ObjectLiteral::Property::GETTER: - __ CallRuntime(Runtime::kDefineClassGetter, 3); + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); break; case ObjectLiteral::Property::SETTER: - __ CallRuntime(Runtime::kDefineClassSetter, 3); + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); break; default: diff --git a/src/parser.cc b/src/parser.cc index 0713b26..e5b366b 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -407,9 +407,8 @@ bool ParserTraits::IsConstructor(const AstRawString* identifier) const { bool ParserTraits::IsThisProperty(Expression* expression) { DCHECK(expression != NULL); Property* property = expression->AsProperty(); - return property != NULL && - property->obj()->AsVariableProxy() != NULL && - property->obj()->AsVariableProxy()->is_this(); + return property != NULL && property->obj()->IsVariableProxy() && + property->obj()->AsVariableProxy()->is_this(); } @@ -433,8 +432,7 @@ void ParserTraits::PushPropertyName(FuncNameInferrer* fni, void ParserTraits::CheckAssigningFunctionLiteralToProperty(Expression* left, Expression* right) { DCHECK(left != NULL); - if (left->AsProperty() != NULL && - right->AsFunctionLiteral() != NULL) { + if (left->IsProperty() && right->IsFunctionLiteral()) { right->AsFunctionLiteral()->set_pretenure(); } } @@ -806,6 +804,8 @@ Parser::Parser(CompilationInfo* info, ParseInfo* parse_info) set_allow_harmony_templates(FLAG_harmony_templates); set_allow_harmony_sloppy(FLAG_harmony_sloppy); set_allow_harmony_unicode(FLAG_harmony_unicode); + set_allow_harmony_computed_property_names( + FLAG_harmony_computed_property_names); for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount; ++feature) { use_counts_[feature] = 0; @@ -3976,6 +3976,8 @@ PreParser::PreParseResult Parser::ParseLazyFunctionBodyWithPreParser( reusable_preparser_->set_allow_harmony_templates(allow_harmony_templates()); reusable_preparser_->set_allow_harmony_sloppy(allow_harmony_sloppy()); reusable_preparser_->set_allow_harmony_unicode(allow_harmony_unicode()); + reusable_preparser_->set_allow_harmony_computed_property_names( + allow_harmony_computed_property_names()); } PreParser::PreParseResult result = reusable_preparser_->PreParseLazyFunction(strict_mode(), @@ -4035,8 +4037,11 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name, if (fni_ != NULL) fni_->Enter(); const bool in_class = true; const bool is_static = false; - ObjectLiteral::Property* property = ParsePropertyDefinition( - NULL, in_class, is_static, &has_seen_constructor, CHECK_OK); + bool is_computed_name = false; // Classes do not care about computed + // property names here. + ObjectLiteral::Property* property = + ParsePropertyDefinition(NULL, in_class, is_static, &is_computed_name, + &has_seen_constructor, CHECK_OK); if (has_seen_constructor && constructor == NULL) { constructor = GetPropertyValue(property); diff --git a/src/parser.h b/src/parser.h index 02dfe15..0c58869 100644 --- a/src/parser.h +++ b/src/parser.h @@ -412,11 +412,6 @@ class ParserTraits { return string->AsArrayIndex(index); } - bool IsConstructorProperty(ObjectLiteral::Property* property) { - return property->key()->raw_value()->EqualsString( - ast_value_factory()->constructor_string()); - } - static Expression* GetPropertyValue(ObjectLiteral::Property* property) { return property->value(); } @@ -426,7 +421,9 @@ class ParserTraits { static void PushLiteralName(FuncNameInferrer* fni, const AstRawString* id) { fni->PushLiteralName(id); } + void PushPropertyName(FuncNameInferrer* fni, Expression* expression); + static void InferFunctionName(FuncNameInferrer* fni, FunctionLiteral* func_to_infer) { fni->AddFunction(func_to_infer); diff --git a/src/preparser.cc b/src/preparser.cc index 1bd3340..51cac17 100644 --- a/src/preparser.cc +++ b/src/preparser.cc @@ -990,8 +990,10 @@ PreParserExpression PreParser::ParseClassLiteral( if (Check(Token::SEMICOLON)) continue; const bool in_class = true; const bool is_static = false; - ParsePropertyDefinition(NULL, in_class, is_static, &has_seen_constructor, - CHECK_OK); + bool is_computed_name = false; // Classes do not care about computed + // property names here. + ParsePropertyDefinition(NULL, in_class, is_static, &is_computed_name, + &has_seen_constructor, CHECK_OK); } Expect(Token::RBRACE, CHECK_OK); diff --git a/src/preparser.h b/src/preparser.h index 3b221ac..c79f6a6 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -87,6 +87,7 @@ class ParserBase : public Traits { allow_harmony_arrow_functions_(false), allow_harmony_object_literals_(false), allow_harmony_sloppy_(false), + allow_harmony_computed_property_names_(false), zone_(zone) {} // Getters that indicate whether certain syntactical constructs are @@ -108,6 +109,9 @@ class ParserBase : public Traits { bool allow_harmony_templates() const { return scanner()->HarmonyTemplates(); } bool allow_harmony_sloppy() const { return allow_harmony_sloppy_; } bool allow_harmony_unicode() const { return scanner()->HarmonyUnicode(); } + bool allow_harmony_computed_property_names() const { + return allow_harmony_computed_property_names_; + } // Setters that determine whether certain syntactical constructs are // allowed to be parsed by this instance of the parser. @@ -140,6 +144,9 @@ class ParserBase : public Traits { void set_allow_harmony_unicode(bool allow) { scanner()->SetHarmonyUnicode(allow); } + void set_allow_harmony_computed_property_names(bool allow) { + allow_harmony_computed_property_names_ = allow; + } protected: enum AllowEvalOrArgumentsAsIdentifier { @@ -490,11 +497,13 @@ class ParserBase : public Traits { ExpressionT ParsePrimaryExpression(bool* ok); ExpressionT ParseExpression(bool accept_IN, bool* ok); ExpressionT ParseArrayLiteral(bool* ok); - IdentifierT ParsePropertyName(bool* is_get, bool* is_set, bool* is_static, + ExpressionT ParsePropertyName(IdentifierT* name, bool* is_get, bool* is_set, + bool* is_static, bool* is_computed_name, bool* ok); ExpressionT ParseObjectLiteral(bool* ok); ObjectLiteralPropertyT ParsePropertyDefinition(ObjectLiteralChecker* checker, bool in_class, bool is_static, + bool* is_computed_name, bool* has_seen_constructor, bool* ok); typename Traits::Type::ExpressionList ParseArguments(bool* ok); @@ -598,6 +607,7 @@ class ParserBase : public Traits { bool allow_harmony_arrow_functions_; bool allow_harmony_object_literals_; bool allow_harmony_sloppy_; + bool allow_harmony_computed_property_names_; typename Traits::Type::Zone* zone_; // Only used by Parser. }; @@ -1031,13 +1041,16 @@ class PreParserFactory { return PreParserExpression::Default(); } PreParserExpression NewObjectLiteralProperty(bool is_getter, + PreParserExpression key, PreParserExpression value, - int pos, bool is_static) { + int pos, bool is_static, + bool is_computed_name) { return PreParserExpression::Default(); } PreParserExpression NewObjectLiteralProperty(PreParserExpression key, PreParserExpression value, - bool is_static) { + bool is_static, + bool is_computed_name) { return PreParserExpression::Default(); } PreParserExpression NewObjectLiteral(PreParserExpressionList properties, @@ -1223,11 +1236,13 @@ class PreParserTraits { // PreParser should not use FuncNameInferrer. UNREACHABLE(); } + static void PushPropertyName(FuncNameInferrer* fni, PreParserExpression expression) { // PreParser should not use FuncNameInferrer. UNREACHABLE(); } + static void InferFunctionName(FuncNameInferrer* fni, PreParserExpression expression) { // PreParser should not use FuncNameInferrer. @@ -1987,24 +2002,55 @@ typename ParserBase::ExpressionT ParserBase::ParseArrayLiteral( template -typename ParserBase::IdentifierT ParserBase::ParsePropertyName( - bool* is_get, bool* is_set, bool* is_static, bool* ok) { - Token::Value next = peek(); - switch (next) { +typename ParserBase::ExpressionT ParserBase::ParsePropertyName( + IdentifierT* name, bool* is_get, bool* is_set, bool* is_static, + bool* is_computed_name, bool* ok) { + Token::Value token = peek(); + int pos = peek_position(); + + // For non computed property names we normalize the name a bit: + // + // "12" -> 12 + // 12.3 -> "12.3" + // 12.30 -> "12.3" + // identifier -> "identifier" + // + // This is important because we use the property name as a key in a hash + // table when we compute constant properties. + switch (token) { case Token::STRING: Consume(Token::STRING); - return this->GetSymbol(scanner_); + *name = this->GetSymbol(scanner()); + break; + case Token::NUMBER: Consume(Token::NUMBER); - return this->GetNumberAsSymbol(scanner_); + *name = this->GetNumberAsSymbol(scanner()); + break; + + case Token::LBRACK: + if (allow_harmony_computed_property_names_) { + *is_computed_name = true; + Consume(Token::LBRACK); + ExpressionT expression = ParseAssignmentExpression(true, CHECK_OK); + Expect(Token::RBRACK, CHECK_OK); + return expression; + } + + // Fall through. case Token::STATIC: *is_static = true; - // Fall through. + + // Fall through. default: - return ParseIdentifierNameOrGetOrSet(is_get, is_set, ok); + *name = ParseIdentifierNameOrGetOrSet(is_get, is_set, CHECK_OK); + break; } - UNREACHABLE(); - return this->EmptyIdentifier(); + + uint32_t index; + return this->IsArrayIndex(*name, &index) + ? factory()->NewNumberLiteral(index, pos) + : factory()->NewStringLiteral(*name, pos); } @@ -2012,9 +2058,11 @@ template typename ParserBase::ObjectLiteralPropertyT ParserBase< Traits>::ParsePropertyDefinition(ObjectLiteralChecker* checker, bool in_class, bool is_static, + bool* is_computed_name, bool* has_seen_constructor, bool* ok) { DCHECK(!in_class || is_static || has_seen_constructor != NULL); ExpressionT value = this->EmptyExpression(); + IdentifierT name = this->EmptyIdentifier(); bool is_get = false; bool is_set = false; bool name_is_static = false; @@ -2022,15 +2070,17 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< Token::Value name_token = peek(); int next_pos = peek_position(); - IdentifierT name = - ParsePropertyName(&is_get, &is_set, &name_is_static, - CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); + ExpressionT name_expression = ParsePropertyName( + &name, &is_get, &is_set, &name_is_static, is_computed_name, + CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); - if (fni_ != NULL) this->PushLiteralName(fni_, name); + if (fni_ != NULL && !*is_computed_name) { + this->PushLiteralName(fni_, name); + } if (!in_class && !is_generator && peek() == Token::COLON) { // PropertyDefinition : PropertyName ':' AssignmentExpression - if (checker != NULL) { + if (!*is_computed_name && checker != NULL) { checker->CheckProperty(name_token, kValueProperty, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); } @@ -2068,7 +2118,7 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< kind = FunctionKind::kNormalFunction; } - if (checker != NULL) { + if (!*is_computed_name && checker != NULL) { checker->CheckProperty(name_token, kValueProperty, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); } @@ -2082,14 +2132,18 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< } else if (in_class && name_is_static && !is_static) { // static MethodDefinition - return ParsePropertyDefinition(checker, true, true, NULL, ok); + return ParsePropertyDefinition(checker, true, true, is_computed_name, NULL, + ok); } else if (is_get || is_set) { // Accessor + name = this->EmptyIdentifier(); bool dont_care = false; name_token = peek(); - name = ParsePropertyName(&dont_care, &dont_care, &dont_care, - CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); + + name_expression = ParsePropertyName( + &name, &dont_care, &dont_care, &dont_care, is_computed_name, + CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); // Validate the property. if (is_static && this->IsPrototype(name)) { @@ -2101,7 +2155,7 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< *ok = false; return this->EmptyObjectLiteralProperty(); } - if (checker != NULL) { + if (!*is_computed_name && checker != NULL) { checker->CheckProperty(name_token, is_get ? kGetterProperty : kSetterProperty, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); @@ -2114,8 +2168,17 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< FunctionLiteral::ANONYMOUS_EXPRESSION, is_get ? FunctionLiteral::GETTER_ARITY : FunctionLiteral::SETTER_ARITY, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); - return factory()->NewObjectLiteralProperty(is_get, value, next_pos, - is_static); + + // Make sure the name expression is a string since we need a Name for + // Runtime_DefineAccessorPropertyUnchecked and since we can determine this + // statically we can skip the extra runtime check. + if (!*is_computed_name) { + name_expression = + factory()->NewStringLiteral(name, name_expression->position()); + } + + return factory()->NewObjectLiteralProperty( + is_get, name_expression, value, next_pos, is_static, *is_computed_name); } else if (!in_class && allow_harmony_object_literals_ && Token::IsIdentifier(name_token, strict_mode(), @@ -2129,12 +2192,8 @@ typename ParserBase::ObjectLiteralPropertyT ParserBase< return this->EmptyObjectLiteralProperty(); } - uint32_t index; - LiteralT key = this->IsArrayIndex(name, &index) - ? factory()->NewNumberLiteral(index, next_pos) - : factory()->NewStringLiteral(name, next_pos); - - return factory()->NewObjectLiteralProperty(key, value, is_static); + return factory()->NewObjectLiteralProperty(name_expression, value, is_static, + *is_computed_name); } @@ -2149,6 +2208,7 @@ typename ParserBase::ExpressionT ParserBase::ParseObjectLiteral( this->NewPropertyList(4, zone_); int number_of_boilerplate_properties = 0; bool has_function = false; + bool has_computed_names = false; ObjectLiteralChecker checker(this, strict_mode()); @@ -2159,8 +2219,13 @@ typename ParserBase::ExpressionT ParserBase::ParseObjectLiteral( const bool in_class = false; const bool is_static = false; + bool is_computed_name = false; ObjectLiteralPropertyT property = this->ParsePropertyDefinition( - &checker, in_class, is_static, NULL, CHECK_OK); + &checker, in_class, is_static, &is_computed_name, NULL, CHECK_OK); + + if (is_computed_name) { + has_computed_names = true; + } // Mark top-level object literals that contain function literals and // pretenure the literal so it can be added as a constant function @@ -2169,7 +2234,7 @@ typename ParserBase::ExpressionT ParserBase::ParseObjectLiteral( &has_function); // Count CONSTANT or COMPUTED properties to maintain the enumeration order. - if (this->IsBoilerplateProperty(property)) { + if (!has_computed_names && this->IsBoilerplateProperty(property)) { number_of_boilerplate_properties++; } properties->Add(property, zone()); diff --git a/src/runtime.js b/src/runtime.js index 79824e4..145823b 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -468,6 +468,12 @@ function TO_STRING() { } +// Convert the receiver to a string or symbol - forward to ToName. +function TO_NAME() { + return %ToName(this); +} + + /* ------------------------------------- - - - C o n v e r s i o n s - - - ------------------------------------- diff --git a/src/runtime/runtime-classes.cc b/src/runtime/runtime-classes.cc index 7c827f0..00567d4 100644 --- a/src/runtime/runtime-classes.cc +++ b/src/runtime/runtime-classes.cc @@ -153,18 +153,10 @@ RUNTIME_FUNCTION(Runtime_DefineClassMethod) { HandleScope scope(isolate); DCHECK(args.length() == 3); CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0); - CONVERT_ARG_HANDLE_CHECKED(Object, key, 1); + CONVERT_ARG_HANDLE_CHECKED(Name, name, 1); CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 2); uint32_t index; - if (key->ToArrayIndex(&index)) { - RETURN_FAILURE_ON_EXCEPTION( - isolate, JSObject::SetOwnElement(object, index, function, STRICT)); - } - - Handle name; - ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, name, - Runtime::ToName(isolate, key)); if (name->AsArrayIndex(&index)) { RETURN_FAILURE_ON_EXCEPTION( isolate, JSObject::SetOwnElement(object, index, function, STRICT)); @@ -177,42 +169,6 @@ RUNTIME_FUNCTION(Runtime_DefineClassMethod) { } -RUNTIME_FUNCTION(Runtime_DefineClassGetter) { - HandleScope scope(isolate); - DCHECK(args.length() == 3); - CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0); - CONVERT_ARG_HANDLE_CHECKED(Object, key, 1); - CONVERT_ARG_HANDLE_CHECKED(JSFunction, getter, 2); - - Handle name; - ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, name, - Runtime::ToName(isolate, key)); - RETURN_FAILURE_ON_EXCEPTION( - isolate, - JSObject::DefineAccessor(object, name, getter, - isolate->factory()->null_value(), NONE)); - return isolate->heap()->undefined_value(); -} - - -RUNTIME_FUNCTION(Runtime_DefineClassSetter) { - HandleScope scope(isolate); - DCHECK(args.length() == 3); - CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0); - CONVERT_ARG_HANDLE_CHECKED(Object, key, 1); - CONVERT_ARG_HANDLE_CHECKED(JSFunction, setter, 2); - - Handle name; - ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, name, - Runtime::ToName(isolate, key)); - RETURN_FAILURE_ON_EXCEPTION( - isolate, - JSObject::DefineAccessor(object, name, isolate->factory()->null_value(), - setter, NONE)); - return isolate->heap()->undefined_value(); -} - - RUNTIME_FUNCTION(Runtime_ClassGetSourceCode) { HandleScope shs(isolate); DCHECK(args.length() == 1); diff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc index 407f237..43620bb 100644 --- a/src/runtime/runtime-object.cc +++ b/src/runtime/runtime-object.cc @@ -1606,5 +1606,35 @@ RUNTIME_FUNCTION(RuntimeReference_ClassOf) { if (!obj->IsJSReceiver()) return isolate->heap()->null_value(); return JSReceiver::cast(obj)->class_name(); } + + +RUNTIME_FUNCTION(Runtime_DefineGetterPropertyUnchecked) { + HandleScope scope(isolate); + DCHECK(args.length() == 3); + CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0); + CONVERT_ARG_HANDLE_CHECKED(Name, name, 1); + CONVERT_ARG_HANDLE_CHECKED(JSFunction, getter, 2); + + RETURN_FAILURE_ON_EXCEPTION( + isolate, + JSObject::DefineAccessor(object, name, getter, + isolate->factory()->null_value(), NONE)); + return isolate->heap()->undefined_value(); +} + + +RUNTIME_FUNCTION(Runtime_DefineSetterPropertyUnchecked) { + HandleScope scope(isolate); + DCHECK(args.length() == 3); + CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0); + CONVERT_ARG_HANDLE_CHECKED(Name, name, 1); + CONVERT_ARG_HANDLE_CHECKED(JSFunction, setter, 2); + + RETURN_FAILURE_ON_EXCEPTION( + isolate, + JSObject::DefineAccessor(object, name, isolate->factory()->null_value(), + setter, NONE)); + return isolate->heap()->undefined_value(); +} } } // namespace v8::internal diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 9e6c495..a39e320 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -187,8 +187,6 @@ namespace internal { F(HomeObjectSymbol, 0, 1) \ F(DefineClass, 6, 1) \ F(DefineClassMethod, 3, 1) \ - F(DefineClassGetter, 3, 1) \ - F(DefineClassSetter, 3, 1) \ F(ClassGetSourceCode, 1, 1) \ F(ThrowNonMethodError, 0, 1) \ F(ThrowUnsupportedSuperError, 0, 1) \ @@ -262,6 +260,8 @@ namespace internal { F(DefineDataPropertyUnchecked, 4, 1) \ F(DefineAccessorPropertyUnchecked, 5, 1) \ F(GetDataProperty, 2, 1) \ + F(DefineGetterPropertyUnchecked, 3, 1) \ + F(DefineSetterPropertyUnchecked, 3, 1) \ \ /* Arrays */ \ F(RemoveArrayHoles, 2, 1) \ diff --git a/src/typing.cc b/src/typing.cc index 88c530e..3fb6030 100644 --- a/src/typing.cc +++ b/src/typing.cc @@ -408,7 +408,9 @@ void AstTyper::VisitObjectLiteral(ObjectLiteral* expr) { if ((prop->kind() == ObjectLiteral::Property::MATERIALIZED_LITERAL && !CompileTimeValue::IsCompileTimeValue(prop->value())) || prop->kind() == ObjectLiteral::Property::COMPUTED) { - if (prop->key()->value()->IsInternalizedString() && prop->emit_store()) { + if (!prop->is_computed_name() && + prop->key()->AsLiteral()->value()->IsInternalizedString() && + prop->emit_store()) { prop->RecordTypeFeedback(oracle()); } } diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index 24747ee..571cc33 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -1651,11 +1651,13 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { expr->CalculateEmitStore(zone()); AccessorTable accessor_table(zone()); - for (int i = 0; i < expr->properties()->length(); i++) { - ObjectLiteral::Property* property = expr->properties()->at(i); + int property_index = 0; + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + if (property->is_computed_name()) break; if (property->IsCompileTimeValue()) continue; - Literal* key = property->key(); + Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); if (!result_saved) { __ Push(rax); // Save result on the stack @@ -1735,6 +1737,65 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { __ CallRuntime(Runtime::kDefineAccessorPropertyUnchecked, 5); } + // Object literals have two parts. The "static" part on the left contains no + // computed property names, and so we can compute its map ahead of time; see + // runtime.cc::CreateObjectLiteralBoilerplate. The second "dynamic" part + // starts with the first computed property name, and continues with all + // properties to its right. All the code from above initializes the static + // component of the object literal, and arranges for the map of the result to + // reflect the static order in which the keys appear. For the dynamic + // properties, we compile them into a series of "SetOwnProperty" runtime + // calls. This will preserve insertion order. + for (; property_index < expr->properties()->length(); property_index++) { + ObjectLiteral::Property* property = expr->properties()->at(property_index); + + Expression* value = property->value(); + if (!result_saved) { + __ Push(rax); // Save result on the stack + result_saved = true; + } + + __ Push(Operand(rsp, 0)); // Duplicate receiver. + + if (property->kind() == ObjectLiteral::Property::PROTOTYPE) { + DCHECK(!property->is_computed_name()); + VisitForStackValue(value); + if (property->emit_store()) { + __ CallRuntime(Runtime::kInternalSetPrototype, 2); + } else { + __ Drop(2); + } + } else { + EmitPropertyKey(property); + VisitForStackValue(value); + + switch (property->kind()) { + case ObjectLiteral::Property::CONSTANT: + case ObjectLiteral::Property::MATERIALIZED_LITERAL: + case ObjectLiteral::Property::COMPUTED: + if (property->emit_store()) { + __ Push(Smi::FromInt(NONE)); + __ CallRuntime(Runtime::kDefineDataPropertyUnchecked, 4); + } else { + __ Drop(3); + } + break; + + case ObjectLiteral::Property::PROTOTYPE: + UNREACHABLE(); + break; + + case ObjectLiteral::Property::GETTER: + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); + break; + + case ObjectLiteral::Property::SETTER: + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); + break; + } + } + } + if (expr->has_function()) { DCHECK(result_saved); __ Push(Operand(rsp, 0)); @@ -2393,16 +2454,14 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { for (int i = 0; i < lit->properties()->length(); i++) { ObjectLiteral::Property* property = lit->properties()->at(i); - Literal* key = property->key()->AsLiteral(); Expression* value = property->value(); - DCHECK(key != NULL); if (property->is_static()) { __ Push(Operand(rsp, kPointerSize)); // constructor } else { __ Push(Operand(rsp, 0)); // prototype } - VisitForStackValue(key); + EmitPropertyKey(property); VisitForStackValue(value); EmitSetHomeObjectIfNeeded(value, 2); @@ -2415,11 +2474,11 @@ void FullCodeGenerator::EmitClassDefineProperties(ClassLiteral* lit) { break; case ObjectLiteral::Property::GETTER: - __ CallRuntime(Runtime::kDefineClassGetter, 3); + __ CallRuntime(Runtime::kDefineGetterPropertyUnchecked, 3); break; case ObjectLiteral::Property::SETTER: - __ CallRuntime(Runtime::kDefineClassSetter, 3); + __ CallRuntime(Runtime::kDefineSetterPropertyUnchecked, 3); break; default: diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index ef6b5d3..f08f9b3 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -1358,7 +1358,8 @@ enum ParserFlag { kAllowHarmonyObjectLiterals, kAllowHarmonyTemplates, kAllowHarmonySloppy, - kAllowHarmonyUnicode + kAllowHarmonyUnicode, + kAllowHarmonyComputedPropertyNames }; @@ -1385,6 +1386,8 @@ void SetParserFlags(i::ParserBase* parser, parser->set_allow_harmony_templates(flags.Contains(kAllowHarmonyTemplates)); parser->set_allow_harmony_sloppy(flags.Contains(kAllowHarmonySloppy)); parser->set_allow_harmony_unicode(flags.Contains(kAllowHarmonyUnicode)); + parser->set_allow_harmony_computed_property_names( + flags.Contains(kAllowHarmonyComputedPropertyNames)); } @@ -4582,3 +4585,61 @@ TEST(LexicalScopingSloppyMode) { always_true_flags, arraysize(always_true_flags), always_false_flags, arraysize(always_false_flags)); } + + +TEST(ComputedPropertyName) { + const char* context_data[][2] = {{"({[", "]: 1});"}, + {"({get [", "]() {}});"}, + {"({set [", "](_) {}});"}, + {"({[", "]() {}});"}, + {"({*[", "]() {}});"}, + {"(class {get [", "]() {}});"}, + {"(class {set [", "](_) {}});"}, + {"(class {[", "]() {}});"}, + {"(class {*[", "]() {}});"}, + {NULL, NULL}}; + const char* error_data[] = { + "1, 2", + "var name", + NULL}; + + static const ParserFlag always_flags[] = { + kAllowHarmonyClasses, + kAllowHarmonyComputedPropertyNames, + kAllowHarmonyObjectLiterals, + kAllowHarmonySloppy, + }; + RunParserSyncTest(context_data, error_data, kError, NULL, 0, + always_flags, arraysize(always_flags)); + + const char* name_data[] = { + "1", + "1 + 2", + "'name'", + "\"name\"", + "[]", + "{}", + NULL}; + + RunParserSyncTest(context_data, name_data, kSuccess, NULL, 0, + always_flags, arraysize(always_flags)); +} + + +TEST(ComputedPropertyNameShorthandError) { + const char* context_data[][2] = {{"({", "});"}, + {NULL, NULL}}; + const char* error_data[] = { + "a: 1, [2]", + "[1], a: 1", + NULL}; + + static const ParserFlag always_flags[] = { + kAllowHarmonyClasses, + kAllowHarmonyComputedPropertyNames, + kAllowHarmonyObjectLiterals, + kAllowHarmonySloppy, + }; + RunParserSyncTest(context_data, error_data, kError, NULL, 0, + always_flags, arraysize(always_flags)); +} diff --git a/test/mjsunit/harmony/computed-property-names-classes.js b/test/mjsunit/harmony/computed-property-names-classes.js new file mode 100644 index 0000000..bcd0318 --- /dev/null +++ b/test/mjsunit/harmony/computed-property-names-classes.js @@ -0,0 +1,367 @@ +// 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. + +'use strict'; + +// Flags: --harmony-computed-property-names --harmony-classes + + +function ID(x) { + return x; +} + + +(function TestClassMethodString() { + class C { + a() { return 'A'} + ['b']() { return 'B'; } + c() { return 'C'; } + [ID('d')]() { return 'D'; } + } + assertEquals('A', new C().a()); + assertEquals('B', new C().b()); + assertEquals('C', new C().c()); + assertEquals('D', new C().d()); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(C.prototype)); +})(); + + +(function TestClassMethodNumber() { + class C { + a() { return 'A'; } + [1]() { return 'B'; } + c() { return 'C'; } + [ID(2)]() { return 'D'; } + } + assertEquals('A', new C().a()); + assertEquals('B', new C()[1]()); + assertEquals('C', new C().c()); + assertEquals('D', new C()[2]()); + // Array indexes first. + assertArrayEquals(['1', '2', 'a', 'c'], Object.keys(C.prototype)); +})(); + + +(function TestClassMethodSymbol() { + var sym1 = Symbol(); + var sym2 = Symbol(); + class C { + a() { return 'A'; } + [sym1]() { return 'B'; } + c() { return 'C'; } + [ID(sym2)]() { return 'D'; } + } + assertEquals('A', new C().a()); + assertEquals('B', new C()[sym1]()); + assertEquals('C', new C().c()); + assertEquals('D', new C()[sym2]()); + assertArrayEquals(['a', 'c'], Object.keys(C.prototype)); + assertArrayEquals([sym1, sym2], Object.getOwnPropertySymbols(C.prototype)); +})(); + + + +(function TestStaticClassMethodString() { + class C { + static a() { return 'A'} + static ['b']() { return 'B'; } + static c() { return 'C'; } + static ['d']() { return 'D'; } + } + assertEquals('A', C.a()); + assertEquals('B', C.b()); + assertEquals('C', C.c()); + assertEquals('D', C.d()); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(C)); +})(); + + +(function TestStaticClassMethodNumber() { + class C { + static a() { return 'A'; } + static [1]() { return 'B'; } + static c() { return 'C'; } + static [2]() { return 'D'; } + } + assertEquals('A', C.a()); + assertEquals('B', C[1]()); + assertEquals('C', C.c()); + assertEquals('D', C[2]()); + // Array indexes first. + assertArrayEquals(['1', '2', 'a', 'c'], Object.keys(C)); +})(); + + +(function TestStaticClassMethodSymbol() { + var sym1 = Symbol(); + var sym2 = Symbol(); + class C { + static a() { return 'A'; } + static [sym1]() { return 'B'; } + static c() { return 'C'; } + static [sym2]() { return 'D'; } + } + assertEquals('A', C.a()); + assertEquals('B', C[sym1]()); + assertEquals('C', C.c()); + assertEquals('D', C[sym2]()); + assertArrayEquals(['a', 'c'], Object.keys(C)); + assertArrayEquals([sym1, sym2], Object.getOwnPropertySymbols(C)); +})(); + + + +function assertIteratorResult(value, done, result) { + assertEquals({ value: value, done: done}, result); +} + + +(function TestGeneratorComputedName() { + class C { + *['a']() { + yield 1; + yield 2; + } + } + var iter = new C().a(); + assertIteratorResult(1, false, iter.next()); + assertIteratorResult(2, false, iter.next()); + assertIteratorResult(undefined, true, iter.next()); + assertArrayEquals(['a'], Object.keys(C.prototype)); +})(); + + +(function TestToNameSideEffects() { + var counter = 0; + var key1 = { + toString: function() { + assertEquals(0, counter++); + return 'b'; + } + }; + var key2 = { + toString: function() { + assertEquals(1, counter++); + return 'd'; + } + }; + class C { + a() { return 'A'; } + [key1]() { return 'B'; } + c() { return 'C'; } + [key2]() { return 'D'; } + } + assertEquals(2, counter); + assertEquals('A', new C().a()); + assertEquals('B', new C().b()); + assertEquals('C', new C().c()); + assertEquals('D', new C().d()); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(C.prototype)); +})(); + + +(function TestToNameSideEffectsNumbers() { + var counter = 0; + var key1 = { + valueOf: function() { + assertEquals(0, counter++); + return 1; + }, + toString: null + }; + var key2 = { + valueOf: function() { + assertEquals(1, counter++); + return 2; + }, + toString: null + }; + + class C { + a() { return 'A'; } + [key1]() { return 'B'; } + c() { return 'C'; } + [key2]() { return 'D'; } + } + assertEquals(2, counter); + assertEquals('A', new C().a()); + assertEquals('B', new C()[1]()); + assertEquals('C', new C().c()); + assertEquals('D', new C()[2]()); + // Array indexes first. + assertArrayEquals(['1', '2', 'a', 'c'], Object.keys(C.prototype)); +})(); + + +(function TestGetter() { + class C { + get ['a']() { + return 'A'; + } + } + assertEquals('A', new C().a); + + class C2 { + get b() { + assertUnreachable(); + } + get ['b']() { + return 'B'; + } + } + assertEquals('B', new C2().b); + + class C3 { + get c() { + assertUnreachable(); + } + get ['c']() { + assertUnreachable(); + } + get ['c']() { + return 'C'; + } + } + assertEquals('C', new C3().c); + + class C4 { + get ['d']() { + assertUnreachable(); + } + get d() { + return 'D'; + } + } + assertEquals('D', new C4().d); +})(); + + +(function TestSetter() { + var calls = 0; + class C { + set ['a'](_) { + calls++; + } + } + new C().a = 'A'; + assertEquals(1, calls); + + calls = 0; + class C2 { + set b(_) { + assertUnreachable(); + } + set ['b'](_) { + calls++; + } + } + new C2().b = 'B'; + assertEquals(1, calls); + + calls = 0; + class C3 { + set c(_) { + assertUnreachable() + } + set ['c'](_) { + assertUnreachable() + } + set ['c'](_) { + calls++ + } + } + new C3().c = 'C'; + assertEquals(1, calls); + + calls = 0; + class C4 { + set ['d'](_) { + assertUnreachable() + } + set d(_) { + calls++ + } + } + new C4().d = 'D'; + assertEquals(1, calls); +})(); + + +(function TestPrototype() { + // Normally a static prototype property is not allowed. + class C { + static ['prototype']() { + return 1; + } + } + assertEquals(1, C.prototype()); + + class C2 { + static get ['prototype']() { + return 2; + } + } + assertEquals(2, C2.prototype); + + var calls = 0; + class C3 { + static set ['prototype'](x) { + assertEquals(3, x); + calls++; + } + } + C3.prototype = 3; + assertEquals(1, calls); + + class C4 { + static *['prototype']() { + yield 1; + yield 2; + } + } + var iter = C4.prototype(); + assertIteratorResult(1, false, iter.next()); + assertIteratorResult(2, false, iter.next()); + assertIteratorResult(undefined, true, iter.next()); +})(); + + +(function TestConstructor() { + // Normally a constructor property is not allowed. + class C { + ['constructor']() { + return 1; + } + } + assertTrue(C !== C.prototype.constructor); + assertEquals(1, new C().constructor()); + + class C2 { + get ['constructor']() { + return 2; + } + } + assertEquals(2, new C2().constructor); + + var calls = 0; + class C3 { + set ['constructor'](x) { + assertEquals(3, x); + calls++; + } + } + new C3().constructor = 3; + assertEquals(1, calls); + + class C4 { + *['constructor']() { + yield 1; + yield 2; + } + } + var iter = new C4().constructor(); + assertIteratorResult(1, false, iter.next()); + assertIteratorResult(2, false, iter.next()); + assertIteratorResult(undefined, true, iter.next()); +})(); diff --git a/test/mjsunit/harmony/computed-property-names-object-literals-methods.js b/test/mjsunit/harmony/computed-property-names-object-literals-methods.js new file mode 100644 index 0000000..135d098 --- /dev/null +++ b/test/mjsunit/harmony/computed-property-names-object-literals-methods.js @@ -0,0 +1,121 @@ +// 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-computed-property-names --harmony-object-literals + + +function ID(x) { + return x; +} + + +(function TestMethodComputedNameString() { + var object = { + a() { return 'A'}, + ['b']() { return 'B'; }, + c() { return 'C'; }, + [ID('d')]() { return 'D'; }, + }; + assertEquals('A', object.a()); + assertEquals('B', object.b()); + assertEquals('C', object.c()); + assertEquals('D', object.d()); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(object)); +})(); + + +(function TestMethodComputedNameNumber() { + var object = { + a() { return 'A'; }, + [1]() { return 'B'; }, + c() { return 'C'; }, + [ID(2)]() { return 'D'; }, + }; + assertEquals('A', object.a()); + assertEquals('B', object[1]()); + assertEquals('C', object.c()); + assertEquals('D', object[2]()); + // Array indexes first. + assertArrayEquals(['1', '2', 'a', 'c'], Object.keys(object)); +})(); + + +(function TestMethodComputedNameSymbol() { + var sym1 = Symbol(); + var sym2 = Symbol(); + var object = { + a() { return 'A'; }, + [sym1]() { return 'B'; }, + c() { return 'C'; }, + [ID(sym2)]() { return 'D'; }, + }; + assertEquals('A', object.a()); + assertEquals('B', object[sym1]()); + assertEquals('C', object.c()); + assertEquals('D', object[sym2]()); + assertArrayEquals(['a', 'c'], Object.keys(object)); + assertArrayEquals([sym1, sym2], Object.getOwnPropertySymbols(object)); +})(); + + +function assertIteratorResult(value, done, result) { + assertEquals({ value: value, done: done}, result); +} + + +(function TestGeneratorComputedName() { + var object = { + *['a']() { + yield 1; + yield 2; + } + }; + var iter = object.a(); + assertIteratorResult(1, false, iter.next()); + assertIteratorResult(2, false, iter.next()); + assertIteratorResult(undefined, true, iter.next()); + assertArrayEquals(['a'], Object.keys(object)); +})(); + + +(function TestToNameSideEffects() { + var counter = 0; + var key1 = { + toString: function() { + assertEquals(0, counter++); + return 'b'; + } + }; + var key2 = { + toString: function() { + assertEquals(1, counter++); + return 'd'; + } + }; + var object = { + a() { return 'A'; }, + [key1]() { return 'B'; }, + c() { return 'C'; }, + [key2]() { return 'D'; }, + }; + assertEquals(2, counter); + assertEquals('A', object.a()); + assertEquals('B', object.b()); + assertEquals('C', object.c()); + assertEquals('D', object.d()); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(object)); +})(); + + +(function TestDuplicateKeys() { + 'use strict'; + // ES5 does not allow duplicate keys. + // ES6 does but we haven't changed our code yet. + + var object = { + a() { return 1; }, + ['a']() { return 2; }, + }; + assertEquals(2, object.a()); +})(); diff --git a/test/mjsunit/harmony/computed-property-names.js b/test/mjsunit/harmony/computed-property-names.js new file mode 100644 index 0000000..36ce675 --- /dev/null +++ b/test/mjsunit/harmony/computed-property-names.js @@ -0,0 +1,270 @@ +// 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-computed-property-names + + +function ID(x) { + return x; +} + + + +(function TestBasicsString() { + var object = { + a: 'A', + ['b']: 'B', + c: 'C', + [ID('d')]: 'D', + }; + assertEquals('A', object.a); + assertEquals('B', object.b); + assertEquals('C', object.c); + assertEquals('D', object.d); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(object)); +})(); + + +(function TestBasicsNumber() { + var object = { + a: 'A', + [1]: 'B', + c: 'C', + [ID(2)]: 'D', + }; + assertEquals('A', object.a); + assertEquals('B', object[1]); + assertEquals('C', object.c); + assertEquals('D', object[2]); + // Array indexes first. + assertArrayEquals(['1', '2', 'a', 'c'], Object.keys(object)); +})(); + + +(function TestBasicsSymbol() { + var sym1 = Symbol(); + var sym2 = Symbol(); + var object = { + a: 'A', + [sym1]: 'B', + c: 'C', + [ID(sym2)]: 'D', + }; + assertEquals('A', object.a); + assertEquals('B', object[sym1]); + assertEquals('C', object.c); + assertEquals('D', object[sym2]); + assertArrayEquals(['a', 'c'], Object.keys(object)); + assertArrayEquals([sym1, sym2], Object.getOwnPropertySymbols(object)); +})(); + + +(function TestToNameSideEffects() { + var counter = 0; + var key1 = { + toString: function() { + assertEquals(0, counter++); + return 'b'; + } + }; + var key2 = { + toString: function() { + assertEquals(1, counter++); + return 'd'; + } + }; + var object = { + a: 'A', + [key1]: 'B', + c: 'C', + [key2]: 'D', + }; + assertEquals(2, counter); + assertEquals('A', object.a); + assertEquals('B', object.b); + assertEquals('C', object.c); + assertEquals('D', object.d); + assertArrayEquals(['a', 'b', 'c', 'd'], Object.keys(object)); +})(); + + +(function TestToNameSideEffectsNumbers() { + var counter = 0; + var key1 = { + valueOf: function() { + assertEquals(0, counter++); + return 1; + }, + toString: null + }; + var key2 = { + valueOf: function() { + assertEquals(1, counter++); + return 2; + }, + toString: null + }; + + var object = { + a: 'A', + [key1]: 'B', + c: 'C', + [key2]: 'D', + }; + assertEquals(2, counter); + assertEquals('A', object.a); + assertEquals('B', object[1]); + assertEquals('C', object.c); + assertEquals('D', object[2]); + // Array indexes first. + assertArrayEquals(['1', '2', 'a', 'c'], Object.keys(object)); +})(); + + +(function TestDoubleName() { + var object = { + [1.2]: 'A', + [1e55]: 'B', + [0.000001]: 'C', + [-0]: 'D', + [Infinity]: 'E', + [-Infinity]: 'F', + [NaN]: 'G', + }; + assertEquals('A', object['1.2']); + assertEquals('B', object['1e+55']); + assertEquals('C', object['0.000001']); + assertEquals('D', object[0]); + assertEquals('E', object[Infinity]); + assertEquals('F', object[-Infinity]); + assertEquals('G', object[NaN]); +})(); + + +(function TestGetter() { + var object = { + get ['a']() { + return 'A'; + } + }; + assertEquals('A', object.a); + + object = { + get b() { + assertUnreachable(); + }, + get ['b']() { + return 'B'; + } + }; + assertEquals('B', object.b); + + object = { + get c() { + assertUnreachable(); + }, + get ['c']() { + assertUnreachable(); + }, + get ['c']() { + return 'C'; + } + }; + assertEquals('C', object.c); + + object = { + get ['d']() { + assertUnreachable(); + }, + get d() { + return 'D'; + } + }; + assertEquals('D', object.d); +})(); + + +(function TestSetter() { + var calls = 0; + var object = { + set ['a'](_) { + calls++; + } + }; + object.a = 'A'; + assertEquals(1, calls); + + calls = 0; + object = { + set b(_) { + assertUnreachable(); + }, + set ['b'](_) { + calls++; + } + }; + object.b = 'B'; + assertEquals(1, calls); + + calls = 0; + object = { + set c(_) { + assertUnreachable() + }, + set ['c'](_) { + assertUnreachable() + }, + set ['c'](_) { + calls++ + } + }; + object.c = 'C'; + assertEquals(1, calls); + + calls = 0; + object = { + set ['d'](_) { + assertUnreachable() + }, + set d(_) { + calls++ + } + }; + object.d = 'D'; + assertEquals(1, calls); +})(); + + +(function TestDuplicateKeys() { + 'use strict'; + // ES5 does not allow duplicate keys. + // ES6 does but we haven't changed our code yet. + + var object = { + a: 1, + ['a']: 2, + }; + assertEquals(2, object.a); +})(); + + +(function TestProto() { + var proto = {}; + var object = { + __proto__: proto + }; + assertEquals(proto, Object.getPrototypeOf(object)); + + object = { + '__proto__': proto + }; + assertEquals(proto, Object.getPrototypeOf(object)); + + var object = { + ['__proto__']: proto + }; + assertEquals(Object.prototype, Object.getPrototypeOf(object)); + assertEquals(proto, object.__proto__); + assertTrue(object.hasOwnProperty('__proto__')); +})();