Skip to content

Commit 5f184e9

Browse files
committed
Consider all union types matching discriminator for excess property checks
Fixes microsoft#51873
1 parent 790c03d commit 5f184e9

12 files changed

+463
-198
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22048,15 +22048,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2204822048
if (match === -1) {
2204922049
return defaultValue;
2205022050
}
22051-
// make sure exactly 1 matches before returning it
22052-
let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1);
22053-
while (nextMatch !== -1) {
22054-
if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) {
22055-
return defaultValue;
22056-
}
22057-
nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1);
22058-
}
22059-
return target.types[match];
22051+
return getUnionType(target.types.filter((_, index) => discriminable[index]));
2206022052
}
2206122053

2206222054
/**

tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(30,5): erro
22
Object literal may only specify known properties, and 'multipleOf' does not exist in type 'Float'.
33
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(41,5): error TS2322: Type '{ p1: "left"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
44
Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "left"; p2: boolean; }'.
5+
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(50,5): error TS2322: Type '{ p1: "left"; p2: true; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
6+
Object literal may only specify known properties, and 'p4' does not exist in type '{ p1: "left"; p2: true; p3: number; } | { p1: "left"; p2: boolean; }'.
57
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): error TS2322: Type '{ p1: "right"; p2: false; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
68
Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'.
9+
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(83,5): error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithOverlappingOptionals'.
10+
Object literal may only specify known properties, and 'b' does not exist in type 'Common | (Common & A)'.
11+
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(93,5): error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithDisjointOverlappingOptionals'.
12+
Object literal may only specify known properties, and 'b' does not exist in type 'Common | A'.
713

814

9-
==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (3 errors) ====
15+
==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (6 errors) ====
1016
// Repro from #32657
1117

1218
interface Base<T> {
@@ -57,12 +63,15 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): erro
5763
p4: "hello"
5864
};
5965

60-
// This has no excess error because variant one and three are both applicable.
66+
// This has excess error because variant two is not applicable.
6167
const b: DisjointDiscriminants = {
6268
p1: 'left',
6369
p2: true,
6470
p3: 42,
6571
p4: "hello"
72+
~~~~~~~~~~~
73+
!!! error TS2322: Type '{ p1: "left"; p2: true; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
74+
!!! error TS2322: Object literal may only specify known properties, and 'p4' does not exist in type '{ p1: "left"; p2: true; p3: number; } | { p1: "left"; p2: boolean; }'.
6675
};
6776

6877
// This has excess error because variant two is the only applicable case
@@ -75,4 +84,45 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): erro
7584
!!! error TS2322: Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'.
7685
p4: "hello"
7786
};
87+
88+
// Repro from #51873
89+
90+
interface Common {
91+
type: "A" | "B" | "C" | "D";
92+
n: number;
93+
}
94+
interface A {
95+
type: "A";
96+
a?: number;
97+
}
98+
interface B {
99+
type: "B";
100+
b?: number;
101+
}
102+
103+
type CommonWithOverlappingOptionals = Common | (Common & A) | (Common & B);
104+
105+
// Should reject { b } because reduced to Common | (Common & A)
106+
const c1: CommonWithOverlappingOptionals = {
107+
type: "A",
108+
n: 1,
109+
a: 1,
110+
b: 1 // excess property
111+
~~~~
112+
!!! error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithOverlappingOptionals'.
113+
!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'Common | (Common & A)'.
114+
}
115+
116+
type CommonWithDisjointOverlappingOptionals = Common | A | B;
117+
118+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
119+
const c2: CommonWithDisjointOverlappingOptionals = {
120+
type: "A",
121+
n: 1,
122+
a: 1,
123+
b: 1 // excess property
124+
~~~~
125+
!!! error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithDisjointOverlappingOptionals'.
126+
!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'Common | A'.
127+
}
78128

tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const a: DisjointDiscriminants = {
4343
p4: "hello"
4444
};
4545

46-
// This has no excess error because variant one and three are both applicable.
46+
// This has excess error because variant two is not applicable.
4747
const b: DisjointDiscriminants = {
4848
p1: 'left',
4949
p2: true,
@@ -58,6 +58,41 @@ const c: DisjointDiscriminants = {
5858
p3: 42,
5959
p4: "hello"
6060
};
61+
62+
// Repro from #51873
63+
64+
interface Common {
65+
type: "A" | "B" | "C" | "D";
66+
n: number;
67+
}
68+
interface A {
69+
type: "A";
70+
a?: number;
71+
}
72+
interface B {
73+
type: "B";
74+
b?: number;
75+
}
76+
77+
type CommonWithOverlappingOptionals = Common | (Common & A) | (Common & B);
78+
79+
// Should reject { b } because reduced to Common | (Common & A)
80+
const c1: CommonWithOverlappingOptionals = {
81+
type: "A",
82+
n: 1,
83+
a: 1,
84+
b: 1 // excess property
85+
}
86+
87+
type CommonWithDisjointOverlappingOptionals = Common | A | B;
88+
89+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
90+
const c2: CommonWithDisjointOverlappingOptionals = {
91+
type: "A",
92+
n: 1,
93+
a: 1,
94+
b: 1 // excess property
95+
}
6196

6297

6398
//// [excessPropertyCheckWithMultipleDiscriminants.js]
@@ -75,7 +110,7 @@ var a = {
75110
p3: 42,
76111
p4: "hello"
77112
};
78-
// This has no excess error because variant one and three are both applicable.
113+
// This has excess error because variant two is not applicable.
79114
var b = {
80115
p1: 'left',
81116
p2: true,
@@ -89,3 +124,17 @@ var c = {
89124
p3: 42,
90125
p4: "hello"
91126
};
127+
// Should reject { b } because reduced to Common | (Common & A)
128+
var c1 = {
129+
type: "A",
130+
n: 1,
131+
a: 1,
132+
b: 1 // excess property
133+
};
134+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
135+
var c2 = {
136+
type: "A",
137+
n: 1,
138+
a: 1,
139+
b: 1 // excess property
140+
};

tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.symbols

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const a: DisjointDiscriminants = {
103103

104104
};
105105

106-
// This has no excess error because variant one and three are both applicable.
106+
// This has excess error because variant two is not applicable.
107107
const b: DisjointDiscriminants = {
108108
>b : Symbol(b, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 45, 5))
109109
>DisjointDiscriminants : Symbol(DisjointDiscriminants, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 31, 1))
@@ -141,3 +141,83 @@ const c: DisjointDiscriminants = {
141141

142142
};
143143

144+
// Repro from #51873
145+
146+
interface Common {
147+
>Common : Symbol(Common, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 58, 2))
148+
149+
type: "A" | "B" | "C" | "D";
150+
>type : Symbol(Common.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 62, 18))
151+
152+
n: number;
153+
>n : Symbol(Common.n, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 63, 32))
154+
}
155+
interface A {
156+
>A : Symbol(A, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 65, 1))
157+
158+
type: "A";
159+
>type : Symbol(A.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 66, 13))
160+
161+
a?: number;
162+
>a : Symbol(A.a, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 67, 14))
163+
}
164+
interface B {
165+
>B : Symbol(B, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 69, 1))
166+
167+
type: "B";
168+
>type : Symbol(B.type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 70, 13))
169+
170+
b?: number;
171+
>b : Symbol(B.b, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 71, 14))
172+
}
173+
174+
type CommonWithOverlappingOptionals = Common | (Common & A) | (Common & B);
175+
>CommonWithOverlappingOptionals : Symbol(CommonWithOverlappingOptionals, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 73, 1))
176+
>Common : Symbol(Common, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 58, 2))
177+
>Common : Symbol(Common, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 58, 2))
178+
>A : Symbol(A, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 65, 1))
179+
>Common : Symbol(Common, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 58, 2))
180+
>B : Symbol(B, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 69, 1))
181+
182+
// Should reject { b } because reduced to Common | (Common & A)
183+
const c1: CommonWithOverlappingOptionals = {
184+
>c1 : Symbol(c1, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 78, 5))
185+
>CommonWithOverlappingOptionals : Symbol(CommonWithOverlappingOptionals, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 73, 1))
186+
187+
type: "A",
188+
>type : Symbol(type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 78, 44))
189+
190+
n: 1,
191+
>n : Symbol(n, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 79, 14))
192+
193+
a: 1,
194+
>a : Symbol(a, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 80, 9))
195+
196+
b: 1 // excess property
197+
>b : Symbol(b, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 81, 9))
198+
}
199+
200+
type CommonWithDisjointOverlappingOptionals = Common | A | B;
201+
>CommonWithDisjointOverlappingOptionals : Symbol(CommonWithDisjointOverlappingOptionals, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 83, 1))
202+
>Common : Symbol(Common, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 58, 2))
203+
>A : Symbol(A, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 65, 1))
204+
>B : Symbol(B, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 69, 1))
205+
206+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
207+
const c2: CommonWithDisjointOverlappingOptionals = {
208+
>c2 : Symbol(c2, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 88, 5))
209+
>CommonWithDisjointOverlappingOptionals : Symbol(CommonWithDisjointOverlappingOptionals, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 83, 1))
210+
211+
type: "A",
212+
>type : Symbol(type, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 88, 52))
213+
214+
n: 1,
215+
>n : Symbol(n, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 89, 14))
216+
217+
a: 1,
218+
>a : Symbol(a, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 90, 9))
219+
220+
b: 1 // excess property
221+
>b : Symbol(b, Decl(excessPropertyCheckWithMultipleDiscriminants.ts, 91, 9))
222+
}
223+

tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.types

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const a: DisjointDiscriminants = {
9393

9494
};
9595

96-
// This has no excess error because variant one and three are both applicable.
96+
// This has excess error because variant two is not applicable.
9797
const b: DisjointDiscriminants = {
9898
>b : DisjointDiscriminants
9999
>{ p1: 'left', p2: true, p3: 42, p4: "hello"} : { p1: "left"; p2: true; p3: number; p4: string; }
@@ -139,3 +139,77 @@ const c: DisjointDiscriminants = {
139139

140140
};
141141

142+
// Repro from #51873
143+
144+
interface Common {
145+
type: "A" | "B" | "C" | "D";
146+
>type : "A" | "B" | "C" | "D"
147+
148+
n: number;
149+
>n : number
150+
}
151+
interface A {
152+
type: "A";
153+
>type : "A"
154+
155+
a?: number;
156+
>a : number
157+
}
158+
interface B {
159+
type: "B";
160+
>type : "B"
161+
162+
b?: number;
163+
>b : number
164+
}
165+
166+
type CommonWithOverlappingOptionals = Common | (Common & A) | (Common & B);
167+
>CommonWithOverlappingOptionals : Common | (Common & A) | (Common & B)
168+
169+
// Should reject { b } because reduced to Common | (Common & A)
170+
const c1: CommonWithOverlappingOptionals = {
171+
>c1 : CommonWithOverlappingOptionals
172+
>{ type: "A", n: 1, a: 1, b: 1 // excess property} : { type: "A"; n: number; a: number; b: number; }
173+
174+
type: "A",
175+
>type : "A"
176+
>"A" : "A"
177+
178+
n: 1,
179+
>n : number
180+
>1 : 1
181+
182+
a: 1,
183+
>a : number
184+
>1 : 1
185+
186+
b: 1 // excess property
187+
>b : number
188+
>1 : 1
189+
}
190+
191+
type CommonWithDisjointOverlappingOptionals = Common | A | B;
192+
>CommonWithDisjointOverlappingOptionals : Common | A | B
193+
194+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
195+
const c2: CommonWithDisjointOverlappingOptionals = {
196+
>c2 : CommonWithDisjointOverlappingOptionals
197+
>{ type: "A", n: 1, a: 1, b: 1 // excess property} : { type: "A"; n: number; a: number; b: number; }
198+
199+
type: "A",
200+
>type : "A"
201+
>"A" : "A"
202+
203+
n: 1,
204+
>n : number
205+
>1 : 1
206+
207+
a: 1,
208+
>a : number
209+
>1 : 1
210+
211+
b: 1 // excess property
212+
>b : number
213+
>1 : 1
214+
}
215+

0 commit comments

Comments
 (0)