Skip to content

Commit 34d9d4b

Browse files
authored
Merge pull request #28870 from Microsoft/discriminatedUnionInference
Improved type inference for discriminated unions
2 parents 95fb220 + 1896248 commit 34d9d4b

File tree

6 files changed

+191
-10
lines changed

6 files changed

+191
-10
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12473,10 +12473,10 @@ namespace ts {
1247312473
return propertiesIdenticalTo(source, target);
1247412474
}
1247512475
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
12476-
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties);
12476+
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false);
1247712477
if (unmatchedProperty) {
1247812478
if (reportErrors) {
12479-
const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties));
12479+
const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false));
1248012480
if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code &&
1248112481
headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) {
1248212482
suppressNextError = true; // Retain top-level error for interface implementing issues, otherwise omit it
@@ -13900,20 +13900,29 @@ namespace ts {
1390013900
return getTypeFromInference(inference);
1390113901
}
1390213902

13903-
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean) {
13903+
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean) {
1390413904
const properties = target.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(<IntersectionType>target) : getPropertiesOfObjectType(target);
1390513905
for (const targetProp of properties) {
1390613906
if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional)) {
1390713907
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
1390813908
if (!sourceProp) {
1390913909
yield targetProp;
1391013910
}
13911+
else if (matchDiscriminantProperties) {
13912+
const targetType = getTypeOfSymbol(targetProp);
13913+
if (targetType.flags & TypeFlags.Unit) {
13914+
const sourceType = getTypeOfSymbol(sourceProp);
13915+
if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) {
13916+
yield targetProp;
13917+
}
13918+
}
13919+
}
1391113920
}
1391213921
}
1391313922
}
1391413923

13915-
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean): Symbol | undefined {
13916-
return getUnmatchedProperties(source, target, requireOptionalProperties).next().value;
13924+
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined {
13925+
return getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next().value;
1391713926
}
1391813927

1391913928
function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) {
@@ -13925,7 +13934,8 @@ namespace ts {
1392513934
// Two tuple types with incompatible arities are definitely unrelated.
1392613935
// Two object types that each have a property that is unmatched in the other are definitely unrelated.
1392713936
return isTupleType(source) && isTupleType(target) && tupleTypesDefinitelyUnrelated(source, target) ||
13928-
!!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false) && !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false);
13937+
!!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) &&
13938+
!!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true);
1392913939
}
1393013940

