-
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
Indexed access behavior differs between type aliases and type parameters. #16036
Comments
Define type Merged<T> = T[keyof T]; such that a tagged union of T's properties can be expressed as type MyTaggedUnion3 = Merged<Tagged<MyOpts>>; evaluates as expected to: But when type MergedTagged<T> = Merged<Tagged<T>>; then: type MyTaggedUnion4 = MergedTagged<MyOpts>; evaluates unexpectedly to: |
I have a hunch as to why this is happening. The evaluation of the literal expression type MyOpts = { x: number, y: string } is the same (I presume?) as: type MyOpts = { [k in 'x']: number } & { [k in 'y']: string }; The type expression Tagged<MyOpts>
==> Tagged<{ x: number, y: string }>
==> Tagged<{ [k in 'x']: number } & { [k in 'y']: string }>;
==> Tagged<{ [k in 'x']: number }> & Tagged<{ [k in 'y']: string }>;
==> { [k in 'x']: ... } & { [k in 'y']: ... };
==> { x: ..., y: ... } The next type expression, Merged<{ x: ..., y: ... }>
==> Merged<{ [k in 'x']: ... } & { [k in 'y']: ... }>;
==> ({ [k in 'x']: ... } & { [k in 'y']: ... })[ 'x' | 'y' ];
==> { [k in 'x']: ... }['x'] | { [k in 'y']: ... }['y'];
==> { ... } | { ... } But the body of a type operator's expression is perhaps evaluated differently. type MergedTagged<T> = Merged<Tagged<T>>; If Merged<Tagged<{ [K in keyof T]: T[K] }>>
==> Merged<{ [K in keyof T]: { tag: K, val: T[K] } }>
==> { [K in keyof T]: { tag: K, val: T[K] } }[keyof T]
==> { tag: K, val: T[K] } so when MergedTagged<{ [k in 'x']: number } & { [k in 'y']: string }>
==> MergedTagged<{ [k in 'x'|'y']: string|number }>
==> Merged<Tagged<{ [k in 'x'|'y']: string|number }>>
==> { tag: 'x'|'y', val: string|number } If that's true, or even close to the mark, then I don't know if this still counts as a bug or not. But if plain type parameters |
@ahejlsberg any idea what's going on here? |
This is due to one of our higher order mapped type relationships (#12351). Specifically, we have a rule that says a type function setState<T, K extends keyof T>(obj: T, props: Pick<T, K>) {
for (let k in props) {
obj[k] = props[k]; // Pick<T, K>[K] is equivalent to T[K]
}
} The reason you get different types from You can get the desired result by intersecting with a dummy index signature: type TaggedUnion<Opts> = (Tagged<Opts> & { [x: string]: any })[keyof Opts]; The index signature will never come into play, so its only effect is to disable the higher order equivalence pattern. |
Fixed by #18042. |
TypeScript Version: 2.3.2
Summary:
Tagged<Opts>[keyof Opts]
evaluates differently depending on whetherOpts
is a type alias or a type parameter.Code
Playground link
Expected behavior:
Tagged<O>[keyof O]
should produce the same thing, whetherO
is an alias to a type literal or the name of a type parameter.Actual behavior:
The literal version produces the desirable result:
The parameterized version produces the weaker result:
Possibly related: #15983
The text was updated successfully, but these errors were encountered: