google / google/closure-compiler
Don't clobber declared types with looser inferred types
- Dominant language
- JavaScript
- Stars
- 7.7k
- Forks
- 1.2k
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 6
Description
[repro]
```js
/** @return {?} */ function g() {}
function f(/** number */ x) {
x = g();
var /** string */ y = x;
}
```
In this case, the declared type of `x` is clobbered by the looser inferred type `?`, allowing us to assign it to a string, which should be an error. Note that attempting to assign a string to `x` directly (e.g. `x = 'str';` still [produces an error], so type inference clearly still thinks that `x` is a number.
A first-order solution would be to simply refuse to narrow if the inferred type is loose, but this falls apart with generics. Here is a [more complicated example] where the narrowing, while loose, does provide an important bit of information:
```js
/** @constructor @template T */
function Base() {}
/** @constructor @extends {Base}
@template T */
function Sub() {}
function f(/** !Base */ x) {
//x = /** @type {!Sub} */ (new Sub()); // would be an error!
x = new Sub();
var /** !Base */ y = x;
}
```
Note that, like above, the compiler clearly understands that we can't just assign a `Sub` to `Base`, though once we assign a `Sub` to it, inference is happy to assign it to a `Sub` from there. It seems to me that the most correct solution would be to do a "narrowing meet" operation between `Base` and `Sub` to get `Sub`. This gets more complicated by variance: invariant types are easy, but if `Base` were covariant then a `Sub` can be assigned to a `Base` with no problem. We may therefore need some sort of "bounded wildcard" type, which could narrow the type down to `Sub` more correctly. This may to may not be related to bounded generics, but it's worth considering them together.
In practice this [shows up] e.g. in
```js
var /** !Array */ x;
x = [1, 2, 3];
x.push('str'); // no error!
```
which should produce an error [but doesn't], since `x` is inferred as `!Array` after the assignment.
[repro]: https://closure-compiler-debugger.appspot.com/#input0%3Dfunction%2520f(%252F**%2520number%2520*%252F%2520x)%2520%257B%250A%2520%2520x%2520%253D%2520g()%253B%250A%2520%2520var%2520%252F**%2520string%2520*%252F%2520y%2520%253D%2520x%253B%250A%257D%26input1%26conformanceConfig%26externs%3D%252F**%2520%2540return%2520%257B%253F%257D%2520*%252F%2520function%2520g()%2520%257B%257D%26refasterjs-template%26CHECK_SYMBOLS%3Dtrue%26MISSING_PROPERTIES%3Dtrue%26TRANSPILE%3Dtrue%26CHECK_TYPES%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
[produces an error]: https://closure-compiler-debugger.appspot.com/#input0%3Dfunction%2520f(%252F**%2520number%2520*%252F%2520x)%2520%257B%250A%2520%2520x%2520%253D%2520g()%253B%250A%2520%2520x%2520%253D%2520'str'%253B%250A%257D%26input1%26conformanceConfig%26externs%3D%252F**%2520%2540return%2520%257B%253F%257D%2520*%252F%2520function%2520g()%2520%257B%257D%26refasterjs-template%26CHECK_SYMBOLS%3Dtrue%26MISSING_PROPERTIES%3Dtrue%26TRANSPILE%3Dtrue%26CHECK_TYPES%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
[more complicated example]: https://closure-compiler-debugger.appspot.com/#input0%3D%252F**%2520%2540constructor%2520%2540template%2520T%2520*%252F%250Afunction%2520Base()%2520%257B%257D%250A%252F**%2520%2540constructor%2520%2540extends%2520%257BBase%253CT%253E%257D%250A%2520%2520%2520%2520%2540template%2520T%2520*%252F%250Afunction%2520Sub()%2520%257B%257D%250A%250Afunction%2520f(%252F**%2520!Base%253Cnumber%253E%2520*%252F%2520x)%2520%257B%250A%2520%2520%252F%252Fx%2520%253D%2520%252F**%2520%2540type%2520%257B!Sub%253Cstring%253E%257D%2520*%252F%2520(new%2520Sub())%253B%250A%2520%2520x%2520%253D%2520new%2520Sub()%253B%250A%2520%2520var%2520%252F**%2520!Base%253Cstring%253E%2520*%252F%2520y%2520%253D%2520x%253B%250A%257D%26input1%26conformanceConfig%26externs%26refasterjs-template%26CHECK_SYMBOLS%3Dtrue%26MISSING_PROPERTIES%3Dtrue%26TRANSPILE%3Dtrue%26CHECK_TYPES%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
[but doesn't]: https://closure-compiler-debugger.appspot.com/#input0%3Dvar%2520%252F**%2520!Array%253Cnumber%253E%2520*%252F%2520x%253B%250Ax%2520%253D%2520%255B1%252C%25202%252C%25203%255D%253B%250Ax.push('str')%253B%2520%252F%252F%2520no%2520error!%250A%26input1%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_SYMBOLS%3Dtrue%26MISSING_PROPERTIES%3Dtrue%26TRANSPILE%3Dtrue%26CHECK_TYPES%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue
Contributor guide
Assessment
This issue has not been assessed yet.