Skip to content

Commit 9767522

Browse files
authored
Merge pull request #27695 from Microsoft/mixedDiscriminantTypes
Allow non-unit types in union discriminants
2 parents c184184 + 6cdba6d commit 9767522

7 files changed

+775
-7
lines changed

src/compiler/checker.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -14233,12 +14233,26 @@ namespace ts {
1423314233
return undefined;
1423414234
}
1423514235

14236+
function isDiscriminantType(type: Type): boolean {
14237+
if (type.flags & TypeFlags.Union) {
14238+
if (type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral)) {
14239+
return true;
14240+
}
14241+
let combined = 0;
14242+
for (const t of (<UnionType>type).types) combined |= t.flags;
14243+
if (combined & TypeFlags.Unit && !(combined & TypeFlags.Instantiable)) {
14244+
return true;
14245+
}
14246+
}
14247+
return false;
14248+
}
14249+
1423614250
function isDiscriminantProperty(type: Type | undefined, name: __String) {
1423714251
if (type && type.flags & TypeFlags.Union) {
1423814252
const prop = getUnionOrIntersectionProperty(<UnionType>type, name);
1423914253
if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) {
1424014254
if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
14241-
(<TransientSymbol>prop).isDiscriminantProperty = !!((<TransientSymbol>prop).checkFlags & CheckFlags.HasNonUniformType) && isLiteralType(getTypeOfSymbol(prop));
14255+
(<TransientSymbol>prop).isDiscriminantProperty = !!((<TransientSymbol>prop).checkFlags & CheckFlags.HasNonUniformType) && isDiscriminantType(getTypeOfSymbol(prop));
1424214256
}
1424314257
return !!(<TransientSymbol>prop).isDiscriminantProperty;
1424414258
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(27,30): error TS2322: Type '{ a: null; b: string; c: number; }' is not assignable to type '{ a: null; b: string; } | { a: string; c: number; }'.
2+
Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'.
3+
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; }'.
4+
Property 'b' does not exist on type '{ a: T; c: number; }'.
5+
6+
7+
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (2 errors) ====
8+
function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
9+
if (x.kind === false) {
10+
x.a;
11+
}
12+
else if (x.kind === true) {
13+
x.b;
14+
}
15+
else {
16+
x.c;
17+
}
18+
}
19+
20+
function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
21+
switch (x.kind) {
22+
case false:
23+
x.a;
24+
break;
25+
case true:
26+
x.b;
27+
break;
28+
default:
29+
x.c;
30+
}
31+
}
32+
33+
function f13(x: { a: null; b: string } | { a: string, c: number }) {
34+
x = { a: null, b: "foo", c: 4}; // Error
35+
~~~~
36+
!!! error TS2322: Type '{ a: null; b: string; c: number; }' is not assignable to type '{ a: null; b: string; } | { a: string; c: number; }'.
37+
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'.
38+
}
39+
40+
function f14<T>(x: { a: 0; b: string } | { a: T, c: number }) {
41+
if (x.a === 0) {
42+
x.b; // Error
43+
~
44+
!!! error TS2339: Property 'b' does not exist on type '{ a: 0; b: string; } | { a: T; c: number; }'.
45+
!!! error TS2339: Property 'b' does not exist on type '{ a: T; c: number; }'.
46+
}
47+
}
48+
49+
type Result<T> = { error?: undefined, value: T } | { error: Error };
50+
51+
function f15(x: Result<number>) {
52+
if (!x.error) {
53+
x.value;
54+
}
55+
else {
56+
x.error.message;
57+
}
58+
}
59+
60+
f15({ value: 10 });
61+
f15({ error: new Error("boom") });
62+
63+
// Repro from #24193
64+
65+
interface WithError {
66+
error: Error
67+
data: null
68+
}
69+
70+
interface WithoutError<Data> {
71+
error: null
72+
data: Data
73+
}
74+
75+
type DataCarrier<Data> = WithError | WithoutError<Data>
76+
77+
function f20<Data>(carrier: DataCarrier<Data>) {
78+
if (carrier.error === null) {
79+
const error: null = carrier.error
80+
const data: Data = carrier.data
81+
} else {
82+
const error: Error = carrier.error
83+
const data: null = carrier.data
84+
}
85+
}
86+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//// [discriminatedUnionTypes2.ts]
2+
function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
3+
if (x.kind === false) {
4+
x.a;
5+
}
6+
else if (x.kind === true) {
7+
x.b;
8+
}
9+
else {
10+
x.c;
11+
}
12+
}
13+
14+
function f11(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
15+
switch (x.kind) {
16+
case false:
17+
x.a;
18+
break;
19+
case true:
20+
x.b;
21+
break;
22+
default:
23+
x.c;
24+
}
25+
}
26+
27+
function f13(x: { a: null; b: string } | { a: string, c: number }) {
28+
x = { a: null, b: "foo", c: 4}; // Error
29+
}
30+
31+
function f14<T>(x: { a: 0; b: string } | { a: T, c: number }) {
32+
if (x.a === 0) {
33+
x.b; // Error
34+
}
35+
}
36+
37+
type Result<T> = { error?: undefined, value: T } | { error: Error };
38+
39+
function f15(x: Result<number>) {
40+
if (!x.error) {
41+
x.value;
42+
}
43+
else {
44+
x.error.message;
45+
}
46+
}
47+
48+
f15({ value: 10 });
49+
f15({ error: new Error("boom") });
50+
51+
// Repro from #24193
52+
53+
interface WithError {
54+
error: Error
55+
data: null
56+
}
57+
58+
interface WithoutError<Data> {
59+
error: null
60+
data: Data
61+
}
62+
63+
type DataCarrier<Data> = WithError | WithoutError<Data>
64+
65+
function f20<Data>(carrier: DataCarrier<Data>) {
66+
if (carrier.error === null) {
67+
const error: null = carrier.error
68+
const data: Data = carrier.data
69+
} else {
70+
const error: Error = carrier.error
71+
const data: null = carrier.data
72+
}
73+
}
74+
75+
76+
//// [discriminatedUnionTypes2.js]
77+
"use strict";
78+
function f10(x) {
79+
if (x.kind === false) {
80+
x.a;
81+
}
82+
else if (x.kind === true) {
83+
x.b;
84+
}
85+
else {
86+
x.c;
87+
}
88+
}
89+
function f11(x) {
90+
switch (x.kind) {
91+
case false:
92+
x.a;
93+
break;
94+
case true:
95+
x.b;
96+
break;
97+
default:
98+
x.c;
99+
}
100+
}
101+
function f13(x) {
102+
x = { a: null, b: "foo", c: 4 }; // Error
103+
}
104+
function f14(x) {
105+
if (x.a === 0) {
106+
x.b; // Error
107+
}
108+
}
109+
function f15(x) {
110+
if (!x.error) {
111+
x.value;
112+
}
113+
else {
114+
x.error.message;
115+
}
116+
}
117+
f15({ value: 10 });
118+
f15({ error: new Error("boom") });
119+
function f20(carrier) {
120+
if (carrier.error === null) {
121+
var error = carrier.error;
122+
var data = carrier.data;
123+
}
124+
else {
125+
var error = carrier.error;
126+
var data = carrier.data;
127+
}
128+
}

0 commit comments

Comments
 (0)