13

I'm getting a lot of "Unknown type" warnings when running a fairly large library through Closure Compiler, and they seem to occur when my types are declared in self-executing anonymous functions. There's nothing exotic about this, but if I strip the self-executing functions out, the type declarations seem to work (at least in this simple test).

I'm not sure if there's something wrong with my code annotations or if there's anything illegal in the code, but I think this is all kosher and the standard way to modularize an API.

The following test code creates a namespace (just a plain old JS object) and attaches an enum (an object literal) and a function to it.

var mynamespace = {};
(function (mynamespace) {
    /**
     * Some enum.
     * @enum {number}
     */
    mynamespace.SomeEnum = {
        FOO: 1,
        BAR: 2
    };

    /**
     * Frazzle some type.
     * @param {mynamespace.SomeEnum} qux The type to frazzle.
     * @return {boolean} whether the operation succeeded.
     */
    mynamespace.frazzle = function(qux) {
        return true;
    }
}(mynamespace));

// call it
mynamespace.frazzle(mynamespace.SomeEnum.FOO);

Looks fine, right? The closure compiler errors:

[jscomp] Compiling 1 file(s) with 37 extern(s)
[jscomp] X:\dev\solclientjs\sdk\tools\jscomptest.js:14: WARNING - Parse error. Unknown type mynamespace.SomeEnum

[jscomp]      * @param {mynamespace.SomeEnum} qux The type to frazzle.
Shad
  • 15,134
  • 2
  • 22
  • 34
jpdaigle
  • 1,265
  • 10
  • 12
  • After playing around with the compiler for a while, I wasn't able to get it to recognize **any** custom data types. Have you? (And at least it's only a Warning and not a true `error` =) – Shad Mar 03 '11 at 06:10
  • I'm sure it's just a typo but for correctness the closing bracket around your anonymous function should be placed before passing mynamespace into it :) (function (mynamespace) { … })(mynamespace) – Andreas Mar 04 '11 at 17:16
  • @john_doe: It'll work either way. [Here's an example.](http://jsfiddle.net/9pfnn/) And here's [a related question](http://stackoverflow.com/questions/3783007/is-there-a-difference-between-function-and-function/3783287#3783287) on StackOverflow. – user113716 Mar 05 '11 at 02:17

4 Answers4

3

Edit:

Original answer was totally off.

This definitely appears to be a bug in the compiler. I haven't found a bug report with this exact issue, but I found two bug reports that appear to address the inverse of this issue (compiler should be throwing a warning, but it won't unless you unwrap the anonymous function).

http://code.google.com/p/closure-compiler/issues/detail?id=134

http://code.google.com/p/closure-compiler/issues/detail?id=61

In any case it looks like anonymous functions are wonky when used with type expressions.

Bryan Downing
  • 15,194
  • 3
  • 39
  • 60
  • I don't think it has to do with the comments. OP is using annotations that are interpreted by the compiler, and as he stated it works outside the immediately invoked function. When I tested OP's code, it worked fine with `{number}` as the type and [according to the docs](http://code.google.com/closure/compiler/docs/js-for-compiler.html) you should be able to use a *type expression* like `{mynamespace.SomeEnum}` with the *@param* tag. – user113716 Mar 03 '11 at 21:20
  • haha, yeah I thought it was just JSDoc syntax (which it is). So shouldn't that line just be `* @param {number} qux The type to frazzle.`? Because `{number}` is a type expression, but `{mynamespace.SomeEnum}` is not. – Bryan Downing Mar 03 '11 at 21:58
  • I'm not too familiar with Closure Compiler (yet) but the docs do seem to indicate that you can specify custom type. The example they give in the docs is `{goog.ui.Menu}`, so it would seem like OP's use would be correct. Also you don't get the error if you unwrap the code from the function. Seems like a bug, but I'm not sure. – user113716 Mar 03 '11 at 23:13
  • Yeah I'm not too familiar either, but after some digging, I think it's a bug for sure. Answer updated. – Bryan Downing Mar 04 '11 at 04:22
  • OP here - yes, thanks, it sounds like this is definitely a known bug. I guess Google failed me when I was searching for this issue. I notice that in the [Closure JS library](http://code.google.com/p/closure-library/source/browse/trunk/closure/goog/color/color.js), they don't use anonymous self-executing functions as modules. – jpdaigle Mar 04 '11 at 15:10
  • 1
    This is not a bug in the Compiler. The code is just trying to do something the the Compiler is not designed to handle -- i.e. passing an object into a function as argument, augment that argument object, and expect the augmentation to propagate outside. The Compiler will need to do significant amounts of code flow analysis in order to find out which objects get assigned which properties in runtime *inside a funciton*. The Compiler only handles types with properties assigned directly, never within a function. This is a restriction when using the Compiler. – Stephen Chung Mar 13 '11 at 07:55
3

In your example, the argument "mynamespace" is being passed an "alias" to the global mynamespace object. Having an alias (local mynamespace) to your global object (global mynamespace) prevents optimizations on the entire tree under the global object. This is a BAD BAD idea for the Closure Compiler in Advanced mode.

Any function defined under a local variable is volatile. The compiler has no idea (without deep code flow analysis) the local "mynamespace" is an alias to the global "mynamespace". Therefore, it won't automatically associate anything you define under the local variable to the object that it aliases.

The proper ways to do this are:

Option #1, using the global object directly:

(function() {
    mynamespace.someEnum = ...
})();

Option #2, use a goog.scope alias (assuming you are using Advanced mode):

goog.scope(function() {
    var somevar = mynamespace;

    (function() {
        somevar.someEnum = ...
    })();
});

Both options should get you your desired result. However, there is currently no way to do what you want via an argument into a wrapper closure.

Stephen Chung
  • 14,497
  • 1
  • 35
  • 48
1

The Closure Compiler has improved here. The only thing that you need to do to correct the code above so that it is recognized is to declare the namespace as @const:

/** @const */ var mynamespace = {};

The type is then recognized.

John
  • 5,443
  • 15
  • 21
  • Works like a charm. Where is this documented? – Ian Hunter Aug 27 '14 at 19:23
  • A lot of this got better recently as the compiler now uses "inferred constants" when type checking. In general the specific of the type inferencing and checking isn't well documented. This is something that I would like to see improve when the new type inference becomes the default (it is currently being rewritten from scratch). – John Sep 10 '14 at 04:49
0

The Closure Compiler doesn't support local type names. The names must be global and within the function "mynamespace" is a local name.

John
  • 199
  • 1