1339395 - Implement Object Rest/Spread Properties proposal.

This commit is contained in:
Fedor 2019-09-05 20:07:15 +03:00
parent dbc2e38812
commit 32bc386753
13 changed files with 706 additions and 267 deletions

View File

@ -3222,6 +3222,8 @@ ASTSerializer::property(ParseNode* pn, MutableHandleValue dst)
return expression(pn->pn_kid, &val) &&
builder.prototypeMutation(val, &pn->pn_pos, dst);
}
if (pn->isKind(PNK_SPREAD))
return expression(pn, dst);
PropKind kind;
switch (pn->getOp()) {
@ -3342,6 +3344,16 @@ ASTSerializer::objectPattern(ParseNode* pn, MutableHandleValue dst)
return false;
for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
if (propdef->isKind(PNK_SPREAD)) {
RootedValue target(cx);
RootedValue spread(cx);
if (!pattern(propdef->pn_kid, &target))
return false;
if(!builder.spreadExpression(target, &propdef->pn_pos, &spread))
return false;
elts.infallibleAppend(spread);
continue;
}
LOCAL_ASSERT(propdef->isKind(PNK_MUTATEPROTO) != propdef->isOp(JSOP_INITPROP));
RootedValue key(cx);

View File

@ -229,6 +229,69 @@ function GetInternalError(msg) {
// To be used when a function is required but calling it shouldn't do anything.
function NullFunction() {}
// Object Rest/Spread Properties proposal
// Abstract operation: CopyDataProperties (target, source, excluded)
function CopyDataProperties(target, source, excluded) {
// Step 1.
assert(IsObject(target), "target is an object");
// Step 2.
assert(IsObject(excluded), "excluded is an object");
// Steps 3, 6.
if (source === undefined || source === null)
return;
// Step 4.a.
source = ToObject(source);
// Step 4.b.
var keys = OwnPropertyKeys(source, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
// Step 5.
for (var index = 0; index < keys.length; index++) {
var key = keys[index];
// We abbreviate this by calling propertyIsEnumerable which is faster
// and returns false for not defined properties.
if (!callFunction(std_Object_hasOwnProperty, key, excluded) && callFunction(std_Object_propertyIsEnumerable, source, key))
_DefineDataProperty(target, key, source[key]);
}
// Step 6 (Return).
}
// Object Rest/Spread Properties proposal
// Abstract operation: CopyDataProperties (target, source, excluded)
function CopyDataPropertiesUnfiltered(target, source) {
// Step 1.
assert(IsObject(target), "target is an object");
// Step 2 (Not applicable).
// Steps 3, 6.
if (source === undefined || source === null)
return;
// Step 4.a.
source = ToObject(source);
// Step 4.b.
var keys = OwnPropertyKeys(source, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
// Step 5.
for (var index = 0; index < keys.length; index++) {
var key = keys[index];
// We abbreviate this by calling propertyIsEnumerable which is faster
// and returns false for not defined properties.
if (callFunction(std_Object_propertyIsEnumerable, source, key))
_DefineDataProperty(target, key, source[key]);
}
// Step 6 (Return).
}
/*************************************** Testing functions ***************************************/
function outer() {
return function inner() {

View File

@ -5814,36 +5814,78 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla
if (!emitRequireObjectCoercible()) // ... RHS
return false;
bool needsRestPropertyExcludedSet = pattern->pn_count > 1 &&
pattern->last()->isKind(PNK_SPREAD);
if (needsRestPropertyExcludedSet) {
if (!emitDestructuringObjRestExclusionSet(pattern)) // ... RHS SET
return false;
if (!emit1(JSOP_SWAP)) // ... SET RHS
return false;
}
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
ParseNode* subpattern;
if (member->isKind(PNK_MUTATEPROTO))
if (member->isKind(PNK_MUTATEPROTO) || member->isKind(PNK_SPREAD))
subpattern = member->pn_kid;
else
subpattern = member->pn_right;
ParseNode* lhs = subpattern;
MOZ_ASSERT_IF(member->isKind(PNK_SPREAD), !lhs->isKind(PNK_ASSIGN));
if (lhs->isKind(PNK_ASSIGN))
lhs = lhs->pn_left;
size_t emitted;
if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF
if (!emitDestructuringLHSRef(lhs, &emitted)) // ... *SET RHS *LREF
return false;
// Duplicate the value being destructured to use as a reference base.
if (emitted) {
if (!emitDupAt(emitted)) // ... RHS *LREF RHS
if (!emitDupAt(emitted)) // ... *SET RHS *LREF RHS
return false;
} else {
if (!emit1(JSOP_DUP)) // ... RHS RHS
if (!emit1(JSOP_DUP)) // ... *SET RHS RHS
return false;
}
if (member->isKind(PNK_SPREAD)) {
if (!updateSourceCoordNotes(member->pn_pos.begin))
return false;
if (!emitNewInit(JSProto_Object)) // ... *SET RHS *LREF RHS TARGET
return false;
if (!emit1(JSOP_DUP)) // ... *SET RHS *LREF RHS TARGET TARGET
return false;
if (!emit2(JSOP_PICK, 2)) // ... *SET RHS *LREF TARGET TARGET RHS
return false;
if (needsRestPropertyExcludedSet) {
if (!emit2(JSOP_PICK, emitted + 4)) // ... RHS *LREF TARGET TARGET RHS SET
return false;
}
CopyOption option = needsRestPropertyExcludedSet
? CopyOption::Filtered
: CopyOption::Unfiltered;
if (!emitCopyDataProperties(option)) // ... RHS *LREF TARGET
return false;
// Destructure TARGET per this member's lhs.
if (!emitSetOrInitializeDestructuring(lhs, flav)) // ... RHS
return false;
MOZ_ASSERT(member == pattern->last(), "Rest property is always last");
break;
}
// Now push the property name currently being matched, which is the
// current property name "label" on the left of a colon in the object
// initialiser.
bool needsGetElem = true;
if (member->isKind(PNK_MUTATEPROTO)) {
if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF PROP
if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... *SET RHS *LREF PROP
return false;
needsGetElem = false;
} else {
@ -5851,40 +5893,131 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla
ParseNode* key = member->pn_left;
if (key->isKind(PNK_NUMBER)) {
if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY
if (!emitNumberOp(key->pn_dval)) // ... *SET RHS *LREF RHS KEY
return false;
} else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
PropertyName* name = key->pn_atom->asPropertyName();
// The parser already checked for atoms representing indexes and
// used PNK_NUMBER instead, but also watch for ids which TI treats
// as indexes for simplification of downstream analysis.
jsid id = NameToId(name);
if (id != IdToTypeId(id)) {
if (!emitTree(key)) // ... RHS *LREF RHS KEY
return false;
} else {
if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP
return false;
needsGetElem = false;
}
} else {
if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY
if (!emitAtomOp(key->pn_atom, JSOP_GETPROP)) // ... *SET RHS *LREF PROP
return false;
needsGetElem = false;
} else {
if (!emitComputedPropertyName(key)) // ... *SET RHS *LREF RHS KEY
return false;
// Add the computed property key to the exclusion set.
if (needsRestPropertyExcludedSet) {
if (!emitDupAt(emitted + 3)) // ... SET RHS *LREF RHS KEY SET
return false;
if (!emitDupAt(1)) // ... SET RHS *LREF RHS KEY SET KEY
return false;
if (!emit1(JSOP_UNDEFINED)) // ... SET RHS *LREF RHS KEY SET KEY UNDEFINED
return false;
if (!emit1(JSOP_INITELEM)) // ... SET RHS *LREF RHS KEY SET
return false;
if (!emit1(JSOP_POP)) // ... SET RHS *LREF RHS KEY
return false;
}
}
}
// Get the property value if not done already.
if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP
if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... *SET RHS *LREF PROP
return false;
if (subpattern->isKind(PNK_ASSIGN)) {
if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE
if (!emitDefault(subpattern->pn_right, lhs)) // ... *SET RHS *LREF VALUE
return false;
}
// Destructure PROP per this member's lhs.
if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS
if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... *SET RHS
return false;
}
return true;
}
bool
BytecodeEmitter::emitDestructuringObjRestExclusionSet(ParseNode* pattern)
{
MOZ_ASSERT(pattern->isKind(PNK_OBJECT));
MOZ_ASSERT(pattern->isArity(PN_LIST));
MOZ_ASSERT(pattern->last()->isKind(PNK_SPREAD));
ptrdiff_t offset = this->offset();
if (!emitNewInit(JSProto_Object))
return false;
// Try to construct the shape of the object as we go, so we can emit a
// JSOP_NEWOBJECT with the final shape instead.
// In the case of computed property names and indices, we cannot fix the
// shape at bytecode compile time. When the shape cannot be determined,
// |obj| is nulled out.
// No need to do any guessing for the object kind, since we know the upper
// bound of how many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(pattern->pn_count - 1);
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
if (!obj)
return false;
RootedAtom pnatom(cx);
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
if (member->isKind(PNK_SPREAD))
break;
bool isIndex = false;
if (member->isKind(PNK_MUTATEPROTO)) {
pnatom.set(cx->names().proto);
} else {
ParseNode* key = member->pn_left;
if (key->isKind(PNK_NUMBER)) {
if (!emitNumberOp(key->pn_dval))
return false;
isIndex = true;
} else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
pnatom.set(key->pn_atom);
} else {
// Otherwise this is a computed property name which needs to
// be added dynamically.
obj.set(nullptr);
continue;
}
}
// Initialize elements with |undefined|.
if (!emit1(JSOP_UNDEFINED))
return false;
if (isIndex) {
obj.set(nullptr);
if (!emit1(JSOP_INITELEM))
return false;
} else {
uint32_t index;
if (!makeAtomIndex(pnatom, &index))
return false;
if (obj) {
MOZ_ASSERT(!obj->inDictionaryMode());
Rooted<jsid> id(cx, AtomToId(pnatom));
if (!NativeDefineProperty(cx, obj, id, UndefinedHandleValue, nullptr, nullptr,
JSPROP_ENUMERATE))
{
return false;
}
if (obj->inDictionaryMode())
obj.set(nullptr);
}
if (!emitIndex32(JSOP_INITPROP, index))
return false;
}
}
if (obj) {
// The object survived and has a predictable shape: update the
// original bytecode.
if (!replaceNewInitWithNewObject(obj, offset))
return false;
}
@ -6765,6 +6898,53 @@ BytecodeEmitter::emitRequireObjectCoercible()
return true;
}
bool
BytecodeEmitter::emitCopyDataProperties(CopyOption option)
{
DebugOnly<int32_t> depth = this->stackDepth;
uint32_t argc;
if (option == CopyOption::Filtered) {
MOZ_ASSERT(depth > 2); // TARGET SOURCE SET
argc = 3;
if (!emitAtomOp(cx->names().CopyDataProperties,
JSOP_GETINTRINSIC)) // TARGET SOURCE SET COPYDATAPROPERTIES
{
return false;
}
} else {
MOZ_ASSERT(depth > 1); // TARGET SOURCE
argc = 2;
if (!emitAtomOp(cx->names().CopyDataPropertiesUnfiltered,
JSOP_GETINTRINSIC)) // TARGET SOURCE COPYDATAPROPERTIES
{
return false;
}
}
if (!emit1(JSOP_UNDEFINED)) // TARGET SOURCE *SET COPYDATAPROPERTIES UNDEFINED
return false;
if (!emit2(JSOP_PICK, argc + 1)) // SOURCE *SET COPYDATAPROPERTIES UNDEFINED TARGET
return false;
if (!emit2(JSOP_PICK, argc + 1)) // *SET COPYDATAPROPERTIES UNDEFINED TARGET SOURCE
return false;
if (option == CopyOption::Filtered) {
if (!emit2(JSOP_PICK, argc + 1)) // COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET
return false;
}
if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) // IGNORED
return false;
checkTypeSet(JSOP_CALL_IGNORES_RV);
if (!emit1(JSOP_POP)) // -
return false;
MOZ_ASSERT(depth - int(argc) == this->stackDepth);
return true;
}
bool
BytecodeEmitter::emitIterator()
{
@ -9449,6 +9629,22 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp,
continue;
}
if (propdef->isKind(PNK_SPREAD)) {
MOZ_ASSERT(type == ObjectLiteral);
if (!emit1(JSOP_DUP))
return false;
if (!emitTree(propdef->pn_kid))
return false;
if (!emitCopyDataProperties(CopyOption::Unfiltered))
return false;
objp.set(nullptr);
continue;
}
bool extraPop = false;
if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) {
extraPop = true;
@ -9472,16 +9668,6 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp,
{
continue;
}
// The parser already checked for atoms representing indexes and
// used PNK_NUMBER instead, but also watch for ids which TI treats
// as indexes for simpliciation of downstream analysis.
jsid id = NameToId(key->pn_atom->asPropertyName());
if (id != IdToTypeId(id)) {
if (!emitTree(key))
return false;
isIndex = true;
}
} else {
if (!emitComputedPropertyName(key))
return false;
@ -9562,8 +9748,7 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp,
MOZ_ASSERT(!IsHiddenInitOp(op));
MOZ_ASSERT(!objp->inDictionaryMode());
Rooted<jsid> id(cx, AtomToId(key->pn_atom));
RootedValue undefinedValue(cx, UndefinedValue());
if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr,
if (!NativeDefineProperty(cx, objp, id, UndefinedHandleValue, nullptr, nullptr,
JSPROP_ENUMERATE))
{
return false;
@ -9606,15 +9791,16 @@ BytecodeEmitter::emitObject(ParseNode* pn)
if (!emitNewInit(JSProto_Object))
return false;
/*
* Try to construct the shape of the object as we go, so we can emit a
* JSOP_NEWOBJECT with the final shape instead.
*/
RootedPlainObject obj(cx);
// No need to do any guessing for the object kind, since we know exactly
// how many properties we plan to have.
// Try to construct the shape of the object as we go, so we can emit a
// JSOP_NEWOBJECT with the final shape instead.
// In the case of computed property names and indices, we cannot fix the
// shape at bytecode compile time. When the shape cannot be determined,
// |obj| is nulled out.
// No need to do any guessing for the object kind, since we know the upper
// bound of how many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count);
obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject);
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
if (!obj)
return false;
@ -9622,29 +9808,38 @@ BytecodeEmitter::emitObject(ParseNode* pn)
return false;
if (obj) {
/*
* The object survived and has a predictable shape: update the original
* bytecode.
*/
ObjectBox* objbox = parser->newObjectBox(obj);
if (!objbox)
// The object survived and has a predictable shape: update the original
// bytecode.
if (!replaceNewInitWithNewObject(obj, offset))
return false;
static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH,
"newinit and newobject must have equal length to edit in-place");
uint32_t index = objectList.add(objbox);
jsbytecode* code = this->code(offset);
code[0] = JSOP_NEWOBJECT;
code[1] = jsbytecode(index >> 24);
code[2] = jsbytecode(index >> 16);
code[3] = jsbytecode(index >> 8);
code[4] = jsbytecode(index);
}
return true;
}
bool
BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset)
{
ObjectBox* objbox = parser->newObjectBox(obj);
if (!objbox)
return false;
static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH,
"newinit and newobject must have equal length to edit in-place");
uint32_t index = objectList.add(objbox);
jsbytecode* code = this->code(offset);
MOZ_ASSERT(code[0] == JSOP_NEWINIT);
code[0] = JSOP_NEWOBJECT;
code[1] = jsbytecode(index >> 24);
code[2] = jsbytecode(index >> 16);
code[3] = jsbytecode(index >> 8);
code[4] = jsbytecode(index);
return true;
}
bool
BytecodeEmitter::emitArrayComp(ParseNode* pn)
{

View File

@ -540,6 +540,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(ParseNode* pn, bool needsProto = false);
MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ParseNode* pn);
MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset);
MOZ_MUST_USE bool emitHoistedFunctionsInList(ParseNode* pn);
MOZ_MUST_USE bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp,
@ -667,6 +669,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// []/{} expression).
MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav);
// emitDestructuringObjRestExclusionSet emits the property exclusion set
// for the rest-property in an object pattern.
MOZ_MUST_USE bool emitDestructuringObjRestExclusionSet(ParseNode* pattern);
// emitDestructuringOps assumes the to-be-destructured value has been
// pushed on the stack and emits code to destructure each part of a [] or
// {} lhs expression.
@ -684,6 +690,15 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// object, with no overall effect on the stack.
MOZ_MUST_USE bool emitRequireObjectCoercible();
enum class CopyOption {
Filtered, Unfiltered
};
// Calls either the |CopyDataProperties| or the
// |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or
// two in the latter case) elements from the stack.
MOZ_MUST_USE bool emitCopyDataProperties(CopyOption option);
// emitIterator expects the iterable to already be on the stack.
// It will replace that stack value with the corresponding iterator
MOZ_MUST_USE bool emitIterator();

