Skip to content

Commit 031a370

Browse files
committed
Discriminate partial inferences if not complete enough to satisfy constraint
1 parent aa9a886 commit 031a370

File tree

5 files changed

+149
-1
lines changed

5 files changed

+149
-1
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11552,7 +11552,38 @@ namespace ts {
1155211552
if (!inferredType) {
1155311553
if (inference.indexes) {
1155411554
// Build a candidate from all indexes
11555-
(inference.candidates || (inference.candidates = [])).push(getIntersectionType(inference.indexes));
11555+
let aggregateInference = getIntersectionType(inference.indexes);
11556+
const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]);
11557+
if (constraint) {
11558+
const instantiatedConstraint = instantiateType(constraint, context);
11559+
if (!context.compareTypes(aggregateInference, getTypeWithThisArgument(instantiatedConstraint, aggregateInference))) {
11560+
if (instantiatedConstraint.flags & TypeFlags.Union) {
11561+
const discriminantProps = findDiscriminantProperties(getPropertiesOfType(aggregateInference), instantiatedConstraint);
11562+
if (discriminantProps) {
11563+
let match: Type;
11564+
findDiscriminant: for (const p of discriminantProps) {
11565+
const candidatePropType = getTypeOfPropertyOfType(aggregateInference, p.escapedName);
11566+
for (const type of (instantiatedConstraint as UnionType).types) {
11567+
const propType = getTypeOfPropertyOfType(type, p.escapedName);
11568+
if (propType && checkTypeAssignableTo(candidatePropType, propType, /*errorNode*/ undefined)) {
11569+
if (match && match !== type) {
11570+
match = undefined;
11571+
break findDiscriminant;
11572+
}
11573+
else {
11574+
match = type;
11575+
}
11576+
}
11577+
}
11578+
}
11579+
if (match) {
11580+
aggregateInference = getSpreadType(match, aggregateInference, /*symbol*/ undefined, /*propegatedFlags*/ 0);
11581+
}
11582+
}
11583+
}
11584+
}
11585+
}
11586+
(inference.candidates || (inference.candidates = [])).push(aggregateInference);
1155611587
}
1155711588
if (inference.candidates) {
1155811589
// Extract all object literal types and replace them with a single widened and normalized type.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//// [typeInferenceOnIndexUnion.ts]
2+
type Options = { k: "a", a: number } | { k: "b", b: string };
3+
declare function f<T extends Options>(p: T["k"]): T;
4+
const x = f("a"); // expect it to be `{ k: "a", a: number }`
5+
6+
type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} };
7+
declare function f2<T extends Options2>(p: T["k"], c: T["c"]): T;
8+
const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }`
9+
10+
11+
//// [typeInferenceOnIndexUnion.js]
12+
var x = f("a"); // expect it to be `{ k: "a", a: number }`
13+
var x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }`
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== tests/cases/compiler/typeInferenceOnIndexUnion.ts ===
2+
type Options = { k: "a", a: number } | { k: "b", b: string };
3+
>Options : Symbol(Options, Decl(typeInferenceOnIndexUnion.ts, 0, 0))
4+
>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 0, 16))
5+
>a : Symbol(a, Decl(typeInferenceOnIndexUnion.ts, 0, 24))
6+
>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 0, 40))
7+
>b : Symbol(b, Decl(typeInferenceOnIndexUnion.ts, 0, 48))
8+
9+
declare function f<T extends Options>(p: T["k"]): T;
10+
>f : Symbol(f, Decl(typeInferenceOnIndexUnion.ts, 0, 61))
11+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 1, 19))
12+
>Options : Symbol(Options, Decl(typeInferenceOnIndexUnion.ts, 0, 0))
13+
>p : Symbol(p, Decl(typeInferenceOnIndexUnion.ts, 1, 38))
14+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 1, 19))
15+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 1, 19))
16+
17+
const x = f("a"); // expect it to be `{ k: "a", a: number }`
18+
>x : Symbol(x, Decl(typeInferenceOnIndexUnion.ts, 2, 5))
19+
>f : Symbol(f, Decl(typeInferenceOnIndexUnion.ts, 0, 61))
20+
21+
type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} };
22+
>Options2 : Symbol(Options2, Decl(typeInferenceOnIndexUnion.ts, 2, 17))
23+
>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 4, 17))
24+
>a : Symbol(a, Decl(typeInferenceOnIndexUnion.ts, 4, 25))
25+
>c : Symbol(c, Decl(typeInferenceOnIndexUnion.ts, 4, 36))
26+
>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 4, 48))
27+
>b : Symbol(b, Decl(typeInferenceOnIndexUnion.ts, 4, 56))
28+
>c : Symbol(c, Decl(typeInferenceOnIndexUnion.ts, 4, 67))
29+
30+
declare function f2<T extends Options2>(p: T["k"], c: T["c"]): T;
31+
>f2 : Symbol(f2, Decl(typeInferenceOnIndexUnion.ts, 4, 76))
32+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20))
33+
>Options2 : Symbol(Options2, Decl(typeInferenceOnIndexUnion.ts, 2, 17))
34+
>p : Symbol(p, Decl(typeInferenceOnIndexUnion.ts, 5, 40))
35+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20))
36+
>c : Symbol(c, Decl(typeInferenceOnIndexUnion.ts, 5, 50))
37+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20))
38+
>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20))
39+
40+
const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }`
41+
>x2 : Symbol(x2, Decl(typeInferenceOnIndexUnion.ts, 6, 5))
42+
>f2 : Symbol(f2, Decl(typeInferenceOnIndexUnion.ts, 4, 76))
43+
>x : Symbol(x, Decl(typeInferenceOnIndexUnion.ts, 6, 20))
44+
>y : Symbol(y, Decl(typeInferenceOnIndexUnion.ts, 6, 26))
45+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/typeInferenceOnIndexUnion.ts ===
2+
type Options = { k: "a", a: number } | { k: "b", b: string };
3+
>Options : Options
4+
>k : "a"
5+
>a : number
6+
>k : "b"
7+
>b : string
8+
9+
declare function f<T extends Options>(p: T["k"]): T;
10+
>f : <T extends Options>(p: T["k"]) => T
11+
>T : T
12+
>Options : Options
13+
>p : T["k"]
14+
>T : T
15+
>T : T
16+
17+
const x = f("a"); // expect it to be `{ k: "a", a: number }`
18+
>x : { k: "a"; a: number; }
19+
>f("a") : { k: "a"; a: number; }
20+
>f : <T extends Options>(p: T["k"]) => T
21+
>"a" : "a"
22+
23+
type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} };
24+
>Options2 : Options2
25+
>k : "a"
26+
>a : number
27+
>c : {}
28+
>k : "b"
29+
>b : string
30+
>c : {}
31+
32+
declare function f2<T extends Options2>(p: T["k"], c: T["c"]): T;
33+
>f2 : <T extends Options2>(p: T["k"], c: T["c"]) => T
34+
>T : T
35+
>Options2 : Options2
36+
>p : T["k"]
37+
>T : T
38+
>c : T["c"]
39+
>T : T
40+
>T : T
41+
42+
const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }`
43+
>x2 : { k: "a"; c: { x: number; y: number; }; a: number; }
44+
>f2("a", { x: 1, y: 2 }) : { k: "a"; c: { x: number; y: number; }; a: number; }
45+
>f2 : <T extends Options2>(p: T["k"], c: T["c"]) => T
46+
>"a" : "a"
47+
>{ x: 1, y: 2 } : { x: number; y: number; }
48+
>x : number
49+
>1 : 1
50+
>y : number
51+
>2 : 2
52+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type Options = { k: "a", a: number } | { k: "b", b: string };
2+
declare function f<T extends Options>(p: T["k"]): T;
3+
const x = f("a"); // expect it to be `{ k: "a", a: number }`
4+
5+
type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} };
6+
declare function f2<T extends Options2>(p: T["k"], c: T["c"]): T;
7+
const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }`

0 commit comments

Comments
 (0)