-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Bug Report: TS creates contravariant intersection when K extending keyof T assigned new type #54547
Comments
The Playground link is very very long, and the sample here has unbound identifiers. Can you please provide a minimal sample that more clearly demonstrates the problem you're describing? |
This is as condensed as I can get it: Typescript Playground Most of the space is scaffolding. The link above doesn't represent the desire to re-type the output in accordance with the generic input so that the object[K] has type U[], but I think it does represent the problem. Everything is still scaffolding to demonstrate the following: const joinSample = <T, K extends keyof T, U extends HasID>(
parent: T,
parentKey: K,
) => {
const keyArray = parent[parentKey] as string[];
return {
...parent,
[parentKey]: keyArray.map((k) => 123) // This assignment causes the problem
};
}
//...
const joined: FullFoo = joinSample(sampleFoo, 'bars'); // Error - the type for joined is inferred as {id: string, bars: string[], bars: number[]} The expectation is that when constructing the return object, it would understand the standard JS behavior that if you spread a parent, then add a specific child key, that it will overwrite the specific key if in the parent. |
The problem here is that there isn't a correct type to describe what First let's fix const joinSample = <T, K extends keyof T>(
parent: T,
parentKey: K,
) => {
const keyArray = parent[parentKey] as string[];
return {
...parent,
[parentKey]: keyArray.map((k) => 123)
} as T & Record<K, number[]>;
} This behaves as desired. However, if you invoked this as const joined: FullFoo = joinSample<typeof sampleFoo, 'bars' | 'id'>(sampleFoo, 'bars'); then the implied behavior based on the type definition is that you'd have an object with |
Chalk up another use case for #27808. |
First, you if you're having to cast the output, then you aren't getting appropriate inference and I'd argue it's still a problem. Second, your application of So T has keys { bars: number[]; id: string } | {bars: string[]; id: number[]} This could be further narrowed when the function is called. |
@RoboZoom I think Ryan’s point is, ultimately, that there’s no way to represent that type operation generically (since your implementation is generic) using the type operators that are currently available. That would require spread types. Basically there’s no type that TypeScript can currently infer that would accurately represent the output of this function. |
The point of that example is to show that the inferred signature is the more correct one. If we gave it the type you're saying it should have, the output would be wrong. |
Bug Report
🔎 Search Terms
T[K] assignment does not transform type of T
🕗 Version & Regression Information
Version: 4.8.4 to Present
This is bug became relevant when 4.8.4 released.
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
The above code infers the output to
joinSample
asT & [x: string] : (U | undefined)[]
which indicates that the T has the same key twice, with different types.This is demonstrated in the playground when
joined
is typed toFullFoo
. Untyped, when run, it produces an object that meets the criteria forFullFoo
. When declared with typeFullFoo
, it raises an error.🙂 Expected behavior
I would expect Typescript to substitute the type of parentKey from its previous value (in this case, always
string[]
) to(U | undefined)[]
. The compiler knows that K is a keyof T - instead of creating anever
condition, it should replace the type of T[K] to U.The text was updated successfully, but these errors were encountered: