Bug 875002 - Allow shorthand properties in object literals; r=jorendorff
authorArpad Borsos <arpad.borsos@googlemail.com>
Sat, 07 Jun 2014 22:29:26 +0200
changeset 192058 65a0003423904d352823c9d89e99aefbf1a9ac3b
parent 192057 645a7aeef10215bd82d701c083956b88ab163fd3
child 192059 73db6acaf49f853cc7438ae88eb28b6a1818bf3a
push id45740
push userarpad.borsos@googlemail.com
push dateThu, 03 Jul 2014 07:24:20 +0000
treeherdermozilla-inbound@73db6acaf49f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs875002
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 875002 - Allow shorthand properties in object literals; r=jorendorff
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/NameFunctions.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/SyntaxParseHandler.h
js/src/jit-test/tests/basic/object-shorthand.js
js/src/jit-test/tests/basic/testBug775807.js
js/src/jit/AsmJS.cpp
js/src/js.msg
js/src/jsreflect.cpp
js/src/tests/js1_8_5/extensions/reflect-parse.js
js/src/tests/js1_8_5/extensions/regress-696109.js
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3213,17 +3213,17 @@ EmitDestructuringOpsHelper(ExclusiveCont
          */
         doElemOp = true;
         if (pn->isKind(PNK_ARRAY)) {
             if (!EmitNumberOp(cx, index, bce))
                 return false;
             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)) {
                 if (!EmitNumberOp(cx, key->pn_dval, bce))
                     return false;
             } else {
                 MOZ_ASSERT(key->isKind(PNK_STRING) || key->isKind(PNK_NAME));
                 PropertyName *name = key->pn_atom->asPropertyName();
@@ -5918,21 +5918,16 @@ EmitConditionalExpression(ExclusiveConte
 
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
  * the comment on EmitSwitch.
  */
 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);
 
     /*
      * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
      * a new object and defining (in source order) each property on the object
      * (or mutating the object's [[Prototype]], in the case of __proto__).
      */
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -247,30 +247,29 @@ class FullParseHandler
     ParseNode *newObjectLiteral(uint32_t begin) {
         ParseNode *literal = new_<ListNode>(PNK_OBJECT, TokenPos(begin, begin + 1));
         // Later in this stack: remove dependency on this opcode.
         if (literal)
             literal->setOp(JSOP_NEWINIT);
         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));
         literal->pn_xflags |= PNX_NONCONST;
 
         ParseNode *propdef = newBinary(PNK_COLON, name, fn, op);
         if (!propdef)
             return false;
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -145,18 +145,19 @@ class NameResolver
                         /* Don't skip too high in the tree */
                         break;
                     }
                     cur = parents[tmp];
                 }
                 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--;
                 /* fallthrough */
 
               default:
                 /* Save any other nodes we encounter on the way up. */
                 JS_ASSERT(*size < MaxParents);
@@ -216,17 +217,17 @@ class NameResolver
         /*
          * Other than the actual assignment, other relevant nodes to naming are
          * those in object initializers and then particular nodes marking a
          * contribution.
          */
         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))
                         return false;
                 } else if (left->isKind(PNK_NUMBER)) {
                     if (!appendNumericPropertyReference(left->pn_dval))
                         return false;
                 }
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -455,26 +455,26 @@ Parser<FullParseHandler>::cloneLeftHandS
 
     if (opn->isArity(PN_LIST)) {
         JS_ASSERT(opn->isKind(PNK_ARRAY) || opn->isKind(PNK_OBJECT));
         pn->makeEmpty();
         for (ParseNode *opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) {
             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)
                     return nullptr;
                 ParseNode *target = cloneLeftHandSide(opn2->pn_right);
                 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);
             } else {
                 pn2 = cloneLeftHandSide(opn2);
             }
 
             if (!pn2)
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -68,16 +68,17 @@ class UpvarCookie
 };
 
 #define FOR_EACH_PARSE_NODE_KIND(F) \
     F(NOP) \
     F(SEMI) \
     F(COMMA) \
     F(CONDITIONAL) \
     F(COLON) \
