-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Handle generic mapped types in getTypeOfPropertyOfContextualType. #27586
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
Conversation
Thanks @mattmccutchen, I hope this gets merged. I added a bunch of test cases in #24694 (comment) that I think should be resolved by this. |
@mattmccutchen I checked out your branch to see the effect on my repro cases, and got the following: type TakeString = (s: string) => any;
// Various functions accepting an object whose properties are TakeString functions.
// Note these all use mapped types.
declare function mapped1<T extends {[P in string]: TakeString}>(obj: T): void;
declare function mapped2<T extends {[P in keyof T]: TakeString}>(obj: T): void;
declare function mapped3<T extends {[P in keyof any]: TakeString}>(obj: T): void;
declare function mapped4<T>(obj: T & {[P in keyof T]: TakeString}): void;
declare function mapped5<T, K extends keyof T>(obj: T & {[P in K]: TakeString}): void;
declare function mapped6<K extends string>(obj: {[P in K]: TakeString}): void;
declare function mapped7<K extends keyof any>(obj: {[P in K]: TakeString}): void;
declare function mapped8<K extends 'foo'>(obj: {[P in K]: TakeString}): void;
declare function mapped9<K extends 'foo'|'bar'>(obj: {[P in K]: TakeString}): void;
// CURRENT MASTER THIS PR
// ============== =======
mapped1({foo: s => 42}); // OK: 's' is contextually typed as 'string' ✓ OK (unchanged)
mapped2({foo: s => 42}); // ERROR: 's' implicitly has an 'any' type ? ERROR: s still 'any'
mapped3({foo: s => 42}); // OK: 's' is contextually typed as 'string' ✓ OK (unchanged)
mapped4({foo: s => 42}); // ERROR: 's' implicitly has an 'any' type ? ERROR: s still 'any'
mapped5({foo: s => 42}); // OK: 's' is contextually typed as 'string' ✓ OK (unchanged)
mapped6({foo: s => 42}); // ERROR: 's' implicitly has an 'any' type ✓ OK: s now 'string'
mapped7({foo: s => 42}); // OK: 's' is contextually typed as 'string' ✓ OK (unchanged)
mapped8({foo: s => 42}); // ERROR: 's' implicitly has an 'any' type ✓ OK: s now 'string'
mapped9({foo: s => 42}); // OK: 's' is contextually typed as 'string' ✓ OK (unchanged) So However |
@yortus You can give the compiler a hint about the contextual type by unioning in a type with an index signature: declare function mapped2b<T extends {[n: string]: TakeString} | {[P in keyof T]: TakeString}>(obj: T): void;
declare function mapped4b<T>(obj: T & ({[n: string]: TakeString} | {[P in keyof T]: TakeString})): void;
mapped2b({foo: s => 42}); // OK
mapped4b({foo: s => 42}); // OK |
Haha, and I thought I had tried every way to express it 😄. There is definitely an art to constructing some types. |
@yortus Now I think I really figured out what is going on in your test cases. (Good test cases!) TypeScript has a rule that if you try to access the members of a generic mapped type In cases 2 and 4, the first pass makes no inference at all (because the inference for I realize now that if I could make |
The changes to existing baselines look acceptable to me. Fixes microsoft#24694.
e406505
to
269c7a0
Compare
OK, so there are several separate issues brought up by the those test cases. In any case, contextual typing works much better in our project with the fix presented here. I do hope it gets merged. I appreciate the time you have put into sleuthing through the compiler internals to figure this out. |
Fixes #24694.