Skip to content

Commit 269c7a0

Browse files
committed
Handle generic mapped types in getTypeOfPropertyOfContextualType.
The changes to existing baselines look acceptable to me. Fixes microsoft#24694.
1 parent 12cd3ea commit 269c7a0

7 files changed

+83
-8
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6793,6 +6793,9 @@ namespace ts {
67936793
// First, if the constraint type is a type parameter, obtain the base constraint. Then,
67946794
// if the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X.
67956795
// Finally, iterate over the constituents of the resulting iteration type.
6796+
//
6797+
// TODO: When fixing #27819 here, update generic mapped type handling in
6798+
// getTypeOfPropertyOfContextualType too if needed.
67966799
const keyType = constraintType.flags & TypeFlags.InstantiableNonPrimitive ? getApparentType(constraintType) : constraintType;
67976800
const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((<IndexType>keyType).type)) : keyType;
67986801
forEachType(iterationType, addMemberForKeyType);
@@ -9457,19 +9460,19 @@ namespace ts {
94579460
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
94589461
// and we might never terminate.
94599462
if (isGenericMappedType(objectType)) {
9460-
return type.simplified = substituteIndexedMappedType(objectType, type);
9463+
return type.simplified = substituteIndexedMappedType(objectType, type.indexType);
94619464
}
94629465
if (objectType.flags & TypeFlags.TypeParameter) {
94639466
const constraint = getConstraintOfTypeParameter(objectType as TypeParameter);
94649467
if (constraint && isGenericMappedType(constraint)) {
9465-
return type.simplified = substituteIndexedMappedType(constraint, type);
9468+
return type.simplified = substituteIndexedMappedType(constraint, type.indexType);
94669469
}
94679470
}
94689471
return type.simplified = type;
94699472
}
94709473

9471-
function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {
9472-
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]);
9474+
function substituteIndexedMappedType(objectType: MappedType, indexType: Type) {
9475+
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [indexType]);
94739476
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
94749477
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
94759478
}
@@ -16703,7 +16706,15 @@ namespace ts {
1670316706

1670416707
function getTypeOfPropertyOfContextualType(type: Type, name: __String) {
1670516708
return mapType(type, t => {
16706-
if (t.flags & TypeFlags.StructuredType) {
16709+
if (isGenericMappedType(t)) {
16710+
const constraint = getConstraintTypeFromMappedType(t);
16711+
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
16712+
const propertyNameType = getLiteralType(unescapeLeadingUnderscores(name));
16713+
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
16714+
return substituteIndexedMappedType(t, propertyNameType);
16715+
}
16716+
}
16717+
else if (t.flags & TypeFlags.StructuredType) {
1670716718
const prop = getPropertyOfType(t, name);
1670816719
if (prop) {
1670916720
return getTypeOfSymbol(prop);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [contextualPropertyOfGenericMappedType.ts]
2+
// Repro for #24694
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
f({ data: 0 }, { data(value, key) {} });
6+
7+
8+
//// [contextualPropertyOfGenericMappedType.js]
9+
// Repro for #24694
10+
f({ data: 0 }, { data: function (value, key) { } });
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/contextualPropertyOfGenericMappedType.ts ===
2+
// Repro for #24694
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
>f : Symbol(f, Decl(contextualPropertyOfGenericMappedType.ts, 0, 0))
6+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
7+
>data : Symbol(data, Decl(contextualPropertyOfGenericMappedType.ts, 2, 37))
8+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
9+
>handlers : Symbol(handlers, Decl(contextualPropertyOfGenericMappedType.ts, 2, 45))
10+
>P : Symbol(P, Decl(contextualPropertyOfGenericMappedType.ts, 2, 59))
11+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
12+
>value : Symbol(value, Decl(contextualPropertyOfGenericMappedType.ts, 2, 75))
13+
>T : Symbol(T, Decl(contextualPropertyOfGenericMappedType.ts, 2, 19))
14+
>P : Symbol(P, Decl(contextualPropertyOfGenericMappedType.ts, 2, 59))
15+
>prop : Symbol(prop, Decl(contextualPropertyOfGenericMappedType.ts, 2, 87))
16+
>P : Symbol(P, Decl(contextualPropertyOfGenericMappedType.ts, 2, 59))
17+
18+
f({ data: 0 }, { data(value, key) {} });
19+
>f : Symbol(f, Decl(contextualPropertyOfGenericMappedType.ts, 0, 0))
20+
>data : Symbol(data, Decl(contextualPropertyOfGenericMappedType.ts, 3, 3))
21+
>data : Symbol(data, Decl(contextualPropertyOfGenericMappedType.ts, 3, 16))
22+
>value : Symbol(value, Decl(contextualPropertyOfGenericMappedType.ts, 3, 22))
23+
>key : Symbol(key, Decl(contextualPropertyOfGenericMappedType.ts, 3, 28))
24+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/contextualPropertyOfGenericMappedType.ts ===
2+
// Repro for #24694
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
>f : <T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }) => void
6+
>data : T
7+
>handlers : { [P in keyof T]: (value: T[P], prop: P) => void; }
8+
>value : T[P]
9+
>prop : P
10+
11+
f({ data: 0 }, { data(value, key) {} });
12+
>f({ data: 0 }, { data(value, key) {} }) : void
13+
>f : <T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }) => void
14+
>{ data: 0 } : { data: number; }
15+
>data : number
16+
>0 : 0
17+
>{ data(value, key) {} } : { data(value: number, key: "data"): void; }
18+
>data : (value: number, key: "data") => void
19+
>value : number
20+
>key : "data"
21+

tests/baselines/reference/infiniteConstraints.errors.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, "alternate">], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, "alternate">>], Record<"val", string>>["val"]'.
2+
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, "main">], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, "main">>], Record<"val", string>>["val"]'.
13
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
24
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
35
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
46
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
57

68

9+
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, "alternate">], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, "alternate">>], Record<"val", string>>["val"]'.
10+
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, "main">], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, "main">>], Record<"val", string>>["val"]'.
711
==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
812
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
913

tests/baselines/reference/reverseMappedContravariantInference.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ conforms({ foo: (v: string) => false })({ foo: "hello" });
1111
>conforms({ foo: (v: string) => false })({ foo: "hello" }) : boolean
1212
>conforms({ foo: (v: string) => false }) : (value: { foo: string; }) => boolean
1313
>conforms : <T>(source: { [K in keyof T]: (val: T[K]) => boolean; }) => (value: T) => boolean
14-
>{ foo: (v: string) => false } : { foo: (v: string) => boolean; }
15-
>foo : (v: string) => boolean
16-
>(v: string) => false : (v: string) => boolean
14+
>{ foo: (v: string) => false } : { foo: (v: string) => false; }
15+
>foo : (v: string) => false
16+
>(v: string) => false : (v: string) => false
1717
>v : string
1818
>false : false
1919
>{ foo: "hello" } : { foo: string; }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Repro for #24694
2+
// @noImplicitAny: true
3+
4+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
5+
f({ data: 0 }, { data(value, key) {} });

0 commit comments

Comments
 (0)