private void visitGetProp(NodeTraversal t, Node n, Node parent) {
  // obj.prop or obj.method()
  // Lots of types can appear on the left, a call to a void function can
  // never be on the left. getPropertyType will decide what is acceptable
  // and what isn't.
  Node property = n.getLastChild();
  Node objNode = n.getFirstChild();
  JSType childType = getJSType(objNode);

  if (childType.isDict()) {
    report(t, property, TypeValidator.ILLEGAL_PROPERTY_ACCESS, "'.'", "dict");
  } else if (n.getJSType() != null && parent.isAssign()) {
    return;
  } else if (validator.expectNotNullOrUndefined(t, n, childType,
      "No properties on this expression", getNativeType(OBJECT_TYPE))) {
    checkPropertyAccess(childType, property.getString(), t, n);
  }
  ensureTyped(t, n);
}

private void visitGetProp(NodeTraversal t, Node n, Node parent) {
  // obj.prop or obj.method()
  // Lots of types can appear on the left, a call to a void function can
  // never be on the left. getPropertyType will decide what is acceptable
  // and what isn't.
  Node property = n.getLastChild();
  Node objNode = n.getFirstChild();
  JSType childType = getJSType(objNode);

  if (childType.isDict()) {
    report(t, property, TypeValidator.ILLEGAL_PROPERTY_ACCESS, "'.'", "dict");
  } else if (validator.expectNotNullOrUndefined(t, n, childType,
      "No properties on this expression", getNativeType(OBJECT_TYPE))) {
    checkPropertyAccess(childType, property.getString(), t, n);
  }
  ensureTyped(t, n);
}

Record type invalid property not reported on function with @this annotation

Code:

var makeClass = function(protoMethods) {
  var clazz = function() {
    this.initialize.apply(this, arguments);
  }
  for (var i in protoMethods) {
    clazz.prototype[i] = protoMethods[i];
  }

  return clazz;
}

/**
 * @constructor
 * @param {{name: string, height: number}} options
 */
var Person = function(options){};
Person = makeClass(/** @lends Person.prototype */ {
  /**
   * @this {Person}
   * @param {{name: string, height: number}} options
   */
  initialize: function(options) {
    /** @type {string} */ this.name_ = options.thisPropDoesNotExist;
  },

  /**
   * @param {string} message
   * @this {Person}
   */
  say: function(message) {
    window.console.log(this.name_ + ' says: ' + message);
  }
});

var joe = new Person({name: 'joe', height: 300});
joe.say('hi');

compiled with:
java -jar build/compiler.jar --formatting=PRETTY_PRINT --jscomp_error=checkTypes --jscomp_error=externsValidation --compilation_level=SIMPLE_OPTIMIZATIONS repro.js

I would expect an error on this line:
    /** @type {string} */ this.name_ = options.thisPropDoesNotExist;

which works in other contexts.

Thanks!


protected JSType caseTopType(JSType topType) {
  return topType;
}

protected JSType caseTopType(JSType topType) {
  return topType.isAllType() ?
      getNativeType(ARRAY_TYPE) : topType;
}

What steps will reproduce the problem?
1.

/**
 * @param {*} object
 * @return {*}
 */
var test = function(object) {
  if (goog.isArray(object)) {
    /** @type {Array} */ var x = object;
    return x;
  }
};

2. ADVANCED_OPTIMIZATIONS

What is the expected output? What do you see instead?

ERROR - initializing variable
found   : *
required: (Array|null)
    /** @type {Array} */ var x = object;
                                 ^
What version of the product are you using? On what operating system?

Closure Compiler (http://code.google.com/closure/compiler)
Version: v20130411-90-g4e19b4e
Built on: 2013/06/03 12:07

Please provide any additional information below.

goog.is* is supposed to help the compiler to check which type we're dealing with.


private boolean inferTemplatedTypesForCall(
    Node n, FunctionType fnType) {
  final ImmutableList<TemplateType> keys = fnType.getTemplateTypeMap()
      .getTemplateKeys();
  if (keys.isEmpty()) {
    return false;
  }

  // Try to infer the template types
  Map<TemplateType, JSType> inferred =
      inferTemplateTypesFromParameters(fnType, n);

  // Replace all template types. If we couldn't find a replacement, we
  // replace it with UNKNOWN.
  TemplateTypeReplacer replacer = new TemplateTypeReplacer(
      registry, inferred);
  Node callTarget = n.getFirstChild();

  FunctionType replacementFnType = fnType.visit(replacer)
      .toMaybeFunctionType();
  Preconditions.checkNotNull(replacementFnType);

  callTarget.setJSType(replacementFnType);
  n.setJSType(replacementFnType.getReturnType());

  return replacer.madeChanges;
}

private boolean inferTemplatedTypesForCall(
    Node n, FunctionType fnType) {
  final ImmutableList<TemplateType> keys = fnType.getTemplateTypeMap()
      .getTemplateKeys();
  if (keys.isEmpty()) {
    return false;
  }

  // Try to infer the template types
  Map<TemplateType, JSType> inferred = Maps.filterKeys(
      inferTemplateTypesFromParameters(fnType, n),
      new Predicate<TemplateType>() {

        @Override
        public boolean apply(TemplateType key) {
          return keys.contains(key);
        }}
      );

  // Replace all template types. If we couldn't find a replacement, we
  // replace it with UNKNOWN.
  TemplateTypeReplacer replacer = new TemplateTypeReplacer(
      registry, inferred);
  Node callTarget = n.getFirstChild();

  FunctionType replacementFnType = fnType.visit(replacer)
      .toMaybeFunctionType();
  Preconditions.checkNotNull(replacementFnType);

  callTarget.setJSType(replacementFnType);
  n.setJSType(replacementFnType.getReturnType());

  return replacer.madeChanges;
}

Template types on methods incorrectly trigger inference of a template on the class if that template type is unknown

See i.e.

/**
  * @constructor
  * @template CLASS
  */
var Class = function() {};

/**
  * @param {function(CLASS):CLASS} a
  * @template T
  */
Class.prototype.foo = function(a) {
  return 'string';
};

/** @param {number} a
  * @return {string} */
var a = function(a) { return '' };

new Class().foo(a);

The CLASS type is never specified. If the @template T line is removed from the foo method, the block compiles with but with the @annotation on the method, the compiler seems to try to infer CLASS from the usage and fails compilation.


private void processRequireCall(NodeTraversal t, Node n, Node parent) {
  Node left = n.getFirstChild();
  Node arg = left.getNext();
  if (verifyLastArgumentIsString(t, left, arg)) {
    String ns = arg.getString();
    ProvidedName provided = providedNames.get(ns);
    if (provided == null || !provided.isExplicitlyProvided()) {
      unrecognizedRequires.add(
          new UnrecognizedRequire(n, ns, t.getSourceName()));
    } else {
      JSModule providedModule = provided.explicitModule;

      // This must be non-null, because there was an explicit provide.
      Preconditions.checkNotNull(providedModule);

      JSModule module = t.getModule();
      if (moduleGraph != null &&
          module != providedModule &&
          !moduleGraph.dependsOn(module, providedModule)) {
        compiler.report(
            t.makeError(n, XMODULE_REQUIRE_ERROR, ns,
                providedModule.getName(),
                module.getName()));
      }
    }

    maybeAddToSymbolTable(left);
    maybeAddStringNodeToSymbolTable(arg);

    // Requires should be removed before further processing.
    // Some clients run closure pass multiple times, first with
    // the checks for broken requires turned off. In these cases, we
    // allow broken requires to be preserved by the first run to
    // let them be caught in the subsequent run.
    if (provided != null) {
      parent.detachFromParent();
      compiler.reportCodeChange();
    }
  }
}

private void processRequireCall(NodeTraversal t, Node n, Node parent) {
  Node left = n.getFirstChild();
  Node arg = left.getNext();
  if (verifyLastArgumentIsString(t, left, arg)) {
    String ns = arg.getString();
    ProvidedName provided = providedNames.get(ns);
    if (provided == null || !provided.isExplicitlyProvided()) {
      unrecognizedRequires.add(
          new UnrecognizedRequire(n, ns, t.getSourceName()));
    } else {
      JSModule providedModule = provided.explicitModule;

      // This must be non-null, because there was an explicit provide.
      Preconditions.checkNotNull(providedModule);

      JSModule module = t.getModule();
      if (moduleGraph != null &&
          module != providedModule &&
          !moduleGraph.dependsOn(module, providedModule)) {
        compiler.report(
            t.makeError(n, XMODULE_REQUIRE_ERROR, ns,
                providedModule.getName(),
                module.getName()));
      }
    }

    maybeAddToSymbolTable(left);
    maybeAddStringNodeToSymbolTable(arg);

    // Requires should be removed before further processing.
    // Some clients run closure pass multiple times, first with
    // the checks for broken requires turned off. In these cases, we
    // allow broken requires to be preserved by the first run to
    // let them be caught in the subsequent run.
    if (provided != null || requiresLevel.isOn()) {
      parent.detachFromParent();
      compiler.reportCodeChange();
    }
  }
}

The Problem

ProcessClosurePrimitives pass has a bug in processRequireCall method.
The method processes goog.require calls. If a require symbol is invalid i.e is not provided anywhere, the method collects it for further error reporting. If the require symbol is valid, the method removes it from the ast.

All invalid require calls must be left for further using/checking of the code! The related comment in the code confirms this.

Nevertheless, the second condition (requiresLevel.isOn() -> see source code) is invalid and always causes removing of the requires when we want to check these requires.

In any case, the method should not use the requiresLevel to decide if we need removing. The requiresLevel should be used to check if we need error reporting.

The Solution

Remove the condition.
Please see the attached patch.


String getReadableJSTypeName(Node n, boolean dereference) {

  // The best type name is the actual type name.

  // If we're analyzing a GETPROP, the property may be inherited by the
  // prototype chain. So climb the prototype chain and find out where
  // the property was originally defined.
  if (n.isGetProp()) {
    ObjectType objectType = getJSType(n.getFirstChild()).dereference();
    if (objectType != null) {
      String propName = n.getLastChild().getString();
      if (objectType.getConstructor() != null &&
          objectType.getConstructor().isInterface()) {
        objectType = FunctionType.getTopDefiningInterface(
            objectType, propName);
      } else {
        // classes
        while (objectType != null && !objectType.hasOwnProperty(propName)) {
          objectType = objectType.getImplicitPrototype();
        }
      }

      // Don't show complex function names or anonymous types.
      // Instead, try to get a human-readable type name.
      if (objectType != null &&
          (objectType.getConstructor() != null ||
           objectType.isFunctionPrototypeType())) {
        return objectType.toString() + "." + propName;
      }
    }
  }

  JSType type = getJSType(n);
  if (dereference) {
    ObjectType dereferenced = type.dereference();
    if (dereferenced != null) {
      type = dereferenced;
    }
  }
  if (type.isFunctionPrototypeType() ||
      (type.toObjectType() != null &&
       type.toObjectType().getConstructor() != null)) {
    return type.toString();
  }
  String qualifiedName = n.getQualifiedName();
  if (qualifiedName != null) {
    return qualifiedName;
  } else if (type.isFunctionType()) {
    // Don't show complex function names.
    return "function";
  } else {
    return type.toString();
  }
}

String getReadableJSTypeName(Node n, boolean dereference) {
  JSType type = getJSType(n);
  if (dereference) {
    ObjectType dereferenced = type.dereference();
    if (dereferenced != null) {
      type = dereferenced;
    }
  }

  // The best type name is the actual type name.
  if (type.isFunctionPrototypeType() ||
      (type.toObjectType() != null &&
       type.toObjectType().getConstructor() != null)) {
    return type.toString();
  }

  // If we're analyzing a GETPROP, the property may be inherited by the
  // prototype chain. So climb the prototype chain and find out where
  // the property was originally defined.
  if (n.isGetProp()) {
    ObjectType objectType = getJSType(n.getFirstChild()).dereference();
    if (objectType != null) {
      String propName = n.getLastChild().getString();
      if (objectType.getConstructor() != null &&
          objectType.getConstructor().isInterface()) {
        objectType = FunctionType.getTopDefiningInterface(
            objectType, propName);
      } else {
        // classes
        while (objectType != null && !objectType.hasOwnProperty(propName)) {
          objectType = objectType.getImplicitPrototype();
        }
      }

      // Don't show complex function names or anonymous types.
      // Instead, try to get a human-readable type name.
      if (objectType != null &&
          (objectType.getConstructor() != null ||
           objectType.isFunctionPrototypeType())) {
        return objectType.toString() + "." + propName;
      }
    }
  }

  String qualifiedName = n.getQualifiedName();
  if (qualifiedName != null) {
    return qualifiedName;
  } else if (type.isFunctionType()) {
    // Don't show complex function names.
    return "function";
  } else {
    return type.toString();
  }
}

Wrong type name reported on missing property error.

/**
 * @constructor
 */
function C2() {}

/**
 * @constructor
 */
function C3(c2) {
  /**
   * @type {C2}
   * @private
   */
  this.c2_;

  use(this.c2_.prop);
}

Produces:

Property prop never defined on C3.c2_

But should be:

Property prop never defined on C2


private void handleObjectLit(NodeTraversal t, Node n) {
  for (Node child = n.getFirstChild();
      child != null;
      child = child.getNext()) {
    // Maybe STRING, GET, SET

    // We should never see a mix of numbers and strings.
    String name = child.getString();
    T type = typeSystem.getType(getScope(), n, name);

    Property prop = getProperty(name);
    if (!prop.scheduleRenaming(child,
                               processProperty(t, prop, type, null))) {
      // TODO(user): It doesn't look like the user can do much in this
      // case right now.
      if (propertiesToErrorFor.containsKey(name)) {
        compiler.report(JSError.make(
            t.getSourceName(), child, propertiesToErrorFor.get(name),
            Warnings.INVALIDATION, name,
            (type == null ? "null" : type.toString()), n.toString(), ""));
      }
    }
  }
}

private void handleObjectLit(NodeTraversal t, Node n) {
  for (Node child = n.getFirstChild();
      child != null;
      child = child.getNext()) {
    // Maybe STRING, GET, SET
    if (child.isQuotedString()) {
      continue;
    }

    // We should never see a mix of numbers and strings.
    String name = child.getString();
    T type = typeSystem.getType(getScope(), n, name);

    Property prop = getProperty(name);
    if (!prop.scheduleRenaming(child,
                               processProperty(t, prop, type, null))) {
      // TODO(user): It doesn't look like the user can do much in this
      // case right now.
      if (propertiesToErrorFor.containsKey(name)) {
        compiler.report(JSError.make(
            t.getSourceName(), child, propertiesToErrorFor.get(name),
            Warnings.INVALIDATION, name,
            (type == null ? "null" : type.toString()), n.toString(), ""));
      }
    }
  }
}

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name default.js
// @formatting pretty_print
// ==/ClosureCompiler==

/** @const */
var foo = {};
foo.bar = {
  'bar1': function() { console.log('bar1'); }
}

/** @constructor */
function foobar() {}
foobar.prototype = foo.bar;

foo.foobar = new foobar;

console.log(foo.foobar['bar1']);


private boolean hasExceptionHandler(Node cfgNode) {
  return false;
}

private boolean hasExceptionHandler(Node cfgNode) {
  List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
  for (DiGraphEdge<Node, Branch> edge : branchEdges) {
    if (edge.getValue() == Branch.ON_EX) {
      return true;
    }
  }
  return false;
}

Try/catch blocks incorporate code not inside original blocks

What steps will reproduce the problem?

Starting with this code:

-----
function a() {
 var x = '1';
 try {
  x += somefunction();
 } catch(e) {
 }
 x += "2";
 try {
  x += somefunction();
 } catch(e) {
 }
 document.write(x);
}

a();
a();
-----

It gets compiled to:

-----
function b() {
  var a;
  try {
    a = "1" + somefunction()
  }catch(c) {
  }
  try {
    a = a + "2" + somefunction()
  }catch(d) {
  }
  document.write(a)
}
b();
b();
-----

What is the expected output? What do you see instead?

The problem is that it's including the constant "1" and "2" inside the try block when the shouldn't be. When executed uncompiled, the script prints "1212". When compiled, the script prints "undefinedundefined".

This behavior doesn't happen if the entire function gets inlined, or if the code between the two try blocks is sufficiently complex.

What version of the product are you using? On what operating system?

Closure Compiler (http://code.google.com/closure/compiler)
Version: 20120430 (revision 1918)
Built on: 2012/04/30 18:02
java version "1.6.0_33"
Java(TM) SE Runtime Environment (build 1.6.0_33-b03-424-11M3720)
Java HotSpot(TM) 64-Bit Server VM (build 20.8-b03-424, mixed mode)


boolean isAssignedOnceInLifetime() {
  Reference ref = getOneAndOnlyAssignment();
  if (ref == null) {
    return false;
  }

  // Make sure this assignment is not in a loop.
  for (BasicBlock block = ref.getBasicBlock();
       block != null; block = block.getParent()) {
    if (block.isFunction) {
      break;
    } else if (block.isLoop) {
      return false;
    }
  }

  return true;
}

boolean isAssignedOnceInLifetime() {
  Reference ref = getOneAndOnlyAssignment();
  if (ref == null) {
    return false;
  }

  // Make sure this assignment is not in a loop.
  for (BasicBlock block = ref.getBasicBlock();
       block != null; block = block.getParent()) {
    if (block.isFunction) {
      if (ref.getSymbol().getScope() != ref.scope) {
        return false;
      }
      break;
    } else if (block.isLoop) {
      return false;
    }
  }

  return true;
}

Overzealous optimization confuses variables

The following code:

	// ==ClosureCompiler==
	// @compilation_level ADVANCED_OPTIMIZATIONS
	// ==/ClosureCompiler==
	var uid;
	function reset() {
		uid = Math.random();
	}
	function doStuff() {
		reset();
		var _uid = uid;

		if (uid < 0.5) {
			doStuff();
		}

		if (_uid !== uid) {
			throw 'reset() was called';
		}
	}
	doStuff();

...gets optimized to:

	var a;function b(){a=Math.random();0.5>a&&b();if(a!==a)throw"reset() was called";}b();

Notice how _uid gets optimized away and (uid!==_uid) becomes (a!==a) even though doStuff() might have been called and uid's value may have changed and become different from _uid.

As an aside, replacing the declaration with "var _uid = +uid;" fixes it, as does adding an extra "uid = _uid" after "var _uid = uid".


private void handleBlockComment(Comment comment) {
  if (comment.getValue().indexOf("/* @") != -1 || comment.getValue().indexOf("\n * @") != -1) {
    errorReporter.warning(
        SUSPICIOUS_COMMENT_WARNING,
        sourceName,
        comment.getLineno(), "", 0);
  }
}

private void handleBlockComment(Comment comment) {
  Pattern p = Pattern.compile("(/|(\n[ \t]*))\\*[ \t]*@[a-zA-Z]");
  if (p.matcher(comment.getValue()).find()) {
    errorReporter.warning(
        SUSPICIOUS_COMMENT_WARNING,
        sourceName,
        comment.getLineno(), "", 0);
  }
}

Inconsistent handling of non-JSDoc comments

What steps will reproduce the problem?
1.
2.
3.
What is the expected output? What do you see instead?

When given:

    /* @preserve Foo License */
    alert("foo");

It spits out:

    stdin:1: WARNING - Parse error. Non-JSDoc comment has annotations. Did you mean to start it with '/**'?
    /* @license Foo License */
    ^

    0 error(s), 1 warning(s)
    alert("foo");

If I take the suggestion and change the opening of the comment to '/**', everything is great.  However, if I change it to '/*!', the warning goes away, but it doesn't preserve the comment either.

I expect it to print the above warning, or preserve the comment.  That it does neither when starting with "/*!" (and every other character I tried) is confusing.

What version of the product are you using? On what operating system?

Tested with my compilation of the "v20130603" tag:

    Closure Compiler (http://code.google.com/closure/compiler)
    Version: v20130603
    Built on: 2013/07/07 15:04

And with the provided binary:

    Closure Compiler (http://code.google.com/closure/compiler)
    Version: v20130411-90-g4e19b4e
    Built on: 2013/06/03 12:07

I'm on Parabola GNU/Linux-libre with Java:

    java version "1.7.0_40"
    OpenJDK Runtime Environment (IcedTea 2.4.0) (ArchLinux build 7.u40_2.4.0-1-i686)
    OpenJDK Server VM (build 24.0-b40, mixed mode)

Please provide any additional information below.


private boolean isSafeReplacement(Node node, Node replacement) {
  // No checks are needed for simple names.
  if (node.isName()) {
    return true;
  }
  Preconditions.checkArgument(node.isGetProp());

    node = node.getFirstChild();
  if (node.isName()
      && isNameAssignedTo(node.getString(), replacement)) {
    return false;
  }

  return true;
}

private boolean isSafeReplacement(Node node, Node replacement) {
  // No checks are needed for simple names.
  if (node.isName()) {
    return true;
  }
  Preconditions.checkArgument(node.isGetProp());

  while (node.isGetProp()) {
    node = node.getFirstChild();
  }
  if (node.isName()
      && isNameAssignedTo(node.getString(), replacement)) {
    return false;
  }

  return true;
}

Different output from RestAPI and command line jar

When I compile using the jar file from the command line I get a result that is not correct. However, when I test it via the REST API or the Web UI I get a correct output. I've attached a file with the code that we are compiling.

What steps will reproduce the problem?
1. Compile the attached file with "java -jar compiler.jar --js test.js"
2. Compile the content of the attached file on http://closure-compiler.appspot.com/home
3. Compare the output, note how the following part is converted in the two cases:

"var foreignObject = gfx.parentNode.parentNode;
var parentContainer = foreignObject.parentNode.parentNode;"

What is the expected output? What do you see instead?
The Web UI converts the lines into: if(b){if(a=b.parentNode.parentNode,b=a.parentNode.parentNode,null!==b)
The command line converts it into: var b=a=a.parentNode.parentNode;
The Web UI results in correct code, the other results in code that tries to do "c.appendChild(b)" with c = b (c=a=a.parentNode.parentNode)

What version of the product are you using? On what operating system?
compiler.jar: v20130411-90-g4e19b4e
Mac OSX 10.8.3
Java: java 1.6.0_45

Please provide any additional information below.
We are also using the compiler form within our java code, with the same result.
Web UI was called with:
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
// @output_file_name default.js
// ==/ClosureCompiler==


static boolean isSimpleNumber(String s) {
  int len = s.length();
  for (int index = 0; index < len; index++) {
    char c = s.charAt(index);
    if (c < '0' || c > '9') {
      return false;
    }
  }
  return len > 0 && s.charAt(0) != '0';
}

static boolean isSimpleNumber(String s) {
  int len = s.length();
  if (len == 0) {
    return false;
  }
  for (int index = 0; index < len; index++) {
    char c = s.charAt(index);
    if (c < '0' || c > '9') {
      return false;
    }
  }
  return len == 1 || s.charAt(0) != '0';
}

The compiler quotes the "0" keys in object literals

What steps will reproduce the problem?
1. Compile alert({0:0, 1:1});

What is the expected output?
alert({0:0, 1:1});

What do you see instead?
alert({"0":0, 1:1});

What version of the product are you using? On what operating system?
Latest version on Goobuntu.


private void annotateCalls(Node n) {
  Preconditions.checkState(n.isCall());

  // Keep track of of the "this" context of a call.  A call without an
  // explicit "this" is a free call.
  Node first = n.getFirstChild();

  // ignore cast nodes.

  if (!NodeUtil.isGet(first)) {
    n.putBooleanProp(Node.FREE_CALL, true);
  }

  // Keep track of the context in which eval is called. It is important
  // to distinguish between "(0, eval)()" and "eval()".
  if (first.isName() &&
      "eval".equals(first.getString())) {
    first.putBooleanProp(Node.DIRECT_EVAL, true);
  }
}

private void annotateCalls(Node n) {
  Preconditions.checkState(n.isCall());

  // Keep track of of the "this" context of a call.  A call without an
  // explicit "this" is a free call.
  Node first = n.getFirstChild();

  // ignore cast nodes.
  while (first.isCast()) {
    first = first.getFirstChild();
  }

  if (!NodeUtil.isGet(first)) {
    n.putBooleanProp(Node.FREE_CALL, true);
  }

  // Keep track of the context in which eval is called. It is important
  // to distinguish between "(0, eval)()" and "eval()".
  if (first.isName() &&
      "eval".equals(first.getString())) {
    first.putBooleanProp(Node.DIRECT_EVAL, true);
  }
}

Casting a function before calling it produces bad code and breaks plugin code

1. Compile this code with ADVANCED_OPTIMIZATIONS:
console.log( /** @type {function(!string):!string} */ ((new window.ActiveXObject( 'ShockwaveFlash.ShockwaveFlash' ))['GetVariable'])( '$version' ) );

produces:

'use strict';console.log((0,(new window.ActiveXObject("ShockwaveFlash.ShockwaveFlash")).GetVariable)("$version"));

2. Compare with this code:
console.log( /** @type {!string} */ ((new window.ActiveXObject( 'ShockwaveFlash.ShockwaveFlash' ))['GetVariable']( '$version' )) )

produces:

'use strict';console.log((new window.ActiveXObject("ShockwaveFlash.ShockwaveFlash")).GetVariable("$version"));

Notice the (0,...) wrapping around the GetVariable function in the first example. This causes the call to fail in every browser (this code is IE-only but it's just for a minimal example). The second version produces a warning that the type of GetVariable could not be determined (I enabled type warnings), and it wouldn't be possible to define these in an externs file without making a horrible mess.

This applies to all cases where functions are cast, but only causes problems (other than bloat) with plugins like this. It seems to serve no purpose whatsoever, so I assume it is a bug.

Running on a mac, not sure what version but it reports Built on: 2013/02/12 17:00, so will have been downloaded about that time.


private void traverse(Node node) {
  // The goal here is to avoid retraversing
  // the entire AST to catch newly created opportunities.
  // So we track whether a "unit of code" has changed,
  // and revisit immediately.
  if (!shouldVisit(node)) {
    return;
  }

  int visits = 0;
  do {
    Node c = node.getFirstChild();
    while(c != null) {
      traverse(c);
      Node next = c.getNext();
      c = next;
    }

    visit(node);
    visits++;

    Preconditions.checkState(visits < 10000, "too many interations");
  } while (shouldRetraverse(node));

  exitNode(node);
}

private void traverse(Node node) {
  // The goal here is to avoid retraversing
  // the entire AST to catch newly created opportunities.
  // So we track whether a "unit of code" has changed,
  // and revisit immediately.
  if (!shouldVisit(node)) {
    return;
  }

  int visits = 0;
  do {
    Node c = node.getFirstChild();
    while(c != null) {
      Node next = c.getNext();
      traverse(c);
      c = next;
    }

    visit(node);
    visits++;

    Preconditions.checkState(visits < 10000, "too many interations");
  } while (shouldRetraverse(node));

  exitNode(node);
}

true/false are not always replaced for !0/!1

What steps will reproduce the problem?

function some_function() {
  var fn1;
  var fn2;

  if (any_expression) {
    fn2 = external_ref;
    fn1 = function (content) {
      return fn2();
    }
  }

  return {
    method1: function () {
      if (fn1) fn1();
      return true;
    },
    method2: function () {
      return false;
    }
  }
}

What is the expected output? What do you see instead?

We expect that true/false will be replaced for !0/!1, but it doesn't happend.

function some_function() {
  var a, b;
  any_expression && (b = external_ref, a = function () {
    return b()
  });
  return {
    method1: function () {
      a && a();
      return true
    },
    method2: function () {
      return false
    }
  }
};

What version of the product are you using? On what operating system?

This is output for latest official build.
I also got the same output for 20120430, 20120305. But 20111117 is OK.

Please provide any additional information below.

Here is just one of example. I found too many non-replaced true/false in compiler output. Replacement non-replaced true/false to !1/!0 in conpiler output saves 1-2 kb for 850 kb js file.


private void inlineAliases(GlobalNamespace namespace) {
  // Invariant: All the names in the worklist meet condition (a).
  Deque<Name> workList = new ArrayDeque<Name>(namespace.getNameForest());
  while (!workList.isEmpty()) {
    Name name = workList.pop();

    // Don't attempt to inline a getter or setter property as a variable.
    if (name.type == Name.Type.GET || name.type == Name.Type.SET) {
      continue;
    }

    if (name.globalSets == 1 && name.localSets == 0 &&
        name.aliasingGets > 0) {
      // {@code name} meets condition (b). Find all of its local aliases
      // and try to inline them.
      List<Ref> refs = Lists.newArrayList(name.getRefs());
      for (Ref ref : refs) {
        if (ref.type == Type.ALIASING_GET && ref.scope.isLocal()) {
          // {@code name} meets condition (c). Try to inline it.
          if (inlineAliasIfPossible(ref, namespace)) {
            name.removeRef(ref);
          }
        }
      }
    }

    // Check if {@code name} has any aliases left after the
    // local-alias-inlining above.
    if ((name.type == Name.Type.OBJECTLIT ||
         name.type == Name.Type.FUNCTION) &&
        name.aliasingGets == 0 && name.props != null) {
      // All of {@code name}'s children meet condition (a), so they can be
      // added to the worklist.
      workList.addAll(name.props);
    }
  }
}

private void inlineAliases(GlobalNamespace namespace) {
  // Invariant: All the names in the worklist meet condition (a).
  Deque<Name> workList = new ArrayDeque<Name>(namespace.getNameForest());
  while (!workList.isEmpty()) {
    Name name = workList.pop();

    // Don't attempt to inline a getter or setter property as a variable.
    if (name.type == Name.Type.GET || name.type == Name.Type.SET) {
      continue;
    }

    if (!name.inExterns && name.globalSets == 1 && name.localSets == 0 &&
        name.aliasingGets > 0) {
      // {@code name} meets condition (b). Find all of its local aliases
      // and try to inline them.
      List<Ref> refs = Lists.newArrayList(name.getRefs());
      for (Ref ref : refs) {
        if (ref.type == Type.ALIASING_GET && ref.scope.isLocal()) {
          // {@code name} meets condition (c). Try to inline it.
          if (inlineAliasIfPossible(ref, namespace)) {
            name.removeRef(ref);
          }
        }
      }
    }

    // Check if {@code name} has any aliases left after the
    // local-alias-inlining above.
    if ((name.type == Name.Type.OBJECTLIT ||
         name.type == Name.Type.FUNCTION) &&
        name.aliasingGets == 0 && name.props != null) {
      // All of {@code name}'s children meet condition (a), so they can be
      // added to the worklist.
      workList.addAll(name.props);
    }
  }
}

Using ADVANCED_OPTIMIZATIONS with CompilerOptions.collapsePropertiesOnExternTypes = true a script I used broke, it was something like:

function () {
  return function () {
    var arg = arguments;
    setTimeout(function() { alert(args); }, 0);
  }
}

Unfortunately it was rewritten to:

function () {
  return function () {
    setTimeout(function() { alert(arguments); }, 0);
  }
}

arguments should not be collapsed.


public static boolean isJSIdentifier(String s) {
  int length = s.length();

  if (length == 0 ||
      !Character.isJavaIdentifierStart(s.charAt(0))) {
    return false;
  }

  for (int i = 1; i < length; i++) {
    if (
        !Character.isJavaIdentifierPart(s.charAt(i))) {
      return false;
    }
  }

  return true;
}

public static boolean isJSIdentifier(String s) {
  int length = s.length();

  if (length == 0 ||
      Character.isIdentifierIgnorable(s.charAt(0)) ||
      !Character.isJavaIdentifierStart(s.charAt(0))) {
    return false;
  }

  for (int i = 1; i < length; i++) {
    if (Character.isIdentifierIgnorable(s.charAt(i)) ||
        !Character.isJavaIdentifierPart(s.charAt(i))) {
      return false;
    }
  }

  return true;
}

unicode characters in property names result in invalid output

What steps will reproduce the problem?
1. use unicode characters in a property name for an object, like this:
var test={"a\u0004b":"c"};

2. compile

What is the expected output? What do you see instead?
Because unicode characters are not allowed in property names without quotes, the output should be the same as the input. However, the compiler converts the string \u0004 to the respective unicode character, and the output is:
var test={ab:"c"}; // unicode character between a and b can not be displayed here

What version of the product are you using? On what operating system?
newest current snapshot on multiple os (OSX/linux)

Please provide any additional information below.


private String getRemainingJSDocLine() {
  String result = stream.getRemainingJSDocLine();
  return result;
}

private String getRemainingJSDocLine() {
  String result = stream.getRemainingJSDocLine();
  unreadToken = NO_UNREAD_TOKEN;
  return result;
}

Exception when parsing erroneous jsdoc: /**@return {@code foo} bar   *    baz. */

The following causes an exception in JSDocInfoParser.

/**
 * @return {@code foo} bar
 *    baz. */
var x;

Fix to follow.


private boolean isOneExactlyFunctionOrDo(Node n) {
      // For labels with block children, we need to ensure that a
      // labeled FUNCTION or DO isn't generated when extraneous BLOCKs
      // are skipped.
        // Either a empty statement or an block with more than one child,
        // way it isn't a FUNCTION or DO.
    return (n.getType() == Token.FUNCTION || n.getType() == Token.DO);
}

private boolean isOneExactlyFunctionOrDo(Node n) {
  if (n.getType() == Token.LABEL) {
    Node labeledStatement = n.getLastChild();
    if (labeledStatement.getType() != Token.BLOCK) {
      return isOneExactlyFunctionOrDo(labeledStatement);
    } else {
      // For labels with block children, we need to ensure that a
      // labeled FUNCTION or DO isn't generated when extraneous BLOCKs
      // are skipped.
      if (getNonEmptyChildCount(n, 2) == 1) {
        return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n));
      } else {
        // Either a empty statement or an block with more than one child,
        // way it isn't a FUNCTION or DO.
        return false;
      }
    }
  } else {
    return (n.getType() == Token.FUNCTION || n.getType() == Token.DO);
  }
}

What steps will reproduce the problem?
Try to compile this code with the closure compiler :
var i = 0;
lab1: do{
    lab2: do{
        i++;
        if (1) {
            break lab2;
        } else {
            break lab1;
        }
    } while(false);
} while(false);

console.log(i);

What is the expected output? What do you see instead?
The generated code produced is :
var a=0;do b:do{a++;break b}while(0);while(0);console.log(a);

Which works on all browsers except IE (Looks like IE doesn't like
the missing brackets just after the first do instruction).

What version of the product are you using? On what operating system?
I am using the version of Jun 16 (latest) on ubuntu 10

Please provide any additional information below.
Strangely, this bug doesn't happen when I use PRETTY_PRINT formatting option.


public TypePair getTypesUnderInequality(JSType that) {
  // unions types
  if (that instanceof UnionType) {
    TypePair p = that.getTypesUnderInequality(this);
    return new TypePair(p.typeB, p.typeA);
  }

  // other types
  switch (this.testForEquality(that)) {
    case TRUE:
      return new TypePair(null, null);

    case FALSE:
    case UNKNOWN:
      return new TypePair(this, that);
  }

  // switch case is exhaustive
  throw new IllegalStateException();
}

public TypePair getTypesUnderInequality(JSType that) {
  // unions types
  if (that instanceof UnionType) {
    TypePair p = that.getTypesUnderInequality(this);
    return new TypePair(p.typeB, p.typeA);
  }

  // other types
  switch (this.testForEquality(that)) {
    case TRUE:
      JSType noType = getNativeType(JSTypeNative.NO_TYPE);
      return new TypePair(noType, noType);

    case FALSE:
    case UNKNOWN:
      return new TypePair(this, that);
  }

  // switch case is exhaustive
  throw new IllegalStateException();
}

What steps will reproduce the problem?

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name default.js
// ==/ClosureCompiler==

/** @param {string} x */
function g(x) {}

/** @param {undefined} x */
function f(x) {
  if (x != undefined) { g(x); }
}

What is the expected output? What do you see instead?

JSC_DETERMINISTIC_TEST: condition always evaluates to false
left : undefined
right: undefined at line 6 character 6
if (x != undefined) { g(x); }
      ^
JSC_TYPE_MISMATCH: actual parameter 1 of g does not match formal parameter
found   : undefined
required: string at line 6 character 24
if (x != undefined) { g(x); }
                        ^

the second warning is bogus.


public boolean apply(Node n) {
  // When the node is null it means, we reached the implicit return
  // where the function returns (possibly without an return statement)
  if (n == null) {
    return false;
  }

  // TODO(user): We only care about calls to functions that
  // passes one of the dependent variable to a non-side-effect free
  // function.
  if (n.isCall() && NodeUtil.functionCallHasSideEffects(n)) {
    return true;
  }

  if (n.isNew() && NodeUtil.constructorCallHasSideEffects(n)) {
    return true;
  }

  for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
    if (!ControlFlowGraph.isEnteringNewCfgNode(c) && apply(c)) {
      return true;
    }
  }
  return false;
}

public boolean apply(Node n) {
  // When the node is null it means, we reached the implicit return
  // where the function returns (possibly without an return statement)
  if (n == null) {
    return false;
  }

  // TODO(user): We only care about calls to functions that
  // passes one of the dependent variable to a non-side-effect free
  // function.
  if (n.isCall() && NodeUtil.functionCallHasSideEffects(n)) {
    return true;
  }

  if (n.isNew() && NodeUtil.constructorCallHasSideEffects(n)) {
    return true;
  }

  if (n.isDelProp()) {
    return true;
  }

  for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
    if (!ControlFlowGraph.isEnteringNewCfgNode(c) && apply(c)) {
      return true;
    }
  }
  return false;
}

Switched order of "delete key" and "key in" statements changes semantic

// Input:

var customData = { key: 'value' };

function testRemoveKey( key ) {
	var dataSlot = customData,
		retval = dataSlot && dataSlot[ key ],
		hadKey = dataSlot && ( key in dataSlot );

	if ( dataSlot )
		delete dataSlot[ key ];

	return hadKey ? retval : null;
};

console.log( testRemoveKey( 'key' ) ); // 'value'
console.log( 'key' in customData ); // false

// Compiled version:

var customData={key:"value"};function testRemoveKey(b){var a=customData,c=a&&a[b];a&&delete a[b];return a&&b in a?c:null}console.log(testRemoveKey("key"));console.log("key"in customData);

// null
// false

"b in a" is executed after "delete a[b]" what obviously doesn't make sense in this case.

Reproducible on: http://closure-compiler.appspot.com/home and in "Version: 20120430 (revision 1918) Built on: 2012/04/30 18:02"


@Override public void visit(NodeTraversal t, Node n, Node parent) {
  if (n == scope.getRootNode()) return;

  if (n.getType() == Token.LP && parent == scope.getRootNode()) {
    handleFunctionInputs(parent);
    return;
  }

  attachLiteralTypes(n);
  switch (n.getType()) {
    case Token.FUNCTION:
      if (parent.getType() == Token.NAME) {
        return;
      }
      defineDeclaredFunction(n, parent);
      break;
    case Token.CATCH:
      defineCatch(n, parent);
      break;
    case Token.VAR:
      defineVar(n, parent);
      break;
  }
}

@Override public void visit(NodeTraversal t, Node n, Node parent) {
  if (n == scope.getRootNode()) return;

  if (n.getType() == Token.LP && parent == scope.getRootNode()) {
    handleFunctionInputs(parent);
    return;
  }

  super.visit(t, n, parent);
}

Type checker misses annotations on functions defined within functions

What steps will reproduce the problem?
1. Compile the following code under --warning_level VERBOSE

var ns = {};

/** @param {string=} b */
ns.a = function(b) {}

function d() {
    ns.a();
    ns.a(123);
}

2. Observe that the type checker correctly emits one warning, as 123
doesn't match the type {string}

3. Now compile the code with ns.a defined within an anonymous function,
like so:

var ns = {};

(function() {
    /** @param {string=} b */
    ns.a = function(b) {}
})();

function d() {
    ns.a();
    ns.a(123);
}

4. Observe that a warning is emitted for calling ns.a with 0 parameters, and
not for the type error, as though the @param declaration were ignored.

What version of the product are you using? On what operating system?
r15

Please provide any additional information below.

This sort of module pattern is common enough that it strikes me as worth
supporting.

One last note to make matters stranger: if the calling code isn't itself within
a function, no warnings are emitted at all:

var ns = {};

(function() {
    /** @param {string=} b */
    ns.a = function(b) {}
})();

ns.a();
ns.a(123);


private void findCalledFunctions(
    Node node, Set<String> changed) {
  Preconditions.checkArgument(changed != null);
  // For each referenced function, add a new reference
  if (node.getType() == Token.CALL) {
    Node child = node.getFirstChild();
    if (child.getType() == Token.NAME) {
      changed.add(child.getString());
    }
  }

  for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
    findCalledFunctions(c, changed);
  }
}

private void findCalledFunctions(
    Node node, Set<String> changed) {
  Preconditions.checkArgument(changed != null);
  // For each referenced function, add a new reference
  if (node.getType() == Token.NAME) {
    if (isCandidateUsage(node)) {
      changed.add(node.getString());
    }
  }

  for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
    findCalledFunctions(c, changed);
  }
}

