Bug 875002 - Allow shorthand properties in object literals; r=jorendorff

--HG--
extra : rebase_source : cf461ea4585b0a9fbd51f8260c4d8c4b76ed17ed
This commit is contained in:
Arpad Borsos 2014-06-07 22:29:26 +02:00
parent 81c2062aad
commit 9dda5f5b76
14 changed files with 134 additions and 69 deletions

View File

@ -3218,7 +3218,7 @@ EmitDestructuringOpsHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode
pn3 = pn2;
} else {
JS_ASSERT(pn->isKind(PNK_OBJECT));
JS_ASSERT(pn2->isKind(PNK_COLON));
JS_ASSERT(pn2->isKind(PNK_COLON) || pn2->isKind(PNK_SHORTHAND));
ParseNode *key = pn2->pn_left;
if (key->isKind(PNK_NUMBER)) {
@ -5923,11 +5923,6 @@ EmitConditionalExpression(ExclusiveContext *cx, BytecodeEmitter *bce, Conditiona
MOZ_NEVER_INLINE static bool
EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
if (pn->pn_xflags & PNX_DESTRUCT) {
bce->reportError(pn, JSMSG_BAD_OBJECT_INIT);
return false;
}
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
return EmitSingletonInitialiser(cx, bce, pn);

View File

@ -252,20 +252,19 @@ class FullParseHandler
return literal;
}
bool addPropertyDefinition(ParseNode *literal, ParseNode *name, ParseNode *expr) {
ParseNode *propdef = newBinary(PNK_COLON, name, expr, JSOP_INITPROP);
bool addPropertyDefinition(ParseNode *literal, ParseNode *name, ParseNode *expr,
bool isShorthand = false) {
JS_ASSERT(literal->isArity(PN_LIST));
ParseNode *propdef = newBinary(isShorthand ? PNK_SHORTHAND : PNK_COLON, name, expr,
JSOP_INITPROP);
if (isShorthand)
literal->pn_xflags |= PNX_NONCONST;
if (!propdef)
return false;
literal->append(propdef);
return true;
}
bool addShorthandPropertyDefinition(ParseNode *literal, ParseNode *name) {
JS_ASSERT(literal->isArity(PN_LIST));
literal->pn_xflags |= PNX_DESTRUCT | PNX_NONCONST; // XXX why PNX_DESTRUCT?
return addPropertyDefinition(literal, name, name);
}
bool addAccessorPropertyDefinition(ParseNode *literal, ParseNode *name, ParseNode *fn, JSOp op)
{
JS_ASSERT(literal->isArity(PN_LIST));

View File

@ -150,8 +150,9 @@ class NameResolver
break;
case PNK_COLON:
case PNK_SHORTHAND:
/*
* Record the PNK_COLON but skip the PNK_OBJECT so we're not
* Record the PNK_COLON/SHORTHAND but skip the PNK_OBJECT so we're not
* flagged as a contributor.
*/
pos--;
@ -221,7 +222,7 @@ class NameResolver
for (int pos = size - 1; pos >= 0; pos--) {
ParseNode *node = toName[pos];
if (node->isKind(PNK_COLON)) {
if (node->isKind(PNK_COLON) || node->isKind(PNK_SHORTHAND)) {
ParseNode *left = node->pn_left;
if (left->isKind(PNK_NAME) || left->isKind(PNK_STRING)) {
if (!appendPropertyReference(left->pn_atom))

View File

@ -460,7 +460,7 @@ Parser<FullParseHandler>::cloneLeftHandSide(ParseNode *opn)
ParseNode *pn2;
if (opn->isKind(PNK_OBJECT)) {
JS_ASSERT(opn2->isArity(PN_BINARY));
JS_ASSERT(opn2->isKind(PNK_COLON));
JS_ASSERT(opn2->isKind(PNK_COLON) || opn2->isKind(PNK_SHORTHAND));
ParseNode *tag = cloneParseTree(opn2->pn_left);
if (!tag)
@ -469,7 +469,7 @@ Parser<FullParseHandler>::cloneLeftHandSide(ParseNode *opn)
if (!target)
return nullptr;
pn2 = handler.new_<BinaryNode>(PNK_COLON, JSOP_INITPROP, opn2->pn_pos, tag, target);
pn2 = handler.new_<BinaryNode>(opn2->getKind(), JSOP_INITPROP, opn2->pn_pos, tag, target);
} else if (opn2->isArity(PN_NULLARY)) {
JS_ASSERT(opn2->isKind(PNK_ELISION));
pn2 = cloneParseTree(opn2);

View File

@ -73,6 +73,7 @@ class UpvarCookie
F(COMMA) \
F(CONDITIONAL) \
F(COLON) \
F(SHORTHAND) \
F(POS) \
F(NEG) \
F(PREINCREMENT) \
@ -388,9 +389,8 @@ enum ParseNodeKind
* 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_OBJECT's list, has PNX_DESTRUCT flag
* PNK_SHORTHAND binary Same fields as PNK_COLON. This is used for object
* literal properties using shorthand ({x}).
* PNK_NAME, name pn_atom: name, string, or object atom
* PNK_STRING pn_op: JSOP_NAME, JSOP_STRING, or JSOP_OBJECT
* If JSOP_NAME, pn_op may be JSOP_*ARG or JSOP_*VAR
@ -677,12 +677,8 @@ class ParseNode
#define PNX_GROUPINIT 0x02 /* var [a, b] = [c, d]; unit list */
#define PNX_FUNCDEFS 0x04 /* contains top-level function statements */
#define PNX_SETCALL 0x08 /* call expression in lvalue context */
#define PNX_DESTRUCT 0x10 /* destructuring special cases:
1. shorthand syntax used, at present
object destructuring ({x,y}) only;
2. code evaluating destructuring
arguments occurs before function
body */
#define PNX_DESTRUCT 0x10 /* code evaluating destructuring
arguments occurs before function body */
#define PNX_SPECIALARRAYINIT 0x20 /* one or more of
1. array initialiser has holes
2. array initializer has spread node */

View File

@ -727,7 +727,7 @@ HasFinalReturn(ParseNode *pn)
case PNK_RETURN:
return ENDS_IN_RETURN;
case PNK_COLON:
case PNK_LABEL:
case PNK_LEXICALSCOPE:
return HasFinalReturn(pn->expr());
@ -3194,7 +3194,7 @@ Parser<FullParseHandler>::checkDestructuring(BindData<FullParseHandler> *data,
} else {
JS_ASSERT(left->isKind(PNK_OBJECT));
for (ParseNode *member = left->pn_head; member; member = member->pn_next) {
MOZ_ASSERT(member->isKind(PNK_COLON));
MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND));
ParseNode *expr = member->pn_right;
if (expr->isKind(PNK_ARRAY) || expr->isKind(PNK_OBJECT)) {
@ -4220,8 +4220,7 @@ Parser<FullParseHandler>::forStatement()
isForDecl = true;
tokenStream.consumeKnownToken(tt);
pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST);
}
else if (tt == TOK_LET) {
} else if (tt == TOK_LET) {
handler.disableSyntaxParser();
(void) tokenStream.getToken();
if (tokenStream.peekToken() == TOK_LP) {
@ -4233,8 +4232,7 @@ Parser<FullParseHandler>::forStatement()
return null();
pn1 = variables(PNK_LET, nullptr, blockObj, DontHoistVars);
}
}
else {
} else {
pn1 = expr();
}
pc->parsingForInit = false;
@ -7303,8 +7301,7 @@ Parser<ParseHandler>::objectLiteral()
if (!handler.addPropertyDefinition(literal, propname, propexpr))
return null();
}
else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) {
} else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) {
/*
* Support, e.g., |var {x, y} = o| as destructuring shorthand
* for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
@ -7319,10 +7316,10 @@ Parser<ParseHandler>::objectLiteral()
propname = newName(name);
if (!propname)
return null();
if (!handler.addShorthandPropertyDefinition(literal, propname))
Node ident = identifierName();
if (!handler.addPropertyDefinition(literal, propname, ident, true))
return null();
}
else {
} else {
report(ParseError, false, null(), JSMSG_COLON_AFTER_ID);
return null();
}

View File

@ -123,8 +123,7 @@ class SyntaxParseHandler
Node newObjectLiteral(uint32_t begin) { return NodeGeneric; }
bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
bool addShorthandPropertyDefinition(Node literal, Node name) { return true; }
bool addPropertyDefinition(Node literal, Node name, Node expr, bool isShorthand = false) { return true; }
bool addAccessorPropertyDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
// Statements

View File

@ -0,0 +1,90 @@
load(libdir + 'asserts.js');
// globals:
a = b = get = set = eval = arguments = 10;
assertEq({arguments}.arguments, 10);
var o = {a, b: b, get, set: set};
assertEq(o.a, 10);
assertEq(o.b, 10);
assertEq(o.get, 10);
assertEq(o.set, 10);
var names = ['a', 'get', 'set', 'eval'];
// global
names.forEach(ident =>
assertEq(new Function('return {' + ident + '}.' + ident + ';')(), 10));
// local
names.forEach(ident =>
assertEq(new Function('var ' + ident + ' = 20; return {' + ident + '}.' + ident + ';')(), 20));
// scope
names.forEach(ident =>
assertEq(new Function('var ' + ident + ' = 30; return (function () {return {' + ident + '}.' + ident + ';})();')(), 30));
var reserved = [
'break',
'do',
'in',
'typeof',
'case',
'else',
'instanceof',
'var',
'catch',
'export',
'new',
'void',
'class',
'extends',
'return',
'while',
'const',
'finally',
'super',
'with',
'continue',
'for',
'switch',
'debugger',
'function',
'this',
'delete',
'import',
'try',
'enum',
'null',
'true',
'false'
];
// non-identifiers should also throw
var nonidents = [
'"str"',
'0'
];
reserved.concat(nonidents).forEach(ident =>
assertThrowsInstanceOf(() => new Function('return {' + ident + '}'), SyntaxError));
var reservedStrict = [
'implements',
'interface',
'package',
'private',
'protected',
'public',
'static',
// XXX: according to 12.1.1, these should only be errors in strict code:
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1032150
//'let',
//'yield'
];
reservedStrict.forEach(ident =>
assertEq(new Function('var ' + ident + ' = 10; return {' + ident + '}.' + ident + ';')(), 10));
reservedStrict.concat(['let', 'yield']).forEach(ident =>
assertThrowsInstanceOf(() => new Function('"use strict"; return {' + ident + '}'), SyntaxError));

View File

@ -1,4 +1,4 @@
// |jit-test| dump-bytecode;error:SyntaxError
// |jit-test| dump-bytecode
(function() {
const x = ((function() {

View File

@ -275,7 +275,7 @@ FunctionStatementList(ParseNode *fn)
static inline bool
IsNormalObjectField(ExclusiveContext *cx, ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_COLON));
JS_ASSERT(pn->isKind(PNK_COLON) || pn->isKind(PNK_SHORTHAND));
return pn->getOp() == JSOP_INITPROP &&
BinaryLeft(pn)->isKind(PNK_NAME) &&
BinaryLeft(pn)->name() != cx->names().proto;
@ -291,7 +291,7 @@ ObjectNormalFieldName(ExclusiveContext *cx, ParseNode *pn)
static inline ParseNode *
ObjectFieldInitializer(ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_COLON));
JS_ASSERT(pn->isKind(PNK_COLON) || pn->isKind(PNK_SHORTHAND));
return BinaryRight(pn);
}

View File

@ -274,7 +274,7 @@ MSG_DEF(JSMSG_BAD_DELETE_OPERAND, 220, 0, JSEXN_REFERENCEERR, "invalid delet
MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 221, 0, JSEXN_REFERENCEERR, "invalid increment/decrement operand")
MSG_DEF(JSMSG_UNEXPECTED_TYPE, 222, 2, JSEXN_TYPEERR, "{0} is {1}")
MSG_DEF(JSMSG_LET_DECL_NOT_IN_BLOCK, 223, 0, JSEXN_SYNTAXERR, "let declaration not directly within block")
MSG_DEF(JSMSG_BAD_OBJECT_INIT, 224, 0, JSEXN_SYNTAXERR, "invalid object initializer")
MSG_DEF(JSMSG_UNUSED224, 224, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_CANT_SET_ARRAY_ATTRS, 225, 0, JSEXN_INTERNALERR, "can't set attributes on indexed array properties")
MSG_DEF(JSMSG_EVAL_ARITY, 226, 0, JSEXN_TYPEERR, "eval accepts only one parameter")
MSG_DEF(JSMSG_MISSING_FUN_ARG, 227, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")

View File

@ -532,8 +532,8 @@ class NodeBuilder
bool catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos *pos,
MutableHandleValue dst);
bool propertyInitializer(HandleValue key, HandleValue val, PropKind kind, TokenPos *pos,
MutableHandleValue dst);
bool propertyInitializer(HandleValue key, HandleValue val, PropKind kind, bool isShorthand,
TokenPos *pos, MutableHandleValue dst);
/*
@ -1246,8 +1246,8 @@ NodeBuilder::propertyPattern(HandleValue key, HandleValue patt, TokenPos *pos,
}
bool
NodeBuilder::propertyInitializer(HandleValue key, HandleValue val, PropKind kind, TokenPos *pos,
MutableHandleValue dst)
NodeBuilder::propertyInitializer(HandleValue key, HandleValue val, PropKind kind, bool isShorthand,
TokenPos *pos, MutableHandleValue dst)
{
RootedValue kindName(cx);
if (!atomValue(kind == PROP_INIT
@ -1258,6 +1258,8 @@ NodeBuilder::propertyInitializer(HandleValue key, HandleValue val, PropKind kind
return false;
}
RootedValue isShorthandVal(cx, BooleanValue(isShorthand));
RootedValue cb(cx, callbacks[AST_PROPERTY]);
if (!cb.isNull())
return callback(cb, kindName, key, val, pos, dst);
@ -1266,6 +1268,7 @@ NodeBuilder::propertyInitializer(HandleValue key, HandleValue val, PropKind kind
"key", key,
"value", val,
"kind", kindName,
"shorthand", isShorthandVal,
dst);
}
@ -2802,11 +2805,6 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
case PNK_OBJECT:
{
/* The parser notes any uninitialized properties by setting the PNX_DESTRUCT flag. */
if (pn->pn_xflags & PNX_DESTRUCT) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_OBJECT_INIT);
return false;
}
NodeVector elts(cx);
if (!elts.reserve(pn->pn_count))
return false;
@ -2924,10 +2922,11 @@ ASTSerializer::property(ParseNode *pn, MutableHandleValue dst)
LOCAL_NOT_REACHED("unexpected object-literal property");
}
bool isShorthand = pn->isKind(PNK_SHORTHAND);
RootedValue key(cx), val(cx);
return propertyName(pn->pn_left, &key) &&
expression(pn->pn_right, &val) &&
builder.propertyInitializer(key, val, kind, &pn->pn_pos, dst);
builder.propertyInitializer(key, val, kind, isShorthand, &pn->pn_pos, dst);
}
bool

View File

@ -343,6 +343,8 @@ assertExpr("[1,(2,3)]", arrExpr([lit(1),seqExpr([lit(2),lit(3)])]));
assertExpr("[,(2,3)]", arrExpr([null,seqExpr([lit(2),lit(3)])]));
assertExpr("({})", objExpr([]));
assertExpr("({x:1})", objExpr([{ key: ident("x"), value: lit(1) }]));
assertExpr("({x:x, y})", objExpr([{ key: ident("x"), value: ident("x"), shorthand: false },
{ key: ident("y"), value: ident("y"), shorthand: true }]));
assertExpr("({x:1, y:2})", objExpr([{ key: ident("x"), value: lit(1) },
{ key: ident("y"), value: lit(2) } ]));
assertExpr("({x:1, y:2, z:3})", objExpr([{ key: ident("x"), value: lit(1) },
@ -999,13 +1001,6 @@ try {
if (!thrown)
throw new Error("builder exception not propagated");
// Missing property RHS's in an object literal should throw.
try {
Reflect.parse("({foo})");
throw new Error("object literal missing property RHS didn't throw");
} catch (e if e instanceof SyntaxError) { }
// A simple proof-of-concept that the builder API can be used to generate other
// formats, such as JsonMLAst:
//

View File

@ -7,13 +7,7 @@
*/
// Bug 696109 - fixed a precedence bug in with/while nodes
try {
Reflect.parse("with({foo})bar");
throw new Error("supposed to be a syntax error");
} catch (e if e instanceof SyntaxError) { }
try {
Reflect.parse("while({foo})bar");
throw new Error("supposed to be a syntax error");
} catch (e if e instanceof SyntaxError) { }
Reflect.parse("with({foo})bar");
Reflect.parse("while({foo})bar");
reportCompare(true, true);