-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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 guard failures that become an intersection type #9859
Comments
I'd say this follows from #8911/#9016.
I suspect the |
Note this comment in particular: #8911 (comment) |
then options here are either |
This problem seems to caused by declared type internal overwriting. This behavior should be a bug because following code works correctly. class A<T> {
private _: T;
}
class B<T> {
private _: T;
}
function f() {
var o: A<void> | B<void>;
if (o instanceof A) return o; // A<void>
} |
I am not sure i understand what you meant there. but the important difference between the two samples is the assignment... var o: A<void> | B<void> = new B<void>();
// here o can only be `B<void>` and nothing else. no assignments has happened to o since it got `new B<void>()`
if (o instanceof A) return o; /// now how can we narrow B into A? |
@mhegazy why do you think "working as intended" about that difference? The two samples should be a same narrowed type by type guard. |
@falsandtru assignments cause narrowing as per #8010. Then subsequent narrowing to an unrelated type narrows to the intersection of the two narrowed types as per #9031. According to those two rules, your example is indeed working as intended. Your sample is a bit like the following, which produces the same intersection narrowed type: class A<T> {
private _: T;
}
class B<T> {
private _: T;
}
function f() {
var o: A<void> | B<void>;
if (o instanceof B) {
if (o instanceof A) return o; // B<void> & A<any>, should be A<void>
}
} |
I understood that behavior, but I'm not sure that behavior is a spec, or a bug. |
Why would it be a bug? Control flow analysis can infer:
|
Merging #9861. class A<T> {
private _: T;
}
class B<T> {
private _: T;
}
function f() {
var o: A<void> | B<void> = new B<void>();
if (!(o instanceof B)) return o; // B<void>, should be A<void>
o; // B<any>, should be B<boid>
} Merging #9862. class A<T> {
private _: T;
}
class B<T> {
private _: T;
}
function f() {
var o: A<void> | B<void> = new A<void>();
o instanceof A || o instanceof B;
o; // A<any>, should be A<void> | B<void>
} |
the first case is again because of the initialization. there is no way for The same of the second case, it is A, it can not be anything else. |
I see, I think we can close this issue. However, a compiler option for detection of this assignment (as initialization) type mismatch is helpful to avoid these confusing. A declared type shouldn't be wider than an assigned type with initialization in these CFA behaviors. |
A wider declared type allows for re-assignment later on: let a: string | number = 'foo';
a; // a is narrowed to string here
a = 42;
a; // a is narrowed to number here |
It case seems an anti-pattern. |
@falsandtru why an anti-pattern? |
It should be const a: string = 'foo';
const b: number = 42; Also see #9886. |
There's plenty of discussion about this in #8513. Note the realistic motivating example involves modelling an I don't think you can simply say
in light of legitimate examples where people are trying to model runtime-valid code using TypeScript, where union variables with initializers occur naturally as a result. |
My example is based on immutable programing. So I suggested as optional in #9886. |
OK, then if you make the variable in your first example immutable: function f() {
const o: A<void> | B<void> = new B<void>();
if (o instanceof A) return o; // B<void> & A<any>, should be A<void>
} ...then it seems an even stronger case for the current compiler behaviour being correct, doesn't it? Or have I misunderstood what you mean by immutable? |
It example is a bad case. So I said "But this operation is a mistake, should be removed." in #9886. My suggestion is useful for finding mistakes like this. |
TypeScript Version: 2.0.0
Code
Expected behavior:
A type of
o
narrowed by type guard isA<void>
.Actual behavior:
A type of
o
narrowed by type guard isB<void> & A<any>
.The text was updated successfully, but these errors were encountered: