closure-compiler icon indicating copy to clipboard operation
closure-compiler copied to clipboard

Property that is a class is not type checkable without being marked as @const

Open l1bbcsg opened this issue 7 years ago • 8 comments

Suppose collection.js:

export class A {}
export default {A}

and index.js:

import {A} from './collection.js';
import collection from './collection.js';

class B extends A {}
class C extends collection.A {}

Compiling this produces a warning:

$ closure-compiler --js collection.js --js index.js --warning_level VERBOSE --checks_only

index.js: WARNING - Bad type annotation. Unknown type $jscompDefaultExport$$module$collection.A

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

closure-compiler is the latest java release.

Closure Compiler (http://github.com/google/closure-compiler)
Version: v20180716
Built on: 2018-07-17 22:09

Class B that extends explicitly imported A works fine, but class C that extends A imported as a property of default object produces warning.

The code compiles and works as expected despite the warning. Also note that the warning is missing line information and is not pointing to any particular point in the code. There weren't any annotations in original code either, so the message is quite misleading.

Referencing collection.A in jsdoc annotations also produces a warning:

WARNING - Bad type annotation. Unknown type collection$$module$collection.A
 * @type {collection.A}
          ^

This might to be related to https://github.com/google/closure-compiler/issues/2236 and/or https://github.com/google/closure-compiler/issues/2257 but I could not reproduce the former with the current version of compiler while the latter issue seems to be explicitly about transitive exports.

l1bbcsg avatar Aug 03 '18 11:08 l1bbcsg

This is not limited to classes. collection.js:

/**
 * @typedef {number}
 */
export var TYPE;
export default {TYPE}

and index.js:

import {TYPE} from './collection.js';
import collection from './collection.js';

/**
 * @type {TYPE}
 */
let a = '';

/**
 * @type {collection.TYPE}
 */
let b = '';

Produces:

index.js:7: WARNING - initializing variable
found   : string
required: number
let a = '';
        ^^

index.js:10: WARNING - Bad type annotation. Unknown type collection$$module$collection.TYPE
 * @type {collection.TYPE}
          ^

0 error(s), 2 warning(s), 100.0% typed

Note how compiler understood the type of a and performed a type check producing a correct warning when it was assigned a wrong value (string instead of number) while it failed to do so with b.

l1bbcsg avatar Aug 03 '18 11:08 l1bbcsg

This is easy to repro: https://closure-compiler-debugger.appspot.com/#input0%3Dexport%2520class%2520A%2520%257B%257D%250Aexport%2520default%2520%257BA%257D%26input1%3Dimport%2520%257BA%257D%2520from%2520'.%252Finput0'%253B%250Aimport%2520collection%2520from%2520'.%252Finput0'%253B%250A%250Aclass%2520B%2520extends%2520A%2520%257B%257D%250Aclass%2520C%2520extends%2520collection.A%2520%257B%257D%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue

@jplaisted, Can you take a look?

blickly avatar Aug 03 '18 16:08 blickly

Looks like a type inlining issue. If you mark it as @const it won't complain.

export class A {}
export default {/** @const */ A}

https://closure-compiler-debugger.appspot.com/#input0%3Dexport%2520class%2520A%2520%257B%257D%250Aexport%2520default%2520%257B%252F**%2520%2540const%2520*%252F%2520A%257D%26input1%3Dimport%2520%257BA%257D%2520from%2520'.%252Finput0'%253B%250Aimport%2520collection%2520from%2520'.%252Finput0'%253B%250A%250Aclass%2520B%2520extends%2520A%2520%257B%257D%250Aclass%2520C%2520extends%2520collection.A%2520%257B%257D%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue

In fact it has nothing to do with ES6 modules at all. This has the same error:

class A {}
const collection = {A}

class B extends A {}
class C extends collection.A {}

jplaisted avatar Aug 03 '18 16:08 jplaisted

Ah, good point. Thanks for the workaround.

blickly avatar Aug 03 '18 17:08 blickly

Also @l1bbcsg: kind of off topic, but what's with that style? Why not just import * as collection rather than duplicate the entire module as a default export? Seems like a lot of work to avoid 5 characters :)

jplaisted avatar Aug 03 '18 17:08 jplaisted

Apologizes for bringing modules into the equation, looks like I jumped to conclusions.

The workaround is good enough for classes, but it doesn't seem to work for the second example with typedefs: https://github.com/google/closure-compiler/issues/3043#issuecomment-410230627

As for codestyle, this is mostly an abstract issue that I stumbled upon while trying to migrate a really complicated Closure project from namespaces to modules. I'm doing this through AST transformations, so codestyle is a mess for now since the two approaches are quite different, but we'll think about it later. Thanks for the suggestion though, it looks like a good idea.

l1bbcsg avatar Aug 07 '18 07:08 l1bbcsg

Apologizes for bringing modules into the equation, looks like I jumped to conclusions.

No worries! Thanks for filing. A bug is a bug :)

The workaround is good enough for classes, but it doesn't seem to work for the second example with typedefs: #3043 (comment)

We appear to do the same thing for the typedef we're doing for classes, so I'm not sure why it doesn't work. In the end the annotation is still module.TYPE, even if I make TYPE a class.

https://closure-compiler-debugger.appspot.com/#input0%3D%252F**%250A%2520*%2520%2540typedef%2520%257Bnumber%257D%250A%2520*%252F%250Avar%2520TYPE%253B%250Aconst%2520module%2520%253D%2520%257B%252F**%2520%2540const%2520*%252F%2520TYPE%257D%253B%250A%250A%250A%252F**%250A%2520*%2520%2540type%2520%257Bmodule.TYPE%257D%250A%2520*%252F%250Alet%2520b%2520%253D%2520''%253B%26input1%3D%250A%250A%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue

jplaisted avatar Aug 07 '18 16:08 jplaisted

I just wanted to report that I had the same problem while adapting the pako zip library to work with GCC. Here's what my use case boiled down to:

var lib = {};
(function () {
  class Greeter {
	sayHello () {
	  alert("hello");
	}
  }
  lib.Greeter = Greeter;
}());
/** @type {lib.Greeter?} */
var myGreeter = null;
if (Math.random() < 0.2) {
  myGreeter = new lib.Greeter();
  myGreeter.sayHello();
}

It gives this error:

JSC_UNRECOGNIZED_TYPE_ERROR: Bad type annotation. Unknown type lib.Greeter at line 10 character 11
/** @type {lib.Greeter?} */

but adding /** @const */ to the lib.Greeter line silences the warning.

Thanks to @jplaisted for the @const workaround - it worked for me as well.

cmacordex avatar Aug 27 '18 22:08 cmacordex