View File

@ -1345,15 +1345,8 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>&
if (!name)
return true;
// Also don't optimize if the name doesn't map directly to its id for TI's
// purposes.
if (NameToId(name) != IdToTypeId(NameToId(name)))
return true;
// Optimization 3: We have expr["foo"] where foo is not an index. Convert
// to a property access (like expr.foo) that optimizes better downstream.
// Don't bother with this for names that TI considers to be indexes, to
// simplify downstream analysis.
ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
if (!dottedAccess)
return false;

View File

@ -302,10 +302,9 @@ class FullParseHandler
}
MOZ_MUST_USE bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) {
TokenPos pos(begin, inner->pn_pos.end);
ParseNode* spread = new_<UnaryNode>(PNK_SPREAD, JSOP_NOP, pos, inner);
ParseNode* spread = newSpread(begin, inner);
if (!spread)
return null();
return false;
literal->append(spread);
literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
return true;
@ -396,6 +395,18 @@ class FullParseHandler
return true;
}
MOZ_MUST_USE bool addSpreadProperty(ParseNode* literal, uint32_t begin, ParseNode* inner) {
MOZ_ASSERT(literal->isKind(PNK_OBJECT));
MOZ_ASSERT(literal->isArity(PN_LIST));
setListFlag(literal, PNX_NONCONST);
ParseNode* spread = newSpread(begin, inner);
if (!spread)
return false;
literal->append(spread);
return true;
}
MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn,
JSOp op)
{

View File

@ -4361,85 +4361,105 @@ Parser<ParseHandler>::objectBindingPattern(DeclarationKind kind, YieldHandling y
RootedAtom propAtom(context);
for (;;) {
TokenKind tt;
if (!tokenStream.getToken(&tt))
if (!tokenStream.peekToken(&tt))
return null();
if (tt == TOK_RC)
break;
TokenPos namePos = pos();
if (tt == TOK_TRIPLEDOT) {
// rest-binding property
tokenStream.consumeKnownToken(TOK_TRIPLEDOT);
uint32_t begin = pos().begin;
tokenStream.ungetToken();
PropertyType propType;
Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
if (!propName)
return null();
if (propType == PropertyType::Normal) {
// Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|.
if (!tokenStream.getToken(&tt, TokenStream::Operand))
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt);
if (!binding)
Node inner = bindingIdentifierOrPattern(kind, yieldHandling, tt);
if (!inner)
return null();
bool hasInitializer;
if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
return null();
Node bindingExpr = hasInitializer
? bindingInitializer(binding, kind, yieldHandling)
: binding;
if (!bindingExpr)
return null();
if (!handler.addPropertyDefinition(literal, propName, bindingExpr))
return null();
} else if (propType == PropertyType::Shorthand) {
// Handle e.g., |var {x, y} = o| as destructuring shorthand
// for |var {x: x, y: y} = o|.
MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt));
Node binding = bindingIdentifier(kind, yieldHandling);
if (!binding)
return null();
if (!handler.addShorthand(literal, propName, binding))
return null();
} else if (propType == PropertyType::CoverInitializedName) {
// Handle e.g., |var {x=1, y=2} = o| as destructuring shorthand
// with default values.
MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt));
Node binding = bindingIdentifier(kind, yieldHandling);
if (!binding)
return null();
tokenStream.consumeKnownToken(TOK_ASSIGN);
Node bindingExpr = bindingInitializer(binding, kind, yieldHandling);
if (!bindingExpr)
return null();
if (!handler.addPropertyDefinition(literal, propName, bindingExpr))
if (!handler.addSpreadProperty(literal, begin, inner))
return null();
} else {
errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME);
return null();
TokenPos namePos = tokenStream.nextToken().pos;
PropertyType propType;
Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
if (!propName)
return null();
if (propType == PropertyType::Normal) {
// Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|.
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt);
if (!binding)
return null();
bool hasInitializer;
if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
return null();
Node bindingExpr = hasInitializer
? bindingInitializer(binding, kind, yieldHandling)
: binding;
if (!bindingExpr)
return null();
if (!handler.addPropertyDefinition(literal, propName, bindingExpr))
return null();
} else if (propType == PropertyType::Shorthand) {
// Handle e.g., |var {x, y} = o| as destructuring shorthand
// for |var {x: x, y: y} = o|.
MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt));
Node binding = bindingIdentifier(kind, yieldHandling);
if (!binding)
return null();
if (!handler.addShorthand(literal, propName, binding))
return null();
} else if (propType == PropertyType::CoverInitializedName) {
// Handle e.g., |var {x=1, y=2} = o| as destructuring
// shorthand with default values.
MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt));
Node binding = bindingIdentifier(kind, yieldHandling);
if (!binding)
return null();
tokenStream.consumeKnownToken(TOK_ASSIGN);
Node bindingExpr = bindingInitializer(binding, kind, yieldHandling);
if (!bindingExpr)
return null();
if (!handler.addPropertyDefinition(literal, propName, bindingExpr))
return null();
} else {
errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME);
return null();
}
}
if (!tokenStream.getToken(&tt))
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
if (tt == TOK_RC)
if (!matched)
break;
if (tt != TOK_COMMA) {
reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED, begin);
if (tt == TOK_TRIPLEDOT) {
error(JSMSG_REST_WITH_COMMA);
return null();
}
}
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::None,
reportMissingClosing(JSMSG_CURLY_AFTER_LIST,
JSMSG_CURLY_OPENED, begin));
handler.setEndPosition(literal, pos().end);
return literal;
}
@ -9682,173 +9702,200 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError*
RootedAtom propAtom(context);
for (;;) {
TokenKind tt;
if (!tokenStream.getToken(&tt))
if (!tokenStream.peekToken(&tt))
return null();
if (tt == TOK_RC)
break;
TokenPos namePos = pos();
tokenStream.ungetToken();
if (tt == TOK_TRIPLEDOT) {
// object spread
tokenStream.consumeKnownToken(TOK_TRIPLEDOT);
uint32_t begin = pos().begin;
PropertyType propType;
Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
if (!propName)
return null();
if (propType == PropertyType::Normal) {
TokenPos exprPos;
if (!tokenStream.peekTokenPos(&exprPos, TokenStream::Operand))
return null();
Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
possibleError);
if (!propExpr)
TokenPos innerPos;
if (!tokenStream.peekTokenPos(&innerPos, TokenStream::Operand))
return null();
handler.checkAndSetIsDirectRHSAnonFunction(propExpr);
Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
possibleError);
if (!inner)
return null();
if (possibleError)
checkDestructuringAssignmentTarget(inner, innerPos, possibleError);
if (!handler.addSpreadProperty(literal, begin, inner))
return null();
} else {
TokenPos namePos = tokenStream.nextToken().pos;
PropertyType propType;
Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom);
if (!propName)
if (foldConstants && !FoldConstants(context, &propExpr, this))
return null();
if (propAtom == context->names().proto) {
if (seenPrototypeMutation) {
// Directly report the error when we're not in a
// destructuring context.
if (propType == PropertyType::Normal) {
TokenPos exprPos;
if (!tokenStream.peekTokenPos(&exprPos, TokenStream::Operand))
return null();
Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
possibleError);
if (!propExpr)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(propExpr);
if (possibleError)
checkDestructuringAssignmentElement(propExpr, exprPos, possibleError);
if (foldConstants && !FoldConstants(context, &propExpr, this))
return null();
if (propAtom == context->names().proto) {
if (seenPrototypeMutation) {
// Directly report the error when we're not in a
// destructuring context.
if (!possibleError) {
errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY);
return null();
}
// Otherwise delay error reporting until we've
// determined whether or not we're destructuring.
possibleError->setPendingExpressionErrorAt(namePos,
JSMSG_DUPLICATE_PROTO_PROPERTY);
}
seenPrototypeMutation = true;
// Note: this occurs *only* if we observe TOK_COLON! Only
// __proto__: v mutates [[Prototype]]. Getters, setters,
// method/generator definitions, computed property name
// versions of all of these, and shorthands do not.
if (!handler.addPrototypeMutation(literal, namePos.begin, propExpr))
return null();
} else {
if (!handler.isConstant(propExpr))
handler.setListFlag(literal, PNX_NONCONST);
if (!handler.addPropertyDefinition(literal, propName, propExpr))
return null();
}
} else if (propType == PropertyType::Shorthand) {
/*
* Support, e.g., |({x, y} = o)| as destructuring shorthand
* for |({x: x, y: y} = o)|, and |var o = {x, y}| as
* initializer shorthand for |var o = {x: x, y: y}|.
*/
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
Node nameExpr = identifierReference(name);
if (!nameExpr)
return null();
if (possibleError)
checkDestructuringAssignmentTarget(nameExpr, namePos, possibleError);
if (!handler.addShorthand(literal, propName, nameExpr))
return null();
} else if (propType == PropertyType::CoverInitializedName) {
/*
* Support, e.g., |({x=1, y=2} = o)| as destructuring
* shorthand with default values, as per ES6 12.14.5
*/
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
Node lhs = identifierReference(name);
if (!lhs)
return null();
tokenStream.consumeKnownToken(TOK_ASSIGN);
if (!seenCoverInitializedName) {
// "shorthand default" or "CoverInitializedName" syntax is
// only valid in the case of destructuring.
seenCoverInitializedName = true;
if (!possibleError) {
errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY);
// Destructuring defaults are definitely not allowed
// in this object literal, because of something the
// caller knows about the preceding code. For example,
// maybe the preceding token is an operator:
// |x + {y=z}|.
error(JSMSG_COLON_AFTER_ID);
return null();
}
// Otherwise delay error reporting until we've determined
// whether or not we're destructuring.
possibleError->setPendingExpressionErrorAt(namePos,
JSMSG_DUPLICATE_PROTO_PROPERTY);
// Here we set a pending error so that later in the parse,
// once we've determined whether or not we're
// destructuring, the error can be reported or ignored
// appropriately.
possibleError->setPendingExpressionErrorAt(pos(), JSMSG_COLON_AFTER_ID);
}
seenPrototypeMutation = true;
// Note: this occurs *only* if we observe TOK_COLON! Only
// __proto__: v mutates [[Prototype]]. Getters, setters,
// method/generator definitions, computed property name
// versions of all of these, and shorthands do not.
if (!handler.addPrototypeMutation(literal, namePos.begin, propExpr))
if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) {
// |chars| is "arguments" or "eval" here.
if (!strictModeErrorAt(namePos.begin, JSMSG_BAD_STRICT_ASSIGN, chars))
return null();
}
Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!rhs)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(rhs);
Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP);
if (!propExpr)
return null();
} else {
if (!handler.isConstant(propExpr))
handler.setListFlag(literal, PNX_NONCONST);
if (!handler.addPropertyDefinition(literal, propName, propExpr))
return null();
}
} else {
RootedAtom funName(context);
if (!tokenStream.isCurrentTokenType(TOK_RB)) {
funName = propAtom;
if (possibleError)
checkDestructuringAssignmentElement(propExpr, exprPos, possibleError);
} else if (propType == PropertyType::Shorthand) {
/*
* Support, e.g., |({x, y} = o)| as destructuring shorthand
* for |({x: x, y: y} = o)|, and |var o = {x, y}| as initializer
* shorthand for |var o = {x: x, y: y}|.
*/
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
Node nameExpr = identifierReference(name);
if (!nameExpr)
return null();
if (possibleError)
checkDestructuringAssignmentTarget(nameExpr, namePos, possibleError);
if (!handler.addShorthand(literal, propName, nameExpr))
return null();
} else if (propType == PropertyType::CoverInitializedName) {
/*
* Support, e.g., |({x=1, y=2} = o)| as destructuring shorthand
* with default values, as per ES6 12.14.5
*/
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
Node lhs = identifierReference(name);
if (!lhs)
return null();
tokenStream.consumeKnownToken(TOK_ASSIGN);
if (!seenCoverInitializedName) {
// "shorthand default" or "CoverInitializedName" syntax is only
// valid in the case of destructuring.
seenCoverInitializedName = true;
if (!possibleError) {
// Destructuring defaults are definitely not allowed in this object literal,
// because of something the caller knows about the preceding code.
// For example, maybe the preceding token is an operator: `x + {y=z}`.
error(JSMSG_COLON_AFTER_ID);
return null();
if (propType == PropertyType::Getter || propType == PropertyType::Setter) {
funName = prefixAccessorName(propType, propAtom);
if (!funName)
return null();
}
}
// Here we set a pending error so that later in the parse, once we've
// determined whether or not we're destructuring, the error can be
// reported or ignored appropriately.
possibleError->setPendingExpressionErrorAt(pos(), JSMSG_COLON_AFTER_ID);
}
if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) {
// |chars| is "arguments" or "eval" here.
if (!strictModeErrorAt(namePos.begin, JSMSG_BAD_STRICT_ASSIGN, chars))
Node fn = methodDefinition(namePos.begin, propType, funName);
if (!fn)
return null();
}
Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!rhs)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(fn);
handler.checkAndSetIsDirectRHSAnonFunction(rhs);
JSOp op = JSOpFromPropertyType(propType);
if (!handler.addObjectMethodDefinition(literal, propName, fn, op))
return null();
Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP);
if (!propExpr)
return null();
if (!handler.addPropertyDefinition(literal, propName, propExpr))
return null();
} else {
RootedAtom funName(context);
if (!tokenStream.isCurrentTokenType(TOK_RB)) {
funName = propAtom;
if (propType == PropertyType::Getter || propType == PropertyType::Setter) {
funName = prefixAccessorName(propType, propAtom);
if (!funName)
return null();
if (possibleError) {
possibleError->setPendingDestructuringErrorAt(namePos,
JSMSG_BAD_DESTRUCT_TARGET);
}
}
Node fn = methodDefinition(namePos.begin, propType, funName);
if (!fn)
return null();
handler.checkAndSetIsDirectRHSAnonFunction(fn);
JSOp op = JSOpFromPropertyType(propType);
if (!handler.addObjectMethodDefinition(literal, propName, fn, op))
return null();
if (possibleError)
possibleError->setPendingDestructuringErrorAt(namePos, JSMSG_BAD_DESTRUCT_TARGET);
}
if (!tokenStream.getToken(&tt))
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return null();
if (tt == TOK_RC)
if (!matched)
break;
if (tt != TOK_COMMA) {
reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED, openedPos);
return null();
}
if (tt == TOK_TRIPLEDOT && possibleError)
possibleError->setPendingDestructuringErrorAt(pos(), JSMSG_REST_WITH_COMMA);
}
MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::None,
reportMissingClosing(JSMSG_CURLY_AFTER_LIST,
JSMSG_CURLY_OPENED, openedPos));
handler.setEndPosition(literal, pos().end);
return literal;
}

