diff -r b92111851326 js/src/frontend/BytecodeEmitter.cpp --- a/js/src/frontend/BytecodeEmitter.cpp Wed May 23 10:06:30 2012 -0700 +++ b/js/src/frontend/BytecodeEmitter.cpp Wed May 23 15:10:08 2012 -0700 @@ -5854,16 +5854,65 @@ EmitUnary(JSContext *cx, BytecodeEmitter bce->sc->inForInit = false; if (!EmitTree(cx, bce, pn2)) return false; bce->sc->inForInit = oldInForInit; return Emit1(cx, bce, op) >= 0; } +static bool +EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint16_t ndefaults) +{ + if (Emit1(cx, bce, JSOP_DEFAULTSNEEDED) < 0) + return false; + ptrdiff_t top = bce->offset(); + size_t tableSize = (size_t)(JUMP_OFFSET_LEN * (3 + ndefaults)); + int noteIndex = NewSrcNote3(cx, bce, SRC_SWITCH, 0, 0); + if (noteIndex < 0) + return false; + if (EmitN(cx, bce, JSOP_TABLESWITCH, tableSize) < 0) + return false; + jsbytecode *pc = bce->code(top + JUMP_OFFSET_LEN); + SET_JUMP_OFFSET(pc, 1); + pc += JUMP_OFFSET_LEN; + SET_JUMP_OFFSET(pc, ndefaults); + /* We'll fill in the table backwards. */ + pc = bce->code(top + tableSize); + ParseNode *arg = pn->pn_head; + ParseNode *pnlast = pn->last(); + /* Fill body of switch, which sets defaults where needed. */ + do { + ParseNode *def = arg->pn_next; + if (def->isKind(PNK_DEFAULTARG)) { + pc -= JUMP_OFFSET_LEN; + SET_JUMP_OFFSET(pc, bce->offset() - top); + if (!EmitTree(cx, bce, def->pn_kid)) + return false; + if (!BindNameToSlot(cx, bce, arg)) + return false; + if (!EmitVarOp(cx, arg, JSOP_SETARG, bce)) + return false; + if (Emit1(cx, bce, JSOP_POP) < 0) + return false; + if (def == pnlast) + break; + arg = def->pn_next; + } else { + JS_ASSERT(arg->isKind(PNK_NAME)); + arg = def; + } + } while (arg != pnlast); + JS_ASSERT(pc - bce->code(top) == 3*JUMP_OFFSET_LEN); + SET_JUMP_OFFSET(bce->code(top), bce->offset() - top); + if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, bce->offset() - top)) + return false; + return true; +} + JSBool frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_CHECK_RECURSION(cx, return JS_FALSE); EmitLevelManager elm(bce); JSBool ok = true; @@ -5872,22 +5921,28 @@ frontend::EmitTree(JSContext *cx, Byteco /* Emit notes to tell the current bytecode's source line number. */ UPDATE_LINE_NUMBER_NOTES(cx, bce, pn->pn_pos.begin.lineno); switch (pn->getKind()) { case PNK_FUNCTION: ok = EmitFunc(cx, bce, pn); break; - case PNK_ARGSBODY: { ParseNode *pnlast = pn->last(); + uint16_t ndefaults = bce->sc->fun()->ndefaults; + if (ndefaults) { + bce->switchToProlog(); + if (!EmitDefaults(cx, bce, pn, ndefaults)) + return false; + bce->switchToMain(); + } for (ParseNode *pn2 = pn->pn_head; pn2 != pnlast; pn2 = pn2->pn_next) { - if (!pn2->isDefn()) + if (!pn2->isDefn() || pn2->isKind(PNK_DEFAULTARG)) continue; if (!BindNameToSlot(cx, bce, pn2)) return JS_FALSE; if (JOF_OPTYPE(pn2->getOp()) == JOF_QARG && bce->shouldNoteClosedName(pn2)) { if (!bce->noteClosedArg(pn2)) return JS_FALSE; } if (pn2->pn_next == pnlast && bce->sc->fun()->hasRest()) { diff -r b92111851326 js/src/frontend/ParseNode.h --- a/js/src/frontend/ParseNode.h Wed May 23 10:06:30 2012 -0700 +++ b/js/src/frontend/ParseNode.h Wed May 23 15:10:08 2012 -0700 @@ -97,16 +97,17 @@ enum ParseNodeKind { PNK_LB, PNK_RB, PNK_STATEMENTLIST, PNK_XMLCURLYEXPR, PNK_RC, PNK_LP, PNK_RP, PNK_NAME, + PNK_DEFAULTARG, PNK_NUMBER, PNK_STRING, PNK_REGEXP, PNK_TRUE, PNK_FALSE, PNK_NULL, PNK_THIS, PNK_FUNCTION, @@ -382,16 +383,17 @@ enum ParseNodeKind { * pn_xflags: PN_ENDCOMMA if extra comma at end * PNK_RC list pn_head: list of pn_count binary PNK_COLON nodes * PNK_COLON binary key-value pair in object initializer or * destructuring lhs * pn_left: property id, pn_right: value * var {x} = object destructuring shorthand shares * PN_NAME node for x on left and right of PNK_COLON * node in PNK_RC's list, has PNX_DESTRUCT flag + * PNK_DEFAULTARG unary holds the default value of a argument * PNK_NAME, name pn_atom: name, string, or object atom * PNK_STRING, pn_op: JSOP_NAME, JSOP_STRING, or JSOP_OBJECT, or * JSOP_REGEXP * PNK_REGEXP If JSOP_NAME, pn_op may be JSOP_*ARG or JSOP_*VAR * with pn_cookie telling (staticLevel, slot) (see * jsscript.h's UPVAR macros) and pn_dflags telling * const-ness and static analysis results * PNK_NAME name If pn_used, PNK_NAME uses the lexdef member instead diff -r b92111851326 js/src/frontend/Parser.cpp --- a/js/src/frontend/Parser.cpp Wed May 23 10:06:30 2012 -0700 +++ b/js/src/frontend/Parser.cpp Wed May 23 15:10:08 2012 -0700 @@ -1280,23 +1280,24 @@ LeaveFunction(ParseNode *fn, Parser *par } funbox->bindings.transfer(funtc->sc->context, &funtc->sc->bindings); return true; } bool -Parser::functionArguments(ParseNode **listp, bool &hasRest) +Parser::functionArguments(ParseNode **listp, uint16_t &ndefaults, bool &hasRest) { if (tokenStream.getToken() != TOK_LP) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL); return false; } + ndefaults = 0; hasRest = false; if (!tokenStream.matchToken(TOK_RP)) { #if JS_HAS_DESTRUCTURING JSAtom *duplicatedArg = NULL; bool destructuringArg = false; ParseNode *list = NULL; #endif @@ -1408,16 +1409,38 @@ Parser::functionArguments(ParseNode **li } #endif uint16_t slot; if (!tc->sc->bindings.addArgument(context, name, &slot)) return false; if (!DefineArg(tc->sc->funbox->node, name, slot, this)) return false; + + if (tokenStream.matchToken(TOK_ASSIGN)) { + if (hasRest) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_REST_WITH_DEFAULT); + return false; + } + ndefaults++; + ParseNode *def_expr = assignExpr(); + if (!def_expr) + return false; + ParseNode *def = UnaryNode::create(PNK_DEFAULTARG, this); + if (!def) { + freeTree(def_expr); + return false; + } + def->pn_kid = def_expr; + tc->sc->funbox->node->pn_body->append(def); + } else if (!hasRest && ndefaults) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); + return false; + } + break; } default: reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_MISSING_FORMAL); /* FALL THROUGH */ case TOK_ERROR: return false; @@ -1567,21 +1590,23 @@ Parser::functionDef(HandlePropertyName f if (outertc->sc->inStrictMode()) funsc.setInStrictMode(); // inherit strict mode from parent RootedVarFunction fun(context, funbox->function()); /* Now parse formal argument list and compute fun->nargs. */ ParseNode *prelude = NULL; + uint16_t ndefaults; bool hasRest; - if (!functionArguments(&prelude, hasRest)) + if (!functionArguments(&prelude, ndefaults, hasRest)) return NULL; fun->setArgCount(funsc.bindings.numArgs()); + fun->setDefaultCount(ndefaults); if (hasRest) fun->setHasRest(); #if JS_HAS_DESTRUCTURING /* * If there were destructuring formal parameters, bind the destructured-to * local variables now that we've parsed all the regular and destructuring * formal parameters. Because js::Bindings::add must be called first for diff -r b92111851326 js/src/frontend/Parser.h --- a/js/src/frontend/Parser.h Wed May 23 10:06:30 2012 -0700 +++ b/js/src/frontend/Parser.h Wed May 23 15:10:08 2012 -0700 @@ -205,17 +205,17 @@ struct Parser : private AutoGCRooter ParseNode *memberExpr(JSBool allowCallSyntax); ParseNode *primaryExpr(TokenKind tt, bool afterDoubleDot); ParseNode *parenExpr(JSBool *genexp = NULL); /* * Additional JS parsers. */ enum FunctionType { Getter, Setter, Normal }; - bool functionArguments(ParseNode **list, bool &hasRest); + bool functionArguments(ParseNode **list, uint16_t &ndefaults, bool &hasRest); ParseNode *functionDef(HandlePropertyName name, FunctionType type, FunctionSyntaxKind kind); ParseNode *unaryOpExpr(ParseNodeKind kind, JSOp op); ParseNode *condition(); ParseNode *comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP); diff -r b92111851326 js/src/jit-test/tests/arguments/defaults-basic.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit-test/tests/arguments/defaults-basic.js Wed May 23 15:10:08 2012 -0700 @@ -0,0 +1,16 @@ +function f1(a, bIs, b=3) { + assertEq(a, 1); + assertEq(b, bIs); +} +assertEq(f1.length, 3); +f1(1, 3); +f1(1, 42, 42); +function f2(a, bIs, cIs, b=3, c=4) { + assertEq(a, 1); + assertEq(b, bIs); + assertEq(c, cIs); +} +assertEq(f2.length, 5); +f2(1, 3, 4); +f2(1, 42, 4, 42); +f2(1, 42, 43, 42, 43); \ No newline at end of file diff -r b92111851326 js/src/jit-test/tests/arguments/defaults-evaluation-order.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit-test/tests/arguments/defaults-evaluation-order.js Wed May 23 15:10:08 2012 -0700 @@ -0,0 +1,10 @@ +function f1(a, bIs, cIs, dIs, b=a, c=d, d=5) { + assertEq(a, 1); + assertEq(b, bIs); + assertEq(c, cIs); + assertEq(d, dIs); +} +f1(1, 1, undefined, 5); +f1(1, 42, undefined, 5, 42); +f1(1, 42, 43, 5, 42, 43); +f1(1, 42, 43, 44, 42, 43, 44); \ No newline at end of file diff -r b92111851326 js/src/jit-test/tests/arguments/defaults-invalid-syntax.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit-test/tests/arguments/defaults-invalid-syntax.js Wed May 23 15:10:08 2012 -0700 @@ -0,0 +1,8 @@ +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(function () { + eval("function f(...rest=23) {}"); +}, SyntaxError); +assertThrowsInstanceOf(function () { + eval("function f(a=16, b) {}"); +}, SyntaxError); \ No newline at end of file diff -r b92111851326 js/src/jit-test/tests/arguments/defaults-with-rest.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit-test/tests/arguments/defaults-with-rest.js Wed May 23 15:10:08 2012 -0700 @@ -0,0 +1,10 @@ +load(libdir + "eqArrayHelper.js"); + +function f1(a, bIs, b=3, ...rest) { + assertEq(a, 1); + assertEq(bIs, b); + assertEqArray(rest, []); +} +assertEq(f1.length, 3); +f1(1, 3); +f1(1, 42, 42); \ No newline at end of file diff -r b92111851326 js/src/js.msg --- a/js/src/js.msg Wed May 23 10:06:30 2012 -0700 +++ b/js/src/js.msg Wed May 23 15:10:08 2012 -0700 @@ -344,8 +344,10 @@ MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 29 MSG_DEF(JSMSG_NOT_ITERABLE, 291, 1, JSEXN_TYPEERR, "{0} is not iterable") MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 292, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'url' property") MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 293, 0, JSEXN_TYPEERR, "findScripts query object has 'innermost' property without both 'url' and 'line' properties") MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND, 294, 0, JSEXN_TYPEERR, "variable not found in environment") MSG_DEF(JSMSG_PARAMETER_AFTER_REST, 295, 0, JSEXN_SYNTAXERR, "parameter after rest parameter") MSG_DEF(JSMSG_NO_REST_NAME, 296, 0, JSEXN_SYNTAXERR, "no parameter name after ...") MSG_DEF(JSMSG_ARGUMENTS_AND_REST, 297, 0, JSEXN_SYNTAXERR, "'arguments' object may not be used in conjunction with a rest parameter") MSG_DEF(JSMSG_FUNCTION_ARGUMENTS_AND_REST, 298, 0, JSEXN_ERR, "the 'arguments' property of a function with a rest parameter may not be used") +MSG_DEF(JSMSG_REST_WITH_DEFAULT, 299, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default") +MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 300, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default") diff -r b92111851326 js/src/jsfun.cpp --- a/js/src/jsfun.cpp Wed May 23 10:06:30 2012 -0700 +++ b/js/src/jsfun.cpp Wed May 23 15:10:08 2012 -0700 @@ -1219,16 +1219,17 @@ js_NewFunction(JSContext *cx, JSObject * funobj = NewObjectWithClassProto(cx, &FunctionClass, NULL, SkipScopeParent(parent), kind); if (!funobj) return NULL; } fun = static_cast(funobj); /* Initialize all function members. */ fun->nargs = uint16_t(nargs); + fun->ndefaults = 0; fun->flags = flags & (JSFUN_FLAGS_MASK | JSFUN_KINDMASK); if ((flags & JSFUN_KINDMASK) >= JSFUN_INTERPRETED) { JS_ASSERT(!native); fun->mutableScript().init(NULL); fun->initEnvironment(parent); } else { fun->u.native = native; JS_ASSERT(fun->u.native); diff -r b92111851326 js/src/jsfun.h --- a/js/src/jsfun.h Wed May 23 10:06:30 2012 -0700 +++ b/js/src/jsfun.h Wed May 23 15:10:08 2012 -0700 @@ -45,16 +45,17 @@ optimization level -- see above */ namespace js { class FunctionExtended; } struct JSFunction : public JSObject { uint16_t nargs; /* maximum number of specified arguments, reflected as f.length/f.arity */ + uint16_t ndefaults; /* number of trailing parameters with defaults */ uint16_t flags; /* flags, see JSFUN_* below and in jsapi.h */ union U { js::Native native; /* native method pointer or null */ struct Scripted { JSScript *script_; /* interpreted bytecode descriptor or null; use the accessor! */ JSObject *env_; /* environment for new activations; use the accessor! */ @@ -81,16 +82,21 @@ struct JSFunction : public JSObject /* Returns the strictness of this function, which must be interpreted. */ inline bool inStrictMode() const; void setArgCount(uint16_t nargs) { JS_ASSERT(this->nargs == 0); this->nargs = nargs; } + void setDefaultCount(uint16_t ndefaults) { + JS_ASSERT(this->ndefaults == 0); + this->ndefaults = ndefaults; + } + void setHasRest() { JS_ASSERT(!hasRest()); this->flags |= JSFUN_HAS_REST; } /* uint16_t representation bounds number of call object dynamic slots. */ enum { MAX_ARGS_AND_VARS = 2 * ((1U << 16) - 1) }; diff -r b92111851326 js/src/jsinterp.cpp --- a/js/src/jsinterp.cpp Wed May 23 10:06:30 2012 -0700 +++ b/js/src/jsinterp.cpp Wed May 23 15:10:08 2012 -0700 @@ -2807,16 +2807,26 @@ BEGIN_CASE(JSOP_LOOKUPSWITCH) #undef SEARCH_PAIRS end_lookup_switch: len = GET_JUMP_OFFSET(pc2); } END_VARLEN_CASE } +BEGIN_CASE(JSOP_DEFAULTSNEEDED) +{ + JSFunction *fun = regs.fp()->fun(); + unsigned nformal = fun->nargs - fun->hasRest(); + unsigned nactual = regs.fp()->numActualArgs(); + JS_ASSERT(fun->ndefaults); + PUSH_INT32((nactual >= nformal) ? 0 : JS_MIN(nformal - nactual, fun->ndefaults)); +} +END_CASE(JSOP_DEFAULTSNEEDED) + BEGIN_CASE(JSOP_ARGUMENTS) JS_ASSERT(!regs.fp()->fun()->hasRest()); if (script->needsArgsObj()) { ArgumentsObject *obj = ArgumentsObject::create(cx, regs.fp()); if (!obj) goto error; PUSH_COPY(ObjectValue(*obj)); } else { diff -r b92111851326 js/src/jsopcode.tbl --- a/js/src/jsopcode.tbl Wed May 23 10:06:30 2012 -0700 +++ b/js/src/jsopcode.tbl Wed May 23 15:10:08 2012 -0700 @@ -512,8 +512,10 @@ OPDEF(JSOP_REST, 224, "rest", /* Pop the stack, convert to a jsid (int or string), and push back. */ OPDEF(JSOP_TOID, 225, "toid", NULL, 1, 1, 1, 0, JOF_BYTE) /* Push the implicit 'this' value for calls to the associated name. */ OPDEF(JSOP_IMPLICITTHIS, 226, "implicitthis", "", 5, 0, 1, 0, JOF_ATOM) /* This opcode is the target of the entry jump for some loop. */ OPDEF(JSOP_LOOPENTRY, 227, "loopentry", NULL, 1, 0, 0, 0, JOF_BYTE) + +OPDEF(JSOP_DEFAULTSNEEDED, 228, "defaultsneeded", NULL, 1, 0, 1, 0, JOF_BYTE)