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
mapped types break the generic constraintsΒ #46807
huh, it looks like it isn't detecting the properties are co-variant in the first case, instead the generic only resolves based on the first property in the declaration. playground
typeShape1<T>={foo: T,bar: T}typeShape2<T>={bar: T,foo: T,}declarefunctionretriveShape1<T>(arg: Shape1<T>):TdeclarefunctionretriveShape2<T>(arg: Shape2<T>):TretriveShape1({// using vs1 that has `foo` defined first...foo: 'text',bar: 1// produces error on 'bar' saying it should match `foo`})retriveShape2({// using vs2 that has `bar` defined first....foo: 'text',// produces an error on `foo` saying it should match `bar`.bar: 1})
Shouldn't these both just have the generic resolve to number | string since they are co-variant or is something else going on?
This is the intended behavior, though admittedly it's not necessarily obvious why.
Generic inference can reject inference results that are sound. For example, if you have
declarefunctionfn<T>(a: T,b: T): void;
the call
fn(0,"hello");
is rejected even though we could have inferred the type argument
fn<string|number>(0,"hello");
(side note: please don't use "1" and 1 as your example values, it's confusing π ).
This is done to make generics useful, since otherwise there'd be no way to write a function that rejects the call fn(0, "hello"), even though this is one of the prime raison d'Γͺtres of generics in the first place.
However, this logic has to eventually stop somewhere, since the function
declarefunctionfn<T>(arg: T[]): void;
"should" be callable with a heterogeneous array:
fn([1,"two"]);
The question is then, given some call, are we looking at something that looks more like "two arguments with linked types", or are we looking at something that's more like "a legitimately heterogenous array" ? For an object type with simple properties, it's the former. For a mapped type, it's the latter.
Side note: there are no constraints whatsoever in this example.
Reacted by Martin Johns, Tadhg McDonald-Jensen and fishDog
Activity
MartinJohns commentedon Nov 15, 2021
Your code has no constraint, and nothing is broken. The type for
Tis just inferred to bestring | number.Bigfish8 commentedon Nov 15, 2021
Thanks for your reply, but why the type for
Tin first case is notstring | number.tadhgmister commentedon Nov 15, 2021
huh, it looks like it isn't detecting the properties are co-variant in the first case, instead the generic only resolves based on the first property in the declaration. playground
Shouldn't these both just have the generic resolve to
number | stringsince they are co-variant or is something else going on?RyanCavanaugh commentedon Nov 16, 2021
This is the intended behavior, though admittedly it's not necessarily obvious why.
Generic inference can reject inference results that are sound. For example, if you have
the call
is rejected even though we could have inferred the type argument
(side note: please don't use "1" and 1 as your example values, it's confusing π ).
This is done to make generics useful, since otherwise there'd be no way to write a function that rejects the call
fn(0, "hello"), even though this is one of the prime raison d'Γͺtres of generics in the first place.However, this logic has to eventually stop somewhere, since the function
"should" be callable with a heterogeneous array:
The question is then, given some call, are we looking at something that looks more like "two arguments with linked types", or are we looking at something that's more like "a legitimately heterogenous array" ? For an object type with simple properties, it's the former. For a mapped type, it's the latter.
Side note: there are no constraints whatsoever in this example.
Bigfish8 commentedon Nov 17, 2021
Very appreciated for your detailed replyοΌ