View File

@ -298,6 +298,7 @@ class SyntaxParseHandler
MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; }
MOZ_MUST_USE bool addSpreadProperty(Node literal, uint32_t begin, Node inner) { return true; }
MOZ_MUST_USE bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
MOZ_MUST_USE bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; }
Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }

View File

@ -0,0 +1,26 @@
// Tests that wasm module scripts have special URLs.
if (!wasmIsSupported())
quit();
var g = newGlobal();
g.eval(`
function initWasm(s) { return new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(s))); }
o = initWasm('(module (func) (export "" 0))');
o2 = initWasm('(module (func) (func) (export "" 1))');
`);
function isWasm(script) { return script.format === "wasm"; }
function isValidWasmURL(url) {
// The URLs will have the following format:
// wasm: [<uri-econded-filename-of-host> ":"] <64-bit-hash>
return /^wasm:(?:[^:]*:)*?[0-9a-f]{16}$/.test(url);
}
var dbg = new Debugger(g);
var foundScripts = dbg.findScripts().filter(isWasm);
assertEq(foundScripts.length, 2);
assertEq(isValidWasmURL(foundScripts[0].source.url), true);
assertEq(isValidWasmURL(foundScripts[1].source.url), true);
assertEq(foundScripts[0].source.url != foundScripts[1].source.url, true);

