[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:
gsathya 2017-01-05 15:44:25 -08:00 committed by Commit bot
parent a5a376c530
commit a40b7172fe
25 changed files with 445 additions and 123 deletions

View File

@ -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:

View File

@ -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_; }

View File

@ -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);

View File

@ -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++) {

View File

@ -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;

View File

@ -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:

View File

@ -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) \

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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;

View File

@ -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()) {

View File

@ -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(

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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) \

View File

@ -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 ", " = {};"},

View 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 });