1393113941
function getTypeFromInference(inference: InferenceInfo) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [discriminatedUnionInference.ts]
2+
// Repro from #28862
3+
4+
type Foo<A> = { type: "foo", (): A[] };
5+
type Bar<A> = { type: "bar", (): A };
6+
7+
type FooBar<A> = Foo<A> | Bar<A>;
8+
9+
type InferA<T> = T extends FooBar<infer A> ? A : never;
10+
11+
type FooA = InferA<Foo<number>>; // number
12+
13+
// Repro from #28862
14+
15+
type Item<T> = { kind: 'a', data: T } | { kind: 'b', data: T[] };
16+
17+
declare function foo<T>(item: Item<T>): T;
18+
19+
let x1 = foo({ kind: 'a', data: 42 }); // number
20+
let x2 = foo({ kind: 'b', data: [1, 2] }); // number
21+
22+
23+
//// [discriminatedUnionInference.js]
24+
"use strict";
25+
// Repro from #28862
26+
var x1 = foo({ kind: 'a', data: 42 }); // number
27+
var x2 = foo({ kind: 'b', data: [1, 2] }); // number
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
=== tests/cases/conformance/types/typeRelationships/typeInference/discriminatedUnionInference.ts ===
2+
// Repro from #28862
3+
4+
type Foo<A> = { type: "foo", (): A[] };
5+
>Foo : Symbol(Foo, Decl(discriminatedUnionInference.ts, 0, 0))
6+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 2, 9))
7+
>type : Symbol(type, Decl(discriminatedUnionInference.ts, 2, 15))
8+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 2, 9))
9+
10+
type Bar<A> = { type: "bar", (): A };
11+
>Bar : Symbol(Bar, Decl(discriminatedUnionInference.ts, 2, 39))
12+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 3, 9))
13+
>type : Symbol(type, Decl(discriminatedUnionInference.ts, 3, 15))
14+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 3, 9))
15+
16+
type FooBar<A> = Foo<A> | Bar<A>;
17+
>FooBar : Symbol(FooBar, Decl(discriminatedUnionInference.ts, 3, 37))
18+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 5, 12))
19+
>Foo : Symbol(Foo, Decl(discriminatedUnionInference.ts, 0, 0))
20+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 5, 12))
21+
>Bar : Symbol(Bar, Decl(discriminatedUnionInference.ts, 2, 39))
22+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 5, 12))
23+
24+
type InferA<T> = T extends FooBar<infer A> ? A : never;
25+
>InferA : Symbol(InferA, Decl(discriminatedUnionInference.ts, 5, 33))
26+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 7, 12))
27+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 7, 12))
28+
>FooBar : Symbol(FooBar, Decl(discriminatedUnionInference.ts, 3, 37))
29+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 7, 39))
30+
>A : Symbol(A, Decl(discriminatedUnionInference.ts, 7, 39))
31+
32+
type FooA = InferA<Foo<number>>; // number
33+
>FooA : Symbol(FooA, Decl(discriminatedUnionInference.ts, 7, 55))
34+
>InferA : Symbol(InferA, Decl(discriminatedUnionInference.ts, 5, 33))
35+
>Foo : Symbol(Foo, Decl(discriminatedUnionInference.ts, 0, 0))
36+
37+
// Repro from #28862
38+
39+
type Item<T> = { kind: 'a', data: T } | { kind: 'b', data: T[] };
40+
>Item : Symbol(Item, Decl(discriminatedUnionInference.ts, 9, 32))
41+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 13, 10))
42+
>kind : Symbol(kind, Decl(discriminatedUnionInference.ts, 13, 16))
43+
>data : Symbol(data, Decl(discriminatedUnionInference.ts, 13, 27))
44+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 13, 10))
45+
>kind : Symbol(kind, Decl(discriminatedUnionInference.ts, 13, 41))
46+
>data : Symbol(data, Decl(discriminatedUnionInference.ts, 13, 52))
47+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 13, 10))
48+
49+
declare function foo<T>(item: Item<T>): T;
50+
>foo : Symbol(foo, Decl(discriminatedUnionInference.ts, 13, 65))
51+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 15, 21))
52+
>item : Symbol(item, Decl(discriminatedUnionInference.ts, 15, 24))
53+
>Item : Symbol(Item, Decl(discriminatedUnionInference.ts, 9, 32))
54+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 15, 21))
55+
>T : Symbol(T, Decl(discriminatedUnionInference.ts, 15, 21))
56+
57+
let x1 = foo({ kind: 'a', data: 42 }); // number
58+
>x1 : Symbol(x1, Decl(discriminatedUnionInference.ts, 17, 3))
59+
>foo : Symbol(foo, Decl(discriminatedUnionInference.ts, 13, 65))
60+
>kind : Symbol(kind, Decl(discriminatedUnionInference.ts, 17, 14))
61+
>data : Symbol(data, Decl(discriminatedUnionInference.ts, 17, 25))
62+
63+
let x2 = foo({ kind: 'b', data: [1, 2] }); // number
64+
>x2 : Symbol(x2, Decl(discriminatedUnionInference.ts, 18, 3))
65+
>foo : Symbol(foo, Decl(discriminatedUnionInference.ts, 13, 65))
66+
>kind : Symbol(kind, Decl(discriminatedUnionInference.ts, 18, 14))
67+
>data : Symbol(data, Decl(discriminatedUnionInference.ts, 18, 25))
68+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=== tests/cases/conformance/types/typeRelationships/typeInference/discriminatedUnionInference.ts ===
2+
// Repro from #28862
3+
4+
type Foo<A> = { type: "foo", (): A[] };
5+
>Foo : Foo<A>
6+
>type : "foo"
7+
8+
type Bar<A> = { type: "bar", (): A };
9+
>Bar : Bar<A>
10+
>type : "bar"
11+
12+
type FooBar<A> = Foo<A> | Bar<A>;
13+
>FooBar : FooBar<A>
14+
15+
type InferA<T> = T extends FooBar<infer A> ? A : never;
16+
>InferA : InferA<T>
17+
18+
type FooA = InferA<Foo<number>>; // number
19+
>FooA : number
20+
21+
// Repro from #28862
22+
23+
type Item<T> = { kind: 'a', data: T } | { kind: 'b', data: T[] };
24+
>Item : Item<T>
25+
>kind : "a"
26+
>data : T
27+
>kind : "b"
28+
>data : T[]
29+
30+
declare function foo<T>(item: Item<T>): T;
31+
>foo : <T>(item: Item<T>) => T
32+
>item : Item<T>
33+
34+
let x1 = foo({ kind: 'a', data: 42 }); // number
35+
>x1 : number
36+
>foo({ kind: 'a', data: 42 }) : number
37+
>foo : <T>(item: Item<T>) => T
38+
>{ kind: 'a', data: 42 } : { kind: "a"; data: number; }
39+
>kind : "a"
40+
>'a' : "a"
41+
>data : number
42+
>42 : 42
43+
44+
let x2 = foo({ kind: 'b', data: [1, 2] }); // number
45+
>x2 : number
46+
>foo({ kind: 'b', data: [1, 2] }) : number
47+
>foo : <T>(item: Item<T>) => T
48+
>{ kind: 'b', data: [1, 2] } : { kind: "b"; data: number[]; }
49+
>kind : "b"
50+
>'b' : "b"
51+
>data : number[]
52+
>[1, 2] : number[]
53+
>1 : 1
54+
>2 : 2
55+

tests/baselines/reference/genericRestParameters3.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ tests/cases/conformance/types/rest/genericRestParameters3.ts(31,21): error TS234
2222
Property '0' is missing in type 'CoolArray<any>' but required in type '[(...args: any[]) => void]'.
2323
tests/cases/conformance/types/rest/genericRestParameters3.ts(38,32): error TS2345: Argument of type '[10, 20]' is not assignable to parameter of type 'CoolArray<number>'.
2424
Property 'hello' is missing in type '[10, 20]' but required in type 'CoolArray<number>'.
25-
tests/cases/conformance/types/rest/genericRestParameters3.ts(43,1): error TS2345: Argument of type '[]' is not assignable to parameter of type 'CoolArray<never>'.
26-
Property 'hello' is missing in type '[]' but required in type 'CoolArray<never>'.
25+
tests/cases/conformance/types/rest/genericRestParameters3.ts(43,1): error TS2345: Argument of type '[]' is not assignable to parameter of type 'CoolArray<{}>'.
26+
Property 'hello' is missing in type '[]' but required in type 'CoolArray<{}>'.
2727
tests/cases/conformance/types/rest/genericRestParameters3.ts(44,5): error TS2345: Argument of type '[number]' is not assignable to parameter of type 'CoolArray<{}>'.
2828
Property 'hello' is missing in type '[number]' but required in type 'CoolArray<{}>'.
2929
tests/cases/conformance/types/rest/genericRestParameters3.ts(45,5): error TS2345: Argument of type '[number, number]' is not assignable to parameter of type 'CoolArray<{}>'.
@@ -115,8 +115,8 @@ tests/cases/conformance/types/rest/genericRestParameters3.ts(53,5): error TS2345
115115

116116
baz(); // Error
117117
~~~~~
118-
!!! error TS2345: Argument of type '[]' is not assignable to parameter of type 'CoolArray<never>'.
119-
!!! error TS2345: Property 'hello' is missing in type '[]' but required in type 'CoolArray<never>'.
118+
!!! error TS2345: Argument of type '[]' is not assignable to parameter of type 'CoolArray<{}>'.
119+
!!! error TS2345: Property 'hello' is missing in type '[]' but required in type 'CoolArray<{}>'.
120120
!!! related TS2728 tests/cases/conformance/types/rest/genericRestParameters3.ts:24:5: 'hello' is declared here.
121121
baz(1); // Error
122122
~
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @strict: true
2+
3+
// Repro from #28862
4+
5+
type Foo<A> = { type: "foo", (): A[] };
6+
type Bar<A> = { type: "bar", (): A };
7+
8+
type FooBar<A> = Foo<A> | Bar<A>;
9+
10+
type InferA<T> = T extends FooBar<infer A> ? A : never;
11+
12+
type FooA = InferA<Foo<number>>; // number
13+
14+
// Repro from #28862
15+
16+
type Item<T> = { kind: 'a', data: T } | { kind: 'b', data: T[] };
17+
18+
declare function foo<T>(item: Item<T>): T;
19+
20+
let x1 = foo({ kind: 'a', data: 42 }); // number
21+
let x2 = foo({ kind: 'b', data: [1, 2] }); // number

0 commit comments

Comments
 (0)