Closure Compiler failed to translate all instances of a function name

What steps will reproduce the problem?
1. Compile the attached jQuery Multicheck plugin using SIMPLE optimization.

What is the expected output? What do you see instead?
You expect that the function preload_check_all() gets its name translated appropriately. In fact, the Closure Compiler breaks the code by changing the function declaration but NOT changing the call to the function on line 76.


private Node tryFoldArrayAccess(Node n, Node left, Node right) {
  Node parent = n.getParent();
  // If GETPROP/GETELEM is used as assignment target the array literal is
  // acting as a temporary we can't fold it here:
  //    "[][0] += 1"

  if (right.getType() != Token.NUMBER) {
    // Sometimes people like to use complex expressions to index into
    // arrays, or strings to index into array methods.
    return n;
  }

  double index = right.getDouble();
  int intIndex = (int) index;
  if (intIndex != index) {
    error(INVALID_GETELEM_INDEX_ERROR, right);
    return n;
  }

  if (intIndex < 0) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  Node elem = left.getFirstChild();
  for (int i = 0; elem != null && i < intIndex; i++) {
    elem = elem.getNext();
  }

  if (elem == null) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  if (elem.getType() == Token.EMPTY) {
    elem = NodeUtil.newUndefinedNode(elem);
  } else {
    left.removeChild(elem);
  }

  // Replace the entire GETELEM with the value
  n.getParent().replaceChild(n, elem);
  reportCodeChange();
  return elem;
}

private Node tryFoldArrayAccess(Node n, Node left, Node right) {
  Node parent = n.getParent();
  // If GETPROP/GETELEM is used as assignment target the array literal is
  // acting as a temporary we can't fold it here:
  //    "[][0] += 1"
  if (isAssignmentTarget(n)) {
    return n;
  }

  if (right.getType() != Token.NUMBER) {
    // Sometimes people like to use complex expressions to index into
    // arrays, or strings to index into array methods.
    return n;
  }

  double index = right.getDouble();
  int intIndex = (int) index;
  if (intIndex != index) {
    error(INVALID_GETELEM_INDEX_ERROR, right);
    return n;
  }

  if (intIndex < 0) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  Node elem = left.getFirstChild();
  for (int i = 0; elem != null && i < intIndex; i++) {
    elem = elem.getNext();
  }

  if (elem == null) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  if (elem.getType() == Token.EMPTY) {
    elem = NodeUtil.newUndefinedNode(elem);
  } else {
    left.removeChild(elem);
  }

  // Replace the entire GETELEM with the value
  n.getParent().replaceChild(n, elem);
  reportCodeChange();
  return elem;
}

peephole constants folding pass is trying to fold [][11] as if it were a property lookup instead of a property assignment

What steps will reproduce the problem?
1.Try on line CC with Advance
2.On the following 2-line code
3.
What is the expected output? What do you see instead?
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==

var Mdt=[];
Mdt[11] = ['22','19','19','16','21','18','16','20','17','17','21','17'];

The error:
JSC_INDEX_OUT_OF_BOUNDS_ERROR: Array index out of bounds: NUMBER 11.0
2 [sourcename: Input_0] : number at line 2 character 4

What version of the product are you using? On what operating system?
The online version on 201.07.27


public void matchConstraint(JSType constraint) {
  // We only want to match constraints on anonymous types.
  if (hasReferenceName()) {
    return;
  }

  // Handle the case where the constraint object is a record type.
  //
  // param constraint {{prop: (number|undefined)}}
  // function f(constraint) {}
  // f({});
  //
  // We want to modify the object literal to match the constraint, by
  // taking any each property on the record and trying to match
  // properties on this object.
  if (constraint.isRecordType()) {
    matchRecordTypeConstraint(constraint.toObjectType());
  }
}

public void matchConstraint(JSType constraint) {
  // We only want to match constraints on anonymous types.
  if (hasReferenceName()) {
    return;
  }

  // Handle the case where the constraint object is a record type.
  //
  // param constraint {{prop: (number|undefined)}}
  // function f(constraint) {}
  // f({});
  //
  // We want to modify the object literal to match the constraint, by
  // taking any each property on the record and trying to match
  // properties on this object.
  if (constraint.isRecordType()) {
    matchRecordTypeConstraint(constraint.toObjectType());
  } else if (constraint.isUnionType()) {
    for (JSType alt : constraint.toMaybeUnionType().getAlternates()) {
      if (alt.isRecordType()) {
        matchRecordTypeConstraint(alt.toObjectType());
      }
    }
  }
}

anonymous object type inference inconsistency when used in union

Code:
/** @param {{prop: string, prop2: (string|undefined)}} record */
var func = function(record) {
  window.console.log(record.prop);
}

/** @param {{prop: string, prop2: (string|undefined)}|string} record */
var func2 = function(record) {
  if (typeof record == 'string') {
    window.console.log(record);
  } else {
    window.console.log(record.prop);
  }
}

