Respect quoting for object literals in JSNI code.

Previously object literals where emmitted in the shortest possible manner,
potentially becoming incosistent if they where used with Closure js compiler.

Now labels in object literals are emmitted in the same manner they are
defined.

Change-Id: Ie4620adb0e76a3225635f5e1fe4357bc0ab8cf87
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index fc83adf..19e322b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -1762,7 +1762,7 @@
       if (castMap != null && castableTypeMapName != null) {
         return transform(castMap);
       }
-      return new JsObjectLiteral(SourceOrigin.UNKNOWN);
+      return JsObjectLiteral.EMPTY;
     }
 
     private JField getClassLiteralField(JType type) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index 274627a..1e4e0a4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -346,11 +346,11 @@
       }
 
       @Override
-      public void resolve(JsNameRef x) {
-        if (x.getQualifier() != null) {
-          return;
-        }
-        // Only resolve unqualified names
+      public void resolveQualifiedName(JsNameRef x) {
+      }
+
+      @Override
+      protected void resolveUnqualifiedName(JsNameRef x) {
         JsName name = getScope().findExistingName(x.getIdent());
 
         // Ensure that we're resolving a name from the function's parameters
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java b/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java
index 28823ad..91d4839 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java
@@ -256,7 +256,7 @@
     jslink = jsProgram.getScope().declareName("jslink");
     JsVars vars = new JsVars(info);
     JsVar var = new JsVar(info, jslink);
-    var.setInitExpr(new JsObjectLiteral(info));
+    var.setInitExpr(JsObjectLiteral.EMPTY);
     vars.add(var);
     jsProgram.getFragmentBlock(0).getStatements().add(0, vars);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
index 2b6862e..8f46b2a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
@@ -56,7 +56,7 @@
 import com.google.gwt.dev.js.ast.JsBooleanLiteral;
 import com.google.gwt.dev.js.ast.JsExpression;
 import com.google.gwt.dev.js.ast.JsLiteral;
-import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsNullLiteral;
 import com.google.gwt.dev.js.ast.JsNumberLiteral;
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
@@ -507,14 +507,14 @@
         if (values.length == 1) {
           return new JsNumberLiteral(literal.getSourceInfo(), ((JLongLiteral) literal).getValue());
         }
-        JsObjectLiteral objectLiteral = new JsObjectLiteral(sourceInfo);
-        objectLiteral.setInternable();
+        JsObjectLiteral.Builder objectLiteralBuilder = JsObjectLiteral.builder(sourceInfo)
+            .setInternable();
 
-        assert values.length == names.length;
-        for (int i = 0; i < names.length; i++) {
-          addPropertyToObject(sourceInfo, names[i], values[i], objectLiteral);
+        assert values.length == longComponentNames.length;
+        for (int i = 0; i < longComponentNames.length; i++) {
+          addPropertyToObject(sourceInfo, longComponentNames[i], values[i], objectLiteralBuilder);
         }
-        return objectLiteral;
+        return objectLiteralBuilder.build();
       }
     },
     STRING_LITERAL_TRANSLATOR() {
@@ -530,29 +530,15 @@
       }
     };
 
-    private static final JsName[] names;
-
-    static {
-      // The names of the components in an emulated long ('l', 'm', and 'h') are accessed directly
-      // through JSNI in LongLib (the implementor of emulated long operations), hence it is
-      // important that they don't get renamed hence the corresponding JsNames are created
-      // unscoped (null scope) and unobfuscatable.
-      String[] stringNames = {"l","m","h"};
-      names = new JsName[stringNames.length];
-      for (int i = 0; i < stringNames.length; i++) {
-        names[i] = new JsName(null, stringNames[i], stringNames[i]);
-        names[i].setUnobfuscatable();
-      }
-    }
+    private static String[] longComponentNames = { "l", "m", "h" };
 
     abstract JsLiteral translate(JExpression literal);
   }
 
