You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
narrowing in switches doesnt work on constrained unions #20375
assuming my understanding it right: an extended union is a subset (subtype) of the original union
declarefunctionnever(never: never): never;typeX='A'|'B'|'C';voidfunctionfn<YextendsX>(y: Y): Y{switch(y){case'A': returny;case'B': returny;case'C': returny;default: returnnever(y);// <-- y expected to be never, actual Y}}
same problem with objects
typeX={k: 'A'}|{k: 'B'}|{k: 'C'};voidfunctionfn<YextendsX>(y: Y): Y{switch(y.k){case'A': returny;case'B': returny;case'C': returny;default: returnnever(y);// <-- y expected to be never, actual Y}}
Nvm earlier comment, misunderstood what you meant by "extended". The weird part here is y is being inferred as never in each of the 'A', 'B' and 'C' cases.
Generics that are constrained to literal types are not treated like literals at the moment. generics have a more conservative behavior when narrowing in general since the actual type is not known, but generics with literal constraints should be behave like a union of literals.
In the first example, y is never in the case A/B/C branches. This, too , seems wrong.
declarevarn: never;functionfn<Yextends"A"|"B"|"C">(y: Y): Y{switch(y){case'A': n=y;returny;// ok!case'B': n=y;returny;case'C': n=y;returny;default: n=y;returnnever(y);// <-- y expected to be never, actual Y}}
Upon thinking about it some more (and fixing the bug), I don't think this is actually super wrong. It's correct in an annoying way. Consider the non-type-parameter example, which the compiler correctly complains about:
case 'c' can never happen, so the compiler adds an error that 'c' not comparable to 'a' | 'b'. This is equivalent to the above example with Y='a' | 'b'; 'a' | 'b' extends 'a' | 'b' | 'c', but case 'c' will never be true. At the function's declaration we don't know which type Y will be, so case 'c' (or 'a' or 'b') might never be true. We can't know. So the correct thing is not to narrow at all, which is the current behaviour.
However, this is useless and annoying because even though case 'c' is useless with Y='a' | 'b', it's not hurting anything. I changed the narrowing code to use the base constraint of the type instead of the type itself. The PR is up at #21483
what you say is indeed true in a non-practical way, yes one cannot narrow an unknown something like we know what it is, but here instead we are narrowing in assumption that we cover all possible cases out there, so i guess it's still correct
The fact that type narrowing does not work as expected in conjunction with generic constraints is very subtle and confusing behavior. I've been trying to figure out why we haven't been able to get this to work in our codebase for quite a while, thinking it was an issue with our interface definitions, before finally stumbling across this issue.
We use enum discriminator properties a lot, so this is pretty annoying to workaround. It would be great if this could be prioritized, it very much feels like a bug.
Reacted by Alex Burner, Kevin Ryan, Jakub Wolny, Benjamin Goering, Cyril Silverman, Micah Zoltu, Aamir Jawaid and githorse
Activity
masaeedu commentedon Nov 30, 2017
Nvm earlier comment, misunderstood what you meant by "extended". The weird part here is
yis being inferred asneverin each of the'A','B'and'C'cases.mhegazy commentedon Dec 2, 2017
Generics that are constrained to literal types are not treated like literals at the moment. generics have a more conservative behavior when narrowing in general since the actual type is not known, but generics with literal constraints should be behave like a union of literals.
sandersn commentedon Dec 4, 2017
In the first example,
yisneverin the case A/B/C branches. This, too , seems wrong.RyanCavanaugh commentedon Jan 30, 2018
This is indeed super wrong
sandersn commentedon Jan 30, 2018
Upon thinking about it some more (and fixing the bug), I don't think this is actually super wrong. It's correct in an annoying way. Consider the non-type-parameter example, which the compiler correctly complains about:
case 'c'can never happen, so the compiler adds an error that 'c' not comparable to 'a' | 'b'. This is equivalent to the above example withY='a' | 'b';'a' | 'b' extends 'a' | 'b' | 'c', butcase 'c'will never be true. At the function's declaration we don't know which typeYwill be, socase 'c'(or 'a' or 'b') might never be true. We can't know. So the correct thing is not to narrow at all, which is the current behaviour.However, this is useless and annoying because even though
case 'c'is useless withY='a' | 'b', it's not hurting anything. I changed the narrowing code to use the base constraint of the type instead of the type itself. The PR is up at #21483zpdDG4gta8XKpMCd commentedon Jan 30, 2018
what you say is indeed true in a non-practical way, yes one cannot narrow an unknown something like we know what it is, but here instead we are narrowing in assumption that we cover all possible cases out there, so i guess it's still correct
marshall007 commentedon Jul 20, 2018
The fact that type narrowing does not work as expected in conjunction with generic constraints is very subtle and confusing behavior. I've been trying to figure out why we haven't been able to get this to work in our codebase for quite a while, thinking it was an issue with our interface definitions, before finally stumbling across this issue.
We use
enumdiscriminator properties a lot, so this is pretty annoying to workaround. It would be great if this could be prioritized, it very much feels like a bug.jcalz commentedon Apr 11, 2019
Is this different from #13995?
ahejlsberg commentedon Mar 13, 2021
This issue is now fixed in #43183.