func({prop: 'a'});
func2({prop: 'a'});

errors with:
ERROR - actual parameter 1 of func2 does not match formal parameter
found   : {prop: string}
required: (string|{prop: string, prop2: (string|undefined)})
func2({prop: 'a'});

the type of the record input to func and func2 are identical but the parameters to func2 allow some other type.


private boolean isQualifiedNameInferred(
    String qName, Node n, JSDocInfo info,
    Node rhsValue, JSType valueType) {
  if (valueType == null) {
    return true;
  }

  // Prototypes of constructors and interfaces are always declared.
  if (qName != null && qName.endsWith(".prototype")) {
      return false;
  }

  boolean inferred = true;
  if (info != null) {
    inferred = !(info.hasType()
        || info.hasEnumParameterType()
        || (isConstantSymbol(info, n) && valueType != null
            && !valueType.isUnknownType())
        || FunctionTypeBuilder.isFunctionTypeDeclaration(info));
  }

  if (inferred && rhsValue != null && rhsValue.isFunction()) {
    if (info != null) {
      return false;
    } else if (!scope.isDeclared(qName, false) &&
        n.isUnscopedQualifiedName()) {

      // Check if this is in a conditional block.
      // Functions assigned in conditional blocks are inferred.
      for (Node current = n.getParent();
           !(current.isScript() || current.isFunction());
           current = current.getParent()) {
        if (NodeUtil.isControlStructure(current)) {
          return true;
        }
      }

      // Check if this is assigned in an inner scope.
      // Functions assigned in inner scopes are inferred.
      AstFunctionContents contents =
          getFunctionAnalysisResults(scope.getRootNode());
      if (contents == null ||
          !contents.getEscapedQualifiedNames().contains(qName)) {
        return false;
      }
    }
  }
  return inferred;
}

private boolean isQualifiedNameInferred(
    String qName, Node n, JSDocInfo info,
    Node rhsValue, JSType valueType) {
  if (valueType == null) {
    return true;
  }

  // Prototypes of constructors and interfaces are always declared.
  if (qName != null && qName.endsWith(".prototype")) {
    String className = qName.substring(0, qName.lastIndexOf(".prototype"));
    Var slot = scope.getSlot(className);
    JSType classType = slot == null ? null : slot.getType();
    if (classType != null
        && (classType.isConstructor() || classType.isInterface())) {
      return false;
    }
  }

  boolean inferred = true;
  if (info != null) {
    inferred = !(info.hasType()
        || info.hasEnumParameterType()
        || (isConstantSymbol(info, n) && valueType != null
            && !valueType.isUnknownType())
        || FunctionTypeBuilder.isFunctionTypeDeclaration(info));
  }

  if (inferred && rhsValue != null && rhsValue.isFunction()) {
    if (info != null) {
      return false;
    } else if (!scope.isDeclared(qName, false) &&
        n.isUnscopedQualifiedName()) {

      // Check if this is in a conditional block.
      // Functions assigned in conditional blocks are inferred.
      for (Node current = n.getParent();
           !(current.isScript() || current.isFunction());
           current = current.getParent()) {
        if (NodeUtil.isControlStructure(current)) {
          return true;
        }
      }

      // Check if this is assigned in an inner scope.
      // Functions assigned in inner scopes are inferred.
      AstFunctionContents contents =
          getFunctionAnalysisResults(scope.getRootNode());
      if (contents == null ||
          !contents.getEscapedQualifiedNames().contains(qName)) {
        return false;
      }
    }
  }
  return inferred;
}

Type of prototype property incorrectly inferred to string

What steps will reproduce the problem?
1. Compile the following code:

/** @param {Object} a */
function f(a) {
  a.prototype = '__proto';
}

/** @param {Object} a */
function g(a) {
  a.prototype = function(){};
}

What is the expected output? What do you see instead?

Should type check. Instead, gives error:

WARNING - assignment to property prototype of Object
found   : function (): undefined
required: string
  a.prototype = function(){};
  ^


private Node tryFoldSimpleFunctionCall(Node n) {
  Preconditions.checkState(n.isCall());
  Node callTarget = n.getFirstChild();
  if (callTarget != null && callTarget.isName() &&
        callTarget.getString().equals("String")) {
    // Fold String(a) to '' + (a) on immutable literals,
    // which allows further optimizations
    //
    // We can't do this in the general case, because String(a) has
    // slightly different semantics than '' + (a). See
    // http://code.google.com/p/closure-compiler/issues/detail?id=759
    Node value = callTarget.getNext();
    if (value != null) {
      Node addition = IR.add(
          IR.string("").srcref(callTarget),
          value.detachFromParent());
      n.getParent().replaceChild(n, addition);
      reportCodeChange();
      return addition;
    }
  }
  return n;
}

private Node tryFoldSimpleFunctionCall(Node n) {
  Preconditions.checkState(n.isCall());
  Node callTarget = n.getFirstChild();
  if (callTarget != null && callTarget.isName() &&
        callTarget.getString().equals("String")) {
    // Fold String(a) to '' + (a) on immutable literals,
    // which allows further optimizations
    //
    // We can't do this in the general case, because String(a) has
    // slightly different semantics than '' + (a). See
    // http://code.google.com/p/closure-compiler/issues/detail?id=759
    Node value = callTarget.getNext();
    if (value != null && value.getNext() == null &&
        NodeUtil.isImmutableValue(value)) {
      Node addition = IR.add(
          IR.string("").srcref(callTarget),
          value.detachFromParent());
      n.getParent().replaceChild(n, addition);
      reportCodeChange();
      return addition;
    }
  }
  return n;
}

String conversion optimization is incorrect

What steps will reproduce the problem?

var f = {
  valueOf: function() { return undefined; }
}
String(f)

What is the expected output? What do you see instead?

Expected output: "[object Object]"
Actual output: "undefined"

What version of the product are you using? On what operating system?

