Skip to content

Commit 13443ae

Browse files
committed
Narrow non-declared unions by discriminant
1 parent e20b87f commit 13443ae

File tree

5 files changed

+431
-6
lines changed

5 files changed

+431
-6
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16175,14 +16175,16 @@ namespace ts {
1617516175
}
1617616176

1617716177
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
16178-
if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) {
16178+
if (!isAccessExpression(expr)) {
1617916179
return false;
1618016180
}
1618116181
const name = getAccessedPropertyName(expr);
1618216182
if (name === undefined) {
1618316183
return false;
1618416184
}
16185-
return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name);
16185+
let propType;
16186+
return isMatchingReference(reference, expr.expression) &&
16187+
(isDiscriminantProperty(computedType, name) || (propType = getTypeOfPropertyOfType(computedType, name)) && isUnitType(propType));
1618616188
}
1618716189

1618816190
function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type {
@@ -16250,10 +16252,10 @@ namespace ts {
1625016252
if (isMatchingReference(reference, right)) {
1625116253
return narrowTypeByEquality(type, operator, left, assumeTrue);
1625216254
}
16253-
if (isMatchingReferenceDiscriminant(left, declaredType)) {
16255+
if (isMatchingReferenceDiscriminant(left, type)) {
1625416256
return narrowTypeByDiscriminant(type, <AccessExpression>left, t => narrowTypeByEquality(t, operator, right, assumeTrue));
1625516257
}
16256-
if (isMatchingReferenceDiscriminant(right, declaredType)) {
16258+
if (isMatchingReferenceDiscriminant(right, type)) {
1625716259
return narrowTypeByDiscriminant(type, <AccessExpression>right, t => narrowTypeByEquality(t, operator, left, assumeTrue));
1625816260
}
1625916261
if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) {

tests/baselines/reference/discriminantsAndTypePredicates.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,62 @@ function foo2(x: A | B): any {
2929
return x; // B
3030
}
3131
x; // never
32-
}
32+
}
33+
34+
// Repro from #30557
35+
36+
interface TypeA {
37+
Name: "TypeA";
38+
Value1: "Cool stuff!";
39+
}
40+
41+
interface TypeB {
42+
Name: "TypeB";
43+
Value2: 0;
44+
}
45+
46+
type Type = TypeA | TypeB;
47+
48+
declare function isType(x: unknown): x is Type;
49+
50+
function WorksProperly(data: Type) {
51+
if (data.Name === "TypeA") {
52+
// TypeA
53+
const value1 = data.Value1;
54+
}
55+
}
56+
57+
function DoesNotWork(data: unknown) {
58+
if (isType(data)) {
59+
if (data.Name === "TypeA") {
60+
// TypeB
61+
const value1 = data.Value1;
62+
}
63+
}
64+
}
65+
66+
function narrowToNever(data: Type): "Cool stuff!" | 0 {
67+
if (data.Name === "TypeA") {
68+
return data.Value1;
69+
}
70+
if (data.Name === "TypeB") {
71+
return data.Value2;
72+
}
73+
return data;
74+
}
75+
76+
function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 {
77+
if (isType(data)) {
78+
if (data.Name === "TypeA") {
79+
return data.Value1;
80+
}
81+
if (data.Name === "TypeB") {
82+
return data.Value2;
83+
}
84+
return data;
85+
}
86+
}
87+
3388

3489
//// [discriminantsAndTypePredicates.js]
3590
// Repro from #10145
@@ -57,3 +112,37 @@ function foo2(x) {
57112
}
58113
x; // never
59114
}
115+
function WorksProperly(data) {
116+
if (data.Name === "TypeA") {
117+
// TypeA
118+
var value1 = data.Value1;
119+
}
120+
}
121+
function DoesNotWork(data) {
122+
if (isType(data)) {
123+
if (data.Name === "TypeA") {
124+
// TypeB
125+
var value1 = data.Value1;
126+
}
127+
}
128+
}
129+
function narrowToNever(data) {
130+
if (data.Name === "TypeA") {
131+
return data.Value1;
132+
}
133+
if (data.Name === "TypeB") {
134+
return data.Value2;
135+
}
136+
return data;
137+
}
138+
function narrowToNeverUnknown(data) {
139+
if (isType(data)) {
140+
if (data.Name === "TypeA") {
141+
return data.Value1;
142+
}
143+
if (data.Name === "TypeB") {
144+
return data.Value2;
145+
}
146+
return data;
147+
}
148+
}

tests/baselines/reference/discriminantsAndTypePredicates.symbols

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,141 @@ function foo2(x: A | B): any {
9292
x; // never
9393
>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 20, 14))
9494
}
95+
96+
// Repro from #30557
97+
98+
interface TypeA {
99+
>TypeA : Symbol(TypeA, Decl(discriminantsAndTypePredicates.ts, 30, 1))
100+
101+
Name: "TypeA";
102+
>Name : Symbol(TypeA.Name, Decl(discriminantsAndTypePredicates.ts, 34, 17))
103+
104+
Value1: "Cool stuff!";
105+
>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
106+
}
107+
108+
interface TypeB {
109+
>TypeB : Symbol(TypeB, Decl(discriminantsAndTypePredicates.ts, 37, 1))
110+
111+
Name: "TypeB";
112+
>Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17))
113+
114+
Value2: 0;
115+
>Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18))
116+
}
117+
118+
type Type = TypeA | TypeB;
119+
>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1))
120+
>TypeA : Symbol(TypeA, Decl(discriminantsAndTypePredicates.ts, 30, 1))
121+
>TypeB : Symbol(TypeB, Decl(discriminantsAndTypePredicates.ts, 37, 1))
122+
123+
declare function isType(x: unknown): x is Type;
124+
>isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26))
125+
>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 46, 24))
126+
>x : Symbol(x, Decl(discriminantsAndTypePredicates.ts, 46, 24))
127+
>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1))
128+
129+
function WorksProperly(data: Type) {
130+
>WorksProperly : Symbol(WorksProperly, Decl(discriminantsAndTypePredicates.ts, 46, 47))
131+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23))
132+
>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1))
133+
134+
if (data.Name === "TypeA") {
135+
>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
136+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23))
137+
>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
138+
139+
// TypeA
140+
const value1 = data.Value1;
141+
>value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 51, 6))
142+
>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
143+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 48, 23))
144+
>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
145+
}
146+
}
147+
148+
function DoesNotWork(data: unknown) {
149+
>DoesNotWork : Symbol(DoesNotWork, Decl(discriminantsAndTypePredicates.ts, 53, 1))
150+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21))
151+
152+
if (isType(data)) {
153+
>isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26))
154+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21))
155+
156+
if (data.Name === "TypeA") {
157+
>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
158+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21))
159+
>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
160+
161+
// TypeB
162+
const value1 = data.Value1;
163+
>value1 : Symbol(value1, Decl(discriminantsAndTypePredicates.ts, 59, 10))
164+
>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
165+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 55, 21))
166+
>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
167+
}
168+
}
169+
}
170+
171+
function narrowToNever(data: Type): "Cool stuff!" | 0 {
172+
>narrowToNever : Symbol(narrowToNever, Decl(discriminantsAndTypePredicates.ts, 62, 1))
173+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23))
174+
>Type : Symbol(Type, Decl(discriminantsAndTypePredicates.ts, 42, 1))
175+
176+
if (data.Name === "TypeA") {
177+
>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
178+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23))
179+
>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
180+
181+
return data.Value1;
182+
>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
183+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23))
184+
>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
185+
}
186+
if (data.Name === "TypeB") {
187+
>data.Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17))
188+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23))
189+
>Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17))
190+
191+
return data.Value2;
192+
>data.Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18))
193+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23))
194+
>Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18))
195+
}
196+
return data;
197+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 64, 23))
198+
}
199+
200+
function narrowToNeverUnknown(data: unknown): "Cool stuff!" | 0 {
201+
>narrowToNeverUnknown : Symbol(narrowToNeverUnknown, Decl(discriminantsAndTypePredicates.ts, 72, 1))
202+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
203+
204+
if (isType(data)) {
205+
>isType : Symbol(isType, Decl(discriminantsAndTypePredicates.ts, 44, 26))
206+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
207+
208+
if (data.Name === "TypeA") {
209+
>data.Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
210+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
211+
>Name : Symbol(Name, Decl(discriminantsAndTypePredicates.ts, 34, 17), Decl(discriminantsAndTypePredicates.ts, 39, 17))
212+
213+
return data.Value1;
214+
>data.Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
215+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
216+
>Value1 : Symbol(TypeA.Value1, Decl(discriminantsAndTypePredicates.ts, 35, 18))
217+
}
218+
if (data.Name === "TypeB") {
219+
>data.Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17))
220+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
221+
>Name : Symbol(TypeB.Name, Decl(discriminantsAndTypePredicates.ts, 39, 17))
222+
223+
return data.Value2;
224+
>data.Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18))
225+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
226+
>Value2 : Symbol(TypeB.Value2, Decl(discriminantsAndTypePredicates.ts, 40, 18))
227+
}
228+
return data;
229+
>data : Symbol(data, Decl(discriminantsAndTypePredicates.ts, 74, 30))
230+
}
231+
}
232+

0 commit comments

Comments
 (0)