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
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.