Skip to content

Commit a9797d2

Browse files
authored
fix(50340): typeof ... === "undefined" check on discriminated union of undefined and object type doesn't narrow correctly (#50344)
* fix(50340): narrow type by discriminant in typeof * add additional test cases
1 parent 43f8ae6 commit a9797d2

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25328,11 +25328,19 @@ namespace ts {
2532825328
}
2532925329
const target = getReferenceCandidate(typeOfExpr.expression);
2533025330
if (!isMatchingReference(reference, target)) {
25331+
const propertyAccess = getDiscriminantPropertyAccess(typeOfExpr.expression, type);
25332+
if (propertyAccess) {
25333+
return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue));
25334+
}
2533125335
if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) {
2533225336
return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2533325337
}
2533425338
return type;
2533525339
}
25340+
return narrowTypeByLiteralExpression(type, literal, assumeTrue);
25341+
}
25342+
25343+
function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) {
2533625344
return assumeTrue ?
2533725345
narrowTypeByTypeName(type, literal.text) :
2533825346
getTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//// [narrowingTypeofUndefined.ts]
2+
declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } }
3+
4+
if (typeof a.error === 'undefined') {
5+
a.result.prop; // number
6+
}
7+
else {
8+
a.error.prop; // string
9+
}
10+
11+
if (typeof a.error !== 'undefined') {
12+
a.error.prop; // string
13+
}
14+
else {
15+
a.result.prop; // number
16+
}
17+
18+
19+
//// [narrowingTypeofUndefined.js]
20+
if (typeof a.error === 'undefined') {
21+
a.result.prop; // number
22+
}
23+
else {
24+
a.error.prop; // string
25+
}
26+
if (typeof a.error !== 'undefined') {
27+
a.error.prop; // string
28+
}
29+
else {
30+
a.result.prop; // number
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/narrowingTypeofUndefined.ts ===
2+
declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } }
3+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
4+
>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18))
5+
>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27))
6+
>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43))
7+
>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 67))
8+
>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 85))
9+
>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95))
10+
11+
if (typeof a.error === 'undefined') {
12+
>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
13+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
14+
>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
15+
16+
a.result.prop; // number
17+
>a.result.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95))
18+
>a.result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85))
19+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
20+
>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85))
21+
>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95))
22+
}
23+
else {
24+
a.error.prop; // string
25+
>a.error.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27))
26+
>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
27+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
28+
>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
29+
>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27))
30+
}
31+
32+
if (typeof a.error !== 'undefined') {
33+
>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
34+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
35+
>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
36+
37+
a.error.prop; // string
38+
>a.error.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27))
39+
>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
40+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
41+
>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67))
42+
>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27))
43+
}
44+
else {
45+
a.result.prop; // number
46+
>a.result.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95))
47+
>a.result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85))
48+
>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13))
49+
>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85))
50+
>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95))
51+
}
52+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
=== tests/cases/compiler/narrowingTypeofUndefined.ts ===
2+
declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } }
3+
>a : { error: { prop: string;}; result: undefined; } | { error: undefined; result: { prop: number;}; }
4+
>error : { prop: string; }
5+
>prop : string
6+
>result : undefined
7+
>error : undefined
8+
>result : { prop: number; }
9+
>prop : number
10+
11+
if (typeof a.error === 'undefined') {
12+
>typeof a.error === 'undefined' : boolean
13+
>typeof a.error : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
14+
>a.error : { prop: string; }
15+
>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; }
16+
>error : { prop: string; }
17+
>'undefined' : "undefined"
18+
19+
a.result.prop; // number
20+
>a.result.prop : number
21+
>a.result : { prop: number; }
22+
>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; }
23+
>result : { prop: number; }
24+
>prop : number
25+
}
26+
else {
27+
a.error.prop; // string
28+
>a.error.prop : string
29+
>a.error : { prop: string; }
30+
>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; }
31+
>error : { prop: string; }
32+
>prop : string
33+
}
34+
35+
if (typeof a.error !== 'undefined') {
36+
>typeof a.error !== 'undefined' : boolean
37+
>typeof a.error : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
38+
>a.error : { prop: string; }
39+
>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; }
40+
>error : { prop: string; }
41+
>'undefined' : "undefined"
42+
43+
a.error.prop; // string
44+
>a.error.prop : string
45+
>a.error : { prop: string; }
46+
>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; }
47+
>error : { prop: string; }
48+
>prop : string
49+
}
50+
else {
51+
a.result.prop; // number
52+
>a.result.prop : number
53+
>a.result : { prop: number; }
54+
>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; }
55+
>result : { prop: number; }
56+
>prop : number
57+
}
58+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } }
2+
3+
if (typeof a.error === 'undefined') {
4+
a.result.prop; // number
5+
}
6+
else {
7+
a.error.prop; // string
8+
}
9+
10+
if (typeof a.error !== 'undefined') {
11+
a.error.prop; // string
12+
}
13+
else {
14+
a.result.prop; // number
15+
}

0 commit comments

Comments
 (0)