Bug 932080 - part1: support default values in destructuring of array and full objects; r=jorendorff

--HG--
extra : rebase_source : d89417a57082915cc447f477d2ab6e85f30d3a2a
This commit is contained in:
Arpad Borsos 2014-12-09 10:33:51 +01:00
Родитель f42106a9ce
Коммит fb018eecf1
4 изменённых файлов: 258 добавлений и 22 удалений

Просмотреть файл

@ -3257,6 +3257,8 @@ EmitDestructuringDeclsWithEmitter(ExclusiveContext *cx, BytecodeEmitter *bce, JS
MOZ_ASSERT(element->pn_kid->isKind(PNK_NAME));
target = element->pn_kid;
}
if (target->isKind(PNK_ASSIGN))
target = target->pn_left;
if (target->isKind(PNK_NAME)) {
if (!EmitName(cx, bce, prologOp, target))
return false;
@ -3276,6 +3278,8 @@ EmitDestructuringDeclsWithEmitter(ExclusiveContext *cx, BytecodeEmitter *bce, JS
ParseNode *target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right;
if (target->isKind(PNK_ASSIGN))
target = target->pn_left;
if (target->isKind(PNK_NAME)) {
if (!EmitName(cx, bce, prologOp, target))
return false;
@ -3341,7 +3345,7 @@ EmitDestructuringOpsHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode
* lhs expression. (Same post-condition as EmitDestructuringOpsHelper)
*/
static bool
EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption)
EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *target, VarEmitOption emitOption)
{
MOZ_ASSERT(emitOption != DefineVars);
@ -3349,10 +3353,12 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
// destructuring initialiser-form, call ourselves to handle it, then pop
// the matched value. Otherwise emit an lvalue bytecode sequence followed
// by an assignment op.
if (pn->isKind(PNK_SPREAD))
pn = pn->pn_kid;
if (pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)) {
if (!EmitDestructuringOpsHelper(cx, bce, pn, emitOption))
if (target->isKind(PNK_SPREAD))
target = target->pn_kid;
else if (target->isKind(PNK_ASSIGN))
target = target->pn_left;
if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) {
if (!EmitDestructuringOpsHelper(cx, bce, target, emitOption))
return false;
if (emitOption == InitializeVars) {
// Per its post-condition, EmitDestructuringOpsHelper has left the
@ -3363,15 +3369,15 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
} else if (emitOption == PushInitialValues) {
// The lhs is a simple name so the to-be-destructured value is
// its initial value and there is nothing to do.
MOZ_ASSERT(pn->getOp() == JSOP_SETLOCAL || pn->getOp() == JSOP_INITLEXICAL);
MOZ_ASSERT(pn->pn_dflags & PND_BOUND);
MOZ_ASSERT(target->getOp() == JSOP_SETLOCAL || target->getOp() == JSOP_INITLEXICAL);
MOZ_ASSERT(target->pn_dflags & PND_BOUND);
} else {
switch (pn->getKind()) {
switch (target->getKind()) {
case PNK_NAME:
if (!BindNameToSlot(cx, bce, pn))
if (!BindNameToSlot(cx, bce, target))
return false;
switch (pn->getOp()) {
switch (target->getOp()) {
case JSOP_SETNAME:
case JSOP_STRICTSETNAME:
case JSOP_SETGNAME:
@ -3388,11 +3394,11 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
// but the operands are on the stack in the wrong order for
// JSOP_SETPROP, so we have to add a JSOP_SWAP.
jsatomid atomIndex;
if (!bce->makeAtomIndex(pn->pn_atom, &atomIndex))
if (!bce->makeAtomIndex(target->pn_atom, &atomIndex))
return false;
if (!pn->isOp(JSOP_SETCONST)) {
bool global = pn->isOp(JSOP_SETGNAME) || pn->isOp(JSOP_STRICTSETGNAME);
if (!target->isOp(JSOP_SETCONST)) {
bool global = target->isOp(JSOP_SETGNAME) || target->isOp(JSOP_STRICTSETGNAME);
JSOp bindOp = global ? JSOP_BINDGNAME : JSOP_BINDNAME;
if (!EmitIndex32(cx, bindOp, atomIndex, bce))
return false;
@ -3400,7 +3406,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
return false;
}
if (!EmitIndexOp(cx, pn->getOp(), atomIndex, bce))
if (!EmitIndexOp(cx, target->getOp(), atomIndex, bce))
return false;
break;
}
@ -3408,7 +3414,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
case JSOP_SETLOCAL:
case JSOP_SETARG:
case JSOP_INITLEXICAL:
if (!EmitVarOp(cx, pn, pn->getOp(), bce))
if (!EmitVarOp(cx, target, target->getOp(), bce))
return false;
break;
@ -3427,12 +3433,12 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
// In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we
// need a property set -- but the operands are on the stack in the
// wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP.
if (!EmitTree(cx, bce, pn->pn_expr))
if (!EmitTree(cx, bce, target->pn_expr))
return false;
if (Emit1(cx, bce, JSOP_SWAP) < 0)
return false;
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!EmitAtomOp(cx, pn, setOp, bce))
if (!EmitAtomOp(cx, target, setOp, bce))
return false;
break;
}
@ -3443,14 +3449,14 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
// `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP
// is emitted by EmitElemOperands.
JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!EmitElemOp(cx, pn, setOp, bce))
if (!EmitElemOp(cx, target, setOp, bce))
return false;
break;
}
case PNK_CALL:
MOZ_ASSERT(pn->pn_xflags & PNX_SETCALL);
if (!EmitTree(cx, bce, pn))
MOZ_ASSERT(target->pn_xflags & PNX_SETCALL);
if (!EmitTree(cx, bce, target))
return false;
// Pop the call return value. Below, we pop the RHS too, balancing
@ -3500,6 +3506,33 @@ EmitIteratorNext(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn=nullp
return true;
}
/**
* EmitDefault will check if the value on top of the stack is "undefined".
* If so, it will replace that value on the stack with the value defined by |defaultExpr|.
*/
static bool
EmitDefault(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *defaultExpr)
{
if (Emit1(cx, bce, JSOP_DUP) < 0) // VALUE VALUE
return false;
if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) // VALUE VALUE UNDEFINED
return false;
if (Emit1(cx, bce, JSOP_STRICTEQ) < 0) // VALUE EQL?
return false;
// Emit source note to enable ion compilation.
if (NewSrcNote(cx, bce, SRC_IF) < 0)
return false;
ptrdiff_t jump = EmitJump(cx, bce, JSOP_IFEQ, 0); // VALUE
if (jump < 0)
return false;
if (Emit1(cx, bce, JSOP_POP) < 0) // .
return false;
if (!EmitTree(cx, bce, defaultExpr)) // DEFAULTVALUE
return false;
SetJumpOffsetAt(bce, jump);
return true;
}
static bool
EmitDestructuringOpsArrayHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pattern,
VarEmitOption emitOption)
@ -3526,7 +3559,14 @@ EmitDestructuringOpsArrayHelper(ExclusiveContext *cx, BytecodeEmitter *bce, Pars
* current property name "label" on the left of a colon in the object
* initializer.
*/
if (member->isKind(PNK_SPREAD)) {
ParseNode *pndefault = nullptr;
ParseNode *elem = member;
if (elem->isKind(PNK_ASSIGN)) {
pndefault = elem->pn_right;
elem = elem->pn_left;
}
if (elem->isKind(PNK_SPREAD)) {
/* Create a new array with the rest of the iterator */
ptrdiff_t off = EmitN(cx, bce, JSOP_NEWARRAY, 3); // ... OBJ? ITER ARRAY
if (off < 0)
@ -3581,8 +3621,11 @@ EmitDestructuringOpsArrayHelper(ExclusiveContext *cx, BytecodeEmitter *bce, Pars
return false;
}
if (pndefault && !EmitDefault(cx, bce, pndefault))
return false;
// Destructure into the pattern the element contains.
ParseNode *subpattern = member;
ParseNode *subpattern = elem;
if (subpattern->isKind(PNK_ELISION)) {
// The value destructuring into an elision just gets ignored.
if (Emit1(cx, bce, JSOP_POP) < 0) // ... OBJ? ITER
@ -3684,6 +3727,12 @@ EmitDestructuringOpsObjectHelper(ExclusiveContext *cx, BytecodeEmitter *bce, Par
if (needsGetElem && !EmitElemOpBase(cx, bce, JSOP_GETELEM)) // ... OBJ PROP
return false;
if (subpattern->isKind(PNK_ASSIGN)) {
if (!EmitDefault(cx, bce, subpattern->pn_right))
return false;
subpattern = subpattern->pn_left;
}
// Destructure PROP per this member's subpattern.
int32_t depthBefore = bce->stackDepth;
if (!EmitDestructuringLHS(cx, bce, subpattern, emitOption))

Просмотреть файл

@ -3385,6 +3385,8 @@ Parser<FullParseHandler>::checkDestructuringObject(BindData<FullParseHandler> *d
MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND));
expr = member->pn_right;
}
if (expr->isKind(PNK_ASSIGN))
expr = expr->pn_left;
bool ok;
if (expr->isKind(PNK_ARRAY) || expr->isKind(PNK_OBJECT)) {
@ -3429,6 +3431,8 @@ Parser<FullParseHandler>::checkDestructuringArray(BindData<FullParseHandler> *da
report(ParseError, false, target, JSMSG_BAD_DESTRUCT_TARGET);
return false;
}
} else if (target->isKind(PNK_ASSIGN)) {
target = target->pn_left;
}
bool ok;

Просмотреть файл

@ -0,0 +1,166 @@
load(libdir + 'asserts.js');
load(libdir + 'eqArrayHelper.js');
var arrayPattern = '[a = 1, b = 2, c = 3, d = 4, e = 5, f = 6]';
var objectPattern = '{0: a = 1, 1: b = 2, 2: c = 3, 3: d = 4, 4: e = 5, 5: f = 6}';
var nestedPattern = '{a: a = 1, b: [b = 2] = [], c: {c: [c]} = {c: [3]}, d: {d, e} = {d: 4, e: 5}, f: f = 6}';
function testAll(fn) {
assertEqArray(fn(arrayPattern, []), [1, 2, 3, 4, 5, 6]);
assertEqArray(fn(arrayPattern, [2, 3, 4, 5, 6, 7, 8, 9]), [2, 3, 4, 5, 6, 7]);
assertEqArray(fn(arrayPattern, [undefined, 0, false, null, "", undefined]), [1, 0, false, null, "", 6]);
assertEqArray(fn(arrayPattern, [0, false]), [0, false, 3, 4, 5, 6]);
assertEqArray(fn(objectPattern, []), [1, 2, 3, 4, 5, 6]);
assertEqArray(fn(objectPattern, [2, 3, 4, 5, 6, 7, 8, 9]), [2, 3, 4, 5, 6, 7]);
assertEqArray(fn(objectPattern, [undefined, 0, false, null, "", undefined]), [1, 0, false, null, "", 6]);
assertEqArray(fn(objectPattern, [0, false]), [0, false, 3, 4, 5, 6]);
assertEqArray(fn(nestedPattern, {}), [1, 2, 3, 4, 5, 6]);
assertEqArray(fn(nestedPattern, {a: 2, b: [], c: undefined}), [2, 2, 3, 4, 5, 6]);
assertEqArray(fn(nestedPattern, {a: undefined, b: [3], c: {c: [4]}}), [1, 3, 4, 4, 5, 6]);
assertEqArray(fn(nestedPattern, {a: undefined, b: [3], c: {c: [4]}, d: {d: 5, e: 6}}), [1, 3, 4, 5, 6, 6]);
}
function testVar(pattern, input) {
return new Function('input',
'var ' + pattern + ' = input;' +
'return [a, b, c, d, e, f];'
)(input);
}
testAll(testVar);
function testLet(pattern, input) {
return new Function('input',
'let ' + pattern + ' = input;' +
'return [a, b, c, d, e, f];'
)(input);
}
testAll(testLet);
function testConst(pattern, input) {
return new Function('input',
'const ' + pattern + ' = input;' +
'return [a, b, c, d, e, f];'
)(input);
}
testAll(testConst);
function testGlobal(pattern, input) {
return new Function('input',
'(' + pattern + ') = input;' +
'return [a, b, c, d, e, f];'
)(input);
}
testAll(testGlobal);
function testClosure(pattern, input) {
return new Function('input',
'var rest; (function () {' +
'(' + pattern + ') = input;' +
'})();' +
'return [a, b, c, d, e, f];'
)(input);
}
testAll(testClosure);
function testArgument(pattern, input) {
return new Function('input',
'return (function (' + pattern + ') {' +
'return [a, b, c, d, e, f]; })(input);'
)(input);
}
testAll(testArgument);
function testArgumentFunction(pattern, input) {
return new Function(pattern,
'return [a, b, c, d, e, f];'
)(input);
}
// XXX: ES6 requires the `Function` constructor to accept arbitrary
// `BindingElement`s as formal parameters. See Bug 1037939.
// Once fixed, please update the assertions below.
assertThrowsInstanceOf(() => testAll(testArgumentFunction), SyntaxError);
function testThrow(pattern, input) {
return new Function('input',
'try { throw input }' +
'catch(' + pattern + ') {' +
'return [a, b, c, d, e, f]; }'
)(input);
}
testAll(testThrow);
// XXX: Support for let blocks and expressions will be removed in bug 1023609.
// However, they test a special code path in destructuring assignment so having
// these tests here for now seems like a good idea.
function testLetBlock(pattern, input) {
return new Function('input',
'let (' + pattern + ' = input)' +
'{ return [a, b, c, d, e, f]; }'
)(input);
}
testAll(testLetBlock);
function testLetExpression(pattern, input) {
return new Function('input',
'return (let (' + pattern + ' = input) [a, b, c, d, e, f]);'
)(input);
}
testAll(testLetExpression);
// test global const
const [ca = 1, cb = 2] = [];
assertEq(ca, 1);
assertEq(cb, 2);
const {a: {a: cc = 3} = {a: undefined}} = {};
assertEq(cc, 3);
// test that the assignment happens in source order
var a = undefined, b = undefined, c = undefined;
({a: a = 1, c: c = 2, b: b = 3}) = {
get a() {
assertEq(a, undefined);
assertEq(c, undefined);
assertEq(b, undefined);
return undefined;
},
get b() {
assertEq(a, 1);
assertEq(c, 2);
assertEq(b, undefined);
return 4;
},
get c() {
assertEq(a, 1);
assertEq(c, undefined);
assertEq(b, undefined);
return undefined;
}
};
assertEq(b, 4);
assertThrowsInstanceOf(() => { var {a: {a} = null} = {}; }, TypeError);
assertThrowsInstanceOf(() => { var [[a] = 2] = []; }, TypeError);
// destructuring assignment might have duplicate variable names.
var [a = 1, a = 2] = [3];
assertEq(a, 2);
// assignment to properties of default params
[a = {y: 2}, a.x = 1] = [];
assertEq(typeof a, 'object');
assertEq(a.x, 1);
assertEq(a.y, 2);
// defaults are evaluated even if there is no binding
var evaled = false;
({a: {} = (evaled = true, null)}) = {};
assertEq(evaled, true);
evaled = false;
assertThrowsInstanceOf(() => { [[] = (evaled = true, 2)] = [] }, TypeError);
assertEq(evaled, true);
assertThrowsInstanceOf(() => new Function('var [...rest = defaults] = [];'), SyntaxError);

Просмотреть файл

@ -679,12 +679,20 @@ testParamPatternCombinations(function(n) ("{a" + n + ":x" + n + "," + "b" + n +
assignProp("b" + n, ident("y" + n)),
assignProp("c" + n, ident("z" + n))])));
testParamPatternCombinations(function(n) ("{a" + n + ":x" + n + " = 10," + "b" + n + ":y" + n + " = 10," + "c" + n + ":z" + n + " = 10}"),
function(n) (objPatt([assignProp("a" + n, ident("x" + n), lit(10)),
assignProp("b" + n, ident("y" + n), lit(10)),
assignProp("c" + n, ident("z" + n), lit(10))])));
testParamPatternCombinations(function(n) ("[x" + n + "," + "y" + n + "," + "z" + n + "]"),
function(n) (arrPatt([assignElem("x" + n), assignElem("y" + n), assignElem("z" + n)])));
testParamPatternCombinations(function(n) ("[a" + n + ", ..." + "b" + n + "]"),
function(n) (arrPatt([assignElem("a" + n), spread(ident("b" + n))])));
testParamPatternCombinations(function(n) ("[a" + n + ", " + "b" + n + " = 10]"),
function(n) (arrPatt([assignElem("a" + n), assignElem("b" + n, lit(10))])));
// destructuring variable declarations
function testVarPatternCombinations(makePattSrc, makePattPatt) {
@ -721,6 +729,12 @@ testVarPatternCombinations(function (n) ("{a" + n + ":x" + n + "," + "b" + n + "
assignProp("c" + n, ident("z" + n))]),
init: lit(0) }));
testVarPatternCombinations(function (n) ("{a" + n + ":x" + n + " = 10," + "b" + n + ":y" + n + " = 10," + "c" + n + ":z" + n + " = 10} = 0"),
function (n) ({ id: objPatt([assignProp("a" + n, ident("x" + n), lit(10)),
assignProp("b" + n, ident("y" + n), lit(10)),
assignProp("c" + n, ident("z" + n), lit(10))]),
init: lit(0) }));
testVarPatternCombinations(function(n) ("[x" + n + "," + "y" + n + "," + "z" + n + "] = 0"),
function(n) ({ id: arrPatt([assignElem("x" + n), assignElem("y" + n), assignElem("z" + n)]),
init: lit(0) }));
@ -729,6 +743,9 @@ testVarPatternCombinations(function(n) ("[a" + n + ", ..." + "b" + n + "] = 0"),
function(n) ({ id: arrPatt([assignElem("a" + n), spread(ident("b" + n))]),
init: lit(0) }));
testVarPatternCombinations(function(n) ("[a" + n + ", " + "b" + n + " = 10] = 0"),
function(n) ({ id: arrPatt([assignElem("a" + n), assignElem("b" + n, lit(10))]),
init: lit(0) }));
// destructuring assignment
function testAssignmentCombinations(makePattSrc, makePattPatt) {