Skip to content

Commit be20dbb

Browse files
authored
Infer between generic mapped types before inferring from apparent type (#56640)
1 parent 9e0e9d3 commit be20dbb

8 files changed

+216
-7
lines changed

src/compiler/checker.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25544,6 +25544,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2554425544
}
2554525545
else {
2554625546
source = getReducedType(source);
25547+
if (isGenericMappedType(source) && isGenericMappedType(target)) {
25548+
invokeOnce(source, target, inferFromGenericMappedTypes);
25549+
}
2554725550
if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
2554825551
const apparentSource = getApparentType(source);
2554925552
// getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type.
@@ -25581,6 +25584,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2558125584
priority = savePriority;
2558225585
}
2558325586

25587+
// Ensure an inference action is performed only once for the given source and target types.
25588+
// This includes two things:
25589+
// Avoiding inferring between the same pair of source and target types,
25590+
// and avoiding circularly inferring between source and target types.
25591+
// For an example of the last, consider if we are inferring between source type
25592+
// `type Deep<T> = { next: Deep<Deep<T>> }` and target type `type Loop<U> = { next: Loop<U> }`.
25593+
// We would then infer between the types of the `next` property: `Deep<Deep<T>>` = `{ next: Deep<Deep<Deep<T>>> }` and `Loop<U>` = `{ next: Loop<U> }`.
25594+
// We will then infer again between the types of the `next` property:
25595+
// `Deep<Deep<Deep<T>>>` and `Loop<U>`, and so on, such that we would be forever inferring
25596+
// between instantiations of the same types `Deep` and `Loop`.
25597+
// In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`,
25598+
// such that we would go on inferring forever, even though we would never infer
25599+
// between the same pair of types.
2558425600
function invokeOnce<Source extends Type, Target extends Type>(source: Source, target: Target, action: (source: Source, target: Target) => void) {
2558525601
const key = source.id + "," + target.id;
2558625602
const status = visited && visited.get(key);
@@ -25888,6 +25904,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2588825904
}
2588925905
}
2589025906