All versions (http://closure-compiler.appspot.com/ as well).

Please provide any additional information below.

The compiler optimizes String(x) calls by replacing them with x + ''. This is correct in most cases, but incorrect in corner cases like the one mentioned above.


private Node tryFoldArrayAccess(Node n, Node left, Node right) {
  Node parent = n.getParent();
  // If GETPROP/GETELEM is used as assignment target the array literal is
  // acting as a temporary we can't fold it here:
  //    "[][0] += 1"
  if (isAssignmentTarget(n)) {
    return n;
  }

  if (!right.isNumber()) {
    // Sometimes people like to use complex expressions to index into
    // arrays, or strings to index into array methods.
    return n;
  }

  double index = right.getDouble();
  int intIndex = (int) index;
  if (intIndex != index) {
    error(INVALID_GETELEM_INDEX_ERROR, right);
    return n;
  }

  if (intIndex < 0) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  Node current = left.getFirstChild();
  Node elem = null;
  for (int i = 0; current != null && i < intIndex; i++) {
      elem = current;

    current = current.getNext();
  }

  if (elem == null) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  if (elem.isEmpty()) {
    elem = NodeUtil.newUndefinedNode(elem);
  } else {
    left.removeChild(elem);
  }

  // Replace the entire GETELEM with the value
  n.getParent().replaceChild(n, elem);
  reportCodeChange();
  return elem;
}

private Node tryFoldArrayAccess(Node n, Node left, Node right) {
  Node parent = n.getParent();
  // If GETPROP/GETELEM is used as assignment target the array literal is
  // acting as a temporary we can't fold it here:
  //    "[][0] += 1"
  if (isAssignmentTarget(n)) {
    return n;
  }

  if (!right.isNumber()) {
    // Sometimes people like to use complex expressions to index into
    // arrays, or strings to index into array methods.
    return n;
  }

  double index = right.getDouble();
  int intIndex = (int) index;
  if (intIndex != index) {
    error(INVALID_GETELEM_INDEX_ERROR, right);
    return n;
  }

  if (intIndex < 0) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  Node current = left.getFirstChild();
  Node elem = null;
  for (int i = 0; current != null; i++) {
    if (i != intIndex) {
      if (mayHaveSideEffects(current)) {
        return n;
      }
    } else {
      elem = current;
    }

    current = current.getNext();
  }

  if (elem == null) {
    error(INDEX_OUT_OF_BOUNDS_ERROR, right);
    return n;
  }

  if (elem.isEmpty()) {
    elem = NodeUtil.newUndefinedNode(elem);
  } else {
    left.removeChild(elem);
  }

  // Replace the entire GETELEM with the value
  n.getParent().replaceChild(n, elem);
  reportCodeChange();
  return elem;
}

tryFoldArrayAccess does not check for side effects

What steps will reproduce the problem?
1. Compile the following program with simple or advanced optimization:
console.log([console.log('hello, '), 'world!'][1]);

What is the expected output? What do you see instead?
The expected output would preserve side effects. It would not transform the program at all or transform it into:

console.log((console.log("hello"), "world!"));

Instead, the program is transformed into:

console.log("world!");

What version of the product are you using? On what operating system?
Revision 2022. Ubuntu 12.04.

Please provide any additional information below.
tryFoldArrayAccess in com.google.javascript.jscomp.PeepholeFoldConstants should check whether   every array element that is not going to be preserved has no side effects.


private void findAliases(NodeTraversal t) {
  Scope scope = t.getScope();
  for (Var v : scope.getVarIterable()) {
    Node n = v.getNode();
    int type = n.getType();
    Node parent = n.getParent();
    if (parent.isVar()) {
      if (n.hasChildren() && n.getFirstChild().isQualifiedName()) {
      String name = n.getString();
      Var aliasVar = scope.getVar(name);
      aliases.put(name, aliasVar);

      String qualifiedName =
          aliasVar.getInitialValue().getQualifiedName();
      transformation.addAlias(name, qualifiedName);
      // Bleeding functions already get a BAD_PARAMETERS error, so just
      // do nothing.
      // Parameters of the scope function also get a BAD_PARAMETERS
      // error.
    } else {
      // TODO(robbyw): Support using locals for private variables.
      report(t, n, GOOG_SCOPE_NON_ALIAS_LOCAL, n.getString());
    }
    }
  }
}

private void findAliases(NodeTraversal t) {
  Scope scope = t.getScope();
  for (Var v : scope.getVarIterable()) {
    Node n = v.getNode();
    int type = n.getType();
    Node parent = n.getParent();
    if (parent.isVar() &&
        n.hasChildren() && n.getFirstChild().isQualifiedName()) {
      String name = n.getString();
      Var aliasVar = scope.getVar(name);
      aliases.put(name, aliasVar);

      String qualifiedName =
          aliasVar.getInitialValue().getQualifiedName();
      transformation.addAlias(name, qualifiedName);
    } else if (v.isBleedingFunction()) {
      // Bleeding functions already get a BAD_PARAMETERS error, so just
      // do nothing.
    } else if (parent.getType() == Token.LP) {
      // Parameters of the scope function also get a BAD_PARAMETERS
      // error.
    } else {
      // TODO(robbyw): Support using locals for private variables.
      report(t, n, GOOG_SCOPE_NON_ALIAS_LOCAL, n.getString());
    }
  }
}

goog.scope doesn't properly check declared functions

The following code is a compiler error:

goog.scope(function() {
  var x = function(){};
});

but the following code is not:

goog.scope(function() {
  function x() {}
});

Both code snippets should be a compiler error, because they prevent the goog.scope from being unboxed.


private FlowScope traverseNew(Node n, FlowScope scope) {

  Node constructor = n.getFirstChild();
  scope = traverse(constructor, scope);
  JSType constructorType = constructor.getJSType();
  JSType type = null;
  if (constructorType != null) {
    constructorType = constructorType.restrictByNotNullOrUndefined();
    if (constructorType.isUnknownType()) {
      type = getNativeType(UNKNOWN_TYPE);
    } else {
      FunctionType ct = constructorType.toMaybeFunctionType();
      if (ct == null && constructorType instanceof FunctionType) {
        // If constructorType is a NoObjectType, then toMaybeFunctionType will
        // return null. But NoObjectType implements the FunctionType
        // interface, precisely because it can validly construct objects.
        ct = (FunctionType) constructorType;
      }
      if (ct != null && ct.isConstructor()) {
        type = ct.getInstanceType();
      }
    }
  }
  n.setJSType(type);
  for (Node arg = constructor.getNext(); arg != null; arg = arg.getNext()) {
    scope = traverse(arg, scope);
  }
  return scope;
}

private FlowScope traverseNew(Node n, FlowScope scope) {
  scope = traverseChildren(n, scope);

  Node constructor = n.getFirstChild();
  JSType constructorType = constructor.getJSType();
  JSType type = null;
  if (constructorType != null) {
    constructorType = constructorType.restrictByNotNullOrUndefined();
    if (constructorType.isUnknownType()) {
      type = getNativeType(UNKNOWN_TYPE);
    } else {
      FunctionType ct = constructorType.toMaybeFunctionType();
      if (ct == null && constructorType instanceof FunctionType) {
        // If constructorType is a NoObjectType, then toMaybeFunctionType will
        // return null. But NoObjectType implements the FunctionType
        // interface, precisely because it can validly construct objects.
        ct = (FunctionType) constructorType;
      }
      if (ct != null && ct.isConstructor()) {
        type = ct.getInstanceType();
        backwardsInferenceFromCallSite(n, ct);
      }
    }
  }
  n.setJSType(type);
  return scope;
}

anonymous object type inference behavior is different when calling constructors

The following compiles fine with:
java -jar build/compiler.jar --compilation_level=ADVANCED_OPTIMIZATIONS --jscomp_error=accessControls --jscomp_error=checkTypes --jscomp_error=checkVars --js ~/Desktop/reverse.js

reverse.js:
/**
 * @param {{prop1: string, prop2: (number|undefined)}} parry
 */
function callz(parry) {
  if (parry.prop2 && parry.prop2 < 5) alert('alright!');
  alert(parry.prop1);
}

callz({prop1: 'hi'});

However, the following does not:
/**
 * @param {{prop1: string, prop2: (number|undefined)}} parry
 * @constructor
 */
function callz(parry) {
  if (parry.prop2 && parry.prop2 < 5) alert('alright!');
  alert(parry.prop1);
}

new callz({prop1: 'hi'});

/Users/dolapo/Desktop/reverse.js:10: ERROR - actual parameter 1 of callz does not match formal parameter
found   : {prop1: string}
required: {prop1: string, prop2: (number|undefined)}
new callz({prop1: 'hi'});

Thanks!


private ExtractionInfo extractMultilineTextualBlock(JsDocToken token,
                                                    WhitespaceOption option) {

  if (token == JsDocToken.EOC || token == JsDocToken.EOL ||
      token == JsDocToken.EOF) {
    return new ExtractionInfo("", token);
  }

  stream.update();
  int startLineno = stream.getLineno();
  int startCharno = stream.getCharno() + 1;

  // Read the content from the first line.
  String line = stream.getRemainingJSDocLine();
  if (option != WhitespaceOption.PRESERVE) {
    line = line.trim();
  }

  StringBuilder builder = new StringBuilder();
  builder.append(line);

  state = State.SEARCHING_ANNOTATION;
  token = next();

  boolean ignoreStar = false;

  // Track the start of the line to count whitespace that
  // the tokenizer skipped. Because this case is rare, it's easier
  // to do this here than in the tokenizer.

  do {
    switch (token) {
      case STAR:
        if (ignoreStar) {
          // Mark the position after the star as the new start of the line.
        } else {
          // The star is part of the comment.
          if (builder.length() > 0) {
            builder.append(' ');
          }

          builder.append('*');
        }

        token = next();
        continue;

      case EOL:
        if (option != WhitespaceOption.SINGLE_LINE) {
          builder.append("\n");
        }

        ignoreStar = true;
        token = next();
        continue;

      default:
        ignoreStar = false;
        state = State.SEARCHING_ANNOTATION;

            // All tokens must be separated by a space.

        if (token == JsDocToken.EOC ||
            token == JsDocToken.EOF ||
            // When we're capturing a license block, annotations
            // in the block are ok.
            (token == JsDocToken.ANNOTATION &&
             option != WhitespaceOption.PRESERVE)) {
          String multilineText = builder.toString();

          if (option != WhitespaceOption.PRESERVE) {
            multilineText = multilineText.trim();
          }

          int endLineno = stream.getLineno();
          int endCharno = stream.getCharno();

          if (multilineText.length() > 0) {
            jsdocBuilder.markText(multilineText, startLineno, startCharno,
                endLineno, endCharno);
          }

          return new ExtractionInfo(multilineText, token);
        }

        if (builder.length() > 0) {
          builder.append(' ');
        }
        builder.append(toString(token));

        line = stream.getRemainingJSDocLine();

        if (option != WhitespaceOption.PRESERVE) {
          line = trimEnd(line);
        }

        builder.append(line);
        token = next();
    }
  } while (true);
}

private ExtractionInfo extractMultilineTextualBlock(JsDocToken token,
                                                    WhitespaceOption option) {

  if (token == JsDocToken.EOC || token == JsDocToken.EOL ||
      token == JsDocToken.EOF) {
    return new ExtractionInfo("", token);
  }

  stream.update();
  int startLineno = stream.getLineno();
  int startCharno = stream.getCharno() + 1;

  // Read the content from the first line.
  String line = stream.getRemainingJSDocLine();
  if (option != WhitespaceOption.PRESERVE) {
    line = line.trim();
  }

  StringBuilder builder = new StringBuilder();
  builder.append(line);

  state = State.SEARCHING_ANNOTATION;
  token = next();

  boolean ignoreStar = false;

  // Track the start of the line to count whitespace that
  // the tokenizer skipped. Because this case is rare, it's easier
  // to do this here than in the tokenizer.
  int lineStartChar = -1;

  do {
    switch (token) {
      case STAR:
        if (ignoreStar) {
          // Mark the position after the star as the new start of the line.
          lineStartChar = stream.getCharno() + 1;
        } else {
          // The star is part of the comment.
          if (builder.length() > 0) {
            builder.append(' ');
          }

          builder.append('*');
        }

        token = next();
        continue;

      case EOL:
        if (option != WhitespaceOption.SINGLE_LINE) {
          builder.append("\n");
        }

        ignoreStar = true;
        lineStartChar = 0;
        token = next();
        continue;

      default:
        ignoreStar = false;
        state = State.SEARCHING_ANNOTATION;

        boolean isEOC = token == JsDocToken.EOC;
        if (!isEOC) {
          if (lineStartChar != -1 && option == WhitespaceOption.PRESERVE) {
            int numSpaces = stream.getCharno() - lineStartChar;
            for (int i = 0; i < numSpaces; i++) {
              builder.append(' ');
            }
            lineStartChar = -1;
          } else if (builder.length() > 0) {
            // All tokens must be separated by a space.
            builder.append(' ');
          }
        }

        if (token == JsDocToken.EOC ||
            token == JsDocToken.EOF ||
            // When we're capturing a license block, annotations
            // in the block are ok.
            (token == JsDocToken.ANNOTATION &&
             option != WhitespaceOption.PRESERVE)) {
          String multilineText = builder.toString();

          if (option != WhitespaceOption.PRESERVE) {
            multilineText = multilineText.trim();
          }

          int endLineno = stream.getLineno();
          int endCharno = stream.getCharno();

          if (multilineText.length() > 0) {
            jsdocBuilder.markText(multilineText, startLineno, startCharno,
                endLineno, endCharno);
          }

          return new ExtractionInfo(multilineText, token);
        }

        builder.append(toString(token));

        line = stream.getRemainingJSDocLine();

        if (option != WhitespaceOption.PRESERVE) {
          line = trimEnd(line);
        }

        builder.append(line);
        token = next();
    }
  } while (true);
}

Preserve doesn't preserve whitespace at start of line

What steps will reproduce the problem?

Code such as:
/**
 * @preserve

This
  was
    ASCII
       Art

*/

What is the expected output? What do you see instead?

The words line up on the left:
/*
This
was
ASCII
Art
*/

What version of the product are you using? On what operating system?

Live web verison.

Please provide any additional information below.


public void matchConstraint(ObjectType constraintObj) {
  // We only want to match contraints on anonymous types.

  // Handle the case where the constraint object is a record type.
  //
  // param constraintObj {{prop: (number|undefined)}}
  // function f(constraintObj) {}
  // f({});
  //
  // We want to modify the object literal to match the constraint, by
  // taking any each property on the record and trying to match
  // properties on this object.
  if (constraintObj.isRecordType()) {
    for (String prop : constraintObj.getOwnPropertyNames()) {
      JSType propType = constraintObj.getPropertyType(prop);
      if (!isPropertyTypeDeclared(prop)) {
        JSType typeToInfer = propType;
        if (!hasProperty(prop)) {
          typeToInfer = getNativeType(JSTypeNative.VOID_TYPE)
              .getLeastSupertype(propType);
        }
        defineInferredProperty(prop, typeToInfer, null);
      }
    }
  }
}

public void matchConstraint(ObjectType constraintObj) {
  // We only want to match contraints on anonymous types.
  if (hasReferenceName()) {
    return;
  }

  // Handle the case where the constraint object is a record type.
  //
  // param constraintObj {{prop: (number|undefined)}}
  // function f(constraintObj) {}
  // f({});
  //
  // We want to modify the object literal to match the constraint, by
  // taking any each property on the record and trying to match
  // properties on this object.
  if (constraintObj.isRecordType()) {
    for (String prop : constraintObj.getOwnPropertyNames()) {
      JSType propType = constraintObj.getPropertyType(prop);
      if (!isPropertyTypeDeclared(prop)) {
        JSType typeToInfer = propType;
        if (!hasProperty(prop)) {
          typeToInfer = getNativeType(JSTypeNative.VOID_TYPE)
              .getLeastSupertype(propType);
        }
        defineInferredProperty(prop, typeToInfer, null);
      }
    }
  }
}

weird object literal invalid property error on unrelated object prototype

Apologies in advance for the convoluted repro case and the vague summary.

Compile the following code (attached as repro.js) with:
java -jar build/compiler.jar --compilation_level=ADVANCED_OPTIMIZATIONS --jscomp_error=accessControls --jscomp_error=checkTypes --jscomp_error=checkVars --js repro.js *

/**
 * @param {{text: string}} opt_data
 * @return {string}
 */
function temp1(opt_data) {
  return opt_data.text;
}

/**
 * @param {{activity: (boolean|number|string|null|Object)}} opt_data
 * @return {string}
 */
function temp2(opt_data) {
  /** @notypecheck */
  function __inner() {
    return temp1(opt_data.activity);
  }
  return __inner();
}

/**
 * @param {{n: number, text: string, b: boolean}} opt_data
 * @return {string}
 */
function temp3(opt_data) {
  return 'n: ' + opt_data.n + ', t: ' + opt_data.text + '.';
}

function callee() {
  var output = temp3({
    n: 0,
    text: 'a string',
    b: true
  })
  alert(output);
}

callee();

yields:
repro.js:30: ERROR - actual parameter 1 of temp3 does not match formal parameter
found   : {b: boolean, n: number, text: (string|undefined)}
required: {b: boolean, n: number, text: string}
  var output = temp3({

It seems like temp3 is actually being called with the right type {b: boolean, n: number, text: string} though it seems to think that text is a (string|undefined)
This seems to happen because of the seemingly unrelated code in functions temp1 and temp2. If I change the name of the text property (as in repro3.js) it works.
Additionally, if I fix the type of the activity property in the record type of temp2 it works (as in repro2.js)

This comes up in our codebase in some situations where we don't have type info for all the objects being passed into a function. It's always a tricky one to find because it reports an error at a location that looks correct.

* it also fails with SIMPLE_OPTIMIZATIONS


private void inferPropertyTypesToMatchConstraint(
    JSType type, JSType constraint) {
  if (type == null || constraint == null) {
    return;
  }

  ObjectType constraintObj =
      ObjectType.cast(constraint.restrictByNotNullOrUndefined());
  if (constraintObj != null && constraintObj.isRecordType()) {
    ObjectType objType = ObjectType.cast(type.restrictByNotNullOrUndefined());
    if (objType != null) {
      for (String prop : constraintObj.getOwnPropertyNames()) {
        JSType propType = constraintObj.getPropertyType(prop);
        if (!objType.isPropertyTypeDeclared(prop)) {
          JSType typeToInfer = propType;
          if (!objType.hasProperty(prop)) {
            typeToInfer =
                getNativeType(VOID_TYPE).getLeastSupertype(propType);
          }
          objType.defineInferredProperty(prop, typeToInfer, null);
        }
      }
    }
  }
}

private void inferPropertyTypesToMatchConstraint(
    JSType type, JSType constraint) {
  if (type == null || constraint == null) {
    return;
  }

  ObjectType constraintObj =
      ObjectType.cast(constraint.restrictByNotNullOrUndefined());
  if (constraintObj != null) {
    type.matchConstraint(constraintObj);
  }
}

assignment to object in conditional causes type error on function w/ record type return type

slightly dodgy code :)

/** @returns {{prop1: (Object|undefined), prop2: (string|undefined), prop3: (string|undefined)}} */
function func(a, b) {
  var results;
  if (a) {
    results = {};
    results.prop1 = {a: 3};
  }
  if (b) {
    results = results || {};
    results.prop2 = 'prop2';
  } else {
    results = results || {};
    results.prop3 = 'prop3';
  }
  return results;
}
results in this error:

JSC_TYPE_MISMATCH: inconsistent return type
found   : ({prop1: {a: number}}|{})
required: {prop1: (Object|null|undefined), prop2: (string|undefined), prop3: (string|undefined)} at line 18 character 7
return results;

defining results on the first line on the function causes it the world.
the still dodgy, but slightly less so, use of this is if the function return type were that record type|undefined and not all branches were guaranteed to be executed.


private boolean canInline(
    Reference declaration,
    Reference initialization,
    Reference reference) {
  if (!isValidDeclaration(declaration)
      || !isValidInitialization(initialization)
      || !isValidReference(reference)) {
    return false;
  }

  // If the value is read more than once, skip it.
  // VAR declarations and EXPR_RESULT don't need the value, but other
  // ASSIGN expressions parents do.
  if (declaration != initialization &&
      !initialization.getGrandparent().isExprResult()) {
    return false;
  }

  // Be very conservative and do no cross control structures or
  // scope boundaries
  if (declaration.getBasicBlock() != initialization.getBasicBlock()
      || declaration.getBasicBlock() != reference.getBasicBlock()) {
    return false;
  }

  // Do not inline into a call node. This would change
  // the context in which it was being called. For example,
  //   var a = b.c;
  //   a();
  // should not be inlined, because it calls a in the context of b
  // rather than the context of the window.
  //   var a = b.c;
  //   f(a)
  // is ok.
  Node value = initialization.getAssignedValue();
  Preconditions.checkState(value != null);
  if (value.isGetProp()
      && reference.getParent().isCall()
      && reference.getParent().getFirstChild() == reference.getNode()) {
    return false;
  }

  if (value.isFunction()) {
    Node callNode = reference.getParent();
    if (reference.getParent().isCall()) {
      CodingConvention convention = compiler.getCodingConvention();
      // Bug 2388531: Don't inline subclass definitions into class defining
      // calls as this confused class removing logic.
      SubclassRelationship relationship =
          convention.getClassesDefinedByCall(callNode);
      if (relationship != null) {
        return false;
      }

      // issue 668: Don't inline singleton getter methods
      // calls as this confused class removing logic.
    }
  }

  return canMoveAggressively(value) ||
      canMoveModerately(initialization, reference);
}

private boolean canInline(
    Reference declaration,
    Reference initialization,
    Reference reference) {
  if (!isValidDeclaration(declaration)
      || !isValidInitialization(initialization)
      || !isValidReference(reference)) {
    return false;
  }

  // If the value is read more than once, skip it.
  // VAR declarations and EXPR_RESULT don't need the value, but other
  // ASSIGN expressions parents do.
  if (declaration != initialization &&
      !initialization.getGrandparent().isExprResult()) {
    return false;
  }

  // Be very conservative and do no cross control structures or
  // scope boundaries
  if (declaration.getBasicBlock() != initialization.getBasicBlock()
      || declaration.getBasicBlock() != reference.getBasicBlock()) {
    return false;
  }

  // Do not inline into a call node. This would change
  // the context in which it was being called. For example,
  //   var a = b.c;
  //   a();
  // should not be inlined, because it calls a in the context of b
  // rather than the context of the window.
  //   var a = b.c;
  //   f(a)
  // is ok.
  Node value = initialization.getAssignedValue();
  Preconditions.checkState(value != null);
  if (value.isGetProp()
      && reference.getParent().isCall()
      && reference.getParent().getFirstChild() == reference.getNode()) {
    return false;
  }

  if (value.isFunction()) {
    Node callNode = reference.getParent();
    if (reference.getParent().isCall()) {
      CodingConvention convention = compiler.getCodingConvention();
      // Bug 2388531: Don't inline subclass definitions into class defining
      // calls as this confused class removing logic.
      SubclassRelationship relationship =
          convention.getClassesDefinedByCall(callNode);
      if (relationship != null) {
        return false;
      }

      // issue 668: Don't inline singleton getter methods
      // calls as this confused class removing logic.
      if (convention.getSingletonGetterClassName(callNode) != null) {
        return false;
      }
    }
  }

  return canMoveAggressively(value) ||
      canMoveModerately(initialization, reference);
}

goog.addSingletonGetter prevents unused class removal

What steps will reproduce the problem?

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name default.js
// @use_closure_library true
// @formatting pretty_print,print_input_delimiter
// @warning_level VERBOSE
// @debug true
// ==/ClosureCompiler==

goog.provide('foo');

var foo = function() { this.values = []; };
goog.addSingletonGetter(foo);

foo.prototype.add = function(value) {this.values.push(value)};

What is the expected output? What do you see instead?

Expect: The code is completely removed.

Instead:

(function($ctor$$) {
  $ctor$$.$getInstance$ = function $$ctor$$$$getInstance$$() {
    return $ctor$$.$instance_$ || ($ctor$$.$instance_$ = new $ctor$$)
  }
})(function() {
});

What version of the product are you using? On what operating system?

http://closure-compiler.appspot.com on Feb 28, 2012

Please provide any additional information below.


void addNumber(double x) {
  // This is not pretty printing. This is to prevent misparsing of x- -4 as
  // x--4 (which is a syntax error).
  char prev = getLastChar();
  boolean negativeZero = isNegativeZero(x);
  if (x < 0 && prev == '-') {
    add(" ");
  }

  if ((long) x == x && !negativeZero) {
    long value = (long) x;
    long mantissa = value;
    int exp = 0;
    if (Math.abs(x) >= 100) {
      while (mantissa / 10 * Math.pow(10, exp + 1) == value) {
        mantissa /= 10;
        exp++;
      }
    }
    if (exp > 2) {
      add(Long.toString(mantissa) + "E" + Integer.toString(exp));
    } else {
      add(Long.toString(value));
    }
  } else {
    add(String.valueOf(x));
  }
}

void addNumber(double x) {
  // This is not pretty printing. This is to prevent misparsing of x- -4 as
  // x--4 (which is a syntax error).
  char prev = getLastChar();
  boolean negativeZero = isNegativeZero(x);
  if ((x < 0 || negativeZero) && prev == '-') {
    add(" ");
  }

  if ((long) x == x && !negativeZero) {
    long value = (long) x;
    long mantissa = value;
    int exp = 0;
    if (Math.abs(x) >= 100) {
      while (mantissa / 10 * Math.pow(10, exp + 1) == value) {
        mantissa /= 10;
        exp++;
      }
    }
    if (exp > 2) {
      add(Long.toString(mantissa) + "E" + Integer.toString(exp));
    } else {
      add(Long.toString(value));
    }
  } else {
    add(String.valueOf(x));
  }
}

Identifier minus a negative number needs a space between the "-"s

What steps will reproduce the problem?
1. Compile the attached file with      java -jar build/compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js bulletfail.js --js_output_file cc.js
2. Try to run the file in a JS engine, for example           node cc.js

What is the expected output? What do you see instead?

The file does not parse properly, because it contains

  g--0.0

This is subtraction of a negative number, but it looks like JS engines interpret it as decrementing g, and then fail to parse the 0.0. (g- -0.0, with a space, would parse ok.)

What version of the product are you using? On what operating system?

Trunk closure compiler on Ubuntu

Please provide any additional information below.


String toStringHelper(boolean forAnnotations) {
  if (hasReferenceName()) {
    return getReferenceName();
  } else if (prettyPrint) {
    // Don't pretty print recursively.
    prettyPrint = false;

    // Use a tree set so that the properties are sorted.
    Set<String> propertyNames = Sets.newTreeSet();
    for (ObjectType current = this;
         current != null && !current.isNativeObjectType() &&
             propertyNames.size() <= MAX_PRETTY_PRINTED_PROPERTIES;
         current = current.getImplicitPrototype()) {
      propertyNames.addAll(current.getOwnPropertyNames());
    }

    StringBuilder sb = new StringBuilder();
    sb.append("{");

    int i = 0;
    for (String property : propertyNames) {
      if (i > 0) {
        sb.append(", ");
      }

      sb.append(property);
      sb.append(": ");
      sb.append(getPropertyType(property).toString());

      ++i;
      if (i == MAX_PRETTY_PRINTED_PROPERTIES) {
        sb.append(", ...");
        break;
      }
    }

    sb.append("}");

    prettyPrint = true;
    return sb.toString();
  } else {
    return "{...}";
  }
}

String toStringHelper(boolean forAnnotations) {
  if (hasReferenceName()) {
    return getReferenceName();
  } else if (prettyPrint) {
    // Don't pretty print recursively.
    prettyPrint = false;

    // Use a tree set so that the properties are sorted.
    Set<String> propertyNames = Sets.newTreeSet();
    for (ObjectType current = this;
         current != null && !current.isNativeObjectType() &&
             propertyNames.size() <= MAX_PRETTY_PRINTED_PROPERTIES;
         current = current.getImplicitPrototype()) {
      propertyNames.addAll(current.getOwnPropertyNames());
    }

    StringBuilder sb = new StringBuilder();
    sb.append("{");

    int i = 0;
    for (String property : propertyNames) {
      if (i > 0) {
        sb.append(", ");
      }

      sb.append(property);
      sb.append(": ");
      sb.append(getPropertyType(property).toStringHelper(forAnnotations));

      ++i;
      if (!forAnnotations && i == MAX_PRETTY_PRINTED_PROPERTIES) {
        sb.append(", ...");
        break;
      }
    }

    sb.append("}");

    prettyPrint = true;
    return sb.toString();
  } else {
    return forAnnotations ? "?" : "{...}";
  }
}

externExport with @typedef can generate invalid externs

What steps will reproduce the problem?
1. Create a file that has a @typedef and code referencing the type def above and below the typedef declaration.
2. Run the closure compiler and grab the externExport string stored on the last result for review.
3. I have attached both source and output files displaying the issue.

What is the expected output? What do you see instead?

The code above the @typedef references the aliased name of the @typedef as expected however the code below the @typedef tries embedding the body of the @typedef and ends up truncating it if the length is too long with a "...". This throws bad type errors when compiling against this extern. What is odd is this only seems to be the case when the parameter with the type is optional. When neither are optional it embeds the types, which is not a big deal, except when types are long; they get truncated and throw errors.

What version of the product are you using? On what operating system?

plovr built from revision 3103:d6db24beeb7f
Revision numbers for embedded Closure Tools:
Closure Library:    1374
Closure Compiler:   1559
Closure Templates:    23

Please provide any additional information below.


JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
  // TODO(user): Investigate whether it is really necessary to keep two
  // different mechanisms for resolving named types, and if so, which order
  // makes more sense. Now, resolution via registry is first in order to
  // avoid triggering the warnings built into the resolution via properties.
  boolean resolved = resolveViaRegistry(t, enclosing);
  if (detectImplicitPrototypeCycle()) {
    handleTypeCycle(t);
  }

  if (resolved) {
    super.resolveInternal(t, enclosing);
    finishPropertyContinuations();
    return registry.isLastGeneration() ?
        getReferencedType() : this;
  }

  resolveViaProperties(t, enclosing);
  if (detectImplicitPrototypeCycle()) {
    handleTypeCycle(t);
  }

  super.resolveInternal(t, enclosing);
  if (isResolved()) {
    finishPropertyContinuations();
  }
  return registry.isLastGeneration() ?
      getReferencedType() : this;
}

JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
  // TODO(user): Investigate whether it is really necessary to keep two
  // different mechanisms for resolving named types, and if so, which order
  // makes more sense. Now, resolution via registry is first in order to
  // avoid triggering the warnings built into the resolution via properties.
  boolean resolved = resolveViaRegistry(t, enclosing);
  if (detectInheritanceCycle()) {
    handleTypeCycle(t);
  }

  if (resolved) {
    super.resolveInternal(t, enclosing);
    finishPropertyContinuations();
    return registry.isLastGeneration() ?
        getReferencedType() : this;
  }

  resolveViaProperties(t, enclosing);
  if (detectInheritanceCycle()) {
    handleTypeCycle(t);
  }

  super.resolveInternal(t, enclosing);
  if (isResolved()) {
    finishPropertyContinuations();
  }
  return registry.isLastGeneration() ?
      getReferencedType() : this;
}

Converting from an interface type to a constructor which @implements itself causes stack overflow.

// Options: --externs externs/es3.js --property_renaming OFF --variable_renaming OFF --jscomp_warning=checkTypes --js=t.js

// File: t.js
/**
 * @interface
 */
var OtherType = function() {}

/**
 * @implements {MyType}
 * @constructor
 */
var MyType = function() {}

/**
 * @type {MyType}
 */
var x = /** @type {!OtherType} */ (new Object());

Get Infinite recursion in:

PrototypeObjectType.isSubtype @ 350

Options:

- prevent cycles in the inheritance/implements graph
- detect cycles after they are created and exit compilation before any subsequent passes run
- detect and remove cycles after they are created but before any subsequent passes run
- make every subsequent pass robust against cycles in that graph


public void visit(NodeTraversal t, Node n, Node parent) {

  // Record global variable and function declarations
  if (t.inGlobalScope()) {
    if (NodeUtil.isVarDeclaration(n)) {
      NameInformation ns = createNameInformation(t, n, parent);
      Preconditions.checkNotNull(ns);
      recordSet(ns.name, n);
    } else if (NodeUtil.isFunctionDeclaration(n)) {
      Node nameNode = n.getFirstChild();
      NameInformation ns = createNameInformation(t, nameNode, n);
      if (ns != null) {
        JsName nameInfo = getName(nameNode.getString(), true);
        recordSet(nameInfo.name, nameNode);
      }
    } else if (NodeUtil.isObjectLitKey(n, parent)) {
      NameInformation ns = createNameInformation(t, n, parent);
      if (ns != null) {
        recordSet(ns.name, n);
      }
    }
  }

  // Record assignments and call sites
  if (n.isAssign()) {
    Node nameNode = n.getFirstChild();

    NameInformation ns = createNameInformation(t, nameNode, n);
    if (ns != null) {
      if (ns.isPrototype) {
        recordPrototypeSet(ns.prototypeClass, ns.prototypeProperty, n);
      } else {
        recordSet(ns.name, nameNode);
      }
    }
  } else if (n.isCall()) {
    Node nameNode = n.getFirstChild();
    NameInformation ns = createNameInformation(t, nameNode, n);
    if (ns != null && ns.onlyAffectsClassDef) {
      JsName name = getName(ns.name, false);
      if (name != null) {
      refNodes.add(new ClassDefiningFunctionNode(
          name, n, parent, parent.getParent()));
      }
    }
  }
}

