Bug 1018628 - Part 1: Support default parameter for destructuring. r=jorendorff

This commit is contained in:
Tooru Fujisawa 2014-11-22 11:54:42 +09:00
parent e0448e192b
commit d6d3eb3888
15 changed files with 329 additions and 154 deletions

View File

@ -7003,35 +7003,45 @@ BytecodeEmitter::emitUnary(ParseNode* pn)
}
bool
BytecodeEmitter::emitDefaults(ParseNode* pn)
BytecodeEmitter::emitDefaultsAndDestructuring(ParseNode* pn, ParseNode* pndestruct)
{
MOZ_ASSERT(pn->isKind(PNK_ARGSBODY));
uint32_t i = 0;
ParseNode* pnlast = pn->last();
ParseNode* destruct = pndestruct ? pndestruct->pn_head : nullptr;
for (ParseNode* arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) {
if (!(arg->pn_dflags & PND_DEFAULT))
continue;
if (!bindNameToSlot(arg))
return false;
if (!emitVarOp(arg, JSOP_GETARG))
return false;
if (!emit1(JSOP_UNDEFINED))
return false;
if (!emit1(JSOP_STRICTEQ))
return false;
// Emit source note to enable ion compilation.
if (!newSrcNote(SRC_IF))
return false;
ptrdiff_t jump;
if (!emitJump(JSOP_IFEQ, 0, &jump))
return false;
if (!emitTree(arg->expr()))
return false;
if (!emitVarOp(arg, JSOP_SETARG))
return false;
if (!emit1(JSOP_POP))
return false;
SET_JUMP_OFFSET(code(jump), offset() - jump);
if (arg->pn_dflags & PND_DEFAULT) {
if (!bindNameToSlot(arg))
return false;
if (!emitVarOp(arg, JSOP_GETARG))
return false;
if (!emit1(JSOP_UNDEFINED))
return false;
if (!emit1(JSOP_STRICTEQ))
return false;
// Emit source note to enable ion compilation.
if (!newSrcNote(SRC_IF))
return false;
ptrdiff_t jump;
if (!emitJump(JSOP_IFEQ, 0, &jump))
return false;
if (!emitTree(arg->expr()))
return false;
if (!emitVarOp(arg, JSOP_SETARG))
return false;
if (!emit1(JSOP_POP))
return false;
SET_JUMP_OFFSET(code(jump), offset() - jump);
}
if (destruct && destruct->pn_head->pn_right->frameSlot() == i) {
if (!emitTree(destruct))
return false;
if (!emit1(JSOP_POP))
return false;
destruct = destruct->pn_next;
}
++i;
}
return true;
@ -7207,62 +7217,62 @@ BytecodeEmitter::emitTree(ParseNode* pn)
ParseNode* pnlast = pn->last();
// Carefully emit everything in the right order:
// 1. Destructuring
// 2. Defaults
// 3. Functions
// 1. Defaults and Destructuring for each argument
// 2. Functions
ParseNode* pnchild = pnlast->pn_head;
if (pnlast->pn_xflags & PNX_DESTRUCT) {
// Assign the destructuring arguments before defining any functions,
// see bug 419662.
ParseNode* pndestruct = nullptr;
bool hasDestructuring = pnlast->pn_xflags & PNX_DESTRUCT;
if (hasDestructuring) {
MOZ_ASSERT(pnchild->isKind(PNK_SEMI));
MOZ_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_GLOBALCONST));
if (!emitTree(pnchild))
return false;
MOZ_ASSERT(pnchild->pn_kid->isKind(PNK_SEQ));
#ifdef DEBUG
for (ParseNode* pnvar = pnchild->pn_kid->pn_head; pnvar; pnvar = pnvar->pn_next)
MOZ_ASSERT(pnvar->isKind(PNK_VAR) || pnvar->isKind(PNK_GLOBALCONST));
#endif
pndestruct = pnchild->pn_head;
pnchild = pnchild->pn_next;
}
bool hasDefaults = sc->asFunctionBox()->hasDefaults();
if (hasDefaults) {
ParseNode* rest = nullptr;
bool restIsDefn = false;
if (fun->hasRest()) {
MOZ_ASSERT(!sc->asFunctionBox()->argumentsHasLocalBinding());
ParseNode* rest = nullptr;
bool restIsDefn = false;
if (fun->hasRest() && hasDefaults) {
MOZ_ASSERT(!sc->asFunctionBox()->argumentsHasLocalBinding());
// Defaults with a rest parameter need special handling. The
// rest parameter needs to be undefined while defaults are being
// processed. To do this, we create the rest argument and let it
// sit on the stack while processing defaults. The rest
// parameter's slot is set to undefined for the course of
// default processing.
rest = pn->pn_head;
while (rest->pn_next != pnlast)
rest = rest->pn_next;
restIsDefn = rest->isDefn();
if (!emit1(JSOP_REST))
return false;
checkTypeSet(JSOP_REST);
// Only set the rest parameter if it's not aliased by a nested
// function in the body.
if (restIsDefn) {
if (!emit1(JSOP_UNDEFINED))
return false;
if (!bindNameToSlot(rest))
return false;
if (!emitVarOp(rest, JSOP_SETARG))
return false;
if (!emit1(JSOP_POP))
return false;
}
}
if (!emitDefaults(pn))
// Defaults with a rest parameter need special handling. The
// rest parameter needs to be undefined while defaults are being
// processed. To do this, we create the rest argument and let it
// sit on the stack while processing defaults. The rest
// parameter's slot is set to undefined for the course of
// default processing.
rest = pn->pn_head;
while (rest->pn_next != pnlast)
rest = rest->pn_next;
restIsDefn = rest->isDefn();
if (!emit1(JSOP_REST))
return false;
if (fun->hasRest()) {
if (restIsDefn && !emitVarOp(rest, JSOP_SETARG))
checkTypeSet(JSOP_REST);
// Only set the rest parameter if it's not aliased by a nested
// function in the body.
if (restIsDefn) {
if (!emit1(JSOP_UNDEFINED))
return false;
if (!bindNameToSlot(rest))
return false;
if (!emitVarOp(rest, JSOP_SETARG))
return false;
if (!emit1(JSOP_POP))
return false;
}
}
if (!emitDefaultsAndDestructuring(pn, pndestruct))
return false;
if (fun->hasRest() && hasDefaults) {
if (restIsDefn && !emitVarOp(rest, JSOP_SETARG))
return false;
if (!emit1(JSOP_POP))
return false;
}
for (ParseNode* pn2 = pn->pn_head; pn2 != pnlast; pn2 = pn2->pn_next) {
// Only bind the parameter if it's not aliased by a nested function
// in the body.

View File

@ -569,7 +569,7 @@ struct BytecodeEmitter
bool emitBreak(PropertyName* label);
bool emitContinue(PropertyName* label);
bool emitDefaults(ParseNode* pn);
bool emitDefaultsAndDestructuring(ParseNode* pn, ParseNode* pndestruct);
bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp);
bool pushInitialConstants(JSOp op, unsigned n);

View File

@ -1650,11 +1650,6 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn
return false;
}
if (hasDefaults) {
report(ParseError, false, null(), JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT);
return false;
}
funbox->hasDestructuringArgs = true;
/*
@ -1682,16 +1677,22 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn
if (!rhs)
return false;
handler.addFunctionArgument(funcpn, rhs);
if (!pc->define(tokenStream, name, rhs, Definition::ARG))
return false;
Node item = handler.newBinary(PNK_ASSIGN, lhs, rhs);
if (!item)
return false;
Node vars = handler.newDeclarationList(PNK_VAR, item);
if (!vars)
return false;
if (list) {
handler.addList(list, item);
handler.addList(list, vars);
} else {
list = handler.newDeclarationList(PNK_VAR, item);
list = handler.newList(PNK_SEQ, vars);
if (!list)
return false;
*listp = list;
@ -1743,39 +1744,6 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn
RootedPropertyName name(context, tokenStream.currentName());
if (!defineArg(funcpn, name, disallowDuplicateArgs, &duplicatedArg))
return false;
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ASSIGN))
return false;
if (matched) {
// A default argument without parentheses would look like:
// a = expr => body, but both operators are right-associative, so
// that would have been parsed as a = (expr => body) instead.
// Therefore it's impossible to get here with parenFreeArrow.
MOZ_ASSERT(!parenFreeArrow);
if (*hasRest) {
report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT);
return false;
}
disallowDuplicateArgs = true;
if (duplicatedArg) {
report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS);
return false;
}
if (!hasDefaults) {
hasDefaults = true;
// The Function.length property is the number of formals
// before the first default argument.
funbox->length = pc->numArgs() - 1;
}
Node def_expr = assignExprWithoutYield(yieldHandling, JSMSG_YIELD_IN_DEFAULT);
if (!def_expr)
return false;
handler.setLastFunctionArgumentDefault(funcpn, def_expr);
}
break;
}
@ -1784,10 +1752,41 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn
return false;
}
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ASSIGN))
return false;
if (matched) {
// A default argument without parentheses would look like:
// a = expr => body, but both operators are right-associative, so
// that would have been parsed as a = (expr => body) instead.
// Therefore it's impossible to get here with parenFreeArrow.
MOZ_ASSERT(!parenFreeArrow);
if (*hasRest) {
report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT);
return false;
}
disallowDuplicateArgs = true;
if (duplicatedArg) {
report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS);
return false;
}
if (!hasDefaults) {
hasDefaults = true;
// The Function.length property is the number of formals
// before the first default argument.
funbox->length = pc->numArgs() - 1;
}
Node def_expr = assignExprWithoutYield(yieldHandling, JSMSG_YIELD_IN_DEFAULT);
if (!def_expr)
return false;
handler.setLastFunctionArgumentDefault(funcpn, def_expr);
}
if (parenFreeArrow || kind == Setter)
break;
bool matched;
if (!tokenStream.matchToken(&matched, TOK_COMMA))
return false;
if (!matched)

View File

@ -32,3 +32,11 @@ function l(a=8, b=a) {
function a() { return 42; }
}
assertEq(l(), 8);
function m([a, b]=[1, 2], c=a) {
function a() { return 42; }
assertEq(typeof a, "function");
assertEq(a(), 42);
assertEq(b, 2);
assertEq(c, 1);
}
m();

View File

@ -0,0 +1,17 @@
function f1(a, bIs, [b]=[3]) {
assertEq(a, 1);
assertEq(b, bIs);
}
assertEq(f1.length, 2);
f1(1, 3);
f1(1, 42, [42]);
f1(1, 3, undefined);
function f2(a, bIs, [b]=[]) {
assertEq(a, 1);
assertEq(b, bIs);
}
assertEq(f2.length, 2);
f2(1, undefined);
f2(1, 42, [42]);
f2(1, undefined, undefined);

View File

@ -0,0 +1,19 @@
function f1(a, bIs, cIs, dIs, {b}={b: 3}, c=4, [d]=[5]) (
assertEq(a, 1),
assertEq(b, bIs),
assertEq(c, cIs),
assertEq(d, dIs)
);
assertEq(f1.length, 4);
f1(1, 3, 4, 5);
f1(1, 42, 43, 44, {b: 42}, 43, [44]);
let f2 = (a, bIs, cIs, dIs, {b}={b: 3}, c=4, [d]=[5]) => (
assertEq(a, 1),
assertEq(b, bIs),
assertEq(c, cIs),
assertEq(d, dIs)
);
assertEq(f2.length, 4);
f2(1, 3, 4, 5);
f2(1, 42, 43, 44, {b: 42}, 43, [44]);

View File

@ -0,0 +1,9 @@
let f = function(a, bIs, cIs, dIs, {b}={b: 3}, c=4, [d]=[5]) {
assertEq(a, 1);
assertEq(b, bIs);
assertEq(c, cIs);
assertEq(d, dIs);
};
assertEq(f.length, 4);
f(1, 3, 4, 5);
f(1, 42, 43, 44, {b: 42}, 43, [44]);

View File

@ -0,0 +1,29 @@
function f1(a, bIs, cIs, dIs, b=3, {c}={c: 4}, [d]=[5]) {
assertEq(a, 1);
assertEq(b, bIs);
assertEq(c, cIs);
assertEq(d, dIs);
}
assertEq(f1.length, 4);
f1(1, 3, 4, 5);
f1(1, 42, 4, 5, 42);
f1(1, 42, 43, 5, 42, {c: 43});
f1(1, 42, 43, 44, 42, {c: 43}, [44]);
f1(1, 3, 4, 5, undefined);
f1(1, 42, 4, 5, 42, undefined);
f1(1, 3, 42, 5, undefined, {c: 42});
f1(1, 3, 4, 42, undefined, undefined, [42]);
function f2(a, bIs, cIs, dIs, eIs, {b}={b: 3}, [c]=[b], d=c, {ee: e}={ee: d}) {
assertEq(a, 1);
assertEq(b, bIs);
assertEq(c, cIs);
assertEq(d, dIs);
assertEq(e, eIs);
}
assertEq(f2.length, 5);
f2(1, 3, 3, 3, 3);
f2(1, 42, 42, 42, 42, {b: 42});
f2(1, 42, 43, 43, 43, {b: 42}, [43]);
f2(1, 42, 43, 44, 44, {b: 42}, [43], 44);
f2(1, 42, 43, 44, 45, {b: 42}, [43], 44, {ee: 45});

View File

@ -0,0 +1,27 @@
function f1(a, bIs, cIs, {b}={b: 3}, {cc: c}={cc: 4}) {
assertEq(a, 1);
assertEq(b, bIs);
assertEq(c, cIs);
}
assertEq(f1.length, 3);
f1(1, 3, 4);
f1(1, 42, 4, {b: 42});
f1(1, 42, 4, {b: 42}, undefined);
f1(1, 42, 43, {b: 42}, {cc: 43});
f1(1, 3, 4, undefined);
f1(1, 3, 4, undefined, undefined);
f1(1, 3, 43, undefined, {cc: 43});
function f2(a, bIs, cIs, {b}={}, {cc: c}={}) {
assertEq(a, 1);
assertEq(b, bIs);
assertEq(c, cIs);
}
assertEq(f2.length, 3);
f2(1, undefined, undefined);
f2(1, 42, undefined, {b: 42});
f2(1, 42, undefined, {b: 42}, undefined);
f2(1, 42, 43, {b: 42}, {cc: 43});
f2(1, undefined, undefined, undefined);
f2(1, undefined, undefined, undefined, undefined);
f2(1, undefined, 43, undefined, {cc: 43});

View File

@ -0,0 +1,26 @@
load(libdir + "asserts.js");
load(libdir + "eqArrayHelper.js");
function f1(a, bIs, [b]=[3], ...rest) {
assertEq(a, 1);
assertEq(bIs, b);
assertEqArray(rest, []);
}
assertEq(f1.length, 2);
f1(1, 3);
f1(1, 42, [42]);
function f2([a]=[rest], ...rest) {
assertEq(a, undefined);
}
f2();
function f3([a]=[rest], ...rest) {
assertEq(a, 1);
assertEqArray(rest, [2, 3, 4]);
}
f3([1], 2, 3, 4);
function f4([a]=rest, ...rest) {
}
assertThrowsInstanceOf(f4, TypeError);

View File

@ -3,12 +3,6 @@ load(libdir + "asserts.js");
assertThrowsInstanceOf(function () {
eval("function f(...rest=23) {}");
}, SyntaxError);
assertThrowsInstanceOf(function () {
eval("function f([a]=4) {}");
}, SyntaxError);
assertThrowsInstanceOf(function () {
eval("function f(a=4, [b]) {}");
}, SyntaxError);
assertThrowsInstanceOf(function () {
eval("function f(a=yield 24) {}");
}, SyntaxError);

View File

@ -0,0 +1,28 @@
load(libdir + "asserts.js");
function f1(a, bIs, cIs, dIs, b=1, [c], {d}) {
assertEq(a, 1);
assertEq(b, bIs);
assertEq(c, cIs);
assertEq(d, dIs);
}
assertEq(f1.length, 4);
f1(1, 1, 42, 43, undefined, [42], {d: 43});
f1(1, 42, 43, 44, 42, [43], {d: 44});
assertThrowsInstanceOf(function () {
f1(1, 1, 1, 1);
}, TypeError);
function f2(a=(assertEq(a, undefined), assertEq(b, undefined),
assertEq(c, undefined), assertEq(d, undefined), 1),
[b],
c=(assertEq(a, 1), assertEq(b, 42),
assertEq(c, undefined), assertEq(d, undefined), 2),
{d}) {
assertEq(a, 1);
assertEq(b, 42);
assertEq(c, 2);
assertEq(d, 43);
}
assertEq(f2.length, 0);
f2(undefined, [42], undefined, {d: 43});

View File

@ -0,0 +1,21 @@
load(libdir + "eqArrayHelper.js");
function f1(a, bIs, [b], ...rest) {
assertEq(a, 1);
assertEq(bIs, b);
assertEqArray(rest, []);
}
assertEq(f1.length, 3);
f1(1, 3, [3]);
f1(1, 42, [42]);
function f2([a], ...rest) {
assertEq(a, undefined);
}
f2([]);
function f3([a], ...rest) {
assertEq(a, 1);
assertEqArray(rest, [2, 3, 4]);
}
f3([1], 2, 3, 4);

View File

@ -3443,7 +3443,7 @@ ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector&
pndestruct = head->pn_kid;
LOCAL_ASSERT(pndestruct);
LOCAL_ASSERT(pndestruct->isKind(PNK_VAR));
LOCAL_ASSERT(pndestruct->isKind(PNK_SEQ));
} else {
pndestruct = nullptr;
}
@ -3489,8 +3489,10 @@ ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs, ParseNode* pndestr
ParseNode* pnbody, NodeVector& args, NodeVector& defaults,
MutableHandleValue rest)
{
if (!pnargs)
return true;
uint32_t i = 0;
ParseNode* arg = pnargs ? pnargs->pn_head : nullptr;
ParseNode* destruct = pndestruct ? pndestruct->pn_head : nullptr;
RootedValue node(cx);
bool defaultsNull = true;
@ -3502,51 +3504,31 @@ ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs, ParseNode* pndestr
* Arguments are found in potentially two different places: 1) the
* argsbody sequence (which ends with the body node), or 2) a
* destructuring initialization at the beginning of the body. Loop
* |arg| through the argsbody and |destruct| through the initial
* destructuring assignments, stopping only when we've exhausted
* both.
* |arg| through the argsbody, and use |destruct| if it has same index.
*/
while ((arg && arg != pnbody) || destruct) {
if (destruct && destruct->pn_right->frameSlot() == i) {
if (!pattern(destruct->pn_left, &node) ||
!args.append(node) || !defaults.append(NullValue()))
{
for (ParseNode* arg = pnargs->pn_head; arg && arg != pnbody; arg = arg->pn_next) {
MOZ_ASSERT(arg->isKind(PNK_NAME));
if (destruct && destruct->pn_head->pn_right->frameSlot() == i) {
if (!pattern(destruct->pn_head->pn_left, &node) || !args.append(node))
return false;
}
destruct = destruct->pn_next;
} else if (arg && arg != pnbody) {
/*
* We don't check that arg->frameSlot() == i since we
* can't call that method if the arg def has been turned
* into a use, e.g.:
*
* function(a) { function a() { } }
*
* There's no other way to ask a non-destructuring arg its
* index in the formals list, so we rely on the ability to
* ask destructuring args their index above.
*/
MOZ_ASSERT(arg->isKind(PNK_NAME) || arg->isKind(PNK_ASSIGN));
ParseNode* argName = arg->isKind(PNK_NAME) ? arg : arg->pn_left;
if (!identifier(argName, &node))
} else {
if (!identifier(arg, &node))
return false;
if (rest.isUndefined() && arg->pn_next == pnbody)
rest.setObject(node.toObject());
else if (!args.append(node))
return false;
if (arg->pn_dflags & PND_DEFAULT) {
defaultsNull = false;
ParseNode* expr = arg->expr();
RootedValue def(cx);
if (!expression(expr, &def) || !defaults.append(def))
return false;
} else {
if (!defaults.append(NullValue()))
return false;
}
arg = arg->pn_next;
}
if (arg->pn_dflags & PND_DEFAULT) {
defaultsNull = false;
ParseNode* expr = arg->expr();
RootedValue def(cx);
if (!expression(expr, &def) || !defaults.append(def))
return false;
} else {
LOCAL_NOT_REACHED("missing function argument");
if (!defaults.append(NullValue()))
return false;
}
++i;
}

View File

@ -32,6 +32,12 @@ assertDecl("function foo(a=(function () {})) { function a() {} }",
funDecl(ident("foo"), [ident("a")], blockStmt([funDecl(ident("a"), [], blockStmt([]))]),
[funExpr(null, [], blockStmt([]))]));
// Bug 1018628: default paremeter for destructuring
assertDecl("function f(a=1, [x,y]=[2,3]) { }",
funDecl(ident("f"),
[ident("a"), arrPatt([ident("x"), ident("y")])],
blockStmt([]),
[lit(1), arrExpr([lit(2), lit(3)])]));
// Bug 591437: rebound args have their defs turned into uses
assertDecl("function f(a) { function a() { } }",