-  private static void addPropertyToObject(SourceInfo sourceInfo, JsName propertyName,
-      long propertyValue, JsObjectLiteral objectLiteral) {
-    JsExpression label = propertyName.makeRef(sourceInfo);
+  private static void addPropertyToObject(SourceInfo sourceInfo, String propertyName,
+      long propertyValue, JsObjectLiteral.Builder objectLiteralBuilder) {
     JsExpression value = new JsNumberLiteral(sourceInfo, propertyValue);
-    objectLiteral.addProperty(sourceInfo, label, value);
+    objectLiteralBuilder.add(new JsNameRef(sourceInfo, propertyName), value);
   }
 
   private static JMethod createEmptyMethodFromExample(
diff --git a/dev/core/src/com/google/gwt/dev/js/ClosureJsAstTranslator.java b/dev/core/src/com/google/gwt/dev/js/ClosureJsAstTranslator.java
index 07874f2..db03d31 100644
--- a/dev/core/src/com/google/gwt/dev/js/ClosureJsAstTranslator.java
+++ b/dev/core/src/com/google/gwt/dev/js/ClosureJsAstTranslator.java
@@ -610,17 +610,16 @@
   private Node transform(JsObjectLiteral x) {
     Node n = IR.objectlit();
 
-    for (Object element : x.getPropertyInitializers()) {
-      JsPropertyInitializer propInit = (JsPropertyInitializer) element;
+    for (JsPropertyInitializer element : x.getPropertyInitializers()) {
       Node key;
-      if (propInit.getLabelExpr().getKind() == NodeKind.NUMBER) {
-        key = transformNumberAsString((JsNumberLiteral) propInit.getLabelExpr());
+      if (element.getLabelExpr().getKind() == NodeKind.NUMBER) {
+        key = transformNumberAsString((JsNumberLiteral) element.getLabelExpr());
         key.putBooleanProp(Node.QUOTED_PROP, true);
-      } else if (propInit.getLabelExpr().getKind() == NodeKind.NAME_REF) {
-        key = transformNameAsString(((JsNameRef) propInit.getLabelExpr()).getShortIdent(),
-            propInit.getLabelExpr());
+      } else if (element.getLabelExpr().getKind() == NodeKind.NAME_REF) {
+        key = transformNameAsString(((JsNameRef) element.getLabelExpr()).getShortIdent(),
+            element.getLabelExpr());
       } else {
-        key = transform(propInit.getLabelExpr());
+        key = transform(element.getLabelExpr());
       }
       Preconditions.checkState(key.isString(), key);
       key.setType(Token.STRING_KEY);
@@ -630,7 +629,7 @@
       // obfuscatable.
       // TODO(rluble): Make sure this is handled correctly once rhino is upgraded.
       key.putBooleanProp(Node.QUOTED_PROP, true);
-      n.addChildToBack(IR.propdef(key, transform(propInit.getValueExpr())));
+      n.addChildToBack(IR.propdef(key, transform(element.getValueExpr())));
     }
     return applySourceInfo(n, x);
   }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java b/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java
index baa8754..d43ade2 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsAbstractSymbolResolver.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.js.ast.JsFunction;
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsPropertyInitializer;
 import com.google.gwt.dev.js.ast.JsScope;
 import com.google.gwt.dev.js.ast.JsVisitor;
 import com.google.gwt.dev.util.collect.Stack;
@@ -47,7 +48,11 @@
       return;
     }
 
-    resolve(x);
+    if (x.getQualifier() != null) {
+      resolveQualifiedName(x);
+    } else {
+      resolveUnqualifiedName(x);
+    }
   }
 
   @Override
@@ -73,11 +78,24 @@
     return true;
   }
 
+  @Override
+  public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+    if (x.getLabelExpr() instanceof JsNameRef) {
+      // JsNameRefs in labels of object literals are considered qualified names even though they
+      // are created with no qualifier, matching their use as regular object property names.
+      resolveQualifiedName((JsNameRef) x.getLabelExpr());
+    }
+    accept(x.getValueExpr());
+    return false;
+  }
+
   protected JsScope getScope() {
     return scopeStack.peek();
   }
 
-  protected abstract void resolve(JsNameRef x);
+  protected abstract void resolveQualifiedName(JsNameRef x);
+
+  protected abstract void resolveUnqualifiedName(JsNameRef x);
 
   private void popScope() {
     scopeStack.pop();
diff --git a/dev/core/src/com/google/gwt/dev/js/JsHoister.java b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
index b3ad728..2dfb9b4 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsHoister.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
@@ -180,34 +180,28 @@
     }
 
     @Override
-    public void endVisit(JsObjectLiteral x, JsContext ctx) {
-      JsObjectLiteral toReturn = new JsObjectLiteral(x.getSourceInfo());
-      List<JsPropertyInitializer> inits = toReturn.getPropertyInitializers();
+    public boolean visit(JsObjectLiteral x, JsContext ctx) {
+      JsObjectLiteral.Builder builder = JsObjectLiteral.builder(x.getSourceInfo());
 
-      int size = x.getPropertyInitializers().size();
       if (x.isInternable()) {
-        toReturn.setInternable();
+        builder.setInternable();
       }
 
-      while (size-- > 0) {
+      for (JsPropertyInitializer propertyInitializer : x.getPropertyInitializers()) {
         /*
          * JsPropertyInitializers are the only non-JsExpression objects that we
          * care about, so we just go ahead and create the objects in the loop,
          * rather than expecting it to be on the stack and having to perform
          * narrowing casts at all stack.pop() invocations.
          */
-        JsPropertyInitializer newInit = new JsPropertyInitializer(
-            x.getSourceInfo());
-        newInit.setValueExpr(stack.pop());
-        if (successful) {
-          newInit.setLabelExpr(stack.pop());
-        } else {
-          stack.pop();
-        }
-
-        inits.add(0, newInit);
+        accept(propertyInitializer.getLabelExpr());
+        JsExpression label = stack.pop();
+        accept(propertyInitializer.getValueExpr());
+        JsExpression value = stack.pop();
+        builder.add(propertyInitializer.getSourceInfo(), label, value);
       }
-      stack.push(toReturn);
+      stack.push(builder.build());
+      return false;
     }
 
     @Override
diff --git a/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java b/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java
index 0c47a8e..1bbb7ce 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java
@@ -168,7 +168,7 @@
     List<JsStatement> inits = Lists.newArrayList();
     for (JsName name : namespaces) {
       JsVar var = new JsVar(SourceOrigin.UNKNOWN, name);
-      var.setInitExpr(new JsObjectLiteral(SourceOrigin.UNKNOWN));
+      var.setInitExpr(JsObjectLiteral.EMPTY);
       JsVars vars = new JsVars(SourceOrigin.UNKNOWN);
       vars.add(var);
       inits.add(vars);
diff --git a/dev/core/src/com/google/gwt/dev/js/JsParser.java b/dev/core/src/com/google/gwt/dev/js/JsParser.java
index 6e0773c..b2af735 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsParser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsParser.java
@@ -317,7 +317,7 @@
         return mapContinue(node);
 
       case TokenStream.OBJLIT:
-        return mapObjectLit(node);
+        return mapObjectLiteral(node);
 
       case TokenStream.ARRAYLIT:
         return mapArrayLit(node);
@@ -843,34 +843,28 @@
         numberNode.getDouble());
   }
 
-  private JsExpression mapObjectLit(Node objLitNode) throws JsParserException {
+  private JsExpression mapObjectLiteral(Node objectLiteralNode) throws JsParserException {
     JsObjectLiteral.Builder objectLiteralBuilder =
-        JsObjectLiteral.builder(makeSourceInfo(objLitNode));
-    Node fromPropInit = objLitNode.getFirstChild();
-    while (fromPropInit != null) {
+        JsObjectLiteral.builder(makeSourceInfo(objectLiteralNode));
+    for (Node propertyComponent = objectLiteralNode.getFirstChild(); propertyComponent != null;
+        propertyComponent = propertyComponent.getNext()) {
 
-      Node fromLabelExpr = fromPropInit;
-      JsExpression toLabelExpr = mapExpression(fromLabelExpr);
+      // get the property label from the first node.
+      JsExpression labelExpression = mapExpression(propertyComponent);
 
-      // Advance to the initializer expression.
-      //
-      fromPropInit = fromPropInit.getNext();
-      Node fromValueExpr = fromPropInit;
-      if (fromValueExpr == null) {
+      // advance to the value node
+      propertyComponent = propertyComponent.getNext();
+
+      Node valueNode = propertyComponent;
+      if (valueNode == null) {
         throw createParserException("Expected an init expression for: "
-            + toLabelExpr, objLitNode);
+            + labelExpression, objectLiteralNode);
       }
-      JsExpression toValueExpr = mapExpression(fromValueExpr);
-      objectLiteralBuilder.add(makeSourceInfo(fromLabelExpr), toLabelExpr, toValueExpr);
-
-      // Begin the next property initializer, if there is one.
-      //
-      fromPropInit = fromPropInit.getNext();
+      objectLiteralBuilder.add(
+          labelExpression.getSourceInfo(), labelExpression, mapExpression(valueNode));
     }
-
     return objectLiteralBuilder.build();
   }
-
   private JsExpression mapOptionalExpression(Node exprNode)
       throws JsParserException {
     JsNode unknown = map(exprNode);
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java b/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
index 67d4970..7236c95 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
@@ -30,21 +30,25 @@
   private class JsResolveSymbolsVisitor extends JsAbstractSymbolResolver {
 
     @Override
-    protected void resolve(JsNameRef x) {
-      JsName name;
+    protected void resolveQualifiedName(JsNameRef x) {
       String ident = x.getIdent();
-      if (x.getQualifier() == null) {
-        name = getScope().findExistingName(ident);
-        if (name == null) {
-          // No clue what this is; create a new unobfuscatable name
-          name = program.getScope().declareUnobfuscatableName(ident);
-        }
-      } else {
-        name = program.getObjectScope().findExistingName(ident);
-        if (name == null) {
-          // No clue what this is; create a new unobfuscatable name
-          name = program.getObjectScope().declareUnobfuscatableName(ident);
-        }
+      JsName name = program.getObjectScope().findExistingName(ident);
+      if (name == null) {
+        // No clue what this is; create a new unobfuscatable name
+        name = program.getObjectScope().declareUnobfuscatableName(ident);
+      } else if (name.isObfuscatable()) {
+        name.setUnobfuscatable();
+      }
+      x.resolve(name);
+    }
+
+    @Override
+    protected void resolveUnqualifiedName(JsNameRef x) {
+      String ident = x.getIdent();
+      JsName name = getScope().findExistingName(ident);
+      if (name == null) {
+        // No clue what this is; create a new unobfuscatable name
+        name = program.getScope().declareUnobfuscatableName(ident);
       }
       x.resolve(name);
     }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index 1f18c16..92ca587 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -706,24 +706,11 @@
   public boolean visit(JsObjectLiteral x, JsContext ctx) {
     _lbrace();
     boolean sep = false;
-    for (Object element : x.getPropertyInitializers()) {
+    for (JsPropertyInitializer element : x.getPropertyInitializers()) {
       sep = _sepCommaOptSpace(sep);
-      JsPropertyInitializer propInit = (JsPropertyInitializer) element;
-      printLabel : {
-        JsExpression labelExpr = propInit.getLabelExpr();
-        // labels can be either string, integral, or decimal literals
-        if (labelExpr instanceof JsStringLiteral) {
-          String propName = ((JsStringLiteral) labelExpr).getValue();
-          if (JsUtils.isValidJsIdentifier(propName) && !JsProtectedNames.isKeyword(propName)) {
-            // Print unquoted if the property name is a valid identifier.
-            p.print(propName);
-            break printLabel;
-          }
-        }
-        accept(labelExpr);
-      }
+      accept(element.getLabelExpr());
       _colon();
-      JsExpression valueExpr = propInit.getValueExpr();
+      JsExpression valueExpr = element.getValueExpr();
       _parenPushIfCommaExpr(valueExpr);
       accept(valueExpr);
       _parenPopIfCommaExpr(valueExpr);
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java
index 77af6cb..71b1256 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java
@@ -14,8 +14,10 @@
 package com.google.gwt.dev.js.ast;
 
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -41,7 +43,7 @@
     private boolean internable = false;
 
     public Builder add(String property, JsExpression value) {
-      add(new JsNameRef(sourceInfo, property), value);
+      add(new JsStringLiteral(sourceInfo, property), value);
       return this;
     }
 
@@ -62,7 +64,7 @@
 
     public JsObjectLiteral build() {
       JsObjectLiteral objectLiteral = new JsObjectLiteral(sourceInfo);
-      objectLiteral.getPropertyInitializers().addAll(propertyInitializers);
+      objectLiteral.properties.addAll(propertyInitializers);
       if (internable) {
         objectLiteral.setInternable();
       }
@@ -70,32 +72,16 @@
     }
   }
 
+  public static final JsObjectLiteral EMPTY = new JsObjectLiteral(SourceOrigin.UNKNOWN);
+
   private final List<JsPropertyInitializer> properties = Lists.newArrayList();
 
   private boolean internable = false;
 
-  public JsObjectLiteral(SourceInfo sourceInfo) {
+  private JsObjectLiteral(SourceInfo sourceInfo) {
     super(sourceInfo);
   }
 
-  /**
-   * Adds a property and its initial value to the object literal.
-   * <p>
-   * NOTE: Does not check for duplicate names.
-   */
-  public void addProperty(SourceInfo sourceInfo, JsExpression label, JsExpression value) {
-    properties.add(new JsPropertyInitializer(sourceInfo, label, value));
-  }
-
-  /**
-   * Adds a property and its initial value to the object literal.
-   * <p>
-   * NOTE: Does not check for duplicate names.
-   */
-  public void addProperty(SourceInfo sourceInfo, String label, JsExpression value) {
-    addProperty(sourceInfo, new JsStringLiteral(sourceInfo, label), value);
-  }
-
   @Override
   public boolean equals(Object that) {
     if (that == null || this.getClass() != that.getClass()) {
@@ -111,7 +97,7 @@
   }
 
   public List<JsPropertyInitializer> getPropertyInitializers() {
-    return properties;
+    return Collections.unmodifiableList(properties);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
index 477bde4..77262b2 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -26,6 +26,8 @@
  */
 public final class JsProgram extends JsNode {
 
+  public static final String OBJECT_SCOPE_NAME = "Object";
+  public static final String GLOBAL_SCOPE_NAME = "Global";
   private final CorrelationFactory correlator;
 
   private JsProgramFragment[] fragments;
@@ -46,8 +48,8 @@
 
     this.correlator = correlator;
 
-    topScope = new JsNormalScope(JsRootScope.INSTANCE, "Global");
-    objectScope = new JsNormalScope(JsRootScope.INSTANCE, "Object");
+    topScope = new JsNormalScope(JsRootScope.INSTANCE, GLOBAL_SCOPE_NAME);
+    objectScope = new JsNormalScope(JsRootScope.INSTANCE, OBJECT_SCOPE_NAME);
     setFragmentCount(1);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java b/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java
index 51a14c7..fcea391 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java
@@ -32,10 +32,9 @@
       JsExpression valueExpr) {
     super(sourceInfo);
 
-    assert labelExpr instanceof JsStringLiteral || labelExpr instanceof JsNumberLiteral ||
-        labelExpr instanceof JsNameRef;
     this.labelExpr = labelExpr;
     this.valueExpr = valueExpr;
+    assert isLabelProper();
   }
 
   @Override
@@ -43,8 +42,11 @@
     if (that == null || that.getClass() != this.getClass()) {
       return false;
     }
-    return labelExpr.equals(((JsPropertyInitializer) that).labelExpr) &&
-        valueExpr.equals(((JsPropertyInitializer) that).valueExpr);
+    JsPropertyInitializer thatPropertyInitializer = (JsPropertyInitializer) that;
+
+    assert isLabelProper() && ((JsPropertyInitializer) that).isLabelProper();
+    return areLabelsEqual(labelExpr, thatPropertyInitializer.labelExpr) &&
+        valueExpr.equals(thatPropertyInitializer.valueExpr);
   }
 
   @Override
@@ -62,19 +64,13 @@
 
   @Override
   public int hashCode() {
-    return labelExpr.hashCode() + 17 * valueExpr.hashCode();
+    return labelExpr.toString().hashCode() + 17 * valueExpr.hashCode();
   }
 
   public boolean hasSideEffects() {
     return labelExpr.hasSideEffects() || valueExpr.hasSideEffects();
   }
 
-  public void setLabelExpr(JsExpression labelExpr) {
-    assert labelExpr instanceof JsStringLiteral || labelExpr instanceof JsNumberLiteral ||
-        labelExpr instanceof JsNameRef : labelExpr.toString() + " is not a valid property label";
-    this.labelExpr = labelExpr;
-  }
-
   public void setValueExpr(JsExpression valueExpr) {
     this.valueExpr = valueExpr;
   }
@@ -87,4 +83,27 @@
     }
     v.endVisit(this, ctx);
   }
+
+  private static boolean areLabelsEqual(JsExpression thisLabel, JsExpression thatLabel) {
+    if (thisLabel instanceof JsNameRef && thatLabel instanceof JsNameRef) {
+      JsNameRef thisJsNameRef = (JsNameRef) thisLabel;
+      JsNameRef thatJsNameRef = (JsNameRef) thatLabel;
+      return thisJsNameRef.getIdent().equals(thatJsNameRef.getIdent());
+    }
+    return thisLabel.equals(thatLabel);
+  }
+
+  private boolean isLabelProper() {
+    if (labelExpr instanceof JsStringLiteral || labelExpr instanceof JsNumberLiteral) {
+      return true;
+    }
+
+    if (!(labelExpr instanceof JsNameRef)) {
+      return false;
+    }
+
+    JsNameRef labelJsNameRef = (JsNameRef) labelExpr;
+
+    return labelJsNameRef.getQualifier() == null;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
index 9c4072f..e6fff20 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
@@ -135,6 +135,12 @@
   public abstract List<JsScope> getChildren();
 
   /**
+   * Returns the descriptive name.
+   */
+  public String getDescription() {
+    return description;
+  }
+  /**
    * Returns the parent scope of this scope, or <code>null</code> if this is the root scope.
    */
   public abstract JsScope getParent();
diff --git a/dev/core/src/com/google/gwt/dev/js/rhino/Parser.java b/dev/core/src/com/google/gwt/dev/js/rhino/Parser.java
index e4c90e5..5fb4a7b 100644
--- a/dev/core/src/com/google/gwt/dev/js/rhino/Parser.java
+++ b/dev/core/src/com/google/gwt/dev/js/rhino/Parser.java
@@ -1277,8 +1277,11 @@
 
             tt = ts.getToken();
             switch (tt) {
-              // map NAMEs to STRINGs in object literal context.
               case TokenStream.NAME:
+                String name = ts.getString();
+                sourceAddString(ts.NAME, name);
+                property = nf.createName(ts.getString());
+                break;
               case TokenStream.STRING:
                 String s = ts.getString();
                 sourceAddString(ts.NAME, s);
diff --git a/dev/core/test/com/google/gwt/dev/js/JsAstTest.java b/dev/core/test/com/google/gwt/dev/js/JsAstTest.java
index b54b9ff..b1b212c 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsAstTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsAstTest.java
@@ -36,7 +36,7 @@
     JsArrayLiteral arrayOneTwoLiteral2 = new JsArrayLiteral(sourceInfo, one,two);
     JsArrayLiteral arrayTwoOneLiteral = new JsArrayLiteral(sourceInfo, two, one);
 
-    JsObjectLiteral emptyObject = new JsObjectLiteral(sourceInfo);
+    JsObjectLiteral emptyObject = JsObjectLiteral.EMPTY;
 
     assertEquals(arrayOneTwoLiteral, arrayOneTwoLiteral2);
     assertEquals(arrayOneTwoLiteral2, arrayOneTwoLiteral);
diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
index 7bd9cd5..b91b39e 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
@@ -66,7 +66,8 @@
     // quotes are not necessary around many property variables in object
     // literals
     assertEquals("var x={1:'b'}", parse("var x = {1 : 'b'}"));
-    assertEquals("var x={$a_:'b'}", parse("var x = {'$a_' : 'b'}"));
+    assertEquals("var x={'$a_':'b'}", parse("var x = {'$a_' : 'b'}"));
+    assertEquals("var x={$a_:'b'}", parse("var x = {$a_ : 'b'}"));
     assertEquals("var x={1.2:'b'}", parse("var x = {1.2 : 'b'}"));
   }