Skip to content

Commit 4fcb8b8

Browse files
nebkatjakebailey
andauthored
Consider all union types matching discriminator for excess property checks (#51884)
Co-authored-by: Jake Bailey <[email protected]>
1 parent 3ba3ace commit 4fcb8b8

12 files changed

+814
-198
lines changed

src/compiler/checker.ts

+1-9
Original file line numberDiff line numberDiff line change
@@ -22585,15 +22585,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2258522585
if (match === -1) {
2258622586
return defaultValue;
2258722587
}
22588-
// make sure exactly 1 matches before returning it
22589-
let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1);
22590-
while (nextMatch !== -1) {
22591-
if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) {
22592-
return defaultValue;
22593-
}
22594-
nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1);
22595-
}
22596-
return target.types[match];
22588+
return getUnionType(target.types.filter((_, index) => discriminable[index]));
2259722589
}
2259822590

2259922591
/**

tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt

+103-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@ 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'.
13+
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(130,5): error TS2322: Type '"string"' is not assignable to type '"number"'.
14+
tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(136,5): error TS2322: Type '"string"' is not assignable to type '"number"'.
715

816

9-
==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (3 errors) ====
17+
==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (8 errors) ====
1018
// Repro from #32657
1119

1220
interface Base<T> {
@@ -57,12 +65,15 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): erro
5765
p4: "hello"
5866
};
5967

60-
// This has no excess error because variant one and three are both applicable.
68+
// This has excess error because variant two is not applicable.
6169
const b: DisjointDiscriminants = {
6270
p1: 'left',
6371
p2: true,
6472
p3: 42,
6573
p4: "hello"
74+
~~
75+
!!! error TS2322: Type '{ p1: "left"; p2: true; p3: number; p4: string; }' is not assignable to type 'DisjointDiscriminants'.
76+
!!! 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; }'.
6677
};
6778

6879
// This has excess error because variant two is the only applicable case
@@ -75,4 +86,94 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(57,5): erro
7586
!!! error TS2322: Object literal may only specify known properties, and 'p3' does not exist in type '{ p1: "right"; p2: false; p4: string; }'.
7687
p4: "hello"
7788
};
89+
90+
// Repro from #51873
91+
92+
interface Common {
93+
type: "A" | "B" | "C" | "D";
94+
n: number;
95+
}
96+
interface A {
97+
type: "A";
98+
a?: number;
99+
}
100+
interface B {
101+
type: "B";
102+
b?: number;
103+
}
104+
105+
type CommonWithOverlappingOptionals = Common | (Common & A) | (Common & B);
106+
107+
// Should reject { b } because reduced to Common | (Common & A)
108+
const c1: CommonWithOverlappingOptionals = {
109+
type: "A",
110+
n: 1,
111+
a: 1,
112+
b: 1 // excess property
113+
~
114+
!!! error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithOverlappingOptionals'.
115+
!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'Common | (Common & A)'.
116+
}
117+
118+
type CommonWithDisjointOverlappingOptionals = Common | A | B;
119+
120+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
121+
const c2: CommonWithDisjointOverlappingOptionals = {
122+
type: "A",
123+
n: 1,
124+
a: 1,
125+
b: 1 // excess property
126+
~
127+
!!! error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithDisjointOverlappingOptionals'.
128+
!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'Common | A'.
129+
}
130+
131+
// Repro from https://github.com/microsoft/TypeScript/pull/51884#issuecomment-1472736068
132+
133+
export type BaseAttribute<T> = {
134+
type?: string | undefined;
135+
required?: boolean | undefined;
136+
defaultsTo?: T | undefined;
137+
};
138+
139+
export type Attribute =
140+
| string
141+
| StringAttribute
142+
| NumberAttribute
143+
| OneToOneAttribute
144+
145+
export type Attribute2 =
146+
| string
147+
| StringAttribute
148+
| NumberAttribute
149+
150+
export type StringAttribute = BaseAttribute<string> & {
151+
type: 'string';
152+
};
153+
154+
export type NumberAttribute = BaseAttribute<number> & {
155+
type: 'number';
156+
autoIncrement?: boolean | undefined;
157+
};
158+
159+
export type OneToOneAttribute = BaseAttribute<any> & {
160+
model: string;
161+
};
162+
163+
// both should error due to excess properties
164+
const attributes: Attribute = {
165+
type: 'string',
166+
~~~~
167+
!!! error TS2322: Type '"string"' is not assignable to type '"number"'.
168+
autoIncrement: true,
169+
required: true,
170+
};
171+
172+
const attributes2: Attribute2 = {
173+
type: 'string',
174+
~~~~
175+
!!! error TS2322: Type '"string"' is not assignable to type '"number"'.
176+
autoIncrement: true,
177+
required: true,
178+
};
78179

tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.js

+109-2
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,10 +58,92 @@ 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+
}
96+
97+
// Repro from https://github.com/microsoft/TypeScript/pull/51884#issuecomment-1472736068
98+
99+
export type BaseAttribute<T> = {
100+
type?: string | undefined;
101+
required?: boolean | undefined;
102+
defaultsTo?: T | undefined;
103+
};
104+
105+
export type Attribute =
106+
| string
107+
| StringAttribute
108+
| NumberAttribute
109+
| OneToOneAttribute
110+
111+
export type Attribute2 =
112+
| string
113+
| StringAttribute
114+
| NumberAttribute
115+
116+
export type StringAttribute = BaseAttribute<string> & {
117+
type: 'string';
118+
};
119+
120+
export type NumberAttribute = BaseAttribute<number> & {
121+
type: 'number';
122+
autoIncrement?: boolean | undefined;
123+
};
124+
125+
export type OneToOneAttribute = BaseAttribute<any> & {
126+
model: string;
127+
};
128+
129+
// both should error due to excess properties
130+
const attributes: Attribute = {
131+
type: 'string',
132+
autoIncrement: true,
133+
required: true,
134+
};
135+
136+
const attributes2: Attribute2 = {
137+
type: 'string',
138+
autoIncrement: true,
139+
required: true,
140+
};
61141

62142

63143
//// [excessPropertyCheckWithMultipleDiscriminants.js]
144+
"use strict";
64145
// Repro from #32657
146+
Object.defineProperty(exports, "__esModule", { value: true });
65147
var foo = {
66148
type: "number",
67149
value: 10,
@@ -75,7 +157,7 @@ var a = {
75157
p3: 42,
76158
p4: "hello"
77159
};
78-
// This has no excess error because variant one and three are both applicable.
160+
// This has excess error because variant two is not applicable.
79161
var b = {
80162
p1: 'left',
81163
p2: true,
@@ -89,3 +171,28 @@ var c = {
89171
p3: 42,
90172
p4: "hello"
91173
};
174+
// Should reject { b } because reduced to Common | (Common & A)
175+
var c1 = {
176+
type: "A",
177+
n: 1,
178+
a: 1,
179+
b: 1 // excess property
180+
};
181+
// Should still reject { b } because reduced to Common | A, even though these are now disjoint
182+
var c2 = {
183+
type: "A",
184+
n: 1,
185+
a: 1,
186+
b: 1 // excess property
187+
};
188+
// both should error due to excess properties
189+
var attributes = {
190+
type: 'string',
191+
autoIncrement: true,
192+
required: true,
193+
};
194+
var attributes2 = {
195+
type: 'string',
196+
autoIncrement: true,
197+
required: true,
198+
};

0 commit comments

Comments
 (0)