closure-compiler
closure-compiler copied to clipboard
`@type` doesn't work on functions when a forward reference or requireType'd
Inline Case (working)
This has the typedef inline in a single file:
/**
* @typedef {function(!string, number):string}
*/
let Foo;
/**
* @type {Foo}
*/
const impl = (foo, bar) => [foo]; // incorrectly returns an array
const result = impl('test', 1);
impl(result, 2);
Compiler Args:
java -jar node_modules/google-closure-compiler-java/compiler.jar --dependency_mode=PRUNE --language_in=ECMASCRIPT_2018 --warning_level=VERBOSE --jscomp_error=accessControls --jscomp_error=checkPrototypalTypes --jscomp_error=checkRegExp --jscomp_error=checkTypes --jscomp_error=checkVars --jscomp_error=conformanceViolations --jscomp_error=const --jscomp_error=constantProperty --jscomp_error=deprecatedAnnotations --jscomp_error=duplicateMessage --jscomp_error=es5Strict --jscomp_error=externsValidation --jscomp_error=functionParams --jscomp_error=globalThis --jscomp_error=invalidCasts --jscomp_error=misplacedTypeAnnotation --jscomp_error=missingGetCssName --jscomp_error=missingOverride --jscomp_error=missingPolyfill --jscomp_error=missingProperties --jscomp_error=missingProvide --jscomp_error=missingRequire --jscomp_error=missingReturn --jscomp_error=missingSourcesWarnings --jscomp_error=moduleLoad --jscomp_error=msgDescriptions --jscomp_error=nonStandardJsDocs --jscomp_error=partialAlias --jscomp_error=polymer --jscomp_error=strictModuleDepCheck --jscomp_error=strictPrimitiveOperators --jscomp_error=suspiciousCode --jscomp_error=typeInvalidation --jscomp_error=undefinedNames --jscomp_error=undefinedVars --jscomp_error=underscore --jscomp_error=unknownDefines --jscomp_error=unusedLocalVariables --jscomp_error=unusedPrivateMembers --jscomp_error=useOfGoogBase --jscomp_error=uselessCode --jscomp_error=untranspilableFeatures --jscomp_error=visibility --jscomp_warning=deprecated --jscomp_off=reportUnknownTypes --jscomp_off=strictCheckTypes --jscomp_off=strictMissingProperties --compilation_level=ADVANCED --js_output_file=output.js --js input.js --entry_point input.js
Result:
input.js:9: ERROR - [JSC_TYPE_MISMATCH] inconsistent return type
found : Array<?>
required: string
const impl = (foo, bar) => [foo]; // incorrectly returns an array
^^^^^
1 error(s), 0 warning(s), 100.0% typed
That's all well and good as expected.
Goog Module Case (broken)
Using goog.module
results in the error not occurring:
// typedef.js
goog.module('Foo');
/**
* @typedef {function(!string, number):string}
*/
let Foo;
exports = Foo;
// input.js
goog.module('test');
const Foo = goog.requireType('Foo');
/**
* @type {Foo}
*/
const impl = (foo, bar) => [foo]; // incorrectly returns an array
const result = impl('test', 1);
impl(result, 2);
Compiler Args:
java -jar node_modules/google-closure-compiler-java/compiler.jar --dependency_mode=PRUNE --language_in=ECMASCRIPT_2018 --warning_level=VERBOSE --jscomp_error=accessControls --jscomp_error=checkPrototypalTypes --jscomp_error=checkRegExp --jscomp_error=checkTypes --jscomp_error=checkVars --jscomp_error=conformanceViolations --jscomp_error=const --jscomp_error=constantProperty --jscomp_error=deprecatedAnnotations --jscomp_error=duplicateMessage --jscomp_error=es5Strict --jscomp_error=externsValidation --jscomp_error=functionParams --jscomp_error=globalThis --jscomp_error=invalidCasts --jscomp_error=misplacedTypeAnnotation --jscomp_error=missingGetCssName --jscomp_error=missingOverride --jscomp_error=missingPolyfill --jscomp_error=missingProperties --jscomp_error=missingProvide --jscomp_error=missingRequire --jscomp_error=missingReturn --jscomp_error=missingSourcesWarnings --jscomp_error=moduleLoad --jscomp_error=msgDescriptions --jscomp_error=nonStandardJsDocs --jscomp_error=partialAlias --jscomp_error=polymer --jscomp_error=strictModuleDepCheck --jscomp_error=strictPrimitiveOperators --jscomp_error=suspiciousCode --jscomp_error=typeInvalidation --jscomp_error=undefinedNames --jscomp_error=undefinedVars --jscomp_error=underscore --jscomp_error=unknownDefines --jscomp_error=unusedLocalVariables --jscomp_error=unusedPrivateMembers --jscomp_error=useOfGoogBase --jscomp_error=uselessCode --jscomp_error=untranspilableFeatures --jscomp_error=visibility --jscomp_warning=deprecated --jscomp_off=reportUnknownTypes --jscomp_off=strictCheckTypes --jscomp_off=strictMissingProperties --compilation_level=ADVANCED --js_output_file=output.js --js input.js --js typedef.js '--js=!node_modules/google-closure-library/closure/goog/**test.js' '--js=!node_modules/google-closure-library/third_party/**test.js' '--js=node_modules/google-closure-library/closure/goog/**.js' '--js=node_modules/google-closure-library/third_party/**.js' --hide_warnings_for=/google-closure-library/ --entry_point input.js
Result: No errors. Expected result: Same error as above
Goog Provide Case (different result)
// typedef.js
goog.provide('Foo');
/**
* @typedef {function(!string, number):string}
*/
Foo;
// input.js
goog.require('Foo');
/**
* @type {!Foo}
*/
const impl = (foo, bar) => [foo]; // incorrectly returns an array
const result = impl('test', 1);
impl(result, 2);
Compiler Args:
java -jar node_modules/google-closure-compiler-java/compiler.jar --dependency_mode=PRUNE --language_in=ECMASCRIPT_2018 --warning_level=VERBOSE --jscomp_error=accessControls --jscomp_error=checkPrototypalTypes --jscomp_error=checkRegExp --jscomp_error=checkTypes --jscomp_error=checkVars --jscomp_error=conformanceViolations --jscomp_error=const --jscomp_error=constantProperty --jscomp_error=deprecatedAnnotations --jscomp_error=duplicateMessage --jscomp_error=es5Strict --jscomp_error=externsValidation --jscomp_error=functionParams --jscomp_error=globalThis --jscomp_error=invalidCasts --jscomp_error=misplacedTypeAnnotation --jscomp_error=missingGetCssName --jscomp_error=missingOverride --jscomp_error=missingPolyfill --jscomp_error=missingProperties --jscomp_error=missingProvide --jscomp_error=missingRequire --jscomp_error=missingReturn --jscomp_error=missingSourcesWarnings --jscomp_error=moduleLoad --jscomp_error=msgDescriptions --jscomp_error=nonStandardJsDocs --jscomp_error=partialAlias --jscomp_error=polymer --jscomp_error=strictModuleDepCheck --jscomp_error=strictPrimitiveOperators --jscomp_error=suspiciousCode --jscomp_error=typeInvalidation --jscomp_error=undefinedNames --jscomp_error=undefinedVars --jscomp_error=underscore --jscomp_error=unknownDefines --jscomp_error=unusedLocalVariables --jscomp_error=unusedPrivateMembers --jscomp_error=useOfGoogBase --jscomp_error=uselessCode --jscomp_error=untranspilableFeatures --jscomp_error=visibility --jscomp_warning=deprecated --jscomp_off=reportUnknownTypes --jscomp_off=strictCheckTypes --jscomp_off=strictMissingProperties --compilation_level=ADVANCED --js_output_file=output.js --js input.js --js typedef.js '--js=!node_modules/google-closure-library/closure/goog/**test.js' '--js=!node_modules/google-closure-library/third_party/**test.js' '--js=node_modules/google-closure-library/closure/goog/**.js' '--js=node_modules/google-closure-library/third_party/**.js' --hide_warnings_for=/google-closure-library/ --entry_point input.js
Result:
input.js:6: ERROR - [JSC_TYPE_MISMATCH] initializing variable
found : function(?, ?): ?
required: NoResolvedType
const impl = (foo, bar) => [foo]; // incorrectly returns an array
^^^^^^^^^^^^^^^^^^^
Expected result: Same error as inline case
I also noticed that if I move the @type
annotation to the function expression that the compiler errors indicating that @type
is not supported on functions. Is the only solution to this to copy/paste the full function definition everywhere or is there a better alternative?
Also, this may be related to #3041 but I was unsure.
compiler version: 20200204.0.0 library version: 20200204.0.0
I ran into this same bug a few weeks ago.
It's caused by using a forward referenced type in an @type
on a function. The compiler ignores the @type {!Foo}
completely.
In your Goog Module case, the compiler sorts input.js
before typedef.js
at the beginning of compilation. That turns the /** @type {!Foo} */
into a forward reference by the time the typechecker executes.
Here's an example without modules/provides (debugger link):
/** @type {!Foo} */
const implBeforeFoo = (foo, bar) => [foo];
/** @typedef {function(!string, number):string} */
let Foo;
/** @type {!Foo} */
const implAfterFoo = (foo, bar) => [foo];
The compiler reports a type error on implAfterFoo
, but not on implAfterFoo
.
Is there a way to affect the file sort in the compiler? Is it just the order of --js
args? Or perhaps some other way to work around the error other than copy/pasting the function params/return everywhere the type is used (since that was what I was attempting to avoid in the first place)?
Yes, it is just the order of the args.
Swapping the order of the --js
args fixes the goog.provide/require
case but not the goog.module/requireType
case.
(going through old issues assigned to me)
Unassigning myself, since I don't currently have bandwidth to work on Closure type system improvements