-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Strange flow analysis for guards applied to a variable having an initial type of 'nothing' #8374
Comments
I noticed a much simpler scenario that has somewhat unexpected behavior, that may or may not be related. Here, a type is ruled out based on a negative guard: declare let x: number | string;
if (typeof x !== "number") {
x; // 'x' has type 'string', as expected
} And here, declare let x: number | string;
if (typeof x !== "number" && typeof x !== "string") {
x; // 'x' has type 'nothing', as expected
} However in this much simpler case: declare let x: number;
if (typeof x !== "number") {
x; // 'x' still has type 'number'?
} Shouldn't (I considered opening a new issue but I wasn't sure if that would be received positively) |
Your last example doesn't involve a union type, I wonder if that's why it doesn't produce a |
The whole scenario here is somewhat hypothetical because the type checker has already proven to itself that control could never reach the particular point in well behaved code. So, we're attempting to reason about something in the context of it never happening. It's not clear you can have meaningful rules for that in all cases. That said, I think it is fair to say that a type guard applied to a declare let x: number | string;
function func() {
x; // number | string
if (typeof x !== "number" && typeof x !== "string") {
x; // nothing
if (typeof x === "number") {
x; // nothing
x = "abcd";
x; // string
}
x; // string (same as nothing | string)
return;
}
} Regarding the simpler case: declare let x: number;
if (typeof x !== "number") {
x; // 'x' still has type 'number'?
} This is the result of the compiler optimizing the case where the declared type is a primitive type and the variable is assumed to have been initialized (in this case because it is ambient). There's no point in doing control flow analysis at that point because, other than meaningless type guards, nothing in the control flow could affect the type of the variable. |
Thanks for the explanation. The purpose here was to try to investigate how much the current 'machinery' for flow analysis could be reused for pure assignment analysis (of the kind that is required for captured variable assignment analysis) by 'artificially' starting the analysis from a 'simulated' blank point ("nothing"). In order to achieve that reliably, guards shouldn't "disturb" the analysis, of course, so the solution you suggested (and I was leaning to myself) assignments will be taken into account but guards would be essentially ignored. I'm now more optimistic the revised logic may allow something like that. I'm not sure if I this is that important to you though, maybe you feel this isn't generally a good idea and having special-purpose logic is better. If this 'question' will cause a subtle change in the logic of the compiler, then it's not really just a question.. :) |
TypeScript Version:
1.9.0-dev.20160429
Summary:
For the purpose of captured variable assignment analysis, I'm trying to simulate a variation of the current analysis where a captured variable would start from a 'blank' state (i.e. the
nothing
type). This is in order to isolate the possible side-effects that may be caused to it in the body of the function (or through calls to other side-effect causing functions). It turns out this exposed some very strange behavior of the current analysis for this particular scenario:Code:
Remarks:
I'll try to look at each position individually:
if (typeof x === "number")
branch would never be executed unlessx
has been reassigned somewhere after the initial 'negative' guard, so the type could also benothing
, just like in#5
.nothing
here.nothing
here?nothing
here.Another scenario:
Here's another strange scenario:
(#4)
is OK, I believe.In
(#5)
the compiler may assume the conditional branch above was never executed and fall back tonothing
. However, for the purpose of side-effect analysis this behavior may actually turn out to be beneficial, so I'm not sure at this point.Suggestion:
I believe there could be a simple rule to resolve this, something like:
"If a variable having a current type of
nothing
is passed through a guard, then the guard is always expected to fail and the type would resolve tonothing
within the guard's conditional body. Reassignments in the guarded branch would impact the type locally but not affect statements following it"(although reasonable, I'm not sure if the reassignment rule is really helpful for the sort of analysis that would be needed for captured variables, though).
It may not turn out to be that simple in practice, though, I'm not sure.
The text was updated successfully, but these errors were encountered: