Closure-compiler: Generics (templates) are lost in type annotations within methods

Created on 21 Jul 2018  Â·  8Comments  Â·  Source: google/closure-compiler

Running this command -
java -jar ../build/binaries/closure-compiler-v20180716.jar --js trial.js --language_in ECMASCRIPT_NEXT -O ADVANCED_OPTIMIZATIONS --jscomp_warning '*' --warning_level VERBOSE --js_output_file trial.min.js

With this trial.js -

/** @template T*/
class Helpers
{
  /** @param {T} value
      @return {!Promise<!Array<T>>} */
  getValueAsArray(value)
  {
   return new Promise(
           (resolve, reject) =>
           {
            /** @type {!Array<T>} */
            const objects = [value];
            resolve(objects);
           });
  }
}

/** @type {!Helpers<string>} */
const helpers = new Helpers();
helpers.getValueAsArray("hey").then(array => console.log(array));

Outputs -

trial.js:11: WARNING - Bad type annotation. Unknown type T
            /** @type {!Array<T>} */
                              ^

0 error(s), 1 warning(s), 92.0% typed

I realize the example looks weird. The actual scenario is that the value is not supplied in a parameter, but brought from IndexedDB -

/** @template T*/
class Store
{
  /** @return {!Promise<!Array<T>>} */
  getObjects()
  {
   return new Promise(
           (resolve, reject) =>
           {
            /** @type {!Array<T>} */
            const objects = [];
            // Asynchronously get the stuff from IndexedDB
            resolve(objects);
           });
  }
}

/** @type {!Store<string>} */
const store = new Store();
store.getObjects().then(array => console.log(array));
trial.js:10: WARNING - Bad type annotation. Unknown type T
            /** @type {!Array<T>} */
                              ^

0 error(s), 1 warning(s), 92.1% typed

All 8 comments

It is a known problem that you cannot refer to template variables in type annotations within a function.
I'm sure we have an open issue for that, though I cannot put my finger on it at the moment.
This is one of the things we plan to fix in the near-ish future once we've finished making the type checker truly understand ES6 classes.

@concavelenz may have more comments to make on this.

I'm facing exactly the same issue on my project sadly. I was planning to implement a dependency injection mechanism that works with mixins and decorators, and on paper it would've been a marvelous typesafe yet generic implementation.

I'll temporarily choose a different approach, until it's fixed though. :(

If you are looking for mixin support, I strongly recommend you use ES6 class mixin functions: http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/

Supporting them in the compiler is a little bit interesting. You have to define an interface and mark the mixed constructor as implementing it:

function MixedInterface() {}
/** @type {function(boolean):boolean} */ MixedInterface.prototype.foo;

/** @param {function(new:HTMLElement)} Superclass */
function mixFoo(Superclass) {
  /** @implements {MixedInterface} */
  class Foo extends Superclass {
    foo(test) { return test; }
  }
  return Foo;
}

/**
 * @constructor
 * @extends {HTMLElement}
 * @implements {MixedInterface}
 */
const FooElement = mixFoo(HTMLElement);

This is supported by the compiler and type safe. One note - in ADVANCED mode the MixedInterface must be complete. It must contain all properties added to the prototype (even private ones).

@ChadKillingsworth Thank you!

I found it on another thread, and it's quite useful. That part works marvelously.

My issue is within the decorated / mixed in DI bag creation, with my current approach. During decoration, within the method I would like to create a map, when considering instances: !Object.<!string, !T>; and add this map to the main container. However, within the function body, it won't recognize type T.
— in case it would be helpful or would make sense, here's the bazel output :-/

src/di/src/InjectionAware.js:129: ERROR - Bad type annotation. Unknown type InjectableUnit
     * @type {!Object.<!string, !InjectableUnit>}
                                 ^
  ProTip: "JSC_UNRECOGNIZED_TYPE_ERROR" or "checkTypes" can be added to the `suppress` attribute of:
  //src/di:di
  Alternatively /** @suppress {checkTypes} */ can be added to the source file.

— and the related source code, without the interface of the mixin & even more unnecessary detail than what I already provided :)

/**
 * ...
 * @template InjectableUnit
 * @param    {!string}                                                                registryId        The ID with which 
 *                                                                                                      you want to register 
 *                                                                                                      the given injection 
 *                                                                                                      aware entity.
 * @param    {!function(new:InjectableBuilderFactory<InjectableUnit>)} injectableEntity The reference to 
 *                                                                                                      the entity itself.
 * @returns  {undefined}
 */
my.packageName.InjectionAware.createRegistry = function (registryId, injectableEntity) {
    goog.asserts.assert(
        !(registryId in my.packageName.InjectionAware.REGISTRY),
        'The injectable registry is expected not to have a pool defined with the provided registry id "%s"',
        registryId
        );
    goog.log.info(
        goog.log.getLogger('my.packageName.InjectionAware'),
        'Creating a registry pool with registry id "' + registryId + '"'
        );
    /**
     * @type {!Object.<!string, !InjectableUnit>}
     */
    var registryPoolReference = {};
    my.packageName.InjectionAware.REGISTRY[registryId] = registryPoolReference;
    my.packageName.InjectionAware.doInjectableMixinDecoration(registryPoolReference, injectableEntity);
};

I'll be able to achieve the same goal through some "older-school" composition patterns, it's just that the imagined/planned approach gave me the impression that it'd been simpler.

I'm pretty sure that @concavelenz is working on an improvement to the typechecking that allows referring to template parameters inside the body of templated functions.

Is this still an issue?

@concavelenz - yep -
https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250A%252F%252F%2520ADD%2520YOUR%2520CODE%2520HERE%250A%252F%2520%2540template%2520T%252F%250Aclass%2520Helpers%250A%257B%250A%2520%2520%252F%2520%2540param%2520%257BT%257D%2520value%250A%2520%2520%2520%2520%2520%2520%2540return%2520%257B!Promise%253C!Array%253CT%253E%253E%257D%2520%252F%250A%2520%2520getValueAsArray(value)%250A%2520%2520%257B%250A%2520%2520%2520return%2520new%2520Promise(%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520(resolve%252C%2520reject)%2520%253D%253E%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%252F%2520%2540type%2520%257B!Array%253CT%253E%257D%2520%252F%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520const%2520objects%2520%253D%2520%255Bvalue%255D%253B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520resolve(objects)%253B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D)%253B%250A%2520%2520%257D%250A%257D%250A%250A%252F%2520%2540type%2520%257B!Helpers%253Cstring%253E%257D%2520%252F%250Aconst%2520helpers%2520%253D%2520new%2520Helpers()%253B%250Ahelpers.getValueAsArray(%2522hey%2522).then(array%2520%253D%253E%2520console.log(array))%253B%250A

Looks like the original example does not warn anymore. I guess this can be closed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ChadKillingsworth picture ChadKillingsworth  Â·  5Comments

upsuper picture upsuper  Â·  5Comments

NekR picture NekR  Â·  3Comments

cramforce picture cramforce  Â·  5Comments

jleyba picture jleyba  Â·  7Comments