25907+
function inferFromGenericMappedTypes(source: MappedType, target: MappedType) {
25908+
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
25909+
// from S to T and from X to Y.
25910+
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
25911+
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
25912+
const sourceNameType = getNameTypeFromMappedType(source);
25913+
const targetNameType = getNameTypeFromMappedType(target);
25914+
if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType);
25915+
}
25916+
2589125917
function inferFromObjectTypes(source: Type, target: Type) {
2589225918
if (
2589325919
getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
@@ -25899,13 +25925,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2589925925
return;
2590025926
}
2590125927
if (isGenericMappedType(source) && isGenericMappedType(target)) {
25902-
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
25903-
// from S to T and from X to Y.
25904-
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
25905-
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
25906-
const sourceNameType = getNameTypeFromMappedType(source);
25907-
const targetNameType = getNameTypeFromMappedType(target);
25908-
if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType);
25928+
inferFromGenericMappedTypes(source, target);
2590925929
}
2591025930
if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) {
2591125931
const constraintType = getConstraintTypeFromMappedType(target as MappedType);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
mappedTypeInferenceFromApparentType.ts(10,1): error TS2322: Type 'foo' is not assignable to type 'bar'.
2+
Types of parameters 'target' and 'source' are incompatible.
3+
Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [K in keyof U]: U[K]; }'.
4+
Type 'Obj[K]' is not assignable to type 'U[K]'.
5+
Type 'Obj' is not assignable to type 'U'.
6+
'U' could be instantiated with an arbitrary type which could be unrelated to 'Obj'.
7+
8+
9+
==== mappedTypeInferenceFromApparentType.ts (1 errors) ====
10+
type Obj = {
11+
[s: string]: number;
12+
};
13+
14+
type foo = <T>(target: { [K in keyof T]: T[K] }) => void;
15+
type bar = <U extends string[]>(source: { [K in keyof U]: Obj[K] }) => void;
16+
17+
declare let f: foo;
18+
declare let b: bar;
19+
b = f;
20+
~
21+
!!! error TS2322: Type 'foo' is not assignable to type 'bar'.
22+
!!! error TS2322: Types of parameters 'target' and 'source' are incompatible.
23+
!!! error TS2322: Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [K in keyof U]: U[K]; }'.
24+
!!! error TS2322: Type 'Obj[K]' is not assignable to type 'U[K]'.
25+
!!! error TS2322: Type 'Obj' is not assignable to type 'U'.
26+
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'Obj'.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [tests/cases/compiler/mappedTypeInferenceFromApparentType.ts] ////
2+
3+
=== mappedTypeInferenceFromApparentType.ts ===
4+
type Obj = {
5+
>Obj : Symbol(Obj, Decl(mappedTypeInferenceFromApparentType.ts, 0, 0))
6+
7+
[s: string]: number;
8+
>s : Symbol(s, Decl(mappedTypeInferenceFromApparentType.ts, 1, 5))
9+
10+
};
11+
12+
type foo = <T>(target: { [K in keyof T]: T[K] }) => void;
13+
>foo : Symbol(foo, Decl(mappedTypeInferenceFromApparentType.ts, 2, 2))
14+
>T : Symbol(T, Decl(mappedTypeInferenceFromApparentType.ts, 4, 12))
15+
>target : Symbol(target, Decl(mappedTypeInferenceFromApparentType.ts, 4, 15))
16+
>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 4, 26))
17+
>T : Symbol(T, Decl(mappedTypeInferenceFromApparentType.ts, 4, 12))
18+
>T : Symbol(T, Decl(mappedTypeInferenceFromApparentType.ts, 4, 12))
19+
>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 4, 26))
20+
21+
type bar = <U extends string[]>(source: { [K in keyof U]: Obj[K] }) => void;
22+
>bar : Symbol(bar, Decl(mappedTypeInferenceFromApparentType.ts, 4, 57))
23+
>U : Symbol(U, Decl(mappedTypeInferenceFromApparentType.ts, 5, 12))
24+
>source : Symbol(source, Decl(mappedTypeInferenceFromApparentType.ts, 5, 32))
25+
>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 5, 43))
26+
>U : Symbol(U, Decl(mappedTypeInferenceFromApparentType.ts, 5, 12))
27+
>Obj : Symbol(Obj, Decl(mappedTypeInferenceFromApparentType.ts, 0, 0))
28+
>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 5, 43))
29+
30+
declare let f: foo;
31+
>f : Symbol(f, Decl(mappedTypeInferenceFromApparentType.ts, 7, 11))
32+
>foo : Symbol(foo, Decl(mappedTypeInferenceFromApparentType.ts, 2, 2))
33+
34+
declare let b: bar;
35+
>b : Symbol(b, Decl(mappedTypeInferenceFromApparentType.ts, 8, 11))
36+
>bar : Symbol(bar, Decl(mappedTypeInferenceFromApparentType.ts, 4, 57))
37+
38+
b = f;
39+
>b : Symbol(b, Decl(mappedTypeInferenceFromApparentType.ts, 8, 11))
40+
>f : Symbol(f, Decl(mappedTypeInferenceFromApparentType.ts, 7, 11))
41+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//// [tests/cases/compiler/mappedTypeInferenceFromApparentType.ts] ////
2+
3+
=== mappedTypeInferenceFromApparentType.ts ===
4+
type Obj = {
5+
>Obj : { [s: string]: number; }
6+
7+
[s: string]: number;
8+
>s : string
9+
10+
};
11+
12+
type foo = <T>(target: { [K in keyof T]: T[K] }) => void;
13+
>foo : <T>(target: { [K in keyof T]: T[K]; }) => void
14+
>target : { [K in keyof T]: T[K]; }
15+
16+
type bar = <U extends string[]>(source: { [K in keyof U]: Obj[K] }) => void;
17+
>bar : <U extends string[]>(source: { [K in keyof U]: Obj[K]; }) => void
18+
>source : { [K in keyof U]: Obj[K]; }
19+
20+
declare let f: foo;
21+
>f : foo
22+
23+
declare let b: bar;
24+
>b : bar
25+
26+
b = f;
27+
>b = f : foo
28+
>b : bar
29+
>f : foo
30+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tests/cases/compiler/mappedTypeInferenceToMappedType.ts] ////
2+
3+
=== mappedTypeInferenceToMappedType.ts ===
4+
// #56133
5+
6+
declare class Base<T> {
7+
>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0))
8+
>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 2, 19))
9+
10+
someProp: T;
11+
>someProp : Symbol(Base.someProp, Decl(mappedTypeInferenceToMappedType.ts, 2, 23))
12+
>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 2, 19))
13+
14+
method<U extends unknown[]>(x: { [K in keyof U]: U[K] }): Base<U>;
15+
>method : Symbol(Base.method, Decl(mappedTypeInferenceToMappedType.ts, 3, 16))
16+
>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11))
17+
>x : Symbol(x, Decl(mappedTypeInferenceToMappedType.ts, 4, 32))
18+
>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 4, 38))
19+
>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11))
20+
>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11))
21+
>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 4, 38))
22+
>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0))
23+
>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11))
24+
}
25+
26+
declare class Derived<T> extends Base<T> {
27+
>Derived : Symbol(Derived, Decl(mappedTypeInferenceToMappedType.ts, 5, 1))
28+
>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 7, 22))
29+
>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0))
30+
>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 7, 22))
31+
32+
method<V extends unknown[]>(x: { [K in keyof V]: V[K] }): Base<V>;
33+
>method : Symbol(Derived.method, Decl(mappedTypeInferenceToMappedType.ts, 7, 42))
34+
>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11))
35+
>x : Symbol(x, Decl(mappedTypeInferenceToMappedType.ts, 8, 32))
36+
>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 8, 38))
37+
>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11))
38+
>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11))
39+
>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 8, 38))
40+
>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0))
41+
>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11))
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [tests/cases/compiler/mappedTypeInferenceToMappedType.ts] ////
2+
3+
=== mappedTypeInferenceToMappedType.ts ===
4+
// #56133
5+
6+
declare class Base<T> {
7+
>Base : Base<T>
8+
9+
someProp: T;
10+
>someProp : T
11+
12+
method<U extends unknown[]>(x: { [K in keyof U]: U[K] }): Base<U>;
13+
>method : <U extends unknown[]>(x: { [K in keyof U]: U[K]; }) => Base<U>
14+
>x : { [K in keyof U]: U[K]; }
15+
}
16+
17+
declare class Derived<T> extends Base<T> {
18+
>Derived : Derived<T>
19+
>Base : Base<T>
20+
21+
method<V extends unknown[]>(x: { [K in keyof V]: V[K] }): Base<V>;
22+
>method : <V extends unknown[]>(x: { [K in keyof V]: V[K]; }) => Base<V>
23+
>x : { [K in keyof V]: V[K]; }
24+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type Obj = {
5+
[s: string]: number;
6+
};
7+
8+
type foo = <T>(target: { [K in keyof T]: T[K] }) => void;
9+
type bar = <U extends string[]>(source: { [K in keyof U]: Obj[K] }) => void;
10+
11+
declare let f: foo;
12+
declare let b: bar;
13+
b = f;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// #56133
5+
6+
declare class Base<T> {
7+
someProp: T;
8+
method<U extends unknown[]>(x: { [K in keyof U]: U[K] }): Base<U>;
9+
}
10+
11+
declare class Derived<T> extends Base<T> {
12+
method<V extends unknown[]>(x: { [K in keyof V]: V[K] }): Base<V>;
13+
}

0 commit comments

Comments
 (0)