Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@type doesn't work on functions when a forward reference or requireType'd #3549

Open
wallw-teal opened this issue Feb 13, 2020 · 5 comments
Open

Comments

@wallw-teal
Copy link
Contributor

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

@lauraharker
Copy link
Contributor

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.

@lauraharker lauraharker changed the title Typedef not checked correctly when imported as goog.modules @type doesn't work on functions when a forward reference or requireType'd Feb 14, 2020
@wallw-teal
Copy link
Contributor Author

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)?

@shicks
Copy link
Member

shicks commented Feb 19, 2020

Yes, it is just the order of the args.

@wallw-teal
Copy link
Contributor Author

Swapping the order of the --js args fixes the goog.provide/require case but not the goog.module/requireType case.

@lauraharker
Copy link
Contributor

(going through old issues assigned to me)

Unassigning myself, since I don't currently have bandwidth to work on Closure type system improvements

@lauraharker lauraharker removed their assignment Mar 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants