[ESnext] Implement Object spread
This patch adds parsing of spread object property. -- Changes ParsePropertyName to parse Token::ELLIPSIS. -- Throws if rest is encountered by setting a pattern error. -- Adds a new PropertyKind enum (SPREAD) -- Adds a new ObjectLiteralProperty::kind (SPREAD) -- Adds a new harmony-object-spread flag and protects the parser code with it. -- Adds a new runtime function called CopyDataProperties -- Does not add any support for this feature in fullcodegen. -- Ignition calls out to a runtime function CopyDataProperties to perform spread operation. -- Move FastAssign from builtins-objects.cc to objects.cc -- Refactor Builtin_ObjectAssign to use SetOrCopyDataProperties Object rest will be implemented in a follow on patch. BUG=v8:5549 Review-Url: https://codereview.chromium.org/2606833002 Cr-Commit-Position: refs/heads/master@{#42102}
This commit is contained in:
parent
a5a376c530
commit
a40b7172fe
@ -424,6 +424,7 @@ void ObjectLiteral::AssignFeedbackVectorSlots(Isolate* isolate,
|
||||
Literal* key = property->key()->AsLiteral();
|
||||
Expression* value = property->value();
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1360,7 +1360,8 @@ class ObjectLiteralProperty final : public LiteralProperty {
|
||||
MATERIALIZED_LITERAL, // Property value is a materialized literal.
|
||||
GETTER,
|
||||
SETTER, // Property is an accessor function.
|
||||
PROTOTYPE // Property is __proto__.
|
||||
PROTOTYPE, // Property is __proto__.
|
||||
SPREAD
|
||||
};
|
||||
|
||||
Kind kind() const { return kind_; }
|
||||
|
@ -1025,6 +1025,9 @@ void AstPrinter::PrintObjectProperties(
|
||||
case ObjectLiteral::Property::SETTER:
|
||||
prop_kind = "SETTER";
|
||||
break;
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
prop_kind = "SPREAD";
|
||||
break;
|
||||
}
|
||||
EmbeddedVector<char, 128> buf;
|
||||
SNPrintF(buf, "PROPERTY - %s", prop_kind);
|
||||
|
@ -3446,6 +3446,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_async_await)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_restrictive_generators)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_trailing_commas)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_fields)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_spread)
|
||||
|
||||
void InstallPublicSymbol(Factory* factory, Handle<Context> native_context,
|
||||
const char* name, Handle<Symbol> value) {
|
||||
@ -4007,6 +4008,7 @@ bool Genesis::InstallExperimentalNatives() {
|
||||
static const char* harmony_restrictive_generators_natives[] = {nullptr};
|
||||
static const char* harmony_trailing_commas_natives[] = {nullptr};
|
||||
static const char* harmony_class_fields_natives[] = {nullptr};
|
||||
static const char* harmony_object_spread_natives[] = {nullptr};
|
||||
|
||||
for (int i = ExperimentalNatives::GetDebuggerCount();
|
||||
i < ExperimentalNatives::GetBuiltinsCount(); i++) {
|
||||
|
@ -66,87 +66,6 @@ void Builtins::Generate_ObjectHasOwnProperty(
|
||||
context, object, key));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
MUST_USE_RESULT Maybe<bool> FastAssign(Handle<JSReceiver> to,
|
||||
Handle<Object> next_source) {
|
||||
// Non-empty strings are the only non-JSReceivers that need to be handled
|
||||
// explicitly by Object.assign.
|
||||
if (!next_source->IsJSReceiver()) {
|
||||
return Just(!next_source->IsString() ||
|
||||
String::cast(*next_source)->length() == 0);
|
||||
}
|
||||
|
||||
// If the target is deprecated, the object will be updated on first store. If
|
||||
// the source for that store equals the target, this will invalidate the
|
||||
// cached representation of the source. Preventively upgrade the target.
|
||||
// Do this on each iteration since any property load could cause deprecation.
|
||||
if (to->map()->is_deprecated()) {
|
||||
JSObject::MigrateInstance(Handle<JSObject>::cast(to));
|
||||
}
|
||||
|
||||
Isolate* isolate = to->GetIsolate();
|
||||
Handle<Map> map(JSReceiver::cast(*next_source)->map(), isolate);
|
||||
|
||||
if (!map->IsJSObjectMap()) return Just(false);
|
||||
if (!map->OnlyHasSimpleProperties()) return Just(false);
|
||||
|
||||
Handle<JSObject> from = Handle<JSObject>::cast(next_source);
|
||||
if (from->elements() != isolate->heap()->empty_fixed_array()) {
|
||||
return Just(false);
|
||||
}
|
||||
|
||||
Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate);
|
||||
int length = map->NumberOfOwnDescriptors();
|
||||
|
||||
bool stable = true;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
Handle<Name> next_key(descriptors->GetKey(i), isolate);
|
||||
Handle<Object> prop_value;
|
||||
// Directly decode from the descriptor array if |from| did not change shape.
|
||||
if (stable) {
|
||||
PropertyDetails details = descriptors->GetDetails(i);
|
||||
if (!details.IsEnumerable()) continue;
|
||||
if (details.kind() == kData) {
|
||||
if (details.location() == kDescriptor) {
|
||||
prop_value = handle(descriptors->GetValue(i), isolate);
|
||||
} else {
|
||||
Representation representation = details.representation();
|
||||
FieldIndex index = FieldIndex::ForDescriptor(*map, i);
|
||||
prop_value = JSObject::FastPropertyAt(from, representation, index);
|
||||
}
|
||||
} else {
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, prop_value, JSReceiver::GetProperty(from, next_key),
|
||||
Nothing<bool>());
|
||||
stable = from->map() == *map;
|
||||
}
|
||||
} else {
|
||||
// If the map did change, do a slower lookup. We are still guaranteed that
|
||||
// the object has a simple shape, and that the key is a name.
|
||||
LookupIterator it(from, next_key, from,
|
||||
LookupIterator::OWN_SKIP_INTERCEPTOR);
|
||||
if (!it.IsFound()) continue;
|
||||
DCHECK(it.state() == LookupIterator::DATA ||
|
||||
it.state() == LookupIterator::ACCESSOR);
|
||||
if (!it.IsEnumerable()) continue;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, prop_value, Object::GetProperty(&it), Nothing<bool>());
|
||||
}
|
||||
LookupIterator it(to, next_key, to);
|
||||
bool call_to_js = it.IsFound() && it.state() != LookupIterator::DATA;
|
||||
Maybe<bool> result = Object::SetProperty(
|
||||
&it, prop_value, STRICT, Object::CERTAINLY_NOT_STORE_FROM_KEYED);
|
||||
if (result.IsNothing()) return result;
|
||||
if (stable && call_to_js) stable = from->map() == *map;
|
||||
}
|
||||
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ES6 19.1.2.1 Object.assign
|
||||
BUILTIN(ObjectAssign) {
|
||||
HandleScope scope(isolate);
|
||||
@ -163,43 +82,9 @@ BUILTIN(ObjectAssign) {
|
||||
// 4. For each element nextSource of sources, in ascending index order,
|
||||
for (int i = 2; i < args.length(); ++i) {
|
||||
Handle<Object> next_source = args.at(i);
|
||||
Maybe<bool> fast_assign = FastAssign(to, next_source);
|
||||
if (fast_assign.IsNothing()) return isolate->heap()->exception();
|
||||
if (fast_assign.FromJust()) continue;
|
||||
// 4a. If nextSource is undefined or null, let keys be an empty List.
|
||||
// 4b. Else,
|
||||
// 4b i. Let from be ToObject(nextSource).
|
||||
// Only non-empty strings and JSReceivers have enumerable properties.
|
||||
Handle<JSReceiver> from =
|
||||
Object::ToObject(isolate, next_source).ToHandleChecked();
|
||||
// 4b ii. Let keys be ? from.[[OwnPropertyKeys]]().
|
||||
Handle<FixedArray> keys;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, keys, KeyAccumulator::GetKeys(
|
||||
from, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
|
||||
GetKeysConversion::kKeepNumbers));
|
||||
// 4c. Repeat for each element nextKey of keys in List order,
|
||||
for (int j = 0; j < keys->length(); ++j) {
|
||||
Handle<Object> next_key(keys->get(j), isolate);
|
||||
// 4c i. Let desc be ? from.[[GetOwnProperty]](nextKey).
|
||||
PropertyDescriptor desc;
|
||||
Maybe<bool> found =
|
||||
JSReceiver::GetOwnPropertyDescriptor(isolate, from, next_key, &desc);
|
||||
if (found.IsNothing()) return isolate->heap()->exception();
|
||||
// 4c ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
if (found.FromJust() && desc.enumerable()) {
|
||||
// 4c ii 1. Let propValue be ? Get(from, nextKey).
|
||||
Handle<Object> prop_value;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, prop_value,
|
||||
Runtime::GetObjectProperty(isolate, from, next_key));
|
||||
// 4c ii 2. Let status be ? Set(to, nextKey, propValue, true).
|
||||
Handle<Object> status;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, status, Runtime::SetObjectProperty(isolate, to, next_key,
|
||||
prop_value, STRICT));
|
||||
}
|
||||
}
|
||||
MAYBE_RETURN(
|
||||
JSReceiver::SetOrCopyDataProperties(isolate, to, next_source, true),
|
||||
isolate->heap()->exception());
|
||||
}
|
||||
// 5. Return to.
|
||||
return *to;
|
||||
|
@ -1380,6 +1380,7 @@ void AstGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
|
||||
Literal* key = property->key()->AsLiteral();
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -201,7 +201,8 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
|
||||
V(harmony_do_expressions, "harmony do-expressions") \
|
||||
V(harmony_regexp_named_captures, "harmony regexp named captures") \
|
||||
V(harmony_regexp_property, "harmony unicode regexp property classes") \
|
||||
V(harmony_class_fields, "harmony public fields in class literals")
|
||||
V(harmony_class_fields, "harmony public fields in class literals") \
|
||||
V(harmony_object_spread, "harmony object spread")
|
||||
|
||||
// Features that are complete (but still behind --harmony/es-staging flag).
|
||||
#define HARMONY_STAGED_BASE(V) \
|
||||
|
@ -1242,6 +1242,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1229,6 +1229,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1173,6 +1173,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1241,6 +1241,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1243,6 +1243,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1213,6 +1213,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1176,6 +1176,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1202,6 +1202,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1165,6 +1165,7 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
result_saved = true;
|
||||
}
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
|
@ -1635,6 +1635,7 @@ void BytecodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
RegisterAllocationScope inner_register_scope(this);
|
||||
Literal* key = property->key()->AsLiteral();
|
||||
switch (property->kind()) {
|
||||
case ObjectLiteral::Property::SPREAD:
|
||||
case ObjectLiteral::Property::CONSTANT:
|
||||
UNREACHABLE();
|
||||
case ObjectLiteral::Property::MATERIALIZED_LITERAL:
|
||||
@ -1783,6 +1784,13 @@ void BytecodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
builder()->CallRuntime(function_id, args);
|
||||
break;
|
||||
}
|
||||
case ObjectLiteral::Property::SPREAD: {
|
||||
RegisterList args = register_allocator()->NewRegisterList(2);
|
||||
builder()->MoveRegister(literal, args[0]);
|
||||
VisitForRegisterValue(property->value(), args[1]);
|
||||
builder()->CallRuntime(Runtime::kCopyDataProperties, args);
|
||||
break;
|
||||
}
|
||||
case ObjectLiteral::Property::PROTOTYPE:
|
||||
UNREACHABLE(); // Handled specially above.
|
||||
break;
|
||||
|
149
src/objects.cc
149
src/objects.cc
@ -1999,6 +1999,155 @@ Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate,
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
MUST_USE_RESULT Maybe<bool> FastAssign(Handle<JSReceiver> target,
|
||||
Handle<Object> source, bool use_set) {
|
||||
// Non-empty strings are the only non-JSReceivers that need to be handled
|
||||
// explicitly by Object.assign.
|
||||
if (!source->IsJSReceiver()) {
|
||||
return Just(!source->IsString() || String::cast(*source)->length() == 0);
|
||||
}
|
||||
|
||||
// If the target is deprecated, the object will be updated on first store. If
|
||||
// the source for that store equals the target, this will invalidate the
|
||||
// cached representation of the source. Preventively upgrade the target.
|
||||
// Do this on each iteration since any property load could cause deprecation.
|
||||
if (target->map()->is_deprecated()) {
|
||||
JSObject::MigrateInstance(Handle<JSObject>::cast(target));
|
||||
}
|
||||
|
||||
Isolate* isolate = target->GetIsolate();
|
||||
Handle<Map> map(JSReceiver::cast(*source)->map(), isolate);
|
||||
|
||||
if (!map->IsJSObjectMap()) return Just(false);
|
||||
if (!map->OnlyHasSimpleProperties()) return Just(false);
|
||||
|
||||
Handle<JSObject> from = Handle<JSObject>::cast(source);
|
||||
if (from->elements() != isolate->heap()->empty_fixed_array()) {
|
||||
return Just(false);
|
||||
}
|
||||
|
||||
Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate);
|
||||
int length = map->NumberOfOwnDescriptors();
|
||||
|
||||
bool stable = true;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
Handle<Name> next_key(descriptors->GetKey(i), isolate);
|
||||
Handle<Object> prop_value;
|
||||
// Directly decode from the descriptor array if |from| did not change shape.
|
||||
if (stable) {
|
||||
PropertyDetails details = descriptors->GetDetails(i);
|
||||
if (!details.IsEnumerable()) continue;
|
||||
if (details.kind() == kData) {
|
||||
if (details.location() == kDescriptor) {
|
||||
prop_value = handle(descriptors->GetValue(i), isolate);
|
||||
} else {
|
||||
Representation representation = details.representation();
|
||||
FieldIndex index = FieldIndex::ForDescriptor(*map, i);
|
||||
prop_value = JSObject::FastPropertyAt(from, representation, index);
|
||||
}
|
||||
} else {
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, prop_value, JSReceiver::GetProperty(from, next_key),
|
||||
Nothing<bool>());
|
||||
stable = from->map() == *map;
|
||||
}
|
||||
} else {
|
||||
// If the map did change, do a slower lookup. We are still guaranteed that
|
||||
// the object has a simple shape, and that the key is a name.
|
||||
LookupIterator it(from, next_key, from,
|
||||
LookupIterator::OWN_SKIP_INTERCEPTOR);
|
||||
if (!it.IsFound()) continue;
|
||||
DCHECK(it.state() == LookupIterator::DATA ||
|
||||
it.state() == LookupIterator::ACCESSOR);
|
||||
if (!it.IsEnumerable()) continue;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, prop_value, Object::GetProperty(&it), Nothing<bool>());
|
||||
}
|
||||
|
||||
if (use_set) {
|
||||
LookupIterator it(target, next_key, target);
|
||||
bool call_to_js = it.IsFound() && it.state() != LookupIterator::DATA;
|
||||
Maybe<bool> result = Object::SetProperty(
|
||||
&it, prop_value, STRICT, Object::CERTAINLY_NOT_STORE_FROM_KEYED);
|
||||
if (result.IsNothing()) return result;
|
||||
if (stable && call_to_js) stable = from->map() == *map;
|
||||
} else {
|
||||
// 4a ii 2. Perform ? CreateDataProperty(target, nextKey, propValue).
|
||||
bool success;
|
||||
LookupIterator it = LookupIterator::PropertyOrElement(
|
||||
isolate, target, next_key, &success, LookupIterator::OWN);
|
||||
CHECK(success);
|
||||
CHECK(
|
||||
JSObject::CreateDataProperty(&it, prop_value, Object::THROW_ON_ERROR)
|
||||
.FromJust());
|
||||
}
|
||||
}
|
||||
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
Maybe<bool> JSReceiver::SetOrCopyDataProperties(Isolate* isolate,
|
||||
Handle<JSReceiver> target,
|
||||
Handle<Object> source,
|
||||
bool use_set) {
|
||||
Maybe<bool> fast_assign = FastAssign(target, source, use_set);
|
||||
if (fast_assign.IsNothing()) return Nothing<bool>();
|
||||
if (fast_assign.FromJust()) return Just(true);
|
||||
|
||||
Handle<JSReceiver> from = Object::ToObject(isolate, source).ToHandleChecked();
|
||||
// 3b. Let keys be ? from.[[OwnPropertyKeys]]().
|
||||
Handle<FixedArray> keys;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, keys,
|
||||
KeyAccumulator::GetKeys(from, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES,
|
||||
GetKeysConversion::kKeepNumbers),
|
||||
Nothing<bool>());
|
||||
|
||||
// 4. Repeat for each element nextKey of keys in List order,
|
||||
for (int j = 0; j < keys->length(); ++j) {
|
||||
Handle<Object> next_key(keys->get(j), isolate);
|
||||
// 4a i. Let desc be ? from.[[GetOwnProperty]](nextKey).
|
||||
PropertyDescriptor desc;
|
||||
Maybe<bool> found =
|
||||
JSReceiver::GetOwnPropertyDescriptor(isolate, from, next_key, &desc);
|
||||
if (found.IsNothing()) return Nothing<bool>();
|
||||
// 4a ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
if (found.FromJust() && desc.enumerable()) {
|
||||
// 4a ii 1. Let propValue be ? Get(from, nextKey).
|
||||
Handle<Object> prop_value;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, prop_value,
|
||||
Runtime::GetObjectProperty(isolate, from, next_key), Nothing<bool>());
|
||||
|
||||
if (use_set) {
|
||||
// 4c ii 2. Let status be ? Set(to, nextKey, propValue, true).
|
||||
Handle<Object> status;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, status, Runtime::SetObjectProperty(
|
||||
isolate, target, next_key, prop_value, STRICT),
|
||||
Nothing<bool>());
|
||||
} else {
|
||||
// 4a ii 2. Perform ! CreateDataProperty(target, nextKey, propValue).
|
||||
bool success;
|
||||
LookupIterator it = LookupIterator::PropertyOrElement(
|
||||
isolate, target, next_key, &success, LookupIterator::OWN);
|
||||
CHECK(success);
|
||||
CHECK(JSObject::CreateDataProperty(&it, prop_value,
|
||||
Object::THROW_ON_ERROR)
|
||||
.FromJust());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
Map* Object::GetPrototypeChainRootMap(Isolate* isolate) {
|
||||
DisallowHeapAllocation no_alloc;
|
||||
if (IsSmi()) {
|
||||
|
@ -2002,6 +2002,12 @@ class JSReceiver: public HeapObject {
|
||||
MUST_USE_RESULT static Maybe<bool> HasInPrototypeChain(
|
||||
Isolate* isolate, Handle<JSReceiver> object, Handle<Object> proto);
|
||||
|
||||
// Reads all enumerable own properties of source and adds them to target,
|
||||
// using either Set or CreateDataProperty depending on the use_set argument.
|
||||
MUST_USE_RESULT static Maybe<bool> SetOrCopyDataProperties(
|
||||
Isolate* isolate, Handle<JSReceiver> target, Handle<Object> source,
|
||||
bool use_set);
|
||||
|
||||
// Implementation of [[HasProperty]], ECMA-262 5th edition, section 8.12.6.
|
||||
MUST_USE_RESULT static Maybe<bool> HasProperty(LookupIterator* it);
|
||||
MUST_USE_RESULT static inline Maybe<bool> HasProperty(
|
||||
|
@ -218,7 +218,8 @@ class ParserBase {
|
||||
allow_harmony_async_await_(false),
|
||||
allow_harmony_restrictive_generators_(false),
|
||||
allow_harmony_trailing_commas_(false),
|
||||
allow_harmony_class_fields_(false) {}
|
||||
allow_harmony_class_fields_(false),
|
||||
allow_harmony_object_spread_(false) {}
|
||||
|
||||
#define ALLOW_ACCESSORS(name) \
|
||||
bool allow_##name() const { return allow_##name##_; } \
|
||||
@ -232,6 +233,7 @@ class ParserBase {
|
||||
ALLOW_ACCESSORS(harmony_restrictive_generators);
|
||||
ALLOW_ACCESSORS(harmony_trailing_commas);
|
||||
ALLOW_ACCESSORS(harmony_class_fields);
|
||||
ALLOW_ACCESSORS(harmony_object_spread);
|
||||
|
||||
#undef ALLOW_ACCESSORS
|
||||
|
||||
@ -1150,6 +1152,7 @@ class ParserBase {
|
||||
kShorthandProperty,
|
||||
kMethodProperty,
|
||||
kClassField,
|
||||
kSpreadProperty,
|
||||
kNotSet
|
||||
};
|
||||
|
||||
@ -1456,6 +1459,7 @@ class ParserBase {
|
||||
bool allow_harmony_restrictive_generators_;
|
||||
bool allow_harmony_trailing_commas_;
|
||||
bool allow_harmony_class_fields_;
|
||||
bool allow_harmony_object_spread_;
|
||||
|
||||
friend class DiscardableZoneScope;
|
||||
};
|
||||
@ -2109,6 +2113,22 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParsePropertyName(
|
||||
break;
|
||||
}
|
||||
|
||||
case Token::ELLIPSIS:
|
||||
if (allow_harmony_object_spread()) {
|
||||
// TODO(gsathya): Implement destructuring/rest
|
||||
classifier()->RecordPatternError(scanner()->location(),
|
||||
MessageTemplate::kUnexpectedToken);
|
||||
|
||||
*name = impl()->EmptyIdentifier();
|
||||
Consume(Token::ELLIPSIS);
|
||||
ExpressionClassifier spread_classifier(this);
|
||||
expression = ParseAssignmentExpression(true, CHECK_OK);
|
||||
impl()->RewriteNonPattern(CHECK_OK);
|
||||
impl()->AccumulateFormalParameterContainmentErrors();
|
||||
*kind = PropertyKind::kSpreadProperty;
|
||||
return expression;
|
||||
}
|
||||
|
||||
default:
|
||||
*name = ParseIdentifierName(CHECK_OK);
|
||||
break;
|
||||
@ -2269,6 +2289,8 @@ ParserBase<Impl>::ParseClassPropertyDefinition(
|
||||
*property_kind, *is_static,
|
||||
*is_computed_name);
|
||||
}
|
||||
case PropertyKind::kSpreadProperty:
|
||||
UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
return impl()->EmptyClassLiteralProperty();
|
||||
@ -2330,6 +2352,18 @@ ParserBase<Impl>::ParseObjectPropertyDefinition(ObjectLiteralChecker* checker,
|
||||
is_computed_name, CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
|
||||
|
||||
switch (kind) {
|
||||
case PropertyKind::kSpreadProperty:
|
||||
DCHECK(allow_harmony_object_spread());
|
||||
DCHECK(!is_get && !is_set && !is_generator && !is_async &&
|
||||
!*is_computed_name);
|
||||
DCHECK(name_token == Token::ELLIPSIS);
|
||||
|
||||
*is_computed_name = true;
|
||||
|
||||
return factory()->NewObjectLiteralProperty(
|
||||
impl()->GetLiteralTheHole(kNoSourcePosition), name_expression,
|
||||
ObjectLiteralProperty::SPREAD, true);
|
||||
|
||||
case PropertyKind::kValueProperty: {
|
||||
DCHECK(!is_get && !is_set && !is_generator && !is_async);
|
||||
|
||||
|
@ -552,6 +552,7 @@ Parser::Parser(ParseInfo* info)
|
||||
set_allow_harmony_restrictive_generators(FLAG_harmony_restrictive_generators);
|
||||
set_allow_harmony_trailing_commas(FLAG_harmony_trailing_commas);
|
||||
set_allow_harmony_class_fields(FLAG_harmony_class_fields);
|
||||
set_allow_harmony_object_spread(FLAG_harmony_object_spread);
|
||||
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
|
||||
++feature) {
|
||||
use_counts_[feature] = 0;
|
||||
@ -2751,6 +2752,7 @@ Parser::LazyParsingResult Parser::SkipFunction(
|
||||
SET_ALLOW(harmony_async_await);
|
||||
SET_ALLOW(harmony_trailing_commas);
|
||||
SET_ALLOW(harmony_class_fields);
|
||||
SET_ALLOW(harmony_object_spread);
|
||||
#undef SET_ALLOW
|
||||
}
|
||||
// Aborting inner function preparsing would leave scopes in an inconsistent
|
||||
|
@ -766,6 +766,22 @@ RUNTIME_FUNCTION(Runtime_DefineGetterPropertyUnchecked) {
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_CopyDataProperties) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 2);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, target, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, source, 1);
|
||||
|
||||
// 2. If source is undefined or null, let keys be an empty List.
|
||||
if (source->IsUndefined(isolate) || source->IsNull(isolate)) {
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
MAYBE_RETURN(
|
||||
JSReceiver::SetOrCopyDataProperties(isolate, target, source, false),
|
||||
isolate->heap()->exception());
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_DefineSetterPropertyUnchecked) {
|
||||
HandleScope scope(isolate);
|
||||
|
@ -416,6 +416,7 @@ namespace internal {
|
||||
F(ValueOf, 1, 1) \
|
||||
F(IsJSReceiver, 1, 1) \
|
||||
F(ClassOf, 1, 1) \
|
||||
F(CopyDataProperties, 2, 1) \
|
||||
F(DefineGetterPropertyUnchecked, 4, 1) \
|
||||
F(DefineSetterPropertyUnchecked, 4, 1) \
|
||||
F(ToObject, 1, 1) \
|
||||
|
@ -1269,7 +1269,6 @@ const char* ReadString(unsigned* start) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
enum ParserFlag {
|
||||
kAllowLazy,
|
||||
kAllowNatives,
|
||||
@ -1278,6 +1277,7 @@ enum ParserFlag {
|
||||
kAllowHarmonyRestrictiveGenerators,
|
||||
kAllowHarmonyTrailingCommas,
|
||||
kAllowHarmonyClassFields,
|
||||
kAllowHarmonyObjectSpread,
|
||||
};
|
||||
|
||||
enum ParserSyncTestResult {
|
||||
@ -1294,6 +1294,7 @@ void SetGlobalFlags(i::EnumSet<ParserFlag> flags) {
|
||||
flags.Contains(kAllowHarmonyRestrictiveGenerators);
|
||||
i::FLAG_harmony_trailing_commas = flags.Contains(kAllowHarmonyTrailingCommas);
|
||||
i::FLAG_harmony_class_fields = flags.Contains(kAllowHarmonyClassFields);
|
||||
i::FLAG_harmony_object_spread = flags.Contains(kAllowHarmonyObjectSpread);
|
||||
}
|
||||
|
||||
void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
|
||||
@ -1308,6 +1309,8 @@ void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
|
||||
flags.Contains(kAllowHarmonyTrailingCommas));
|
||||
parser->set_allow_harmony_class_fields(
|
||||
flags.Contains(kAllowHarmonyClassFields));
|
||||
parser->set_allow_harmony_object_spread(
|
||||
flags.Contains(kAllowHarmonyObjectSpread));
|
||||
}
|
||||
|
||||
void TestParserSyncWithFlags(i::Handle<i::String> source,
|
||||
@ -6570,6 +6573,95 @@ TEST(ArrowFunctionASIErrors) {
|
||||
RunParserSyncTest(context_data, data, kError);
|
||||
}
|
||||
|
||||
TEST(ObjectSpreadPositiveTests) {
|
||||
// clang-format off
|
||||
const char* context_data[][2] = {
|
||||
{"x = ", ""},
|
||||
{"'use strict'; x = ", ""},
|
||||
{NULL, NULL}};
|
||||
|
||||
// clang-format off
|
||||
const char* data[] = {
|
||||
"{ ...y }",
|
||||
"{ a: 1, ...y }",
|
||||
"{ b: 1, ...y }",
|
||||
"{ y, ...y}",
|
||||
"{ ...z = y}",
|
||||
"{ ...y, y }",
|
||||
"{ ...y, ...y}",
|
||||
"{ a: 1, ...y, b: 1}",
|
||||
"{ ...y, b: 1}",
|
||||
"{ ...1}",
|
||||
"{ ...null}",
|
||||
"{ ...undefined}",
|
||||
"{ ...1 in {}}",
|
||||
"{ ...[]}",
|
||||
"{ ...async function() { }}",
|
||||
"{ ...async () => { }}",
|
||||
"{ ...new Foo()}",
|
||||
NULL};
|
||||
|
||||
static const ParserFlag flags[] = {kAllowHarmonyObjectSpread,
|
||||
kAllowHarmonyAsyncAwait};
|
||||
RunParserSyncTest(context_data, data, kSuccess, NULL, 0, flags,
|
||||
arraysize(flags));
|
||||
}
|
||||
|
||||
TEST(ObjectSpreadNegativeTests) {
|
||||
{
|
||||
const char* context_data[][2] = {{"x = ", ""},
|
||||
{"'use strict'; x = ", ""},
|
||||
{NULL, NULL}};
|
||||
|
||||
// clang-format off
|
||||
const char* data[] = {
|
||||
"{ ...var z = y}",
|
||||
"{ ...var}",
|
||||
"{ ...foo bar}",
|
||||
NULL};
|
||||
|
||||
static const ParserFlag flags[] = {kAllowHarmonyObjectSpread};
|
||||
RunParserSyncTest(context_data, data, kError, NULL, 0, flags,
|
||||
arraysize(flags));
|
||||
}
|
||||
|
||||
// Destructuring tests
|
||||
{
|
||||
const char* context_data[][2] = {
|
||||
{"var ", " = {};"},
|
||||
{"( ", " = {});"},
|
||||
{"'use strict'; const ", " = {};"},
|
||||
{"function f(", ") {}"},
|
||||
{"function f(argument1, ", ") {}"},
|
||||
{"var f = (", ") => {};"},
|
||||
{"var f = (argument1,", ") => {};"},
|
||||
{"try {} catch(", ") {}"},
|
||||
{NULL, NULL}};
|
||||
|
||||
// clang-format off
|
||||
const char* data[] = {
|
||||
"{ ...y }",
|
||||
"{ a: 1, ...y }",
|
||||
"{ b: 1, ...y }",
|
||||
"{ y, ...y}",
|
||||
"{ ...z = y}",
|
||||
"{ ...y, y }",
|
||||
"{ ...y, ...y}",
|
||||
"{ a: 1, ...y, b: 1}",
|
||||
"{ ...y, b: 1}",
|
||||
"{ ...1}",
|
||||
"{ ...null}",
|
||||
"{ ...undefined}",
|
||||
"{ ...unknown}",
|
||||
"{ ...var z = y}",
|
||||
"({ ...z = {})",
|
||||
NULL};
|
||||
|
||||
static const ParserFlag flags[] = {kAllowHarmonyObjectSpread};
|
||||
RunParserSyncTest(context_data, data, kError, NULL, 0, flags,
|
||||
arraysize(flags));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DestructuringPositiveTests) {
|
||||
const char* context_data[][2] = {{"'use strict'; let ", " = {};"},
|
||||
|
111
test/mjsunit/harmony/object-spread-basic.js
Normal file
111
test/mjsunit/harmony/object-spread-basic.js
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2016 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-object-spread
|
||||
|
||||
var x = {a: 1};
|
||||
var y = { ...x};
|
||||
assertEquals(x, y);
|
||||
|
||||
assertEquals({}, y = { ...{} } );
|
||||
assertEquals({}, y = { ...undefined });
|
||||
assertEquals({}, y = { ...null });
|
||||
|
||||
assertEquals({}, y = { ...1 });
|
||||
assertEquals({0: 'f', 1: 'o', 2: 'o'}, y = { ...'foo' });
|
||||
assertEquals({0: 0, 1: 1}, y = { ...[0, 1] });
|
||||
assertEquals({}, { ...new Proxy({}, {}) });
|
||||
|
||||
assertEquals({a: 2}, y = { ...x, a: 2 });
|
||||
assertEquals({a: 1, b: 1}, y = { ...x, b: 1 });
|
||||
assertEquals({a: 1}, y = { a: 2, ...x });
|
||||
assertEquals({a: 1, b: 1}, y = { a:2, ...x, b: 1 });
|
||||
assertEquals({a: 3}, y = { a: 2, ...x, a: 3 });
|
||||
|
||||
var z = { b: 1}
|
||||
assertEquals({a: 1, b: 1}, y = { ...x, ...z });
|
||||
assertEquals({a: 1, b: 1}, y = { a: 2, ...x, ...z });
|
||||
assertEquals({a: 1, b: 1}, y = { b: 2, ...z, ...x });
|
||||
assertEquals({a: 1, b: 1}, y = { a: 1, ...x, b: 2, ...z });
|
||||
assertEquals({a: 1, b: 2}, y = { a: 1, ...x, ...z, b: 2 });
|
||||
assertEquals({a: 2, b: 2}, y = { ...x, ...z, a:2, b: 2 });
|
||||
|
||||
var x = {}
|
||||
Object.defineProperty(x, 'a', {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: 1
|
||||
});
|
||||
assertEquals({}, { ...x });
|
||||
|
||||
var x = {}
|
||||
Object.defineProperty(x, 'a', {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: 1
|
||||
});
|
||||
var y = { ...x };
|
||||
var prop = Object.getOwnPropertyDescriptor(y, 'a');
|
||||
assertEquals(prop.value, 1);
|
||||
assertTrue(prop.enumerable);
|
||||
assertTrue(prop.configurable);
|
||||
assertTrue(prop.writable);
|
||||
|
||||
var x = { __proto__: z }
|
||||
assertEquals({}, { ...x });
|
||||
|
||||
var x = {
|
||||
get a() { return 1; },
|
||||
set a(_) { assertUnreachable("setter called"); },
|
||||
};
|
||||
assertEquals({ a: 1 }, y = { ...x });
|
||||
|
||||
var x = {
|
||||
method() { return 1; },
|
||||
};
|
||||
assertEquals(x, y = { ...x });
|
||||
|
||||
var x = {
|
||||
*gen() { return {value: 1, done: true} ; },
|
||||
};
|
||||
assertEquals(x, y = { ...x });
|
||||
|
||||
var x = {
|
||||
get a() { throw new Error(); },
|
||||
};
|
||||
assertThrows(() => { y = { ...x } });
|
||||
|
||||
var p = new Proxy({}, {
|
||||
ownKeys() { throw new Error(); }
|
||||
});
|
||||
assertThrows(() => { y = { ...p } });
|
||||
|
||||
var p = new Proxy({}, {
|
||||
ownKeys() { [1]; },
|
||||
get() { throw new Error(); }
|
||||
});
|
||||
assertThrows(() => { y = { ...p } });
|
||||
|
||||
var p = new Proxy({}, {
|
||||
ownKeys() { [1]; },
|
||||
getOwnPropertyDescriptor() { throw new Error(); }
|
||||
});
|
||||
assertThrows(() => { y = { ...p } });
|
||||
|
||||
var p = new Proxy(z, {
|
||||
ownKeys() { return Object.keys(z); },
|
||||
get(_, prop) { return z[prop]; },
|
||||
getOwnPropertyDescriptor(_, prop) {
|
||||
return Object.getOwnPropertyDescriptor(z, prop);
|
||||
},
|
||||
});
|
||||
assertEquals(z, y = { ...p });
|
||||
|
||||
var x = { a:1 };
|
||||
assertEquals(x, y = { set a(_) { throw new Error(); }, ...x });
|
||||
|
||||
var x = { a:1 };
|
||||
assertEquals(x, y = { get a() { throw new Error(); }, ...x });
|
Loading…
Reference in New Issue
Block a user