{"action":"opened","issue":{"url":"https://api.github.com/repos/google/closure-compiler/issues/2891","repository_url":"https://api.github.com/repos/google/closure-compiler","labels_url":"https://api.github.com/repos/google/closure-compiler/issues/2891/labels{/name}","comments_url":"https://api.github.com/repos/google/closure-compiler/issues/2891/comments","events_url":"https://api.github.com/repos/google/closure-compiler/issues/2891/events","html_url":"https://github.com/google/closure-compiler/issues/2891","id":314866991,"number":2891,"title":"Don't clobber declared types with looser inferred types","user":{"login":"shicks","id":143899,"avatar_url":"https://avatars0.githubusercontent.com/u/143899?v=4","gravatar_id":"","url":"https://api.github.com/users/shicks","html_url":"https://github.com/shicks","followers_url":"https://api.github.com/users/shicks/followers","following_url":"https://api.github.com/users/shicks/following{/other_user}","gists_url":"https://api.github.com/users/shicks/gists{/gist_id}","starred_url":"https://api.github.com/users/shicks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shicks/subscriptions","organizations_url":"https://api.github.com/users/shicks/orgs","repos_url":"https://api.github.com/users/shicks/repos","events_url":"https://api.github.com/users/shicks/events{/privacy}","received_events_url":"https://api.github.com/users/shicks/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-04-17T00:52:10Z","updated_at":"2018-04-17T00:52:10Z","closed_at":null,"author_association":"MEMBER","body":"[repro]\r\n\r\n```js\r\n/** @return {?} */ function g() {}\r\nfunction f(/** number */ x) {\r\n  x = g();\r\n  var /** string */ y = x;\r\n}\r\n```\r\n\r\nIn 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.\r\n\r\nA 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:\r\n\r\n```js\r\n/** @constructor @template T */\r\nfunction Base() {}\r\n/** @constructor @extends {Base<T>}\r\n    @template T */\r\nfunction Sub() {}\r\n\r\nfunction f(/** !Base<number> */ x) {\r\n  //x = /** @type {!Sub<string>} */ (new Sub()); // would be an error!\r\n  x = new Sub();\r\n  var /** !Base<string> */ y = x;\r\n}\r\n```\r\n\r\nNote that, like above, the compiler clearly understands that we can't just assign a `Sub<string>` to `Base<number>`, though once we assign a `Sub<?>` to it, inference is happy to assign it to a `Sub<string>` from there.  It seems to me that the most correct solution would be to do a \"narrowing meet\" operation between `Base<number>` and `Sub<?>` to get `Sub<number>`.  This gets more complicated by variance: invariant types are easy, but if `Base` were covariant then a `Sub<number>` can be assigned to a `Base<string|number>` with no problem.  We may therefore need some sort of \"bounded wildcard\" type, which could narrow the type down to `Sub<? extends (string|number)>` more correctly.  This may to may not be related to bounded generics, but it's worth considering them together.\r\n\r\nIn practice this [shows up] e.g. in\r\n\r\n```js\r\nvar /** !Array<number> */ x;\r\nx = [1, 2, 3];\r\nx.push('str'); // no error!\r\n```\r\n\r\nwhich should produce an error [but doesn't], since `x` is inferred as `!Array<?>` after the assignment.\r\n\r\n[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\r\n\r\n[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\r\n\r\n[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\r\n\r\n[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"}}
{"actor":{"display_login":"shicks"}}