+    F(SHORTHAND) \
     F(POS) \
     F(NEG) \
     F(PREINCREMENT) \
     F(POSTINCREMENT) \
     F(PREDECREMENT) \
     F(POSTDECREMENT) \
     F(DOT) \
     F(ELEM) \
@@ -383,19 +384,18 @@ enum ParseNodeKind
  *                          in the desugaring of a generator-expression.
  * PNK_ARRAY    list        pn_head: list of pn_count array element exprs
  *                          [,,] holes are represented by PNK_ELISION nodes
  *                          pn_xflags: PN_ENDCOMMA if extra comma at end
  * PNK_OBJECT   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_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
  *                          with pn_cookie telling (staticLevel, slot) (see
  *                          jsscript.h's UPVAR macros) and pn_dflags telling
  *                          const-ness and static analysis results
  * PNK_REGEXP   nullary     pn_objbox: RegExp model object
  * PNK_NAME     name        If pn_used, PNK_NAME uses the lexdef member instead
@@ -672,22 +672,18 @@ class ParseNode
 #define PND_USE2DEF_FLAGS (PND_ASSIGNED | PND_CLOSED)
 
 /* PN_LIST pn_xflags bits. */
 #define PNX_POPVAR      0x01            /* PNK_VAR or PNK_CONST last result
                                            needs popping */
 #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 */
 #define PNX_NONCONST    0x40            /* initialiser has non-constants */
 
     static_assert(PNX_NONCONST < (1 << NumListFlagBits), "Not enough bits");
 
     unsigned frameLevel() const {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -722,17 +722,17 @@ HasFinalReturn(ParseNode *pn)
         return ENDS_IN_BREAK;
 
       case PNK_WITH:
         return HasFinalReturn(pn->pn_right);
 
       case PNK_RETURN:
         return ENDS_IN_RETURN;
 
-      case PNK_COLON:
+      case PNK_LABEL:
       case PNK_LEXICALSCOPE:
         return HasFinalReturn(pn->expr());
 
       case PNK_THROW:
         return ENDS_IN_RETURN;
 
       case PNK_TRY:
         /* If we have a finally block that returns, we are done. */
@@ -3189,17 +3189,17 @@ Parser<FullParseHandler>::checkDestructu
                 }
                 if (!ok)
                     return false;
             }
         }
     } 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)) {
                 ok = checkDestructuring(data, expr, false);
             } else if (data) {
                 if (!expr->isKind(PNK_NAME)) {
                     report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME);
                     return false;
@@ -4215,31 +4215,29 @@ Parser<FullParseHandler>::forStatement()
              * expressions involving an 'in' operator are illegal in the init
              * clause of an ordinary for loop.
              */
             pc->parsingForInit = true;
             if (tt == TOK_VAR || tt == TOK_CONST) {
                 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) {
                     pn1 = letBlock(LetExpresion);
                 } else {
                     isForDecl = true;
                     blockObj = StaticBlockObject::create(context);
                     if (!blockObj)
                         return null();
                     pn1 = variables(PNK_LET, nullptr, blockObj, DontHoistVars);
                 }
-            }
-            else {
+            } else {
                 pn1 = expr();
             }
             pc->parsingForInit = false;
             if (!pn1)
                 return null();
         }
     }
 
@@ -7298,36 +7296,35 @@ Parser<ParseHandler>::objectLiteral()
                  * so that we can later assume singleton objects delegate to
                  * the default Object.prototype.
                  */
                 if (!handler.isConstant(propexpr) || atom == context->names().proto)
                     handler.setListFlag(literal, PNX_NONCONST);
 
                 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.
                  */
                 if (!abortIfSyntaxParser())
                     return null();
                 tokenStream.ungetToken();
                 if (!tokenStream.checkForKeyword(atom, nullptr))
                     return null();
                 PropertyName *name = handler.isName(propname);
                 JS_ASSERT(atom);
                 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();
             }
         } else {
             /* NB: Getter function in { get x(){} } is unnamed. */
             Rooted<PropertyName*> funName(context, nullptr);
             TokenStream::Position start(keepAtoms);
             tokenStream.tell(&start);
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -118,18 +118,17 @@ class SyntaxParseHandler
     }
     Node newArrayLiteral(uint32_t begin, unsigned blockid) { return NodeGeneric; }
     bool addElision(Node literal, const TokenPos &pos) { return true; }
     bool addSpreadElement(Node literal, uint32_t begin, Node inner) { return true; }
     bool addArrayElement(Node literal, Node element) { return true; }
 
     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
 
     Node newStatementList(unsigned blockid, const TokenPos &pos) { return NodeGeneric; }
     void addStatementToList(Node list, Node stmt, ParseContext<SyntaxParseHandler> *pc) {}
     Node newEmptyStatement(const TokenPos &pos) { return NodeGeneric; }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/object-shorthand.js
