Skip to content

Commit bd61cbb

Browse files
authored
Infer into recursive mapped type targets (#53647)
1 parent a05699c commit bd61cbb

6 files changed

+195
-19
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,7 +2068,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20682068
/** Key is "/path/to/a.ts|/path/to/b.ts". */
20692069
var amalgamatedDuplicates: Map<string, DuplicateInfoForFiles> | undefined;
20702070
var reverseMappedCache = new Map<string, Type | undefined>();
2071-
var inInferTypeForHomomorphicMappedType = false;
2071+
var homomorphicMappedTypeInferenceStack: string[] = [];
20722072
var ambientModulesCache: Symbol[] | undefined;
20732073
/**
20742074
* List of every ambient module with a "*" wildcard.
@@ -24126,17 +24126,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2412624126
* variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
2412724127
*/
2412824128
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined {
24129-
if (inInferTypeForHomomorphicMappedType) {
24130-
return undefined;
24129+
const cacheKey = source.id + "," + target.id + "," + constraint.id;
24130+
if (reverseMappedCache.has(cacheKey)) {
24131+
return reverseMappedCache.get(cacheKey);
2413124132
}
24132-
const key = source.id + "," + target.id + "," + constraint.id;
24133-
if (reverseMappedCache.has(key)) {
24134-
return reverseMappedCache.get(key);
24133+
const recursionKey = source.id + "," + (target.target || target).id;
24134+
if (contains(homomorphicMappedTypeInferenceStack, recursionKey)) {
24135+
return undefined;
2413524136
}
24136-
inInferTypeForHomomorphicMappedType = true;
24137+
homomorphicMappedTypeInferenceStack.push(recursionKey);
2413724138
const type = createReverseMappedType(source, target, constraint);
24138-
inInferTypeForHomomorphicMappedType = false;
24139-
reverseMappedCache.set(key, type);
24139+
homomorphicMappedTypeInferenceStack.pop();
24140+
reverseMappedCache.set(cacheKey, type);
2414024141
return type;
2414124142
}
2414224143

tests/baselines/reference/mappedTypeRecursiveInference.errors.txt

Lines changed: 20 additions & 6 deletions
Large diffs are not rendered by default.

tests/baselines/reference/mappedTypeRecursiveInference.types

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//// [tests/cases/compiler/mappedTypeRecursiveInference2.ts] ////
2+
3+
=== mappedTypeRecursiveInference2.ts ===
4+
type MorphTuple = [string, "|>", any]
5+
>MorphTuple : Symbol(MorphTuple, Decl(mappedTypeRecursiveInference2.ts, 0, 0))
6+
7+
type validateMorph<def extends MorphTuple> = def[1] extends "|>"
8+
>validateMorph : Symbol(validateMorph, Decl(mappedTypeRecursiveInference2.ts, 0, 37))
9+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 2, 19))
10+
>MorphTuple : Symbol(MorphTuple, Decl(mappedTypeRecursiveInference2.ts, 0, 0))
11+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 2, 19))
12+
13+
? [validateDefinition<def[0]>, "|>", (In: def[0]) => unknown]
14+
>validateDefinition : Symbol(validateDefinition, Decl(mappedTypeRecursiveInference2.ts, 4, 9))
15+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 2, 19))
16+
>In : Symbol(In, Decl(mappedTypeRecursiveInference2.ts, 3, 42))
17+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 2, 19))
18+
19+
: def
20+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 2, 19))
21+
22+
type validateDefinition<def> = def extends MorphTuple
23+
>validateDefinition : Symbol(validateDefinition, Decl(mappedTypeRecursiveInference2.ts, 4, 9))
24+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 6, 24))
25+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 6, 24))
26+
>MorphTuple : Symbol(MorphTuple, Decl(mappedTypeRecursiveInference2.ts, 0, 0))
27+
28+
? validateMorph<def>
29+
>validateMorph : Symbol(validateMorph, Decl(mappedTypeRecursiveInference2.ts, 0, 37))
30+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 6, 24))
31+
32+
: {
33+
[k in keyof def]: validateDefinition<def[k]>
34+
>k : Symbol(k, Decl(mappedTypeRecursiveInference2.ts, 9, 11))
35+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 6, 24))
36+
>validateDefinition : Symbol(validateDefinition, Decl(mappedTypeRecursiveInference2.ts, 4, 9))
37+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 6, 24))
38+
>k : Symbol(k, Decl(mappedTypeRecursiveInference2.ts, 9, 11))
39+
}
40+
41+
declare function type<def>(def: validateDefinition<def>): def
42+
>type : Symbol(type, Decl(mappedTypeRecursiveInference2.ts, 10, 7))
43+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 12, 22), Decl(mappedTypeRecursiveInference2.ts, 12, 27))
44+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 12, 22), Decl(mappedTypeRecursiveInference2.ts, 12, 27))
45+
>validateDefinition : Symbol(validateDefinition, Decl(mappedTypeRecursiveInference2.ts, 4, 9))
46+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 12, 22), Decl(mappedTypeRecursiveInference2.ts, 12, 27))
47+
>def : Symbol(def, Decl(mappedTypeRecursiveInference2.ts, 12, 22), Decl(mappedTypeRecursiveInference2.ts, 12, 27))
48+
49+
const shallow = type(["ark", "|>", (x) => x.length])
50+
>shallow : Symbol(shallow, Decl(mappedTypeRecursiveInference2.ts, 14, 5))
51+
>type : Symbol(type, Decl(mappedTypeRecursiveInference2.ts, 10, 7))
52+
>x : Symbol(x, Decl(mappedTypeRecursiveInference2.ts, 14, 36))
53+
>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
54+
>x : Symbol(x, Decl(mappedTypeRecursiveInference2.ts, 14, 36))
55+
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
56+
57+
const objectLiteral = type({ a: ["ark", "|>", (x) => x.length] })
58+
>objectLiteral : Symbol(objectLiteral, Decl(mappedTypeRecursiveInference2.ts, 15, 5))
59+
>type : Symbol(type, Decl(mappedTypeRecursiveInference2.ts, 10, 7))
60+
>a : Symbol(a, Decl(mappedTypeRecursiveInference2.ts, 15, 28))
61+
>x : Symbol(x, Decl(mappedTypeRecursiveInference2.ts, 15, 47))
62+
>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
63+
>x : Symbol(x, Decl(mappedTypeRecursiveInference2.ts, 15, 47))
64+
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
65+
66+
const nestedTuple = type([["ark", "|>", (x) => x.length]])
67+
>nestedTuple : Symbol(nestedTuple, Decl(mappedTypeRecursiveInference2.ts, 16, 5))
68+
>type : Symbol(type, Decl(mappedTypeRecursiveInference2.ts, 10, 7))
69+
>x : Symbol(x, Decl(mappedTypeRecursiveInference2.ts, 16, 41))
70+
>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
71+
>x : Symbol(x, Decl(mappedTypeRecursiveInference2.ts, 16, 41))
72+
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
73+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//// [tests/cases/compiler/mappedTypeRecursiveInference2.ts] ////
2+
3+
=== mappedTypeRecursiveInference2.ts ===
4+
type MorphTuple = [string, "|>", any]
5+
>MorphTuple : [string, "|>", any]
6+
7+
type validateMorph<def extends MorphTuple> = def[1] extends "|>"
8+
>validateMorph : validateMorph<def>
9+
10+
? [validateDefinition<def[0]>, "|>", (In: def[0]) => unknown]
11+
>In : def[0]
12+
13+
: def
14+
15+
type validateDefinition<def> = def extends MorphTuple
16+
>validateDefinition : validateDefinition<def>
17+
18+
? validateMorph<def>
19+
: {
20+
[k in keyof def]: validateDefinition<def[k]>
21+
}
22+
23+
declare function type<def>(def: validateDefinition<def>): def
24+
>type : <def>(def: validateDefinition<def>) => def
25+
>def : validateDefinition<def>
26+
27+
const shallow = type(["ark", "|>", (x) => x.length])
28+
>shallow : ["ark", "|>", (x: "ark") => number]
29+
>type(["ark", "|>", (x) => x.length]) : ["ark", "|>", (x: "ark") => number]
30+
>type : <def>(def: validateDefinition<def>) => def
31+
>["ark", "|>", (x) => x.length] : ["ark", "|>", (x: "ark") => number]
32+
>"ark" : "ark"
33+
>"|>" : "|>"
34+
>(x) => x.length : (x: "ark") => number
35+
>x : "ark"
36+
>x.length : number
37+
>x : "ark"
38+
>length : number
39+
40+
const objectLiteral = type({ a: ["ark", "|>", (x) => x.length] })
41+
>objectLiteral : { a: ["ark", "|>", (x: "ark") => number]; }
42+
>type({ a: ["ark", "|>", (x) => x.length] }) : { a: ["ark", "|>", (x: "ark") => number]; }
43+
>type : <def>(def: validateDefinition<def>) => def
44+
>{ a: ["ark", "|>", (x) => x.length] } : { a: ["ark", "|>", (x: "ark") => number]; }
45+
>a : ["ark", "|>", (x: "ark") => number]
46+
>["ark", "|>", (x) => x.length] : ["ark", "|>", (x: "ark") => number]
47+
>"ark" : "ark"
48+
>"|>" : "|>"
49+
>(x) => x.length : (x: "ark") => number
50+
>x : "ark"
51+
>x.length : number
52+
>x : "ark"
53+
>length : number
54+
55+
const nestedTuple = type([["ark", "|>", (x) => x.length]])
56+
>nestedTuple : [["ark", "|>", (x: "ark") => number]]
57+
>type([["ark", "|>", (x) => x.length]]) : [["ark", "|>", (x: "ark") => number]]
58+
>type : <def>(def: validateDefinition<def>) => def
59+
>[["ark", "|>", (x) => x.length]] : [["ark", "|>", (x: "ark") => number]]
60+
>["ark", "|>", (x) => x.length] : ["ark", "|>", (x: "ark") => number]
61+
>"ark" : "ark"
62+
>"|>" : "|>"
63+
>(x) => x.length : (x: "ark") => number
64+
>x : "ark"
65+
>x.length : number
66+
>x : "ark"
67+
>length : number
68+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type MorphTuple = [string, "|>", any]
5+
6+
type validateMorph<def extends MorphTuple> = def[1] extends "|>"
7+
? [validateDefinition<def[0]>, "|>", (In: def[0]) => unknown]
8+
: def
9+
10+
type validateDefinition<def> = def extends MorphTuple
11+
? validateMorph<def>
12+
: {
13+
[k in keyof def]: validateDefinition<def[k]>
14+
}
15+
16+
declare function type<def>(def: validateDefinition<def>): def
17+
18+
const shallow = type(["ark", "|>", (x) => x.length])
19+
const objectLiteral = type({ a: ["ark", "|>", (x) => x.length] })
20+
const nestedTuple = type([["ark", "|>", (x) => x.length]])

0 commit comments

Comments
 (0)