Repository: google/closure-compiler
Pull request #805 (2015-01-29 22:10:41)

Add @nocollapse annotation
This isn't ready to merge yet, but since this is a significant change.  Would someone be willing to test this internal to Google? 

This adds the `@nocollapse` annotation which will prevent a property from being completely collapsed. The annotation does NOT block sub-properties from being collapsed.

Example:

```JavaScript
var a = {};
/** @nocollapse */
a.b = {}; // this property will NOT be collapsed
a.b.c = {}; //this property will still be collapsed
```

There are multiple variations using the `@nocollapse` annotation. The unit tests show comprehensive examples.

ChadKillingsworth: I mainly want to make sure this doesn't break existing code.
concavelenz: What is the state of things here?
MatrixFrog: I'll try it out internally and see what breaks.
MatrixFrog: It seems that the script I'm using for converting PRs to internal CLs is unhappy about trailing whitespace. Can you set your editor to automatically remove trailing whitespace on save?
ChadKillingsworth: Done. I recently switched editors and thought it was already set that way.

Also, while I'd like the unit tests to be finished at some point, I see no reason we can't go ahead and get this merged in and documented for people to use (as long as it doesn't break internal code). We can handle any cases where the `@nocollapse` annotation isn't properly honored via issues and patches.
MatrixFrog: Okay, got it patched in and the presubmit is running. I'm not sure if the whitespace was actually throwing it off or that was just a red herring, but it's nice to get rid of it anyway.
MatrixFrog: Seeing some failures with the internal presubmit that may actually be caused by this. I'll look into them.
ChadKillingsworth: That doesn't surprise me. If you can give me a repo case that will help.
MatrixFrog: Got a minimal repro case:

    var FOO = {
      'bar': {
        'baz,qux': {
          'beep': 'xxxxx',
        },
      }
    };
    
    alert(FOO);

If you compile this with the current compiler you get just `'use strict';alert({bar:{"baz,qux":{beep:"xxxxx"}}});` (i.e. no interesting changes) but with this PR I get `'use strict';alert({bar:{}});` -- most of the object literal has been eliminated. It seems to be caused by the fact that the key includes a comma, so (I guess) it can't collapse that part of the object.
ChadKillingsworth: It looks like we handle quoted keys on object literals differently than assignments. https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/CollapsePropertiesTest.java#L437

I think quoted properties should be blocked from collapsing (object literal keys included).

@concavelenz thoughts?
dimvar: > I think quoted properties should be blocked from collapsing (object literal keys included).

If this is independent from the nocollapse annotation, I would rather have only the nocollapse change in this PR, and keep the rest of the behavior the same. It simplifies things.
dimvar: Hey Chad, I'll do this review. I will try to have comments by the end of this week. If not, definitely before our next meeting.
ChadKillingsworth: >  I would rather have only the nocollapse change in this PR, and keep the rest of the behavior the same.

This is taking a bit to figure out. The unit tests don't define this well. I'm still unraveling how this is handled.
ChadKillingsworth: This should be ready to test again.
MatrixFrog: Since you already have it as a separate commit, maybe let's merge it as its own PR (which will also make this one quicker to review)?
ChadKillingsworth: Sure thing.
ChadKillingsworth: That looks better.
dimvar: I did a round of comments, I'll do another one when these are addressed. Also, can you squash all the commits into one? It helps when we pull the change in and test it.
googlebot: Thanks for your pull request.

It looks like this may be your first contribution to a Google open source project, in which case you'll need to sign a Contributor License Agreement (CLA) at <https://cla.developers.google.com/>.

If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check the [information on your CLA](https://cla.developers.google.com/clas) or see this help article on [setting the email on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

Once you've done that, please reply here to let us know.  If you signed the CLA as a corporation, please let us know the company's name.

<!-- need_sender_cla -->
googlebot: CLAs look good, thanks!

<!-- ok -->
ChadKillingsworth: Apparently CLAs need github usernames on file now.

Enum handling is still outstanding, I've responded to the rest of the comments.

I'll squash the commits when I get that one handled.
ChadKillingsworth: `@nocollapse` annotations on enum keys are now ignored. However, there isn't convenient place to issue warnings that these will be ignored. There are comments in the unit tests however.

I've squashed the commits as requested. I still have a local branch pointer to the original commits for my own reference.
ChadKillingsworth: Ready for review/testing again:
 * `@nocollapse` now blocks sub-properties from being collapsed.
 * Since enums are always collapsed, a warning is now issued when `@nocollapse` is encountered on an enum key.
 * Code in GlobalNamespace.java was refactored and simplified.
dimvar: Great, thanks. I'll take a look tomorrow again. Do you mind squashing the new commits as well?
ChadKillingsworth: Sure. I'm looking into why their are unit tests failing at the moment in the ProcessDefines pass. I don't know why that would be related.
dimvar: Sounds good. ProcessDefines uses GlobalNamespace, so it's probably some of the changes there that cause the failures.
ChadKillingsworth: Fixed - took care of a couple TODOs at the same time.
dimvar: Looks great. I only had minor comments. I'll need to test this internally and then we can merge.

Patch:
diff --git a/contrib/externs/angular-material.js b/contrib/externs/angular-material.js
index 91cc51ec9..8208a7349 100644
--- a/contrib/externs/angular-material.js
+++ b/contrib/externs/angular-material.js
@@ -56,11 +56,11 @@ md.$dialog.options;
 
 /**
  * @typedef {{
- *   title: (function(string): !md.$dialog.AlertConfig_),
- *   content: (function(string): !md.$dialog.AlertConfig_),
- *   ariaLabel: (function(string): !md.$dialog.AlertConfig_),
- *   ok: (function(string): !md.$dialog.AlertConfig_),
- *   theme: (function(string): !md.$dialog.AlertConfig_)
+ *   title: (function(string): md.$dialog.AlertConfig_),
+ *   content: (function(string): md.$dialog.AlertConfig_),
+ *   ariaLabel: (function(string): md.$dialog.AlertConfig_),
+ *   ok: (function(string): md.$dialog.AlertConfig_),
+ *   theme: (function(string): md.$dialog.AlertConfig_)
  * }}
  */
 md.$dialog.AlertConfig_;
@@ -68,20 +68,20 @@ md.$dialog.AlertConfig_;
 
 /**
  * @typedef {{
- *   title: (function(string): !md.$dialog.ConfirmConfig_),
- *   content: (function(string): !md.$dialog.ConfirmConfig_),
- *   ariaLabel: (function(string): !md.$dialog.ConfirmConfig_),
- *   ok: (function(string): !md.$dialog.ConfirmConfig_),
- *   cancel: (function(string): !md.$dialog.ConfirmConfig_),
- *   theme: (function(string): !md.$dialog.ConfirmConfig_)
+ *   title: (function(string): md.$dialog.ConfirmConfig_),
+ *   content: (function(string): md.$dialog.ConfirmConfig_),
+ *   ariaLabel: (function(string): md.$dialog.ConfirmConfig_),
+ *   ok: (function(string): md.$dialog.ConfirmConfig_),
+ *   cancel: (function(string): md.$dialog.ConfirmConfig_),
+ *   theme: (function(string): md.$dialog.ConfirmConfig_)
  * }}
  */
 md.$dialog.ConfirmConfig_;
 
 
 /**
- * @param {!md.$dialog.options|!md.$dialog.ConfirmConfig_|
- *     !md.$dialog.AlertConfig_} options
+ * @param {md.$dialog.options|md.$dialog.ConfirmConfig_|
+ *     md.$dialog.AlertConfig_} options
  * @return {!angular.$q.Promise}
  */
 md.$dialog.prototype.show = function(options) {};
diff --git a/contrib/externs/chrome_extensions.js b/contrib/externs/chrome_extensions.js
index 37504fd89..82c717aa0 100644
--- a/contrib/externs/chrome_extensions.js
+++ b/contrib/externs/chrome_extensions.js
@@ -8328,7 +8328,7 @@ chrome.networkingPrivate.getState = function(guid, callback) {};
 /**
  * @param {string} guid
  * @param {!Object} properties
- * @param {function()=} callback
+ * @param {function()} callback
  */
 chrome.networkingPrivate.setProperties = function(guid, properties, callback) {
 };
diff --git a/src/com/google/javascript/jscomp/CollapseProperties.java b/src/com/google/javascript/jscomp/CollapseProperties.java
index 199e6a14e..d8ecd3475 100644
--- a/src/com/google/javascript/jscomp/CollapseProperties.java
+++ b/src/com/google/javascript/jscomp/CollapseProperties.java
@@ -92,6 +92,10 @@ class CollapseProperties implements CompilerPass {
       "Variable {0} aliases a constructor, "
       + "so it cannot be assigned multiple times");
 
+  static final DiagnosticType INVALID_NOCOLLAPSE = DiagnosticType.warning(
+      "JSC_INVALID_NOCOLLAPSE",
+      "@nocollapse is not permitted here");
+
   private AbstractCompiler compiler;
 
   /** Global namespace tree */
@@ -506,7 +510,7 @@ class CollapseProperties implements CompilerPass {
    */
   private void flattenReferencesToCollapsibleDescendantNames(
       Name n, String alias) {
-    if (n.props == null) {
+    if (n.props == null || n.isCollapsingExplicitlyDenied()) {
       return;
     }
 
@@ -696,6 +700,14 @@ class CollapseProperties implements CompilerPass {
 
     // Handle this name first so that nested object literals get unrolled.
     if (n.canCollapse()) {
+      // Enum properties are always collapsed. Warn when @nocollapse
+      // is used on an enum property
+      if (n.docInfo != null && n.docInfo.isNoCollapse() &&
+          n.isDescendantOfEnum()) {
+        compiler.report(
+            JSError.make(n.docInfo.getAssociatedNode(), INVALID_NOCOLLAPSE));
+      }
+
       updateObjLitOrFunctionDeclaration(n, alias, canCollapseChildNames);
     }
 
@@ -997,7 +1009,7 @@ class CollapseProperties implements CompilerPass {
    */
   private void updateFunctionDeclarationAtFunctionNode(
       Name n, boolean canCollapseChildNames) {
-    if (!canCollapseChildNames) {
+    if (!canCollapseChildNames || !n.canCollapse()) {
       return;
     }
 
@@ -1118,7 +1130,7 @@ class CollapseProperties implements CompilerPass {
    * @param parent The node to which new global variables should be added
    *     as children
    * @param addAfter The child of after which new
-   *     variables should be added (may be null)
+   *     variables should be added
    * @return The number of variables added
    */
   private int addStubsForUndeclaredProperties(
diff --git a/src/com/google/javascript/jscomp/ConformanceRules.java b/src/com/google/javascript/jscomp/ConformanceRules.java
index 79d48a6c7..c1c3fe492 100644
--- a/src/com/google/javascript/jscomp/ConformanceRules.java
+++ b/src/com/google/javascript/jscomp/ConformanceRules.java
@@ -34,11 +34,11 @@ import com.google.javascript.rhino.JSDocInfo.Visibility;
 import com.google.javascript.rhino.JSTypeExpression;
 import com.google.javascript.rhino.Node;
 import com.google.javascript.rhino.TypeIRegistry;
+import com.google.javascript.rhino.jstype.Property;
 import com.google.javascript.rhino.jstype.FunctionType;
 import com.google.javascript.rhino.jstype.JSType;
 import com.google.javascript.rhino.jstype.JSTypeNative;
 import com.google.javascript.rhino.jstype.ObjectType;
-import com.google.javascript.rhino.jstype.Property;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -965,78 +965,6 @@ public final class ConformanceRules {
     }
   }
 
-  /**
-   * Banned unknown type references of the form "this.prop" unless
-   *  - it is immediately cast,
-   *  - it is a @template type (until template type
-   * restricts are enabled) or
-   *  - the value is unused.
-   *  - the "this" type is unknown (as this is expected to be used with
-   * BanUnknownThis which would have already reported the root cause).
-   */
-  public static final class BanUnknownDirectThisPropsReferences extends AbstractRule {
-    private final ImmutableList<AssertionFunctionSpec> assertions;
-    public BanUnknownDirectThisPropsReferences(AbstractCompiler compiler, Requirement requirement)
-        throws InvalidRequirementSpec {
-      super(compiler, requirement);
-      assertions = ImmutableList.copyOf(
-          compiler.getCodingConvention().getAssertionFunctions());
-    }
-
-    @Override
-    protected ConformanceResult checkConformance(NodeTraversal t, Node n) {
-      if (n.isGetProp()
-          && isKnownThis(n.getFirstChild()) // not a cascading unknown
-          && isUnknown(n)
-          && !isTemplateType(n)
-          && isUsed(n)  // skip most assignements, etc
-          && !isWhiteListed(n)) {
-        return ConformanceResult.VIOLATION;
-      }
-      return ConformanceResult.CONFORMANCE;
-    }
-
-    private boolean isKnownThis(Node n) {
-      return n.isThis() && !isUnknown(n);
-    }
-
-    private boolean isUnknown(Node n) {
-      JSType type = n.getJSType();
-      return (type != null && type.isUnknownType());
-    }
-
-    private boolean isTemplateType(Node n) {
-      JSType type = n.getJSType().restrictByNotNullOrUndefined();
-      return (type != null && type.isTemplateType());
-    }
-
-    private boolean isUsed(Node n) {
-      return (NodeUtil.isAssignmentOp(n.getParent()))
-           ? NodeUtil.isExpressionResultUsed(n.getParent())
-           : NodeUtil.isExpressionResultUsed(n);
-    }
-
-    private boolean isWhiteListed(Node n) {
-      Node parent = n.getParent();
-      return n.getParent().isCast()
-          || isAssertionCall(n.getParent())
-          || (n.getParent().isAssign() && n.getFirstChild() == n);
-    }
-
-    private boolean isAssertionCall(Node n) {
-      if (n.isCall() && n.getFirstChild().isQualifiedName()) {
-        Node target = n.getFirstChild();
-        for (int i = 0; i < assertions.size(); i++) {
-          if (target.matchesQualifiedName(
-              assertions.get(i).getFunctionName())) {
-            return true;
-          }
-        }
-      }
-      return false;
-    }
-  }
-
   /**
    * Banned accessing properties from objects that are unresolved
    * forward-declared type names. For legacy reasons this is allowed but
diff --git a/src/com/google/javascript/jscomp/GlobalNamespace.java b/src/com/google/javascript/jscomp/GlobalNamespace.java
index b711c4b37..36c030949 100644
--- a/src/com/google/javascript/jscomp/GlobalNamespace.java
+++ b/src/com/google/javascript/jscomp/GlobalNamespace.java
@@ -907,9 +907,10 @@ class GlobalNamespace
 
     Type type;
     private boolean declaredType = false;
-    private boolean hasDeclaredTypeDescendant = false;
+    private boolean isDeclared = false;
     int globalSets = 0;
     int localSets = 0;
+    int localSetsWithNoCollapse = 0;
     int aliasingGets = 0;
     int totalGets = 0;
     int callGets = 0;
@@ -964,6 +965,7 @@ class GlobalNamespace
 
     void addRef(Ref ref) {
       addRefInternal(ref);
+      JSDocInfo info;
       switch (ref.type) {
         case SET_FROM_GLOBAL:
           if (declaration == null) {
@@ -974,6 +976,11 @@ class GlobalNamespace
           break;
         case SET_FROM_LOCAL:
           localSets++;
+          info = ref.getNode() == null ? null :
+              NodeUtil.getBestJSDocInfo(ref.getNode());
+          if (info != null && info.isNoCollapse()) {
+            localSetsWithNoCollapse++;
+          }
           break;
         case PROTOTYPE_GET:
         case DIRECT_GET:
@@ -1009,12 +1016,18 @@ class GlobalNamespace
           }
         }
 
+        JSDocInfo info;
         switch (ref.type) {
           case SET_FROM_GLOBAL:
             globalSets--;
             break;
           case SET_FROM_LOCAL:
             localSets--;
+            info = ref.getNode() == null ? null :
+                NodeUtil.getBestJSDocInfo(ref.getNode());
+            if (info != null && info.isNoCollapse()) {
+              localSetsWithNoCollapse--;
+            }
             break;
           case PROTOTYPE_GET:
           case DIRECT_GET:
@@ -1074,10 +1087,28 @@ class GlobalNamespace
       return false;
     }
 
+    boolean isCollapsingExplicitlyDenied() {
+      // Enum keys are always collapsed. @nocollapse annotations are ignored
+      if (isDescendantOfEnum()) {
+        return false;
+      }
+
+      if (docInfo == null) {
+        Ref ref = getDeclaration();
+        if (ref != null) {
+          docInfo = getDocInfoForDeclaration(ref);
+        }
+      }
+
+      return docInfo != null && docInfo.isNoCollapse();
+    }
+
     boolean canCollapse() {
-      return !inExterns && !isGetOrSetDefinition() && (declaredType ||
+      return !inExterns && !isGetOrSetDefinition() &&
+          !isCollapsingExplicitlyDenied() &&
+          (declaredType ||
           (parent == null || parent.canCollapseUnannotatedChildNames()) &&
-          (globalSets > 0 || localSets > 0) &&
+          (globalSets > 0 || localSets > 0) && localSetsWithNoCollapse == 0 &&
           deleteProps == 0);
     }
 
@@ -1099,6 +1130,10 @@ class GlobalNamespace
         return false;
       }
 
+      if (isCollapsingExplicitlyDenied()) {
+        return false;
+      }
+
       if (declaredType) {
         return true;
       }
@@ -1119,18 +1154,20 @@ class GlobalNamespace
 
     /** Whether this is an object literal that needs to keep its keys. */
     boolean shouldKeepKeys() {
-      return type == Type.OBJECTLIT && aliasingGets > 0;
+      return type == Type.OBJECTLIT &&
+          (aliasingGets > 0 || isCollapsingExplicitlyDenied());
     }
 
     boolean needsToBeStubbed() {
-      return globalSets == 0 && localSets > 0;
+      return globalSets == 0 && localSets > 0 && localSetsWithNoCollapse == 0 &&
+          !isCollapsingExplicitlyDenied();
     }
 
     void setDeclaredType() {
       declaredType = true;
       for (Name ancestor = parent; ancestor != null;
            ancestor = ancestor.parent) {
-        ancestor.hasDeclaredTypeDescendant = true;
+        ancestor.isDeclared = true;
       }
     }
 
@@ -1155,7 +1192,7 @@ class GlobalNamespace
      * considered namespaces.
      */
     boolean isNamespaceObjectLit() {
-      return hasDeclaredTypeDescendant && type == Type.OBJECTLIT;
+      return isDeclared && type == Type.OBJECTLIT;
     }
 
     /**
@@ -1166,6 +1203,24 @@ class GlobalNamespace
       return parent == null;
     }
 
+    /**
+     * Determines whether a node is a property of an enum.
+     * This is recursive because static properties can be added to enums after
+     * declaration.
+     */
+    boolean isDescendantOfEnum() {
+      if (parent == null) {
+        return false;
+      }
+
+      if (parent.type == Type.OBJECTLIT && parent.docInfo != null &&
+          parent.docInfo.hasEnumParameterType()) {
+        return true;
+      }
+
+      return parent.isDescendantOfEnum();
+    }
+
     @Override public String toString() {
       return getFullName() + " (" + type + "): globalSets=" + globalSets +
           ", localSets=" + localSets + ", totalGets=" + totalGets +
@@ -1190,6 +1245,8 @@ class GlobalNamespace
           case Token.VAR:
             return ref.node == refParent.getFirstChild() ?
                 refParent.getJSDocInfo() : ref.node.getJSDocInfo();
+          case Token.OBJECTLIT:
+            return ref.node.getJSDocInfo();
         }
       }
 
diff --git a/src/com/google/javascript/jscomp/NewTypeInference.java b/src/com/google/javascript/jscomp/NewTypeInference.java
index 469e5c1dd..d4f46c680 100644
--- a/src/com/google/javascript/jscomp/NewTypeInference.java
+++ b/src/com/google/javascript/jscomp/NewTypeInference.java
@@ -63,6 +63,7 @@ import java.util.Set;
  * - @protected (maybe)
  * - arguments array
  * - separate scope for catch variables
+ * - closure-specific constructs
  * - bounded quantification for generics
  */
 public class NewTypeInference implements CompilerPass {
@@ -1068,71 +1069,58 @@ public class NewTypeInference implements CompilerPass {
       Node expr, TypeEnv inEnv, JSType requiredType, JSType specializedType) {
     Preconditions.checkArgument(
         requiredType != null && !requiredType.isBottom());
-    EnvTypePair resultPair = null;
     switch (expr.getType()) {
       case Token.EMPTY: // can be created by a FOR with empty condition
-        resultPair = new EnvTypePair(inEnv, JSType.UNKNOWN);
-        break;
+        return new EnvTypePair(inEnv, JSType.UNKNOWN);
       case Token.FUNCTION: {
         String fnName = symbolTable.getFunInternalName(expr);
         JSType fnType = envGetType(inEnv, fnName);
         Preconditions.checkState(fnType != null,
             "Could not find type for %s", fnName);
-        resultPair = new EnvTypePair(inEnv, fnType);
-        break;
+        return new EnvTypePair(inEnv, fnType);
       }
       case Token.FALSE:
       case Token.NULL:
       case Token.NUMBER:
       case Token.STRING:
       case Token.TRUE:
-        resultPair = new EnvTypePair(inEnv, scalarValueToType(expr.getType()));
-        break;
+        return new EnvTypePair(inEnv, scalarValueToType(expr.getType()));
       case Token.OBJECTLIT:
-        resultPair = analyzeObjLitFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeObjLitFwd(expr, inEnv, requiredType, specializedType);
       case Token.THIS: {
         if (!currentScope.hasThis()) {
           warnings.add(JSError.make(expr, CheckGlobalThis.GLOBAL_THIS));
-          resultPair = new EnvTypePair(inEnv, JSType.UNKNOWN);
-        } else {
-          JSType thisType = currentScope.getDeclaredTypeOf("this");
-          resultPair = new EnvTypePair(inEnv, thisType);
+          return new EnvTypePair(inEnv, JSType.UNKNOWN);
         }
-        break;
+        JSType thisType = currentScope.getDeclaredTypeOf("this");
+        return new EnvTypePair(inEnv, thisType);
       }
       case Token.NAME:
-        resultPair = analyzeNameFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeNameFwd(expr, inEnv, requiredType, specializedType);
       case Token.AND:
       case Token.OR:
-        resultPair = analyzeLogicalOpFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeLogicalOpFwd(expr, inEnv, requiredType, specializedType);
       case Token.INC:
       case Token.DEC:
-        resultPair = analyzeIncDecFwd(expr, inEnv, requiredType);
-        break;
+        return analyzeIncDecFwd(expr, inEnv, requiredType);
       case Token.BITNOT:
       case Token.NEG:
-        resultPair = analyzeUnaryNumFwd(expr, inEnv);
-        break;
+        return analyzeUnaryNumFwd(expr, inEnv);
       case Token.POS: {
         // We are more permissive with +, because it is used to coerce to number
-        resultPair = analyzeExprFwd(expr.getFirstChild(), inEnv);
-        resultPair.type = JSType.NUMBER;
-        break;
+        EnvTypePair pair = analyzeExprFwd(expr.getFirstChild(), inEnv);
+        pair.type = JSType.NUMBER;
+        return pair;
       }
       case Token.TYPEOF: {
-        resultPair = analyzeExprFwd(expr.getFirstChild(), inEnv);
-        resultPair.type = JSType.STRING;
-        break;
+        EnvTypePair pair = analyzeExprFwd(expr.getFirstChild(), inEnv);
+        pair.type = JSType.STRING;
+        return pair;
       }
       case Token.INSTANCEOF:
-        resultPair = analyzeInstanceofFwd(expr, inEnv, specializedType);
-        break;
+        return analyzeInstanceofFwd(expr, inEnv, specializedType);
       case Token.ADD:
-        resultPair = analyzeAddFwd(expr, inEnv);
-        break;
+        return analyzeAddFwd(expr, inEnv);
       case Token.BITOR:
       case Token.BITAND:
       case Token.BITXOR:
@@ -1143,14 +1131,11 @@ public class NewTypeInference implements CompilerPass {
       case Token.RSH:
       case Token.SUB:
       case Token.URSH:
-        resultPair = analyzeBinaryNumericOpFwd(expr, inEnv);
-        break;
+        return analyzeBinaryNumericOpFwd(expr, inEnv);
       case Token.ASSIGN:
-        resultPair = analyzeAssignFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeAssignFwd(expr, inEnv, requiredType, specializedType);
       case Token.ASSIGN_ADD:
-        resultPair = analyzeAssignAddFwd(expr, inEnv, requiredType);
-        break;
+        return analyzeAssignAddFwd(expr, inEnv, requiredType);
       case Token.ASSIGN_BITOR:
       case Token.ASSIGN_BITXOR:
       case Token.ASSIGN_BITAND:
@@ -1161,103 +1146,81 @@ public class NewTypeInference implements CompilerPass {
       case Token.ASSIGN_MUL:
       case Token.ASSIGN_DIV:
       case Token.ASSIGN_MOD:
-        resultPair = analyzeAssignNumericOpFwd(expr, inEnv);
-        break;
+        return analyzeAssignNumericOpFwd(expr, inEnv);
       case Token.SHEQ:
       case Token.SHNE:
-        resultPair = analyzeStrictComparisonFwd(expr.getType(),
+        return analyzeStrictComparisonFwd(expr.getType(),
             expr.getFirstChild(), expr.getLastChild(), inEnv, specializedType);
-        break;
       case Token.EQ:
       case Token.NE:
-        resultPair = analyzeNonStrictComparisonFwd(expr, inEnv, specializedType);
-        break;
+        return analyzeNonStrictComparisonFwd(expr, inEnv, specializedType);
       case Token.LT:
       case Token.GT:
       case Token.LE:
       case Token.GE:
-        resultPair = analyzeLtGtFwd(expr, inEnv);
-        break;
+        return analyzeLtGtFwd(expr, inEnv);
       case Token.GETPROP:
         Preconditions.checkState(
             !NodeUtil.isAssignmentOp(expr.getParent()) ||
             !NodeUtil.isLValue(expr));
         if (expr.getBooleanProp(Node.ANALYZED_DURING_GTI)) {
           expr.removeProp(Node.ANALYZED_DURING_GTI);
-          resultPair = new EnvTypePair(inEnv, requiredType);
-        } else {
-          resultPair = analyzePropAccessFwd(
-              expr.getFirstChild(), expr.getLastChild().getString(),
-              inEnv, requiredType, specializedType);
+          return new EnvTypePair(inEnv, requiredType);
         }
-        break;
+        return analyzePropAccessFwd(
+            expr.getFirstChild(), expr.getLastChild().getString(),
+            inEnv, requiredType, specializedType);
       case Token.HOOK:
-        resultPair = analyzeHookFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeHookFwd(expr, inEnv, requiredType, specializedType);
       case Token.CALL:
       case Token.NEW:
-        resultPair = analyzeCallNewFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeCallNewFwd(expr, inEnv, requiredType, specializedType);
       case Token.COMMA:
-        resultPair = analyzeExprFwd(
+        return analyzeExprFwd(
             expr.getLastChild(),
             analyzeExprFwd(expr.getFirstChild(), inEnv).env,
             requiredType,
             specializedType);
-        break;
       case Token.NOT: {
-        resultPair = analyzeExprFwd(expr.getFirstChild(),
+        EnvTypePair pair = analyzeExprFwd(expr.getFirstChild(),
             inEnv, JSType.UNKNOWN, specializedType.negate());
-        resultPair.type = resultPair.type.negate().toBoolean();
-        break;
+        pair.type = pair.type.negate().toBoolean();
+        return pair;
       }
       case Token.GETELEM:
-        resultPair = analyzeGetElemFwd(expr, inEnv, requiredType, specializedType);
-        break;
+        return analyzeGetElemFwd(expr, inEnv, requiredType, specializedType);
       case Token.VOID: {
-        resultPair = analyzeExprFwd(expr.getFirstChild(), inEnv);
-        resultPair.type = JSType.UNDEFINED;
-        break;
+        EnvTypePair pair = analyzeExprFwd(expr.getFirstChild(), inEnv);
+        pair.type = JSType.UNDEFINED;
+        return pair;
       }
       case Token.IN:
-        resultPair = analyzeInFwd(expr, inEnv, specializedType);
-        break;
+        return analyzeInFwd(expr, inEnv, specializedType);
       case Token.DELPROP: {
         // IRFactory checks that the operand is a name, getprop or getelem.
         // No further warnings here.
-        resultPair = analyzeExprFwd(expr.getFirstChild(), inEnv);
-        resultPair.type = JSType.BOOLEAN;
-        break;
+        EnvTypePair pair = analyzeExprFwd(expr.getFirstChild(), inEnv);
+        pair.type = JSType.BOOLEAN;
+        return pair;
       }
       case Token.REGEXP:
-        resultPair = new EnvTypePair(inEnv, commonTypes.getRegexpType());
-        break;
+        return new EnvTypePair(inEnv, commonTypes.getRegexpType());
       case Token.ARRAYLIT:
-        resultPair = analyzeArrayLitFwd(expr, inEnv);
-        break;
+        return analyzeArrayLitFwd(expr, inEnv);
       case Token.CAST:
-        resultPair = analyzeCastFwd(expr, inEnv);
-        break;
+        return analyzeCastFwd(expr, inEnv);
       case Token.CASE:
         // For a statement of the form: switch (exp1) { ... case exp2: ... }
         // we analyze the case as if it were (exp1 === exp2).
         // We analyze the body of the case when the test is true and the stm
         // following the body when the test is false.
-        resultPair = analyzeStrictComparisonFwd(Token.SHEQ,
+        return analyzeStrictComparisonFwd(Token.SHEQ,
             expr.getParent().getFirstChild(), expr.getFirstChild(),
             inEnv, specializedType);
-        break;
       default:
         throw new RuntimeException("Unhandled expression type: " +
               Token.name(expr.getType()));
     }
-    // TODO(dimvar): We attach the type to every expression because that's also
-    // what the old type inference does.
-    // But maybe most of these types are never looked at.
-    // See if the passes that use types only need types on a small subset of the
-    // nodes, and only store these types to save mem.
-    expr.setTypeI(resultPair.type);
-    return resultPair;
   }
 
   private EnvTypePair analyzeNameFwd(
diff --git a/src/com/google/javascript/jscomp/newtypes/JSType.java b/src/com/google/javascript/jscomp/newtypes/JSType.java
index 0342802ff..4b5f9d5f8 100644
--- a/src/com/google/javascript/jscomp/newtypes/JSType.java
+++ b/src/com/google/javascript/jscomp/newtypes/JSType.java
@@ -22,9 +22,6 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
-import com.google.javascript.rhino.FunctionTypeI;
-import com.google.javascript.rhino.ObjectTypeI;
-import com.google.javascript.rhino.TypeI;
 
 import java.util.Collection;
 import java.util.LinkedHashSet;
@@ -39,7 +36,7 @@ import java.util.TreeSet;
  * @author blickly@google.com (Ben Lickly)
  * @author dimvar@google.com (Dimitris Vardoulakis)
  */
-public abstract class JSType implements TypeI {
+public abstract class JSType {
   protected static final int BOTTOM_MASK = 0x0;
   protected static final int TYPEVAR_MASK = 0x1;
   protected static final int NON_SCALAR_MASK = 0x2;
@@ -249,7 +246,6 @@ public abstract class JSType implements TypeI {
     return TOP_MASK == getMask();
   }
 
-  @Override
   public boolean isBottom() {
     return BOTTOM_MASK == getMask();
   }
@@ -800,9 +796,8 @@ public abstract class JSType implements TypeI {
     return isSubtypeOfHelper(false, other);
   }
 
-  @Override
-  public boolean isSubtypeOf(TypeI other) {
-    return isSubtypeOfHelper(true, (JSType) other);
+  public boolean isSubtypeOf(JSType other) {
+    return isSubtypeOfHelper(true, other);
   }
 
   private boolean isSubtypeOfHelper(
@@ -1086,48 +1081,6 @@ public abstract class JSType implements TypeI {
     }
   }
 
-  @Override
-  public boolean isConstructor() {
-    FunctionType ft = getFunTypeIfSingletonObj();
-    return ft != null && ft.isConstructor();
-  }
-
-  @Override
-  public boolean isEquivalentTo(TypeI type) {
-    return equals(type);
-  }
-
-  @Override
-  public boolean isFunctionType() {
-    return getFunType() != null;
-  }
-
-  @Override
-  public boolean isInterface() {
-    NominalType nt = getNominalTypeIfUnique();
-    return nt != null && nt.isInterface();
-  }
-
-  @Override
-  public boolean isUnknownType() {
-    return isUnknown();
-  }
-
-  @Override
-  public TypeI restrictByNotNullOrUndefined() {
-    throw new UnsupportedOperationException("restrictByNotNullOrUndefined not implemented yet.");
-  }
-
-  @Override
-  public FunctionTypeI toMaybeFunctionType() {
-    throw new UnsupportedOperationException("toMaybeFunctionType not implemented yet.");
-  }
-
-  @Override
-  public ObjectTypeI toMaybeObjectType() {
-    throw new UnsupportedOperationException("toMaybeObjectType not implemented yet.");
-  }
-
   @Override
   public boolean equals(Object o) {
     if (o == null) {
diff --git a/src/com/google/javascript/jscomp/parsing/Annotation.java b/src/com/google/javascript/jscomp/parsing/Annotation.java
index 767926655..44e936475 100644
--- a/src/com/google/javascript/jscomp/parsing/Annotation.java
+++ b/src/com/google/javascript/jscomp/parsing/Annotation.java
@@ -56,6 +56,7 @@ enum Annotation {
   MEANING,
   MODIFIES,
   NO_ALIAS,
+  NO_COLLAPSE,
   NO_COMPILE,
   NO_SIDE_EFFECTS,
   NOT_IMPLEMENTED,
@@ -118,6 +119,7 @@ enum Annotation {
       put("meaning", Annotation.MEANING).
       put("modifies", Annotation.MODIFIES).
       put("noalias", Annotation.NO_ALIAS).
+      put("nocollapse", Annotation.NO_COLLAPSE).
       put("nocompile", Annotation.NO_COMPILE).
       put("nosideeffects", Annotation.NO_SIDE_EFFECTS).
       put("override", Annotation.OVERRIDE).
diff --git a/src/com/google/javascript/jscomp/parsing/JsDocInfoParser.java b/src/com/google/javascript/jscomp/parsing/JsDocInfoParser.java
index a3fb44578..e3e190e20 100644
--- a/src/com/google/javascript/jscomp/parsing/JsDocInfoParser.java
+++ b/src/com/google/javascript/jscomp/parsing/JsDocInfoParser.java
@@ -658,6 +658,13 @@ public final class JsDocInfoParser {
           }
           return eatUntilEOLIfNotAnnotation();
 
+        case NO_COLLAPSE:
+          if (!jsdocBuilder.recordNoCollapse()) {
+            parser.addParserWarning("msg.jsdoc.nocollapse",
+                stream.getLineno(), stream.getCharno());
+          }
+          return eatUntilEOLIfNotAnnotation();
+
         case NOT_IMPLEMENTED:
           return eatUntilEOLIfNotAnnotation();
 
diff --git a/src/com/google/javascript/rhino/JSDocInfo.java b/src/com/google/javascript/rhino/JSDocInfo.java
index 22c827aae..808ad3a1e 100644
--- a/src/com/google/javascript/rhino/JSDocInfo.java
+++ b/src/com/google/javascript/rhino/JSDocInfo.java
@@ -400,6 +400,7 @@ public class JSDocInfo implements Serializable {
   private static final int MASK_DICT          = 0x00800000; // @dict
   private static final int MASK_STALBEIDGEN   = 0x01000000; // @stableIdGenerator
   private static final int MASK_MAPPEDIDGEN   = 0x02000000; // @idGenerator {mapped}
+  private static final int MASK_NOCOLLAPSE    = 0x04000000; // @nocollapse
 
   // 3 bit type field stored in the top 3 bits of the most significant
   // nibble.
@@ -572,6 +573,10 @@ public class JSDocInfo implements Serializable {
     setFlag(value, MASK_NOCOMPILE);
   }
 
+  void setNoCollapse(boolean value) {
+    setFlag(value, MASK_NOCOLLAPSE);
+  }
+
   private void setFlag(boolean value, int mask) {
     if (value) {
       bitset |= mask;
@@ -755,6 +760,14 @@ public class JSDocInfo implements Serializable {
     return getFlag(MASK_NOCOMPILE);
   }
 
+  /**
+   * Returns whether the {@code @nocompile} annotation is present on this
+   * {@link JSDocInfo}.
+   */
+  public boolean isNoCollapse() {
+    return getFlag(MASK_NOCOLLAPSE);
+  }
+
   /**
    * @return Whether there is a declaration present on this {@link JSDocInfo}.
    */
diff --git a/src/com/google/javascript/rhino/JSDocInfoBuilder.java b/src/com/google/javascript/rhino/JSDocInfoBuilder.java
index 0e88a7708..f72f4b491 100644
--- a/src/com/google/javascript/rhino/JSDocInfoBuilder.java
+++ b/src/com/google/javascript/rhino/JSDocInfoBuilder.java
@@ -756,6 +756,23 @@ final public class JSDocInfoBuilder {
     }
   }
 
+  /**
+   * Records that the {@link JSDocInfo} being built should have its
+   * {@link JSDocInfo#isNoCollapse()} flag set to {@code true}.
+   *
+   * @return {@code true} if the no collapse flag was recorded and {@code false}
+   *     if it was already recorded
+   */
+  public boolean recordNoCollapse() {
+    if (!currentInfo.isNoCollapse()) {
+      currentInfo.setNoCollapse(true);
+      populated = true;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
   /**
    * Records that the {@link JSDocInfo} being built should have its
    * {@link JSDocInfo#isConstructor()} flag set to {@code true}.
diff --git a/src/com/google/javascript/rhino/Messages.properties b/src/com/google/javascript/rhino/Messages.properties
index eda9ded6a..6d8c94a53 100644
--- a/src/com/google/javascript/rhino/Messages.properties
+++ b/src/com/google/javascript/rhino/Messages.properties
@@ -235,6 +235,9 @@ msg.jsdoc.externs =\
 msg.jsdoc.nocompile =\
     extra @nocompile tag
 
+msg.jsdoc.nocollapse =\
+    extra @nocollapse tag
+
 msg.jsdoc.seemissing =\
     @see tag missing description
 
@@ -323,4 +326,4 @@ msg.jsdoc.typetransformation.extra.param =\
     Found extra parameter in {0}
 
 msg.jsdoc.typetransformation.invalid.inside =\
-    Invalid expression inside {0}
\ No newline at end of file
+    Invalid expression inside {0}
diff --git a/src/com/google/javascript/rhino/Node.java b/src/com/google/javascript/rhino/Node.java
index f6bde80b2..688b3bcfd 100644
--- a/src/com/google/javascript/rhino/Node.java
+++ b/src/com/google/javascript/rhino/Node.java
@@ -1641,7 +1641,7 @@ public class Node implements Cloneable, Serializable {
       return false;
     }
 
-    if (compareType && !JSType.isEquivalent(getJSType(), node.getJSType())) {
+    if (compareType && !JSType.isEquivalent((JSType) typei, node.getJSType())) {
       return false;
     }
 
@@ -2054,7 +2054,7 @@ public class Node implements Cloneable, Serializable {
    * specified type.
    */
   public JSType getJSType() {
-    return typei instanceof JSType ? (JSType) typei : null;
+    return (JSType) typei;
   }
 
   public void setJSType(JSType jsType) {
@@ -2062,8 +2062,7 @@ public class Node implements Cloneable, Serializable {
   }
 
   public TypeI getTypeI() {
-    // For the time being, we only want to return the type iff it's an old type.
-    return getJSType();
+    return typei;
   }
 
   public void setTypeI(TypeI type) {
diff --git a/src/com/google/javascript/rhino/jstype/NoObjectType.java b/src/com/google/javascript/rhino/jstype/NoObjectType.java
index 910efbff5..9e5da54b9 100644
--- a/src/com/google/javascript/rhino/jstype/NoObjectType.java
+++ b/src/com/google/javascript/rhino/jstype/NoObjectType.java
@@ -100,11 +100,6 @@ public class NoObjectType extends FunctionType {
     return null;
   }
 
-  @Override
-  public boolean hasReferenceName() {
-    return false;
-  }
-
   @Override
   public boolean matchesNumberContext() {
     return true;


Test patch:
diff --git a/test/com/google/javascript/jscomp/CheckConformanceTest.java b/test/com/google/javascript/jscomp/CheckConformanceTest.java
index 533686409..6382e6934 100644
--- a/test/com/google/javascript/jscomp/CheckConformanceTest.java
+++ b/test/com/google/javascript/jscomp/CheckConformanceTest.java
@@ -917,39 +917,6 @@ public class CheckConformanceTest extends CompilerTestCase {
         "function f() {goog.asserts.assertInstanceof(this, Error);}");
   }
 
-  private String config(String rule, String message) {
-    return "requirement: {\n"
-        + "  type: CUSTOM\n"
-        + "  java_class: '" + rule + "'\n"
-        + "  error_message: '" + message + "'\n"
-        + "}";
-  }
-
-  private String rule(String rule) {
-    return "com.google.javascript.jscomp.ConformanceRules$" + rule;
-  }
-
-  public void testCustomBanUnknownThisProp1() {
-    configuration = config(rule("BanUnknownDirectThisPropsReferences"), "My rule message");
-
-    testSame(
-        EXTERNS,
-        "/** @constructor */ function f() {};"
-        + "f.prototype.method = function() { alert(this.prop); }",
-        CheckConformance.CONFORMANCE_VIOLATION,
-        "Violation: My rule message");
-  }
-
-    public void testCustomBanUnknownThisProp2() {
-    configuration = config(rule("BanUnknownDirectThisPropsReferences"), "My rule message");
-
-    testSame(
-        EXTERNS,
-        "/** @constructor */ function f() {}"
-        + "f.prototype.method = function() { this.prop = foo; };",
-        null);
-  }
-
   public void testCustomBanGlobalVars1() {
     configuration =
         "requirement: {\n" +
diff --git a/test/com/google/javascript/jscomp/CollapsePropertiesTest.java b/test/com/google/javascript/jscomp/CollapsePropertiesTest.java
index 82f433b42..3e8f6c127 100644
--- a/test/com/google/javascript/jscomp/CollapsePropertiesTest.java
+++ b/test/com/google/javascript/jscomp/CollapsePropertiesTest.java
@@ -16,8 +16,6 @@
 
 package com.google.javascript.jscomp;
 
-import static com.google.javascript.jscomp.CollapseProperties.UNSAFE_THIS;
-
 import com.google.javascript.rhino.Node;
 
 /**
@@ -61,6 +59,10 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testMultiLevelCollapse() {
     test("var a = {}; a.b = {}; a.b.c = {}; var d = a.b.c;",
          "var a$b$c = {}; var d = a$b$c;");
+
+    test("var a = {}; a.b = {}; "
+        + "/** @nocollapse */ a.b.c = {}; var d = a.b.c;",
+        "var a$b = {}; a$b.c = {}; var d = a$b.c;");
   }
 
   public void testDecrement() {
@@ -76,6 +78,11 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testObjLitDeclaration() {
     test("var a = {b: {}, c: {}}; var d = a.b; var e = a.c",
          "var a$b = {}; var a$c = {}; var d = a$b; var e = a$c");
+
+    test("var a = {b: {}, /** @nocollapse */ c: {}}; "
+        + "var d = a.b; var e = a.c",
+        "var a$b = {}; var a = {c: {}}; "
+        + "var d = a$b; var e = a.c");
   }
 
   public void testObjLitDeclarationWithGet1() {
@@ -120,16 +127,32 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testObjLitAssignmentDepth1() {
     test("var a = {b: {}, c: {}}; var d = a.b; var e = a.c",
          "var a$b = {}; var a$c = {}; var d = a$b; var e = a$c");
+
+    test("var a = {b: {}, /** @nocollapse */ c: {}}; var d = a.b; var e = a.c",
+        "var a$b = {}; var a = {c: {}}; var d = a$b; var e = a.c");
   }
 
   public void testObjLitAssignmentDepth2() {
     test("var a = {}; a.b = {c: {}, d: {}}; var e = a.b.c; var f = a.b.d",
          "var a$b$c = {}; var a$b$d = {}; var e = a$b$c; var f = a$b$d");
+
+    test("var a = {}; a.b = {c: {}, /** @nocollapse */ d: {}}; "
+        + "var e = a.b.c; var f = a.b.d",
+        "var a$b$c = {}; var a$b = {d: {}}; var e = a$b$c; var f = a$b.d");
   }
 
   public void testObjLitAssignmentDepth3() {
     test("var a = {}; a.b = {}; a.b.c = {d: 1, e: 2}; var f = a.b.c.d",
          "var a$b$c$d = 1; var a$b$c$e = 2; var f = a$b$c$d");
+
+    test("var a = {}; a.b = {}; a.b.c = {d: 1, /** @nocollapse */ e: 2}; "
+        + "var f = a.b.c.d; var g = a.b.c.e",
+        "var a$b$c$d = 1; var a$b$c = {e: 2}; var f = a$b$c$d; "
+        + "var g = a$b$c.e");
+
+    testSame("var a = {}; /** @nocollapse*/ a.b = {}; "
+        + "a.b.c = {d: 1, e: 2}; "
+        + "var f = a.b.c.d; var g = a.b.c.e");
   }
 
   public void testObjLitAssignmentDepth4() {
@@ -141,49 +164,93 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testGlobalObjectDeclaredToPreserveItsPreviousValue1() {
     test("var a = a ? a : {}; a.c = 1;",
          "var a = a ? a : {}; var a$c = 1;");
+
+    test("var a = a ? a : {}; /** @nocollapse */ a.c = 1;",
+        "var a = a ? a : {}; a.c = 1;");
   }
 
   public void testGlobalObjectDeclaredToPreserveItsPreviousValue2() {
     test("var a = a || {}; a.c = 1;",
          "var a = a || {}; var a$c = 1;");
-  }
+
+    test("var a = a || {}; /** @nocollapse */ a.c = 1;",
+        "var a = a || {}; a.c = 1;");
+ }
 
   public void testGlobalObjectDeclaredToPreserveItsPreviousValue3() {
     test("var a = a || {get b() {}}; a.c = 1;",
          "var a = a || {get b() {}}; var a$c = 1;");
+
+    testSame("var a = a || {get b() {}}; /** @nocollapse */ a.c = 1;");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth1_1() {
     test("var a = {b: 0}; a.c = 1; if (a) x();",
          "var a$b = 0; var a = {}; var a$c = 1; if (a) x();");
+
+    test("var a = {/** @nocollapse */ b: 0}; a.c = 1; if (a) x();",
+        "var a = {b: 0}; var a$c = 1; if (a) x();");
+
+    test("var a = {b: 0}; /** @nocollapse */ a.c = 1; if (a) x();",
+        "var a$b = 0; var a = {}; a.c = 1; if (a) x();");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth1_2() {
     test("var a = {b: 0}; a.c = 1; if (!(a && a.c)) x();",
          "var a$b = 0; var a = {}; var a$c = 1; if (!(a && a$c)) x();");
+
+    test("var a = {/** @nocollapse */ b: 0}; a.c = 1; if (!(a && a.c)) x();",
+        "var a = {b: 0}; var a$c = 1; if (!(a && a$c)) x();");
+
+    test("var a = {b: 0}; /** @nocollapse */ a.c = 1; if (!(a && a.c)) x();",
+        "var a$b = 0; var a = {}; a.c = 1; if (!(a && a.c)) x();");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth1_3() {
     test("var a = {b: 0}; a.c = 1; while (a || a.c) x();",
          "var a$b = 0; var a = {}; var a$c = 1; while (a || a$c) x();");
+
+    test("var a = {/** @nocollapse */ b: 0}; a.c = 1; while (a || a.c) x();",
+        "var a = {b: 0}; var a$c = 1; while (a || a$c) x();");
+
+    test("var a = {b: 0}; /** @nocollapse */ a.c = 1; while (a || a.c) x();",
+        "var a$b = 0; var a = {}; a.c = 1; while (a || a.c) x();");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth1_4() {
     testSame("var a = {}; a.c = 1; var d = a || {}; a.c;");
+
+    testSame("var a = {}; /** @nocollapse */ a.c = 1; var d = a || {}; a.c;");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth1_5() {
     testSame("var a = {}; a.c = 1; var d = a.c || a; a.c;");
+
+    testSame("var a = {}; /** @nocollapse */ a.c = 1; var d = a.c || a; a.c;");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth1_6() {
     test("var a = {b: 0}; a.c = 1; var d = !(a.c || a); a.c;",
          "var a$b = 0; var a = {}; var a$c = 1; var d = !(a$c || a); a$c;");
+
+    test("var a = {/** @nocollapse */ b: 0}; a.c = 1; var d = !(a.c || a); "
+        + "a.c;",
+        "var a = {b: 0}; var a$c = 1; var d = !(a$c || a); a$c;");
+
+    test("var a = {b: 0}; /** @nocollapse */ a.c = 1; var d = !(a.c || a); "
+        + "a.c;",
+        "var a$b = 0; var a = {}; a.c = 1; var d = !(a.c || a); a.c;");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth2() {
     test("var a = {b: {}}; a.b.c = 1; if (a.b) x(a.b.c);",
          "var a$b = {}; var a$b$c = 1; if (a$b) x(a$b$c);");
+
+    testSame("var a = {/** @nocollapse */ b: {}}; a.b.c = 1;"
+        + "if (a.b) x(a.b.c);");
+
+    test("var a = {b: {}}; /** @nocollapse */ a.b.c = 1; if (a.b) x(a.b.c);",
+        "var a$b = {}; a$b.c = 1; if (a$b) x(a$b.c);");
   }
 
   public void testGlobalObjectNameInBooleanExpressionDepth3() {
@@ -202,11 +269,21 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testGlobalFunctionNameInBooleanExpressionDepth1() {
     test("function a() {} a.c = 1; if (a) x(a.c);",
          "function a() {} var a$c = 1; if (a) x(a$c);");
+
+    test("function a() {} /** @nocollapse */ a.c = 1; if (a) x(a.c);",
+        "function a() {} a.c = 1; if (a) x(a.c);");
   }
 
   public void testGlobalFunctionNameInBooleanExpressionDepth2() {
     test("var a = {b: function(){}}; a.b.c = 1; if (a.b) x(a.b.c);",
          "var a$b = function(){}; var a$b$c = 1; if (a$b) x(a$b$c);");
+
+    testSame("var a = {/** @nocollapse */ b: function(){}}; a.b.c = 1; "
+        + "if (a.b) x(a.b.c);");
+
+    test("var a = {b: function(){}}; /** @nocollapse */ a.b.c = 1; "
+        + "if (a.b) x(a.b.c);",
+        "var a$b = function(){}; a$b.c = 1; if (a$b) x(a$b.c);");
   }
 
   public void testAliasCreatedForObjectDepth1_1() {
@@ -226,6 +303,10 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testAliasCreatedForObjectDepth2_1() {
     test("var a = {}; a.b = {c: 0}; var d = a.b; a.b.c == d.c;",
          "var a$b = {c: 0}; var d = a$b; a$b.c == d.c;");
+
+    test("var a = {}; /** @nocollapse */ a.b = {c: 0}; var d = a.b; "
+        + "a.b.c == d.c;",
+        "var a = {}; a.b = {c: 0}; var d = a.b; a.b.c == d.c;");
   }
 
   public void testAliasCreatedForObjectDepth2_2() {
@@ -233,11 +314,34 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          "var a$b = {c: 0}; for (var p in a$b) { e(a$b[p]); }");
   }
 
+  public void testEnumDepth1() {
+    test("/** @enum */ var a = {b: 0, c: 1};",
+         "var a$b = 0; var a$c = 1;");
+
+    // @nocollapse on enum keys is ignored
+    test("/** @enum */ var a = { /** @nocollapse */ b: 0, c: 1};",
+        "var a$b = 0; var a$c = 1;", null,
+        CollapseProperties.INVALID_NOCOLLAPSE);
+  }
+
+  public void testEnumDepth2() {
+    test("var a = {}; /** @enum */ a.b = {c: 0, d: 1};",
+        "var a$b$c = 0; var a$b$d = 1;");
+
+    testSame("var a = {}; /** @nocollapse @enum */ a.b = {c: 0, d: 1};");
+  }
+
   public void testAliasCreatedForEnumDepth1_1() {
     // An enum's values are always collapsed, even if the enum object is
     // referenced in a such a way that an alias is created for it.
     test("/** @enum */ var a = {b: 0}; var c = a; c.b = 1; a.b != c.b;",
          "var a$b = 0; var a = {b: a$b}; var c = a; c.b = 1; a$b != c.b;");
+
+    // @nocollapse on enum keys is also ignored
+    test("/** @enum */ var a = { /** @nocollapse */ b: 0}; var c = a; c.b = 1;"
+        + "a.b != c.b;",
+        "var a$b = 0; var a = {b: a$b}; var c = a; c.b = 1; a$b != c.b;",
+        null, CollapseProperties.INVALID_NOCOLLAPSE);
   }
 
   public void testAliasCreatedForEnumDepth1_2() {
@@ -260,6 +364,16 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          + "var d = a.b; d.c = 1; a.b.c != d.c;",
          "var a$b$c = 0; var a$b = {c: a$b$c};"
          + "var d = a$b; d.c = 1; a$b$c != d.c;");
+
+    testSame("var a = {}; /** @nocollapse @enum */ a.b = {c: 0};"
+        + "var d = a.b; d.c = 1; a.b.c == d.c;");
+
+    // @nocollapse on enum keys is ignored
+    test("var a = {}; /** @enum */ a.b = {/** @nocollapse */ c: 0};"
+        + "var d = a.b; d.c = 1; a.b.c != d.c;",
+        "var a$b$c = 0; var a$b = {c: a$b$c};"
+        + "var d = a$b; d.c = 1; a$b$c != d.c;", null,
+        CollapseProperties.INVALID_NOCOLLAPSE);
   }
 
   public void testAliasCreatedForEnumDepth2_2() {
@@ -331,6 +445,11 @@ public class CollapsePropertiesTest extends CompilerTestCase {
     test("/** @constructor */ var a = function(){}; a.b = 1; "
          + "var c = a; c.b = 2; a.b != c.b;",
          "var a = function(){}; var a$b = 1; var c = a; c.b = 2; a$b != c.b;");
+
+    // Sometimes we want to prevent static members of a constructor from
+    // being collapsed.
+    testSame("/** @constructor */ var a = function(){};"
+        + "/** @nocollapse */ a.b = 1; var c = a; c.b = 2; a.b == c.b;");
   }
 
   public void testAliasCreatedForFunctionDepth1_2() {
@@ -340,6 +459,9 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testAliasCreatedForCtorDepth1_2() {
     test("/** @constructor */ var a = function(){}; a.b = 1; f(a); a.b;",
          "var a = function(){}; var a$b = 1; f(a); a$b;");
+
+    testSame("/** @constructor */ var a = function(){};"
+            + "/** @nocollapse */ a.b = 1; f(a); a.b;");
   }
 
   public void testAliasCreatedForFunctionDepth1_3() {
@@ -349,6 +471,9 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testAliasCreatedForCtorDepth1_3() {
     test("/** @constructor */ var a = function(){}; a.b = 1; new f(a); a.b;",
          "var a = function(){}; var a$b = 1; new f(a); a$b;");
+
+    testSame("/** @constructor */ var a = function(){};"
+            + "/** @nocollapse */ a.b = 1; new f(a); a.b;");
   }
 
   public void testAliasCreatedForFunctionDepth2() {
@@ -357,6 +482,11 @@ public class CollapsePropertiesTest extends CompilerTestCase {
         + "a.b.c != d.c;",
         "var a$b = function() {}; a$b.c = 1; var d = a$b;"
         + "a$b.c != d.c;");
+
+    test("var a = {}; a.b = function() {}; /** @nocollapse */ a.b.c = 1;"
+        + "var d = a.b; a.b.c == d.c;",
+        "var a$b = function() {}; a$b.c = 1;"
+        + "var d = a$b; a$b.c == d.c;");
   }
 
   public void testAliasCreatedForCtorDepth2() {
@@ -365,6 +495,12 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          + "a.b.c != d.c;",
          "var a$b = function() {}; var a$b$c = 1; var d = a$b;"
          + "a$b$c != d.c;");
+
+    test("var a = {}; /** @constructor */ a.b = function() {}; "
+        + "/** @nocollapse */ a.b.c = 1; var d = a.b;"
+        + "a.b.c == d.c;",
+        "var a$b = function() {}; a$b.c = 1; var d = a$b;"
+        + "a$b.c == d.c;");
   }
 
   public void testAliasCreatedForClassDepth1_1() {
@@ -375,6 +511,12 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          "var a = {}; var a$b = function(){};"
          + "var c = a; c.b = 0; a$b != c.b;", null,
          CollapseProperties.UNSAFE_NAMESPACE_WARNING);
+
+    test("var a = {}; /** @constructor @nocollapse */ a.b = function(){};"
+        + "var c = a; c.b = 0; a.b == c.b;",
+        "var a = {}; a.b = function(){};"
+        + "var c = a; c.b = 0; a.b == c.b;", null,
+        CollapseProperties.UNSAFE_NAMESPACE_WARNING);
   }
 
   public void testAliasCreatedForClassDepth1_2() {
@@ -395,6 +537,12 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          "var a$b = {}; var a$b$c = function(){};"
          + "var d = a$b; a$b$c != d.c;",
          null, CollapseProperties.UNSAFE_NAMESPACE_WARNING);
+
+    test("var a = {}; a.b = {}; /** @constructor @nocollapse */ a.b.c = "
+        + "function(){}; var d = a.b; a.b.c == d.c;",
+        "var a$b = {}; a$b.c = function(){};"
+        + "var d = a$b; a$b.c == d.c;",
+        null, CollapseProperties.UNSAFE_NAMESPACE_WARNING);
   }
 
   public void testAliasCreatedForClassDepth2_2() {
@@ -415,48 +563,99 @@ public class CollapsePropertiesTest extends CompilerTestCase {
     test("var a = {}; /** @constructor */ a.b = function(){};"
          + "a.b.c = {d: 3}; new f(a.b.c); a.b.c.d;",
          "var a$b = function(){}; var a$b$c = {d:3}; new f(a$b$c); a$b$c.d;");
+
+    testSame("var a = {}; /** @constructor @nocollapse */ a.b = function(){};"
+        + "a.b.c = {d: 3}; new f(a.b.c); a.b.c.d;");
+
+    test("var a = {}; /** @constructor */ a.b = function(){};"
+         + "/** @nocollapse */ a.b.c = {d: 3}; new f(a.b.c); a.b.c.d;",
+         "var a$b = function(){}; a$b.c = {d:3}; new f(a$b.c); "
+         + "a$b.c.d;");
   }
 
   public void testNestedObjLit() {
     test("var a = {}; a.b = {f: 0, c: {d: 1}}; var e = a.b.c.d",
-         "var a$b$f = 0; var a$b$c$d = 1; var e = a$b$c$d;");
+        "var a$b$f = 0; var a$b$c$d = 1; var e = a$b$c$d;");
+
+    test("var a = {}; a.b = {f: 0, /** @nocollapse */ c: {d: 1}};"
+        + "var e = a.b.c.d",
+        "var a$b$f = 0; var a$b ={c: { d: 1 }}; var e = a$b.c.d;");
+
+    test("var a = {}; a.b = {f: 0, c: {/** @nocollapse */ d: 1}};"
+        + "var e = a.b.c.d",
+        "var a$b$f = 0; var a$b$c = {d: 1}; var e = a$b$c.d;");
   }
 
   public void testObjLitDeclarationUsedInSameVarList() {
     // The collapsed properties must defined in the same place in the var list
     // where they were originally defined (and not, for example, at the end).
     test("var a = {b: {}, c: {}}; var d = a.b; var e = a.c;",
-         "var a$b = {}; var a$c = {}; var d = a$b; var e = a$c;");
+        "var a$b = {}; var a$c = {}; var d = a$b; var e = a$c;");
+
+    test("var a = {b: {}, /** @nocollapse */ c: {}}; var d = a.b; var e = a.c;",
+        "var a$b = {}; var a = {c: {}}; var d = a$b; var e = a.c;");
   }
 
   public void testPropGetInsideAnObjLit() {
     test("var x = {}; x.y = 1; var a = {}; a.b = {c: x.y}",
          "var x$y = 1; var a$b$c = x$y;");
+
+    test("var x = {}; /** @nocollapse */ x.y = 1; var a = {}; a.b = {c: x.y}",
+        "var x = {}; x.y = 1; var a$b$c = x.y;");
+
+    test("var x = {}; x.y = 1; var a = {}; a.b = { /** @nocollapse */ c: x.y}",
+        "var x$y = 1; var a$b = {c: x$y};");
+
+    testSame("var x = {}; /** @nocollapse */ x.y = 1; var a = {};"
+        + "/** @nocollapse */ a.b = {c: x.y}");
   }
 
   public void testObjLitWithQuotedKeyThatDoesNotGetRead() {
     test("var a = {}; a.b = {c: 0, 'd': 1}; var e = a.b.c;",
          "var a$b$c = 0; var a$b$d = 1; var e = a$b$c;");
+
+    test("var a = {}; a.b = {c: 0, /** @nocollapse */ 'd': 1}; var e = a.b.c;",
+        "var a$b$c = 0; var a$b = {'d': 1}; var e = a$b$c;");
   }
 
   public void testObjLitWithQuotedKeyThatGetsRead() {
     test("var a = {}; a.b = {c: 0, 'd': 1}; var e = a.b['d'];",
          "var a$b = {c: 0, 'd': 1}; var e = a$b['d'];");
+
+    test("var a = {}; a.b = {c: 0, /** @nocollapse */ 'd': 1};"
+        + "var e = a.b['d'];",
+        "var a$b = {c: 0, 'd': 1}; var e = a$b['d'];");
   }
 
   public void testFunctionWithQuotedPropertyThatDoesNotGetRead() {
     test("var a = {}; a.b = function() {}; a.b['d'] = 1;",
          "var a$b = function() {}; a$b['d'] = 1;");
+
+    test("var a = {}; /** @nocollapse */ a.b = function() {}; a.b['d'] = 1;",
+        "var a = {}; a.b = function() {}; a.b['d'] = 1;");
+
+    test("var a = {}; a.b = function() {}; /** @nocollapse */ a.b['d'] = 1;",
+        "var a$b = function() {}; a$b['d'] = 1;");
   }
 
   public void testFunctionWithQuotedPropertyThatGetsRead() {
     test("var a = {}; a.b = function() {}; a.b['d'] = 1; f(a.b['d']);",
          "var a$b = function() {}; a$b['d'] = 1; f(a$b['d']);");
+
+    testSame("var a = {}; /** @nocollapse */  a.b = function() {};"
+        + "a.b['d'] = 1; f(a.b['d']);");
+
+    test("var a = {}; a.b = function() {}; /** @nocollapse */ a.b['d'] = 1;"
+        + "f(a.b['d']);",
+        "var a$b = function() {}; a$b['d'] = 1; f(a$b['d']);");
   }
 
   public void testObjLitAssignedToMultipleNames1() {
     // An object literal that's assigned to multiple names isn't collapsed.
     testSame("var a = b = {c: 0, d: 1}; var e = a.c; var f = b.d;");
+
+    testSame("var a = b = {c: 0, /** @nocollapse */ d: 1}; var e = a.c;"
+        + "var f = b.d;");
   }
 
   public void testObjLitAssignedToMultipleNames2() {
@@ -470,6 +669,14 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testObjLitRedefinedInLocalScope() {
     test("var a = {}; a.b = {c: 0}; function d() { a.b = {c: 1}; } e(a.b.c);",
          "var a$b = {c: 0}; function d() { a$b = {c: 1}; } e(a$b.c);");
+
+    testSame("var a = {};/** @nocollapse */ a.b = {c: 0}; "
+        + "function d() { a.b = {c: 1}; } e(a.b.c);");
+
+    // redefinition with @nocollapse
+    test("var a = {}; a.b = {c: 0}; "
+        + "function d() { a.b = {/** @nocollapse */ c: 1}; } e(a.b.c);",
+        "var a$b = {c: 0}; function d() { a$b = {c: 1}; } e(a$b.c);");
   }
 
   public void testObjLitAssignedInTernaryExpression1() {
@@ -487,64 +694,111 @@ public class CollapsePropertiesTest extends CompilerTestCase {
   public void testGlobalVarSetToObjLitConditionally1b() {
     test("if (x) var a = {b: 0}; var c = x ? a.b : 0;",
          "if (x) var a$b = 0; var c = x ? a$b : 0;");
+
+    testSame("if (x) var a = { /** @nocollapse */ b: 0}; var c = x ? a.b : 0;");
   }
 
   public void testGlobalVarSetToObjLitConditionally2() {
     test("if (x) var a = {b: 0}; var c = a.b; var d = a.c;",
          "if (x){ var a$b = 0; var a = {}; }var c = a$b; var d = a.c;");
+
+    testSame("if (x) var a = {/** @nocollapse */ b: 0}; var c = a.b;"
+        + "var d = a.c;");
   }
 
   public void testGlobalVarSetToObjLitConditionally3() {
     testSame("var a; if (x) a = {b: 0}; else a = {b: 1}; var c = a.b;");
+
+    testSame("var a; if (x) a = {b: 0}; else a = {/** @nocollapse */ b: 1};"
+        + "var c = a.b;");
   }
 
   public void testObjectPropertySetToObjLitConditionally() {
     test("var a = {}; if (x) a.b = {c: 0}; var d = a.b ? a.b.c : 0;",
          "if (x){ var a$b$c = 0; var a$b = {} } var d = a$b ? a$b$c : 0;");
+
+    test("var a = {}; if (x) a.b = {/** @nocollapse */ c: 0};"
+        + "var d = a.b ? a.b.c : 0;",
+        "if (x){ var a$b = {c: 0};} var d = a$b ? a$b.c : 0;");
   }
 
   public void testFunctionPropertySetToObjLitConditionally() {
     test("function a() {} if (x) a.b = {c: 0}; var d = a.b ? a.b.c : 0;",
          "function a() {} if (x){ var a$b$c = 0; var a$b = {} }"
          + "var d = a$b ? a$b$c : 0;");
+
+    testSame("function a() {} if (x) /** @nocollapse */ a.b = {c: 0};"
+        + "var d = a.b ? a.b.c : 0;");
+
+    test("function a() {} if (x) a.b = {/** @nocollapse */ c: 0};"
+        + "var d = a.b ? a.b.c : 0;",
+        "function a() {} if (x){ var a$b = {c: 0}; } var d = a$b ? a$b.c : 0;");
   }
 
   public void testPrototypePropertySetToAnObjectLiteral() {
     test("var a = {b: function(){}}; a.b.prototype.c = {d: 0};",
-         "var a$b = function(){}; a$b.prototype.c = {d: 0};");
+        "var a$b = function(){}; a$b.prototype.c = {d: 0};");
+
+    testSame("var a = {/** @nocollapse */ b: function(){}};"
+        + "a.b.prototype.c = {d: 0};");
   }
 
   public void testObjectPropertyResetInLocalScope() {
     test("var z = {}; z.a = 0; function f() {z.a = 5; return z.a}",
          "var z$a = 0; function f() {z$a = 5; return z$a}");
+
+    testSame("var z = {}; z.a = 0;"
+            + "function f() { /** @nocollapse */ z.a = 5; return z.a}");
+
+    testSame("var z = {}; /** @nocollapse */ z.a = 0;"
+        + "function f() {z.a = 5; return z.a}");
   }
 
   public void testFunctionPropertyResetInLocalScope() {
     test("function z() {} z.a = 0; function f() {z.a = 5; return z.a}",
          "function z() {} var z$a = 0; function f() {z$a = 5; return z$a}");
+
+    testSame("function z() {} /** @nocollapse */ z.a = 0;"
+        + "function f() {z.a = 5; return z.a}");
+
+    testSame("function z() {} z.a = 0;"
+        + "function f() { /** @nocollapse */ z.a = 5; return z.a}");
   }
 
   public void testNamespaceResetInGlobalScope1() {
     test("var a = {}; /** @constructor */a.b = function() {}; a = {};",
          "var a = {}; var a$b = function() {}; a = {};",
          null, CollapseProperties.NAMESPACE_REDEFINED_WARNING);
+
+    testSame("var a = {}; /** @constructor @nocollapse */a.b = function() {};"
+        + "a = {};", CollapseProperties.NAMESPACE_REDEFINED_WARNING);
   }
 
   public void testNamespaceResetInGlobalScope2() {
     test("var a = {}; a = {}; /** @constructor */a.b = function() {};",
          "var a = {}; a = {}; var a$b = function() {};",
          null, CollapseProperties.NAMESPACE_REDEFINED_WARNING);
+
+    testSame("var a = {}; a = {};"
+            + "/** @constructor @nocollapse */a.b = function() {};",
+        CollapseProperties.NAMESPACE_REDEFINED_WARNING);
   }
 
   public void testNamespaceResetInGlobalScope3() {
     test("var a = {}; /** @constructor */a.b = function() {}; a = a || {};",
          "var a = {}; var a$b = function() {}; a = a || {};");
+
+    testSame("var a = {}; /** @constructor @nocollapse */a.b = function() {};"
+        + "a = a || {};");
   }
 
 
   public void testNamespaceResetInGlobalScope4() {
     test("var a = {}; /** @constructor */a.b = function() {}; var a = a || {};",
          "var a = {}; var a$b = function() {}; var a = a || {};");
+
+    testSame("var a = {}; /** @constructor @nocollapse */a.b = function() {};"
+        + "var a = a || {};");
   }
 
   public void testNamespaceResetInLocalScope1() {
@@ -553,6 +807,10 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          "var a = {};var a$b = function() {};"
          + " function f() { a = {}; }",
          null, CollapseProperties.NAMESPACE_REDEFINED_WARNING);
+
+    testSame("var a = {}; /** @constructor @nocollapse */a.b = function() {};"
+            + " function f() { a = {}; }",
+        CollapseProperties.NAMESPACE_REDEFINED_WARNING);
   }
 
   public void testNamespaceResetInLocalScope2() {
@@ -561,12 +819,24 @@ public class CollapsePropertiesTest extends CompilerTestCase {
          "var a = {}; function f() { a = {}; }"
          + " var a$b = function() {};",
          null, CollapseProperties.NAMESPACE_REDEFINED_WARNING);
+
+    testSame("var a = {}; function f() { a = {}; }"
+            + " /** @constructor @nocollapse */a.b = function() {};",
+        CollapseProperties.NAMESPACE_REDEFINED_WARNING);
   }
 
   public void testNamespaceDefinedInLocalScope() {
     test("var a = {}; (function() { a.b = {}; })();"
          + " /** @constructor */a.b.c = function() {};",
          "var a$b; (function() { a$b = {}; })(); var a$b$c = function() {};");
+
+    test("var a = {}; (function() { /** @nocollapse */ a.b = {}; })();"
+        + " /** @constructor */a.b.c = function() {};",
+        "var a = {}; (function() { a.b = {}; })(); var a$b$c = function() {};");
+
+    test("var a = {}; (function() { a.b = {}; })();"
+        + " /** @constructor @nocollapse */a.b.c = function() {};",
+        "var a$b; (function() { a$b = {}; })(); a$b.c = function() {};");
   }
 
   public void testAddPropertyToObjectInLocalScopeDepth1() {
@@ -785,7 +1055,8 @@ public class CollapsePropertiesTest extends CompilerTestCase {
     // Note: Google's JavaScript Style Guide says to avoid using the 'this'
     // keyword in a static function.
     test("var a = {}; a.b = function() {this.c}; var d = a.b;",
-         "var a$b = function() {this.c}; var d = a$b;", null, UNSAFE_THIS);
+        "var a$b = function() {this.c}; var d = a$b;", null,
+        CollapseProperties.UNSAFE_THIS);
   }
 
   public void testStaticFunctionReferencingThis2() {
@@ -798,7 +1069,8 @@ public class CollapsePropertiesTest extends CompilerTestCase {
 
   public void testStaticFunctionReferencingThis3() {
     test("var a = {b: function() {this.c}};",
-         "var a$b = function() { this.c };", null, UNSAFE_THIS);
+        "var a$b = function() { this.c };", null,
+        CollapseProperties.UNSAFE_THIS);
   }
 
   public void testStaticFunctionReferencingThis4() {
@@ -844,6 +1116,14 @@ public class CollapsePropertiesTest extends CompilerTestCase {
 
   public void testObjLitWithNonIdentifierKeys() {
     testSame("a = {' ': 0, ',': 1}; var c = a[' '];");
+    testSame("var FOO = {\n"
+        + "  'bar': {\n"
+        + "    'baz,qux': {\n"
+        + "      'beep': 'xxxxx',\n"
+        + "    },\n"
+        + "  }\n"
+        + "};"
+        + "alert(FOO);");
   }
 
   public void testChainedAssignments1() {
@@ -1096,8 +1376,8 @@ public class CollapsePropertiesTest extends CompilerTestCase {
         + "protect(O).m2 = function() {};"
         + "protect(O).m3 = function() {};";
 
-    testSame(prelude +
-        "alert(O.m1); alert(O.m2()); alert(!O.m3);");
+    testSame(prelude
+        + "alert(O.m1); alert(O.m2()); alert(!O.m3);");
   }
 
   public void testBug1956277() {
@@ -1119,15 +1399,15 @@ public class CollapsePropertiesTest extends CompilerTestCase {
 
   public void testEnumOfObjects1() {
     test(
-        COMMON_ENUM +
-        "for (var key in Foo.A) {}",
+        COMMON_ENUM
+        + "for (var key in Foo.A) {}",
          "var Foo$A = {c: 2}; var Foo$B$c = 3; for (var key in Foo$A) {}");
   }
 
   public void testEnumOfObjects2() {
     test(
-        COMMON_ENUM +
-        "foo(Foo.A.c);",
+        COMMON_ENUM
+        + "foo(Foo.A.c);",
          "var Foo$A$c = 2; var Foo$B$c = 3; foo(Foo$A$c);");
   }
 
@@ -1813,4 +2093,16 @@ public class CollapsePropertiesTest extends CompilerTestCase {
         + "ns$Outer;\n"
         + "var ns$Outer$Inner=function(){};\n");
   }
+
+  public void testNoCollapseWithInvalidEnums() {
+    test("/** @enum { { a: { b: number}} } */"
+        + "var e = { KEY1: { a: { /** @nocollapse */ b: 123}},\n"
+        + "  KEY2: { a: { b: 456}}\n"
+        + "}", null, null, CollapseProperties.INVALID_NOCOLLAPSE);
+
+    test("/** @enum */ var e = { A: 1, B: 2 };\n"
+        + "/** @type {{ c: { d: number } }} */ e.name1 = {"
+        + "  c: { /** @nocollapse */ d: 123 } };", null, null,
+        CollapseProperties.INVALID_NOCOLLAPSE);
+  }
 }
diff --git a/test/com/google/javascript/jscomp/Es6RewriteLetConstTest.java b/test/com/google/javascript/jscomp/Es6RewriteLetConstTest.java
index 1a0eee652..221270089 100644
--- a/test/com/google/javascript/jscomp/Es6RewriteLetConstTest.java
+++ b/test/com/google/javascript/jscomp/Es6RewriteLetConstTest.java
@@ -740,37 +740,23 @@ public class Es6RewriteLetConstTest extends CompilerTestCase {
     test("let /** number */ x = 5; x = 'str';",
         null, null, TypeValidator.TYPE_MISMATCH_WARNING);
 
-    test(Joiner.on('\n').join(
-        "while (true) {",
-        "  /** @type {number} */ let x = 5;",
-        "  (function() { x++; })();",
-        "  x = 7;",
-        "}"
-    ), null);
-
-    test(Joiner.on('\n').join(
-        "for (/** @type {number} */ let x = 5;;) {",
-        "  (function() { x++; })();",
-        "  x = 7;",
-        "}"
-    ), null);
-
-    // Ideally, these final two would give TYPE_MISMATCH_WARNINGS, but since transpilation
-    // changes these identifiers to properties, we lose the type annotations and warnings.
-    test(Joiner.on('\n').join(
-        "while (true) {",
-        "  /** @type {number} */ let x = 5;",
-        "  (function() { x++; })();",
-        "  x = 5;",
-        "}"
-    ), null);
-
-    test(Joiner.on('\n').join(
-        "for (/** @type {number} */ let x = 5;;) {",
-        "  (function() { x++; })();",
-        "  x = 'str';",
-        "}"
-    ), null);
+    // TODO(dimvar): these tests have been passing by accident.
+    // Uncomment once b/19570923 is fixed.
+
+    // test(Joiner.on('\n').join(
+    //     "while (true) {",
+    //     "  /** @type {number} */ let x = 5;",
+    //     "  (function() { x++; })();",
+    //     "  x = 'str';",
+    //     "}"
+    // ), null, null, TypeValidator.TYPE_MISMATCH_WARNING);
+
+    // test(Joiner.on('\n').join(
+    //     "for (/** @type {number} */ let x = 5;;) {",
+    //     "  (function() { x++; })();",
+    //     "  x = 'str';",
+    //     "}"
+    // ), null, null, TypeValidator.TYPE_MISMATCH_WARNING);
   }
 
   public void testLetForInitializers() {
diff --git a/test/com/google/javascript/jscomp/ProcessDefinesTest.java b/test/com/google/javascript/jscomp/ProcessDefinesTest.java
index 4ce85c065..0f5b0ddcb 100644
--- a/test/com/google/javascript/jscomp/ProcessDefinesTest.java
+++ b/test/com/google/javascript/jscomp/ProcessDefinesTest.java
@@ -281,21 +281,17 @@ public class ProcessDefinesTest extends CompilerTestCase {
   }
 
   public void testNamespacedDefine2b() {
-    // TODO(johnlenz): We should either reject the define as invalid
-    // or replace its value.
     overrides.put("a.B", new Node(Token.TRUE));
     test("var a = { /** @define {boolean} */ B : false };",
-         "var a = {B : false};",
-         null, ProcessDefines.UNKNOWN_DEFINE_WARNING);
+         null,
+         ProcessDefines.INVALID_DEFINE_INIT_ERROR, null);
   }
 
   public void testNamespacedDefine2c() {
-    // TODO(johnlenz): We should either reject the define as invalid
-    // or replace its value.
     overrides.put("a.B", new Node(Token.TRUE));
     test("var a = { /** @define {boolean} */ get B() { return false } };",
-      "var a = {get B() { return false } };",
-      null, ProcessDefines.UNKNOWN_DEFINE_WARNING);
+      null,
+      ProcessDefines.INVALID_DEFINE_INIT_ERROR, null);
   }
 
   public void testNamespacedDefine3() {
diff --git a/test/com/google/javascript/jscomp/parsing/JsDocInfoParserTest.java b/test/com/google/javascript/jscomp/parsing/JsDocInfoParserTest.java
index b2bc17ee2..ad1ac756e 100644
--- a/test/com/google/javascript/jscomp/parsing/JsDocInfoParserTest.java
+++ b/test/com/google/javascript/jscomp/parsing/JsDocInfoParserTest.java
@@ -4066,6 +4066,11 @@ public class JsDocInfoParserTest extends BaseJSTypeTestCase {
         true, "Bad type annotation. type not recognized due to syntax error");
   }
 
+  public void testParseDuplicateNoCollapse() throws Exception {
+    parse("@nocollapse \n * @nocollapse \n * @type {Object}*/",
+        "extra @nocollapse tag");
+  }
+
   /**
    * Asserts that a documentation field exists on the given marker.
    *