@@ -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));
+
--- a/js/src/jit-test/tests/basic/testBug775807.js
+++ b/js/src/jit-test/tests/basic/testBug775807.js
@@ -1,9 +1,9 @@
-// |jit-test| dump-bytecode;error:SyntaxError
+// |jit-test| dump-bytecode
 
 (function() {
     const x = ((function() {
         return {
             e: function() {
                 (function() {
                     for (e in x) {}
                 })()
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -270,33 +270,33 @@ FunctionStatementList(ParseNode *fn)
     ParseNode *last = fn->pn_body->last();
     JS_ASSERT(last->isKind(PNK_STATEMENTLIST));
     return last;
 }
 
 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;
 }
 
 static inline PropertyName *
 ObjectNormalFieldName(ExclusiveContext *cx, ParseNode *pn)
 {
     JS_ASSERT(IsNormalObjectField(cx, pn));
     return BinaryLeft(pn)->name();
 }
 
 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);
 }
 
 static inline bool
 IsDefinition(ParseNode *pn)
 {
     return pn->isKind(PNK_NAME) && pn->isDefn();
 }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -269,17 +269,17 @@ MSG_DEF(JSMSG_BAD_GENERATOR_SYNTAX,   21
 MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE,    216, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side")
 MSG_DEF(JSMSG_LET_COMP_BINDING,       217, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
 MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE,     218, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value")
 MSG_DEF(JSMSG_BAD_SYMBOL,             219, 1, JSEXN_TYPEERR, "{0} is not a well-known @@-symbol")
 MSG_DEF(JSMSG_BAD_DELETE_OPERAND,     220, 0, JSEXN_REFERENCEERR, "invalid delete operand")
 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}")
 MSG_DEF(JSMSG_JSON_BAD_PARSE,         228, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")
 MSG_DEF(JSMSG_JSON_BAD_STRINGIFY,     229, 0, JSEXN_ERR, "JSON.stringify")
 MSG_DEF(JSMSG_NOT_CALLABLE_OR_UNDEFINED, 230, 0, JSEXN_TYPEERR, "value is not a function or undefined")
 MSG_DEF(JSMSG_NOT_NONNULL_OBJECT,     231, 0, JSEXN_TYPEERR, "value is not a non-null object")
 MSG_DEF(JSMSG_DEPRECATED_OCTAL,       232, 0, JSEXN_SYNTAXERR, "octal literals and octal escape sequences are deprecated")
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -527,18 +527,18 @@ class NodeBuilder
     bool variableDeclarator(HandleValue id, HandleValue init, TokenPos *pos,
                             MutableHandleValue dst);
 
     bool switchCase(HandleValue expr, NodeVector &elts, TokenPos *pos, MutableHandleValue dst);
 
     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);
 
 
     /*
      * statements
      */
 
     bool blockStatement(NodeVector &elts, TokenPos *pos, MutableHandleValue dst);
 
@@ -1241,36 +1241,39 @@ NodeBuilder::propertyPattern(HandleValue
     return newNode(AST_PROP_PATT, pos,
                    "key", key,
                    "value", patt,
                    "kind", kindName,
                    dst);
 }
 
 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
                    ? "init"
                    : kind == PROP_GETTER
                    ? "get"
                    : "set", &kindName)) {
         return false;
     }
 
+    RootedValue isShorthandVal(cx, BooleanValue(isShorthand));
+
     RootedValue cb(cx, callbacks[AST_PROPERTY]);
     if (!cb.isNull())
         return callback(cb, kindName, key, val, pos, dst);
 
     return newNode(AST_PROPERTY, pos,
                    "key", key,
                    "value", val,
                    "kind", kindName,
+                   "shorthand", isShorthandVal,
                    dst);
 }
 
 bool
 NodeBuilder::objectExpression(NodeVector &elts, TokenPos *pos, MutableHandleValue dst)
 {
     return listNode(AST_OBJECT_EXPR, "properties", elts, pos, dst);
 }
@@ -2797,21 +2800,16 @@ ASTSerializer::expression(ParseNode *pn,
       {
           RootedValue expr(cx);
           return expression(pn->pn_kid, &expr) &&
                  builder.spreadExpression(expr, &pn->pn_pos, 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;
 
         for (ParseNode *next = pn->pn_head; next; next = next->pn_next) {
             JS_ASSERT(pn->pn_pos.encloses(next->pn_pos));
 
             RootedValue prop(cx);
@@ -2919,20 +2917,21 @@ ASTSerializer::property(ParseNode *pn, M
       case JSOP_INITPROP_SETTER:
         kind = PROP_SETTER;
         break;
 
       default:
         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
 ASTSerializer::literal(ParseNode *pn, MutableHandleValue dst)
 {
     RootedValue val(cx);
     switch (pn->getKind()) {
 #ifdef JS_HAS_TEMPLATE_STRINGS
--- a/js/src/tests/js1_8_5/extensions/reflect-parse.js
+++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js
@@ -338,16 +338,18 @@ assertExpr("[,,,1,2,3,,]", arrExpr([null
 assertExpr("[,,,1,2,3,,,]", arrExpr([null,null,null,lit(1),lit(2),lit(3),null,null]));
 assertExpr("[,,,,,]", arrExpr([null,null,null,null,null]));
 assertExpr("[1, ...a, 2]", arrExpr([lit(1), spread(ident("a")), lit(2)]));
 assertExpr("[,, ...a,, ...b, 42]", arrExpr([null,null, spread(ident("a")),, spread(ident("b")), lit(42)]));
 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) },
                                          { key: ident("y"), value: lit(2) },
                                          { key: ident("z"), value: lit(3) } ]));
 assertExpr("({x:1, 'y':2, z:3})", objExpr([{ key: ident("x"), value: lit(1) },
                                            { key: lit("y"), value: lit(2) },
                                            { key: ident("z"), value: lit(3) } ]));
@@ -994,23 +996,16 @@ var thrown = false;
 try {
     Reflect.parse("42", { builder: { program: function() { throw "expected" } } });
 } catch (e if e === "expected") {
     thrown = true;
 }
 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:
 // 
 //     http://code.google.com/p/es-lab/wiki/JsonMLASTFormat
 // 
 // It's incomplete (e.g., it doesn't convert source-location information and
 // doesn't use all the direct-eval rules), but I think it proves the point.
 
--- a/js/src/tests/js1_8_5/extensions/regress-696109.js
+++ b/js/src/tests/js1_8_5/extensions/regress-696109.js
@@ -2,18 +2,12 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  * Contributor: Dave Herman <dherman@mozilla.com>
  */
 
 // 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);