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
Unexpected literal union type widening in generic type inference #32596
Search Terms: type widening generic union inference
Code
functionexample(){typeSomeUnion="A"|"B"|"C";functionfun<TextendsSomeUnion>(x: T,y: T){}fun("A","A");// no errorfun("A","B");// !!! no error because widening to "A" | "B"fun("B","A");// !!! no error because widening to "A" | "B"}functioncounterexample(){typeSomeUnion=string|number|boolean;functionfun<TextendsSomeUnion>(x: T,y: T){}fun("str","other");// no errorfun("str",42);// error as expectedfun(42,"str");// error as expected}
Expected behavior:
Compile errors in the two "!!!" marked lines.
Actual behavior:
No compile error, instead the type inference inferred "A" | "B" for T.
That's pretty unexpected and IMO a bug, because for literal type parameters it makes no sense to infer any union, it obviously can only be one value (therefor the type widening should stop when seeing a parameter which is directly the generic literal union type).
Also it's asymmetric with non literal union types (see counterexample).
For reference this is my original code (simplified version of my real scenario):
functionextended_example(){typeSomeUnion="A"|"B"|"C";interfaceSomeInterface<TextendsSomeUnion>{prop: T}consta: SomeInterface<"A">={prop: "A"}constb: SomeInterface<"B">={prop: "B"}functionfun<TextendsSomeUnion>(x: T,y: SomeInterface<T>){}fun("A",a);// no errorfun("A",b);// !!! no error because widening to "A" | "B"fun("B",a);// !!! no error because widening to "A" | "B"}
... here I can pass a wrong SomeInterface<T> next to T which is (again pretty unexpected and) not very type safe :/
Interesting. I thinks here it's okay that no error happens because there is no "out of bounds" type widening involved like my ("A" literal typed argument widened to "A" | "B" in my example).
Also it is symmetric with non-literals ...
typeSomeUnion=string|number|boolean;functionfun<TextendsSomeUnion>(x: T,y: T){}declareconststrOrNum: string|number;fun(strOrNum,42);// no error as expected
But this brings me to the following example which should error again (it's generalization of the original example):
It's assumed that a union of two literal types from the same primitive family are an OK inference, but not from two different families.
Any inference that matches the constraint and is a supertype of all candidates is legal and your function implementation should be assuming that this is possible - in the worst case, a caller of your function could write
Activity
ulrichb commentedon Jul 28, 2019
For reference this is my original code (simplified version of my real scenario):
... here I can pass a wrong
SomeInterface<T>next toTwhich is (again pretty unexpected and) not very type safe :/AnyhowStep commentedon Jul 28, 2019
Somewhat unrelated. But what do you think of the following?
ulrichb commentedon Jul 29, 2019
AnyhowStep
Interesting. I thinks here it's okay that no error happens because there is no "out of bounds" type widening involved like my (
"A"literal typed argument widened to"A" | "B"in my example).Also it is symmetric with non-literals ...
But this brings me to the following example which should error again (it's generalization of the original example):
which again is asymmetric with non-literals:
AnyhowStep commentedon Jul 29, 2019
I'm not on my computer but what happens when you try this,
If the above works as you expected, how about,
RyanCavanaugh commentedon Jul 29, 2019
It's assumed that a union of two literal types from the same primitive family are an OK inference, but not from two different families.
Any inference that matches the constraint and is a supertype of all candidates is legal and your function implementation should be assuming that this is possible - in the worst case, a caller of your function could write
See also #14829
You could write this instead:
ulrichb commentedon Jul 29, 2019
Okay. Thanks for info and sharing the workaround with the conditional type.
... and +1 for
NoInfer<T>:)