View File

@ -39,7 +39,7 @@ testThrow(`
testThrow(`
({...a)=>
`, 2);
`, 6);
testThrow(`
function f([... ...a)=>
@ -47,7 +47,7 @@ function f([... ...a)=>
testThrow(`
function f({...a)=>
`, 12);
`, 16);
// arrow
@ -67,7 +67,7 @@ var [... ...a)=>
testThrow(`
var {...a)=>
`, 5);
`, 9);
// initializer

View File

@ -0,0 +1,45 @@
// |reftest| skip-if(!xulRuntime.shell)
function property(key, value = key, shorthand = key === value) {
return { key, value, shorthand };
}
function assertDestrAssign(src, pattern) {
assertExpr(`(${src} = 0)`, aExpr("=", pattern, lit(0)));
}
function assertDestrBinding(src, pattern) {
assertDecl(`var ${src} = 0`, varDecl([{id: pattern, init: lit(0)}]));
}
function test() {
// Target expression must be a simple assignment target or a nested pattern
// in object assignment patterns.
assertDestrAssign("{...x}", objPatt([spread(ident("x"))]));
assertDestrAssign("{...(x)}", objPatt([spread(ident("x"))]));
assertDestrAssign("{...obj.p}", objPatt([spread(dotExpr(ident("obj"), ident("p")))]));
assertDestrAssign("{...{}}", objPatt([spread(objPatt([]))]));
assertDestrAssign("{...[]}", objPatt([spread(arrPatt([]))]));
// Object binding patterns only allow binding identifiers or nested patterns.
assertDestrBinding("{...x}", objPatt([spread(ident("x"))]));
assertDestrBinding("{...{}}", objPatt([spread(objPatt([]))]));
assertDestrBinding("{...[]}", objPatt([spread(arrPatt([]))]));
// The rest-property can be preceded by other properties.
for (var assertDestr of [assertDestrAssign, assertDestrBinding]) {
assertDestr("{a, ...x}", objPatt([property(ident("a")), spread(ident("x"))]));
assertDestr("{a: b, ...x}", objPatt([property(ident("a"), ident("b")), spread(ident("x"))]));
assertDestr("{[a]: b, ...x}", objPatt([property(comp(ident("a")), ident("b")), spread(ident("x"))]));
}
// Tests when __proto__ is used in the object pattern.
for (var assertDestr of [assertDestrAssign, assertDestrBinding]) {
assertDestr("{...__proto__}", objPatt([spread(ident("__proto__"))]));
assertDestr("{__proto__, ...x}", objPatt([property(ident("__proto__")), spread(ident("x"))]));
}
assertDestrAssign("{__proto__: a, ...x}", objPatt([property(lit("__proto__"), ident("a")), spread(ident("x"))]));
assertDestrBinding("{__proto__: a, ...x}", objPatt([property(ident("__proto__"), ident("a")), spread(ident("x"))]));
}
runtest(test);

View File

@ -0,0 +1,29 @@
// |reftest| skip-if(!xulRuntime.shell)
function property(key, value = key, shorthand = key === value) {
return { key, value, shorthand };
}
function test() {
// Any expression can be spreaded.
assertExpr("({...x})", objExpr([spread(ident("x"))]));
assertExpr("({...f()})", objExpr([spread(callExpr(ident("f"), []))]));
assertExpr("({...123})", objExpr([spread(lit(123))]));
// Multiple spread expression are allowed.
assertExpr("({...x, ...obj.p})", objExpr([spread(ident("x")), spread(dotExpr(ident("obj"), ident("p")))]));
// Spread expression can appear anywhere in an object literal.
assertExpr("({p, ...x})", objExpr([property(ident("p")), spread(ident("x"))]));
assertExpr("({p: a, ...x})", objExpr([property(ident("p"), ident("a")), spread(ident("x"))]));
assertExpr("({...x, p: a})", objExpr([spread(ident("x")), property(ident("p"), ident("a"))]));
// Trailing comma after spread expression is allowed.
assertExpr("({...x,})", objExpr([spread(ident("x"))]));
// __proto__ is not special in spread expressions.
assertExpr("({...__proto__})", objExpr([spread(ident("__proto__"))]));
assertExpr("({...__proto__, ...__proto__})", objExpr([spread(ident("__proto__")), spread(ident("__proto__"))]));
}
runtest(test);

View File

@ -71,6 +71,8 @@
macro(constructor, constructor, "constructor") \
macro(continue, continue_, "continue") \
macro(ConvertAndCopyTo, ConvertAndCopyTo, "ConvertAndCopyTo") \
macro(CopyDataProperties, CopyDataProperties, "CopyDataProperties") \
macro(CopyDataPropertiesUnfiltered, CopyDataPropertiesUnfiltered, "CopyDataPropertiesUnfiltered") \
macro(copyWithin, copyWithin, "copyWithin") \
macro(count, count, "count") \
macro(CreateResolvingFunctions, CreateResolvingFunctions, "CreateResolvingFunctions") \