public void visit(NodeTraversal t, Node n, Node parent) {

  // Record global variable and function declarations
  if (t.inGlobalScope()) {
    if (NodeUtil.isVarDeclaration(n)) {
      NameInformation ns = createNameInformation(t, n, parent);
      Preconditions.checkNotNull(ns);
      recordSet(ns.name, n);
    } else if (NodeUtil.isFunctionDeclaration(n)) {
      Node nameNode = n.getFirstChild();
      NameInformation ns = createNameInformation(t, nameNode, n);
      if (ns != null) {
        JsName nameInfo = getName(nameNode.getString(), true);
        recordSet(nameInfo.name, nameNode);
      }
    } else if (NodeUtil.isObjectLitKey(n, parent)) {
      NameInformation ns = createNameInformation(t, n, parent);
      if (ns != null) {
        recordSet(ns.name, n);
      }
    }
  }

  // Record assignments and call sites
  if (n.isAssign()) {
    Node nameNode = n.getFirstChild();

    NameInformation ns = createNameInformation(t, nameNode, n);
    if (ns != null) {
      if (ns.isPrototype) {
        recordPrototypeSet(ns.prototypeClass, ns.prototypeProperty, n);
      } else {
        recordSet(ns.name, nameNode);
      }
    }
  } else if (n.isCall()) {
    Node nameNode = n.getFirstChild();
    NameInformation ns = createNameInformation(t, nameNode, n);
    if (ns != null && ns.onlyAffectsClassDef) {
      JsName name = getName(ns.name, true);
      refNodes.add(new ClassDefiningFunctionNode(
          name, n, parent, parent.getParent()));
    }
  }
}

What steps will reproduce the problem?
Compiler the following code in advanced mode:

{{{
var goog = {};
goog.inherits = function(x, y) {};
var ns = {};
/** @constructor */ ns.PageSelectionModel = function(){};

/** @constructor */
ns.PageSelectionModel.FooEvent = function() {};
/** @constructor */
ns.PageSelectionModel.SelectEvent = function() {};
goog.inherits(ns.PageSelectionModel.ChangeEvent, ns.PageSelectionModel.FooEvent);
}}}

What is the expected output? What do you see instead?
The compiler will crash. The last var check throws an illegal state exception because it knows something is wrong.

The crash is caused by smartNameRemoval. It has special logic for counting references in class-defining function calls (like goog.inherits), and it isn't properly creating a reference to PageSelectionModel.


Node processForInLoop(ForInLoop loopNode) {

    // Return the bare minimum to put the AST in a valid state.
  return newNode(
      Token.FOR,
      transform(loopNode.getIterator()),
      transform(loopNode.getIteratedObject()),
      transformBlock(loopNode.getBody()));
}

Node processForInLoop(ForInLoop loopNode) {
  if (loopNode.isForEach()) {
    errorReporter.error(
        "unsupported language extension: for each",
        sourceName,
        loopNode.getLineno(), "", 0);

    // Return the bare minimum to put the AST in a valid state.
    return newNode(Token.EXPR_RESULT, Node.newNumber(0));
  }
  return newNode(
      Token.FOR,
      transform(loopNode.getIterator()),
      transform(loopNode.getIteratedObject()),
      transformBlock(loopNode.getBody()));
}

Simple "Whitespace only" compression removing "each" keyword from "for each (var x in arr)" loop

What steps will reproduce the problem?
See below code snippet before after compression

---Before---
contactcenter.screenpop.updatePopStatus = function(stamp, status) {
for each ( var curTiming in this.timeLog.timings ) {
if ( curTiming.callId == stamp ) {
curTiming.flag = status;
break;
}
}
};
---After---
contactcenter.screenpop.updatePopStatus=function(stamp,status){for(var curTiming in this.timeLog.timings)if(curTiming.callId==stamp){curTiming.flag=status;break}};

What is the expected output? What do you see instead?
---each keyword should be preserved

What version of the product are you using? On what operating system?
Please provide any additional information below.
for each (** in **) --->  returns object value
for (** in **) --> returns index


void add(String newcode) {
  maybeEndStatement();

  if (newcode.length() == 0) {
    return;
  }

  char c = newcode.charAt(0);
  if ((isWordChar(c) || c == '\\') &&
      isWordChar(getLastChar())) {
    // need space to separate. This is not pretty printing.
    // For example: "return foo;"
    append(" ");
    // Do not allow a forward slash to appear after a DIV.
    // For example,
    // REGEXP DIV REGEXP
    // is valid and should print like
    // / // / /
  }

  append(newcode);
}

void add(String newcode) {
  maybeEndStatement();

  if (newcode.length() == 0) {
    return;
  }

  char c = newcode.charAt(0);
  if ((isWordChar(c) || c == '\\') &&
      isWordChar(getLastChar())) {
    // need space to separate. This is not pretty printing.
    // For example: "return foo;"
    append(" ");
  } else if (c == '/' && getLastChar() == '/') {
    // Do not allow a forward slash to appear after a DIV.
    // For example,
    // REGEXP DIV REGEXP
    // is valid and should print like
    // / // / /
    append(" ");
  }

  append(newcode);
}

alert(/ / / / /);
output: alert(/ /// /);
should be: alert(/ // / /);


void addNumber(double x) {
  // This is not pretty printing. This is to prevent misparsing of x- -4 as
  // x--4 (which is a syntax error).
  char prev = getLastChar();
  if (x < 0 && prev == '-') {
    add(" ");
  }

  if ((long) x == x) {
    long value = (long) x;
    long mantissa = value;
    int exp = 0;
    if (Math.abs(x) >= 100) {
      while (mantissa / 10 * Math.pow(10, exp + 1) == value) {
        mantissa /= 10;
        exp++;
      }
    }
    if (exp > 2) {
      add(Long.toString(mantissa) + "E" + Integer.toString(exp));
    } else {
      add(Long.toString(value));
    }
  } else {
    add(String.valueOf(x));
  }

}

void addNumber(double x) {
  // This is not pretty printing. This is to prevent misparsing of x- -4 as
  // x--4 (which is a syntax error).
  char prev = getLastChar();
  if (x < 0 && prev == '-') {
    add(" ");
  }

  if ((long) x == x && !isNegativeZero(x)) {
    long value = (long) x;
    long mantissa = value;
    int exp = 0;
    if (Math.abs(x) >= 100) {
      while (mantissa / 10 * Math.pow(10, exp + 1) == value) {
        mantissa /= 10;
        exp++;
      }
    }
    if (exp > 2) {
      add(Long.toString(mantissa) + "E" + Integer.toString(exp));
    } else {
      add(Long.toString(value));
    }
  } else {
    add(String.valueOf(x));
  }
}