Skip to content

Commit 71a9176

Browse files
authored
Fix33448 (#35513)
* Filter out discriminants of type 'never'. * Add tests * Accept new baselines * Remove unnecessary '!' assertion
1 parent b98cb06 commit 71a9176

File tree

6 files changed

+430
-9
lines changed

6 files changed

+430
-9
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18311,19 +18311,14 @@ namespace ts {
1831118311
return undefined;
1831218312
}
1831318313

18314-
function isDiscriminantType(type: Type): boolean {
18315-
return !!(type.flags & TypeFlags.Union &&
18316-
(type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral) || !isGenericIndexType(type)));
18317-
}
18318-
1831918314
function isDiscriminantProperty(type: Type | undefined, name: __String) {
1832018315
if (type && type.flags & TypeFlags.Union) {
1832118316
const prop = getUnionOrIntersectionProperty(<UnionType>type, name);
1832218317
if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) {
1832318318
if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
1832418319
(<TransientSymbol>prop).isDiscriminantProperty =
1832518320
((<TransientSymbol>prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant &&
18326-
isDiscriminantType(getTypeOfSymbol(prop));
18321+
!maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable);
1832718322
}
1832818323
return !!(<TransientSymbol>prop).isDiscriminantProperty;
1832918324
}
@@ -19512,8 +19507,14 @@ namespace ts {
1951219507
return type;
1951319508
}
1951419509
const propType = getTypeOfPropertyOfType(type, propName);
19515-
const narrowedPropType = propType && narrowType(propType);
19516-
return propType === narrowedPropType ? type : filterType(type, t => isTypeComparableTo(getTypeOfPropertyOrIndexSignature(t, propName), narrowedPropType!));
19510+
if (!propType) {
19511+
return type;
19512+
}
19513+
const narrowedPropType = narrowType(propType);
19514+
return filterType(type, t => {
19515+
const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName);
19516+
return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType);
19517+
});
1951719518
}
1951819519

1951919520
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {

tests/baselines/reference/discriminatedUnionTypes2.errors.txt

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(27,30): error TS
22
Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'.
33
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(32,11): error TS2339: Property 'b' does not exist on type '{ a: 0; b: string; } | { a: T; c: number; }'.
44
Property 'b' does not exist on type '{ a: T; c: number; }'.
5+
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error TS2339: Property 'value' does not exist on type 'never'.
56

67

7-
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (2 errors) ====
8+
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (3 errors) ====
89
function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
910
if (x.kind === false) {
1011
x.a;
@@ -105,4 +106,55 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(32,11): error TS
105106
foo;
106107
}
107108
}
109+
110+
// Repro from #33448
111+
112+
type a = {
113+
type: 'a',
114+
data: string
115+
}
116+
type b = {
117+
type: 'b',
118+
name: string
119+
}
120+
type c = {
121+
type: 'c',
122+
other: string
123+
}
124+
125+
type abc = a | b | c;
126+
127+
function f(problem: abc & (b | c)) {
128+
if (problem.type === 'b') {
129+
problem.name;
130+
}
131+
else {
132+
problem.other;
133+
}
134+
}
135+
136+
type RuntimeValue =
137+
| { type: 'number', value: number }
138+
| { type: 'string', value: string }
139+
| { type: 'boolean', value: boolean };
140+
141+
function foo1(x: RuntimeValue & { type: 'number' }) {
142+
if (x.type === 'number') {
143+
x.value; // number
144+
}
145+
else {
146+
x.value; // Error, x is never
147+
~~~~~
148+
!!! error TS2339: Property 'value' does not exist on type 'never'.
149+
}
150+
}
151+
152+
function foo2(x: RuntimeValue & ({ type: 'number' } | { type: 'string' })) {
153+
if (x.type === 'number') {
154+
x.value; // number
155+
}
156+
else {
157+
x.value; // string
158+
}
159+
}
108160

tests/baselines/reference/discriminatedUnionTypes2.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,55 @@ function f31(foo: Foo) {
9393
foo;
9494
}
9595
}
96+
97+
// Repro from #33448
98+
99+
type a = {
100+
type: 'a',
101+
data: string
102+
}
103+
type b = {
104+
type: 'b',
105+
name: string
106+
}
107+
type c = {
108+
type: 'c',
109+
other: string
110+
}
111+
112+
type abc = a | b | c;
113+
114+
function f(problem: abc & (b | c)) {
115+
if (problem.type === 'b') {
116+
problem.name;
117+
}
118+
else {
119+
problem.other;
120+
}
121+
}
122+
123+
type RuntimeValue =
124+
| { type: 'number', value: number }
125+
| { type: 'string', value: string }
126+
| { type: 'boolean', value: boolean };
127+
128+
function foo1(x: RuntimeValue & { type: 'number' }) {
129+
if (x.type === 'number') {
130+
x.value; // number
131+
}
132+
else {
133+
x.value; // Error, x is never
134+
}
135+
}
136+
137+
function foo2(x: RuntimeValue & ({ type: 'number' } | { type: 'string' })) {
138+
if (x.type === 'number') {
139+
x.value; // number
140+
}
141+
else {
142+
x.value; // string
143+
}
144+
}
96145

97146

