Skip to content

Commit 6edfef8

Browse files
Andaristsandersn
andauthored
Fixed an issue with reverse mapped types inference when single type variable is left after inferring from matching types (#55941)
Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 30f3ce7 commit 6edfef8

File tree

4 files changed

+380
-1
lines changed

4 files changed

+380
-1
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25354,7 +25354,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2535425354
target = getIntersectionType(targets);
2535525355
}
2535625356
}
25357-
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
25357+
if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
2535825358
target = getActualTypeVariable(target);
2535925359
}
2536025360
if (target.flags & TypeFlags.TypeVariable) {
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//// [tests/cases/compiler/reverseMappedUnionInference.ts] ////
2+
3+
=== reverseMappedUnionInference.ts ===
4+
interface AnyExtractor<Result> {
5+
>AnyExtractor : Symbol(AnyExtractor, Decl(reverseMappedUnionInference.ts, 0, 0))
6+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 0, 23))
7+
8+
matches: (node: any) => boolean;
9+
>matches : Symbol(AnyExtractor.matches, Decl(reverseMappedUnionInference.ts, 0, 32))
10+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 1, 12))
11+
12+
extract: (node: any) => Result | undefined;
13+
>extract : Symbol(AnyExtractor.extract, Decl(reverseMappedUnionInference.ts, 1, 34))
14+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 2, 12))
15+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 0, 23))
16+
}
17+
18+
interface Extractor<T, Result> {
19+
>Extractor : Symbol(Extractor, Decl(reverseMappedUnionInference.ts, 3, 1))
20+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 5, 20))
21+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 5, 22))
22+
23+
matches: (node: unknown) => node is T;
24+
>matches : Symbol(Extractor.matches, Decl(reverseMappedUnionInference.ts, 5, 32))
25+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 6, 12))
26+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 6, 12))
27+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 5, 20))
28+
29+
extract: (node: T) => Result | undefined;
30+
>extract : Symbol(Extractor.extract, Decl(reverseMappedUnionInference.ts, 6, 40))
31+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 7, 12))
32+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 5, 20))
33+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 5, 22))
34+
}
35+
36+
declare function createExtractor<T, Result>(params: {
37+
>createExtractor : Symbol(createExtractor, Decl(reverseMappedUnionInference.ts, 8, 1))
38+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33))
39+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 10, 35))
40+
>params : Symbol(params, Decl(reverseMappedUnionInference.ts, 10, 44))
41+
42+
matcher: (node: unknown) => node is T;
43+
>matcher : Symbol(matcher, Decl(reverseMappedUnionInference.ts, 10, 53))
44+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 11, 12))
45+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 11, 12))
46+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33))
47+
48+
extract: (node: T) => Result;
49+
>extract : Symbol(extract, Decl(reverseMappedUnionInference.ts, 11, 40))
50+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 12, 12))
51+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33))
52+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 10, 35))
53+
54+
}): Extractor<T, Result>;
55+
>Extractor : Symbol(Extractor, Decl(reverseMappedUnionInference.ts, 3, 1))
56+
>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33))
57+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 10, 35))
58+
59+
interface Identifier {
60+
>Identifier : Symbol(Identifier, Decl(reverseMappedUnionInference.ts, 13, 25))
61+
62+
kind: "identifier";
63+
>kind : Symbol(Identifier.kind, Decl(reverseMappedUnionInference.ts, 15, 22))
64+
65+
name: string;
66+
>name : Symbol(Identifier.name, Decl(reverseMappedUnionInference.ts, 16, 21))
67+
}
68+
69+
declare function isIdentifier(node: unknown): node is Identifier;
70+
>isIdentifier : Symbol(isIdentifier, Decl(reverseMappedUnionInference.ts, 18, 1))
71+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 20, 30))
72+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 20, 30))
73+
>Identifier : Symbol(Identifier, Decl(reverseMappedUnionInference.ts, 13, 25))
74+
75+
const identifierExtractor = createExtractor({
76+
>identifierExtractor : Symbol(identifierExtractor, Decl(reverseMappedUnionInference.ts, 22, 5))
77+
>createExtractor : Symbol(createExtractor, Decl(reverseMappedUnionInference.ts, 8, 1))
78+
79+
matcher: isIdentifier,
80+
>matcher : Symbol(matcher, Decl(reverseMappedUnionInference.ts, 22, 45))
81+
>isIdentifier : Symbol(isIdentifier, Decl(reverseMappedUnionInference.ts, 18, 1))
82+
83+
extract: (node) => {
84+
>extract : Symbol(extract, Decl(reverseMappedUnionInference.ts, 23, 24))
85+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 24, 12))
86+
87+
return {
88+
node,
89+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 25, 12))
90+
91+
kind: "identifier" as const,
92+
>kind : Symbol(kind, Decl(reverseMappedUnionInference.ts, 26, 11))
93+
>const : Symbol(const)
94+
95+
value: node.name,
96+
>value : Symbol(value, Decl(reverseMappedUnionInference.ts, 27, 34))
97+
>node.name : Symbol(Identifier.name, Decl(reverseMappedUnionInference.ts, 16, 21))
98+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 24, 12))
99+
>name : Symbol(Identifier.name, Decl(reverseMappedUnionInference.ts, 16, 21))
100+
101+
};
102+
},
103+
});
104+
105+
interface StringLiteral {
106+
>StringLiteral : Symbol(StringLiteral, Decl(reverseMappedUnionInference.ts, 31, 3))
107+
108+
kind: "stringLiteral";
109+
>kind : Symbol(StringLiteral.kind, Decl(reverseMappedUnionInference.ts, 33, 25))
110+
111+
value: string;
112+
>value : Symbol(StringLiteral.value, Decl(reverseMappedUnionInference.ts, 34, 24))
113+
}
114+
115+
declare function isStringLiteral(node: unknown): node is StringLiteral;
116+
>isStringLiteral : Symbol(isStringLiteral, Decl(reverseMappedUnionInference.ts, 36, 1))
117+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 38, 33))
118+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 38, 33))
119+
>StringLiteral : Symbol(StringLiteral, Decl(reverseMappedUnionInference.ts, 31, 3))
120+
121+
const stringExtractor = createExtractor({
122+
>stringExtractor : Symbol(stringExtractor, Decl(reverseMappedUnionInference.ts, 40, 5))
123+
>createExtractor : Symbol(createExtractor, Decl(reverseMappedUnionInference.ts, 8, 1))
124+
125+
matcher: isStringLiteral,
126+
>matcher : Symbol(matcher, Decl(reverseMappedUnionInference.ts, 40, 41))
127+
>isStringLiteral : Symbol(isStringLiteral, Decl(reverseMappedUnionInference.ts, 36, 1))
128+
129+
extract: (node) => {
130+
>extract : Symbol(extract, Decl(reverseMappedUnionInference.ts, 41, 27))
131+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 42, 12))
132+
133+
return {
134+
node,
135+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 43, 12))
136+
137+
kind: "string" as const,
138+
>kind : Symbol(kind, Decl(reverseMappedUnionInference.ts, 44, 11))
139+
>const : Symbol(const)
140+
141+
value: node.value,
142+
>value : Symbol(value, Decl(reverseMappedUnionInference.ts, 45, 30))
143+
>node.value : Symbol(StringLiteral.value, Decl(reverseMappedUnionInference.ts, 34, 24))
144+
>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 42, 12))
145+
>value : Symbol(StringLiteral.value, Decl(reverseMappedUnionInference.ts, 34, 24))
146+
147+
};
148+
},
149+
});
150+
151+
declare function unionType<Result extends readonly unknown[]>(parsers: {
152+
>unionType : Symbol(unionType, Decl(reverseMappedUnionInference.ts, 49, 3))
153+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27))
154+
>parsers : Symbol(parsers, Decl(reverseMappedUnionInference.ts, 51, 62))
155+
156+
[K in keyof Result]: AnyExtractor<Result[K]>;
157+
>K : Symbol(K, Decl(reverseMappedUnionInference.ts, 52, 3))
158+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27))
159+
>AnyExtractor : Symbol(AnyExtractor, Decl(reverseMappedUnionInference.ts, 0, 0))
160+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27))
161+
>K : Symbol(K, Decl(reverseMappedUnionInference.ts, 52, 3))
162+
163+
}): AnyExtractor<Result[number]>;
164+
>AnyExtractor : Symbol(AnyExtractor, Decl(reverseMappedUnionInference.ts, 0, 0))
165+
>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27))
166+
167+
const myUnion = unionType([identifierExtractor, stringExtractor]);
168+
>myUnion : Symbol(myUnion, Decl(reverseMappedUnionInference.ts, 55, 5))
169+
>unionType : Symbol(unionType, Decl(reverseMappedUnionInference.ts, 49, 3))
170+
>identifierExtractor : Symbol(identifierExtractor, Decl(reverseMappedUnionInference.ts, 22, 5))
171+
>stringExtractor : Symbol(stringExtractor, Decl(reverseMappedUnionInference.ts, 40, 5))
172+
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//// [tests/cases/compiler/reverseMappedUnionInference.ts] ////
2+
3+
=== reverseMappedUnionInference.ts ===
4+
interface AnyExtractor<Result> {
5+
matches: (node: any) => boolean;
6+
>matches : (node: any) => boolean
7+
>node : any
8+
9+
extract: (node: any) => Result | undefined;
10+
>extract : (node: any) => Result | undefined
11+
>node : any
12+
}
13+
14+
interface Extractor<T, Result> {
15+
matches: (node: unknown) => node is T;
16+
>matches : (node: unknown) => node is T
17+
>node : unknown
18+
19+
extract: (node: T) => Result | undefined;
20+
>extract : (node: T) => Result | undefined
21+
>node : T
22+
}
23+
24+
declare function createExtractor<T, Result>(params: {
25+
>createExtractor : <T, Result>(params: { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }) => Extractor<T, Result>
26+
>params : { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }
27+
28+
matcher: (node: unknown) => node is T;
29+
>matcher : (node: unknown) => node is T
30+
>node : unknown
31+
32+
extract: (node: T) => Result;
33+
>extract : (node: T) => Result
34+
>node : T
35+
36+
}): Extractor<T, Result>;
37+
38+
interface Identifier {
39+
kind: "identifier";
40+
>kind : "identifier"
41+
42+
name: string;
43+
>name : string
44+
}
45+
46+
declare function isIdentifier(node: unknown): node is Identifier;
47+
>isIdentifier : (node: unknown) => node is Identifier
48+
>node : unknown
49+
50+
const identifierExtractor = createExtractor({
51+
>identifierExtractor : Extractor<Identifier, { node: Identifier; kind: "identifier"; value: string; }>
52+
>createExtractor({ matcher: isIdentifier, extract: (node) => { return { node, kind: "identifier" as const, value: node.name, }; },}) : Extractor<Identifier, { node: Identifier; kind: "identifier"; value: string; }>
53+
>createExtractor : <T, Result>(params: { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }) => Extractor<T, Result>
54+
>{ matcher: isIdentifier, extract: (node) => { return { node, kind: "identifier" as const, value: node.name, }; },} : { matcher: (node: unknown) => node is Identifier; extract: (node: Identifier) => { node: Identifier; kind: "identifier"; value: string; }; }
55+
56+
matcher: isIdentifier,
57+
>matcher : (node: unknown) => node is Identifier
58+
>isIdentifier : (node: unknown) => node is Identifier
59+
60+
extract: (node) => {
61+
>extract : (node: Identifier) => { node: Identifier; kind: "identifier"; value: string; }
62+
>(node) => { return { node, kind: "identifier" as const, value: node.name, }; } : (node: Identifier) => { node: Identifier; kind: "identifier"; value: string; }
63+
>node : Identifier
64+
65+
return {
66+
>{ node, kind: "identifier" as const, value: node.name, } : { node: Identifier; kind: "identifier"; value: string; }
67+
68+
node,
69+
>node : Identifier
70+
71+
kind: "identifier" as const,
72+
>kind : "identifier"
73+
>"identifier" as const : "identifier"
74+
>"identifier" : "identifier"
75+
76+
value: node.name,
77+
>value : string
78+
>node.name : string
79+
>node : Identifier
80+
>name : string
81+
82+
};
83+
},
84+
});
85+
86+
interface StringLiteral {
87+
kind: "stringLiteral";
88+
>kind : "stringLiteral"
89+
90+
value: string;
91+
>value : string
92+
}
93+
94+
declare function isStringLiteral(node: unknown): node is StringLiteral;
95+
>isStringLiteral : (node: unknown) => node is StringLiteral
96+
>node : unknown
97+
98+
const stringExtractor = createExtractor({
99+
>stringExtractor : Extractor<StringLiteral, { node: StringLiteral; kind: "string"; value: string; }>
100+
>createExtractor({ matcher: isStringLiteral, extract: (node) => { return { node, kind: "string" as const, value: node.value, }; },}) : Extractor<StringLiteral, { node: StringLiteral; kind: "string"; value: string; }>
101+
>createExtractor : <T, Result>(params: { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }) => Extractor<T, Result>
102+
>{ matcher: isStringLiteral, extract: (node) => { return { node, kind: "string" as const, value: node.value, }; },} : { matcher: (node: unknown) => node is StringLiteral; extract: (node: StringLiteral) => { node: StringLiteral; kind: "string"; value: string; }; }
103+
104+
matcher: isStringLiteral,
105+
>matcher : (node: unknown) => node is StringLiteral
106+
>isStringLiteral : (node: unknown) => node is StringLiteral
107+
108+
extract: (node) => {
109+
>extract : (node: StringLiteral) => { node: StringLiteral; kind: "string"; value: string; }
110+
>(node) => { return { node, kind: "string" as const, value: node.value, }; } : (node: StringLiteral) => { node: StringLiteral; kind: "string"; value: string; }
111+
>node : StringLiteral
112+
113+
return {
114+
>{ node, kind: "string" as const, value: node.value, } : { node: StringLiteral; kind: "string"; value: string; }
115+
116+
node,
117+
>node : StringLiteral
118+
119+
kind: "string" as const,
120+
>kind : "string"
121+
>"string" as const : "string"
122+
>"string" : "string"
123+
124+
value: node.value,
125+
>value : string
126+
>node.value : string
127+
>node : StringLiteral
128+
>value : string
129+
130+
};
131+
},
132+
});
133+
134+
declare function unionType<Result extends readonly unknown[]>(parsers: {
135+
>unionType : <Result extends readonly unknown[]>(parsers: { [K in keyof Result]: AnyExtractor<Result[K]>; }) => AnyExtractor<Result[number]>
136+
>parsers : { [K in keyof Result]: AnyExtractor<Result[K]>; }
137+
138+
[K in keyof Result]: AnyExtractor<Result[K]>;
139+
}): AnyExtractor<Result[number]>;
140+
141+
const myUnion = unionType([identifierExtractor, stringExtractor]);
142+
>myUnion : AnyExtractor<{ node: Identifier; kind: "identifier"; value: string; } | { node: StringLiteral; kind: "string"; value: string; }>
143+
>unionType([identifierExtractor, stringExtractor]) : AnyExtractor<{ node: Identifier; kind: "identifier"; value: string; } | { node: StringLiteral; kind: "string"; value: string; }>
144+
>unionType : <Result extends readonly unknown[]>(parsers: { [K in keyof Result]: AnyExtractor<Result[K]>; }) => AnyExtractor<Result[number]>
145+
>[identifierExtractor, stringExtractor] : (Extractor<Identifier, { node: Identifier; kind: "identifier"; value: string; }> | Extractor<StringLiteral, { node: StringLiteral; kind: "string"; value: string; }>)[]
146+
>identifierExtractor : Extractor<Identifier, { node: Identifier; kind: "identifier"; value: string; }>
147+
>stringExtractor : Extractor<StringLiteral, { node: StringLiteral; kind: "string"; value: string; }>
148+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
interface AnyExtractor<Result> {
5+
matches: (node: any) => boolean;
6+
extract: (node: any) => Result | undefined;
7+
}
8+
9+
interface Extractor<T, Result> {
10+
matches: (node: unknown) => node is T;
11+
extract: (node: T) => Result | undefined;
12+
}
13+
14+
declare function createExtractor<T, Result>(params: {
15+
matcher: (node: unknown) => node is T;
16+
extract: (node: T) => Result;
17+
}): Extractor<T, Result>;
18+
19+
interface Identifier {
20+
kind: "identifier";
21+
name: string;
22+
}
23+
24+
declare function isIdentifier(node: unknown): node is Identifier;
25+
26+
const identifierExtractor = createExtractor({
27+
matcher: isIdentifier,
28+
extract: (node) => {
29+
return {
30+
node,
31+
kind: "identifier" as const,
32+
value: node.name,
33+
};
34+
},
35+
});
36+
37+
interface StringLiteral {
38+
kind: "stringLiteral";
39+
value: string;
40+
}
41+
42+
declare function isStringLiteral(node: unknown): node is StringLiteral;
43+
44+
const stringExtractor = createExtractor({
45+
matcher: isStringLiteral,
46+
extract: (node) => {
47+
return {
48+
node,
49+
kind: "string" as const,
50+
value: node.value,
51+
};
52+
},
53+
});
54+
55+
declare function unionType<Result extends readonly unknown[]>(parsers: {
56+
[K in keyof Result]: AnyExtractor<Result[K]>;
57+
}): AnyExtractor<Result[number]>;
58+
59+
const myUnion = unionType([identifierExtractor, stringExtractor]);

0 commit comments

Comments
 (0)