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
String literal generic not always inferred when part of union in nested object #19632
declarefunctiondirect<Aextendsstring>(a: A|A[]): Record<A,string>declarefunctionnested<Aextendsstring>(a: {fields: A}): Record<A,string>declarefunctionnestedUnion<Aextendsstring>(a: {fields: A|A[]}): Record<A,string>constdirectUnionSingle=direct("z")constdirectUnionArray=direct(["z","y"])constnestedSingle=nested({fields: "z"})constnestedUnionSingle=nestedUnion({fields: "z"})constnestedUnionArray=nestedUnion({fields: ["z","y"]})declarefunctionhasZField(arg: {z: string}): voidhasZField(directUnionSingle)// okhasZField(directUnionArray)// okhasZField(nestedSingle)// okhasZField(nestedUnionSingle)// error TS2345: Argument of type 'Record<string, string>' is not assignable to parameter of type '{ z: string; }'hasZField(nestedUnionArray)// ok
Expected behavior:
All 5 calls to hasZField compile without errors
Actual behavior:
One call does not compile because it infers A to be string instead of "z"
Not a big deal just a quirk I came across and assume is a bug.
I read through #19497 and I think I understand what's going on in that one but I don't think this is a duplicate. In #19497 as I understand it the issue is essentially about any time a pattern like
declarefunctionarr<T>(val: T): Array<T>// or any type similar to Container<T>
occurs, T is widened from a literal to string.
In my mind that seems mostly unrelated to what is happening in my example. What seems so odd about my example and really makes me think it is a bug is the how the inference works correctly here:
constdirectUnionSingle=direct("z")// inferred to be Record<"z", string>constdirectUnionArray=direct(["z","y"])// inferred to be Record<"z" | "y", string>
but that same pattern fails, but only partially fails, if they are nested inside of an object:
constnestedUnionSingle=nestedUnion({fields: "z"})// inferred to be Record<string, string>constnestedUnionArray=nestedUnion({fields: ["z","y"]})// inferred to be Record<"z" | "y", string>
Andy (Andrewkraft) (@Andy-MS) Also more evidence that this is not a duplicate of #19497 is that all the examples in #19497 work if the generic type is constrained to extend string:
interfaceObservable<Textendsstring>{peek(): T}declarefunctionobs<Textendsstring>(val: T): Observable<T>constt: Observable<'A'>=obs('A')// now works
declarefunctionarr<Textendsstring>(val: T): T[]consty: "a"[]=arr("a");// now works
In my example the generic includes extends string so I'd expect it to work.
"z" as "z" is a workaround for the error just like in #19497 but the error itself is definitely not because of the same design limitation that is described there. Expanding on your example from #19497:
declarefunctionarr<T>(val: T): T[]consty: "a"[]=arr("a");// error as expecteddeclarefunctionarr2<T>(val: T|T[]): T[]consty2: "a"[]=arr2("a");// error as expecteddeclarefunctionarr3<T>(val: {fields: T}): T[]consty3: "a"[]=arr3({fields: "a"});// error as expecteddeclarefunctionarr4<T>(val: {fields: T|T[]}): T[]consty4: "a"[]=arr4({fields: "a"});// error as expecteddeclarefunctionarr5<T>(val: {fields: T|T[]}): T[]consty5: "a"[]=arr5({fields: ["a"]});// error as expecteddeclarefunctionstrarr<Textendsstring>(val: T): T[]consts1: "a"[]=strarr("a");// no error as expecteddeclarefunctionstrarr2<Textendsstring>(val: T|T[]): T[]consts2: "a"[]=strarr2("a");// no error as expecteddeclarefunctionstrarr3<Textendsstring>(val: {fields: T}): T[]consts3: "a"[]=strarr3({fields: "a"});// no error as expecteddeclarefunctionstrarr4<Textendsstring>(val: {fields: T|T[]}): T[]consts4: "a"[]=strarr4({fields: "a"});// *** unexpected error ***declarefunctionstrarr5<Textendsstring>(val: {fields: T|T[]}): T[]consts5: "a"[]=strarr5({fields: ["a"]});// no error as expected
If it is the same design limitation as #19497 then why do 4 of the 5 start working after adding extends string?
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
Activity
kpdonn commentedon Nov 1, 2017
I read through #19497 and I think I understand what's going on in that one but I don't think this is a duplicate. In #19497 as I understand it the issue is essentially about any time a pattern like
occurs,
Tis widened from a literal to string.In my mind that seems mostly unrelated to what is happening in my example. What seems so odd about my example and really makes me think it is a bug is the how the inference works correctly here:
but that same pattern fails, but only partially fails, if they are nested inside of an object:
kpdonn commentedon Nov 1, 2017
Andy (Andrewkraft) (@Andy-MS) Also more evidence that this is not a duplicate of #19497 is that all the examples in #19497 work if the generic type is constrained to extend string:
In my example the generic includes
extends stringso I'd expect it to work.kpdonn commentedon Nov 1, 2017
"z" as "z"is a workaround for the error just like in #19497 but the error itself is definitely not because of the same design limitation that is described there. Expanding on your example from #19497:If it is the same design limitation as #19497 then why do 4 of the 5 start working after adding
extends string?Apply isLiteralContextualType recursively for unions and intersections.
kpdonn commentedon Nov 2, 2017
Heads up I added a PR at #19684 that would fix this behavior if accepted.
As requested, include fix for microsoft#19632
typescript-bot commentedon Nov 17, 2017
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.