98147
//// [discriminatedUnionTypes2.js]
@@ -164,3 +213,27 @@ function f31(foo) {
164213
foo;
165214
}
166215
}
216+
function f(problem) {
217+
if (problem.type === 'b') {
218+
problem.name;
219+
}
220+
else {
221+
problem.other;
222+
}
223+
}
224+
function foo1(x) {
225+
if (x.type === 'number') {
226+
x.value; // number
227+
}
228+
else {
229+
x.value; // Error, x is never
230+
}
231+
}
232+
function foo2(x) {
233+
if (x.type === 'number') {
234+
x.value; // number
235+
}
236+
else {
237+
x.value; // string
238+
}
239+
}

tests/baselines/reference/discriminatedUnionTypes2.symbols

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,126 @@ function f31(foo: Foo) {
273273
}
274274
}
275275

276+
// Repro from #33448
277+
278+
type a = {
279+
>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 93, 1))
280+
281+
type: 'a',
282+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 97, 10))
283+
284+
data: string
285+
>data : Symbol(data, Decl(discriminatedUnionTypes2.ts, 98, 14))
286+
}
287+
type b = {
288+
>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 100, 1))
289+
290+
type: 'b',
291+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10))
292+
293+
name: string
294+
>name : Symbol(name, Decl(discriminatedUnionTypes2.ts, 102, 14))
295+
}
296+
type c = {
297+
>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 104, 1))
298+
299+
type: 'c',
300+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 105, 10))
301+
302+
other: string
303+
>other : Symbol(other, Decl(discriminatedUnionTypes2.ts, 106, 14))
304+
}
305+
306+
type abc = a | b | c;
307+
>abc : Symbol(abc, Decl(discriminatedUnionTypes2.ts, 108, 1))
308+
>a : Symbol(a, Decl(discriminatedUnionTypes2.ts, 93, 1))
309+
>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 100, 1))
310+
>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 104, 1))
311+
312+
function f(problem: abc & (b | c)) {
313+
>f : Symbol(f, Decl(discriminatedUnionTypes2.ts, 110, 21))
314+
>problem : Symbol(problem, Decl(discriminatedUnionTypes2.ts, 112, 11))
315+
>abc : Symbol(abc, Decl(discriminatedUnionTypes2.ts, 108, 1))
316+
>b : Symbol(b, Decl(discriminatedUnionTypes2.ts, 100, 1))
317+
>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 104, 1))
318+
319+
if (problem.type === 'b') {
320+
>problem.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 105, 10), Decl(discriminatedUnionTypes2.ts, 97, 10), Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 97, 10) ... and 5 more)
321+
>problem : Symbol(problem, Decl(discriminatedUnionTypes2.ts, 112, 11))
322+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 105, 10), Decl(discriminatedUnionTypes2.ts, 97, 10), Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 97, 10) ... and 5 more)
323+
324+
problem.name;
325+
>problem.name : Symbol(name, Decl(discriminatedUnionTypes2.ts, 102, 14))
326+
>problem : Symbol(problem, Decl(discriminatedUnionTypes2.ts, 112, 11))
327+
>name : Symbol(name, Decl(discriminatedUnionTypes2.ts, 102, 14))
328+
}
329+
else {
330+
problem.other;
331+
>problem.other : Symbol(other, Decl(discriminatedUnionTypes2.ts, 106, 14))
332+
>problem : Symbol(problem, Decl(discriminatedUnionTypes2.ts, 112, 11))
333+
>other : Symbol(other, Decl(discriminatedUnionTypes2.ts, 106, 14))
334+
}
335+
}
336+
337+
type RuntimeValue =
338+
>RuntimeValue : Symbol(RuntimeValue, Decl(discriminatedUnionTypes2.ts, 119, 1))
339+
340+
| { type: 'number', value: number }
341+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7))
342+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
343+
344+
| { type: 'string', value: string }
345+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 123, 7))
346+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 123, 23))
347+
348+
| { type: 'boolean', value: boolean };
349+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 124, 7))
350+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 124, 24))
351+
352+
function foo1(x: RuntimeValue & { type: 'number' }) {
353+
>foo1 : Symbol(foo1, Decl(discriminatedUnionTypes2.ts, 124, 42))
354+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
355+
>RuntimeValue : Symbol(RuntimeValue, Decl(discriminatedUnionTypes2.ts, 119, 1))
356+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 126, 33))
357+
358+
if (x.type === 'number') {
359+
>x.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 123, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 124, 7) ... and 1 more)
360+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
361+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 123, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 124, 7) ... and 1 more)
362+
363+
x.value; // number
364+
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
365+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
366+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
367+
}
368+
else {
369+
x.value; // Error, x is never
370+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
371+
}
372+
}
373+
374+
function foo2(x: RuntimeValue & ({ type: 'number' } | { type: 'string' })) {
375+
>foo2 : Symbol(foo2, Decl(discriminatedUnionTypes2.ts, 133, 1))
376+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 135, 14))
377+
>RuntimeValue : Symbol(RuntimeValue, Decl(discriminatedUnionTypes2.ts, 119, 1))
378+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 135, 34))
379+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 135, 55))
380+
381+
if (x.type === 'number') {
382+
>x.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 34), Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 55), Decl(discriminatedUnionTypes2.ts, 123, 7) ... and 7 more)
383+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 135, 14))
384+
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 34), Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 55), Decl(discriminatedUnionTypes2.ts, 123, 7) ... and 7 more)
385+
386+
x.value; // number
387+
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
388+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 135, 14))
389+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
390+
}
391+
else {
392+
x.value; // string
393+
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 123, 23))
394+
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 135, 14))
395+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 123, 23))
396+
}
397+
}
398+

0 commit comments

Comments
 (0)