-
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
In switch case, narrow base constraint of type #21483
Conversation
Previously, the type itself was narrowed, which was not useful for `T extends 'a' | 'b' | 'c'` and other type parameters that extend literal unions. Note that the previous behaviour was technically correct: at the function declaration, you can't prove that all branches of the case will be used because the type parameter may be instantiated with a smaller union (like T='a' | 'b', in the example above). But the unused cases don't hurt anything, and people expect narrowing to work here.
@ahejlsberg points out that the type of t will be |
@sandersn Sorry for dropping in out of nowhere. I had been working on Is there a reason that the narrowed types are just the literals as opposed to an intersection type? So you would have: declare var n: never;
function f<T extends 'a' | 'b' | 'c'>(t: T): void {
switch (t) {
case 'a': n = t; break; // t : T & 'a'
case 'b': n = t; break; // t : T & 'b'
case 'c': n = t; break; // t : T & 'c'
default: n = t; break; // t : never
}
} Also, am I correct in saying that Is there consideration into extending something like |
@jack-williams Narrowing, strictly speaking, only removes types from a union. The exception is instanceof checks, but those apply to classes with an inheritance relationship, not to unions of string literal types. That's the problem with this PR, in fact; it substitutes You could solve this problem by introducing a "narrowed type parameter" type such that a switch on ( |
@sandersn Thanks for the detailed explanation. Is function test<T extends number | string | boolean>(x: T) {
if (typeof x === 'number') {
x // has type T & number
}
else if (typeof x === 'string') {
x // has type T & string
} else if (typeof x === 'boolean') {
x // has type T & boolean
} else {
x // has type T extends number | string | boolean, should probably be never
}
} I think that
Yes, from my short and rather uninformed look into this it doesn't seem like an easy fix. I'm wondering if it's possible to special case this just at the points of narrowing, rather than making the checker aware in general. So before narrowing a switch'd type, explode |
Oops. I forgot about the narrowing-as-assertion. Yes, for a type parameter, index type, or conditional type T, typeof will use intersection if the target type is assignable to the constraint of T (eg in For your PR, I think |
This description makes things clearer, thanks. I think what I was doing was (sort-of) in this spirit. I was technically only adding to the type, but the thing I added was the constraint narrowed by the assertion, rather than the assertion directly (so
Agreed. I had this behaviour before and will bring it back. I'll change it then ping some of the team so they know when to look at it. There isn't any point reviewing it until I have the right behaviour.
Definitely! Again, I wasn't trying to suggest that the PR should be done in style xyz. I just wanted to pick your brains as it was relevant to |
@sandersn Rather than narrowing to the constraint (which removes the relationship with the type variable from the type), can we leverage substitution types and effectively narrow to |
Thanks for your contribution. This PR has not been updated in a while and cannot be automatically merged at the time being. For housekeeping purposes we are closing stale PRs. If you'd still like to continue working on this PR, please leave a message and one of the maintainers can reopen it. |
@sandersn sorry to pop in, but was there ever a resolve to this? Found this issue for my current problem but doesn't look like this fix ever made it in |
@heyitsaamir Not as far as I remember. My best memory of this PR is that it started off a bit experimental, ran into problems, and all we could think of was even more experimental approaches. So I think #20375 is quite a difficult problem that requires more than an easy fix. |
Fixes #20375
Previously, the type itself was narrowed, which was not useful for
T extends 'a' | 'b' | 'c'
and other type parameters that extend literal unions. Note that the previous behaviour was technically correct: at the function declaration, you can't prove that all branches of the case will be used because the type parameter may be instantiated with a smaller union (likeT='a' | 'b'
, in the example above). But the unused cases don't hurt anything, and people expect narrowing to work here.For example:
Narrowing is technically incorrect, by analogy with the non-type-parameter example: