Control flow analysis for implicit any variables#11263
Conversation
sandersn
left a comment
There was a problem hiding this comment.
A couple of questions each about the change and the tests, plus a couple of ideas for the tests.
|
|
||
| function isAutoVariableInitializer(initializer: Expression) { | ||
| const expr = initializer && skipParentheses(initializer); | ||
| return !expr || expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>expr) === undefinedSymbol; |
There was a problem hiding this comment.
Can this work for [] or {} as well?
There was a problem hiding this comment.
Yes, but they each need specialized strategies and I'll cover them in separate PRs.
| location = location.parent; | ||
| } | ||
| if (isExpression(location) && !isAssignmentTarget(location)) { | ||
| if (isPartOfExpression(location) && !isAssignmentTarget(location)) { |
There was a problem hiding this comment.
What does this change and why? I'm guessing that now we can get the type of a symbol given a child of the symbol's declaration, but I can't guess why.
There was a problem hiding this comment.
This is a bug that somehow got introduced during some refactoring for the new emitter. The old isExpression was renamed to isPartOfExpression and a new isExpression was introduced that doesn't look at the context (i.e. doesn't access the parent property).
| for (var x in new Date()) { } | ||
|
|
||
| var c, d, e; | ||
| var c: any, d: any, e: any; |
There was a problem hiding this comment.
why add any here? Is it to preserve the intent of the tests — does control flow pick a union type for these variables now?
There was a problem hiding this comment.
The test just needed some values of type any but they aren't actually meaningful once we start analyzing control flow.
| // @noEmitHelpers: true | ||
| // @target: ES5 | ||
| declare var x, y, z, a, b, c; | ||
| declare var x: any, y: any, z: any, a: any, b: any, c: any; |
There was a problem hiding this comment.
I thought that auto typing didn't apply to ambient contexts. Why did you have to add any here?
There was a problem hiding this comment.
Yeah, you're right about that. But having them doesn't hurt and is a better indication of intent (i.e. we expressly don't want CFA here).
| //@target: ES5 | ||
| module M { | ||
| var Symbol; | ||
| var Symbol: any; |
There was a problem hiding this comment.
I don't understand why auto typing would apply here either
|
|
||
| declare let cond: boolean; | ||
|
|
||
| function f1() { |
There was a problem hiding this comment.
It would be nice if these functions had names that showed what they were testing.
| if (cond) { | ||
| x = "hello"; | ||
| } | ||
| const y = x; |
There was a problem hiding this comment.
if you have if (cond) .. else then y: string | number, right? Might be nice to have a case for that.
|
@mhegazy Want to take a look? |
* Fix fialing tests after microsoft/TypeScript#11263 * Fix more failures
|
@ahejlsberg function f(x: number[]): void { ... }
let x = []; // x: any[]
x.push(3); // x: number[]
f(x); // x is ok according to control flow, so no --noImplicitAny error? |
You are correct. this is not flagged as an implicit any error. |
|
@mhegazy this is not safe, though. function f(x: number[]): void {
setImmediate(function () {
console.log(x.reduce((a, b) => a * b, 1));
});
}
let x = [];
x.push(3);
f(x);
x.push("I am not a number"); // ok, now x: (string|number)[] |
|
Typescript is already unsafe in this way. You can also write |
|
Never mind, I was completely wrong. It doesn't work. |
|
@sandersn But I think many people like TS for being (for the most part) statically type-safe and use Thus, I would suggest that you limit what is allowed by this feature or (harder) only allow cases that are provably correct (e.g. determine that the dynamic type never changes to an incompatible type). I can at least imagine the following operations to be potentially unsafe: saving the value in an object, an array or a variable in an outer scope; assigning the value to a setter; passing the value to a function; capturing the value in a closure; calling a member of the value; new-ing the value. |
|
@jods4, the compiler today does not guard against side effects in multiple places. it is not specific to noImplicitAny checks. for instance: function f(x: { a: number }) {
setTimeout(() => {
x.a.toFixed();
}, 10);
}
var x: { kind: "number", a: number } | { kind: "string", a: string };
if (x.kind === "number") {
f(x);
}
x.a = "string"; // potentially unsafe, as x has already been captured.Arrays are just like objects with properties when it comes to narrowing. I do understand your concern, but really this issue is a general design limitation and not specific to this change. |
|
@mhegazy That said, having safety holes doesn't necessarily mean we should add new ones. I guess the question is: do benefits outweight the drawbacks? Your example is a lot more convoluted than the simple example I gave, which was just: let x = [];
x.push(3);
f(x);
x.push("NaN");I guess what annoys me most here is that this is unsafe because I use My opinion:
To maximize both safety and value, I would suggest that under let x; // this is not an error yet.
x = 4; // so x: number at this point
console.log(x.toFixed()); // sure
x += 5; // and so on...
let y; // same thing
y = 4; // so y: number
y = "Four"; // still an error when --noImplicitAny. Can't use dynamic types without explicit declaration. |
This PR introduces control flow analysis for
letandvarvariables that have no type annotation and either no initial value or an initial value ofnullorundefined.In the example above,
xandyimplicitly have declared types ofanybut control flow analysis can determine their actual types at every reference. Therefore, no errors are reported even when the example is compiled with--noImplicitAny.Control flow analysis is unable to determine the actual types of implicit
anyvariables when they are referenced in nested functions. In such cases, the variables will appear to have typeanyin the nested functions and errors will be reported for the references if--noImplicitAnyis enabled.In time it is possible that control flow analysis will be able to more accurately determine types of references to outer variables from nested functions in some cases, but given that nested functions are first class objects that can be arbitrarily passed around and called, it is effectively impossible to analyze all such scenarios.