Skip to content

Commit a1e4b31

Browse files
committed
Allow null/undefined guard with null/undefined on left
Also add a test with baselines.
1 parent 11377f9 commit a1e4b31

10 files changed

+130
-11
lines changed

src/compiler/binder.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,8 @@ namespace ts {
609609
case SyntaxKind.ExclamationEqualsToken:
610610
case SyntaxKind.EqualsEqualsEqualsToken:
611611
case SyntaxKind.ExclamationEqualsEqualsToken:
612-
if (isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) {
612+
if ((isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) ||
613+
(isNarrowingExpression(expr.right) && (expr.left.kind === SyntaxKind.NullKeyword || expr.left.kind === SyntaxKind.Identifier))) {
613614
return true;
614615
}
615616
if (isTypeOfNarrowingBinaryExpression(expr)) {
@@ -633,7 +634,7 @@ namespace ts {
633634
typeOf = expr.left;
634635
}
635636
else {
636-
typeOf = undefined;;
637+
typeOf = undefined;
637638
}
638639
return typeOf && typeOf.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>typeOf).expression);
639640
}

src/compiler/checker.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7861,7 +7861,7 @@ namespace ts {
78617861
case SyntaxKind.ExclamationEqualsToken:
78627862
case SyntaxKind.EqualsEqualsEqualsToken:
78637863
case SyntaxKind.ExclamationEqualsEqualsToken:
7864-
if (isNullOrUndefinedLiteral(expr.right)) {
7864+
if (isNullOrUndefinedLiteral(expr.left) || isNullOrUndefinedLiteral(expr.right)) {
78657865
return narrowTypeByNullCheck(type, expr, assumeTrue);
78667866
}
78677867
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral ||
@@ -7878,18 +7878,20 @@ namespace ts {
78787878
}
78797879

78807880
function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
7881-
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on the right
7881+
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on one side
78827882
const operator = expr.operatorToken.kind;
7883+
const nullLike = isNullOrUndefinedLiteral(expr.left) ? expr.left : expr.right;
7884+
const narrowed = isNullOrUndefinedLiteral(expr.left) ? expr.right : expr.left;
78837885
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
78847886
assumeTrue = !assumeTrue;
78857887
}
7886-
if (!strictNullChecks || !isMatchingReference(reference, expr.left)) {
7888+
if (!strictNullChecks || !isMatchingReference(reference, narrowed)) {
78877889
return type;
78887890
}
78897891
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
78907892
const facts = doubleEquals ?
78917893
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
7892-
expr.right.kind === SyntaxKind.NullKeyword ?
7894+
nullLike.kind === SyntaxKind.NullKeyword ?
78937895
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
78947896
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
78957897
return getTypeWithFacts(type, facts);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//// [nullOrUndefinedTypeGuardIsOrderIndependent.ts]
2+
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
3+
var str: string = "original";
4+
var nil: null;
5+
if (null === strOrNull) {
6+
nil = strOrNull;
7+
}
8+
else {
9+
str = strOrNull;
10+
}
11+
if (undefined !== strOrUndefined) {
12+
str = strOrUndefined;
13+
}
14+
}
15+
16+
17+
//// [nullOrUndefinedTypeGuardIsOrderIndependent.js]
18+
function test(strOrNull, strOrUndefined) {
19+
var str = "original";
20+
var nil;
21+
if (null === strOrNull) {
22+
nil = strOrNull;
23+
}
24+
else {
25+
str = strOrNull;
26+
}
27+
if (undefined !== strOrUndefined) {
28+
str = strOrUndefined;
29+
}
30+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts ===
2+
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
3+
>test : Symbol(test, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 0))
4+
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
5+
>strOrUndefined : Symbol(strOrUndefined, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 39))
6+
7+
var str: string = "original";
8+
>str : Symbol(str, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 1, 7))
9+
10+
var nil: null;
11+
>nil : Symbol(nil, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 2, 7))
12+
13+
if (null === strOrNull) {
14+
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
15+
16+
nil = strOrNull;
17+
>nil : Symbol(nil, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 2, 7))
18+
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
19+
}
20+
else {
21+
str = strOrNull;
22+
>str : Symbol(str, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 1, 7))
23+
>strOrNull : Symbol(strOrNull, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 14))
24+
}
25+
if (undefined !== strOrUndefined) {
26+
>undefined : Symbol(undefined)
27+
>strOrUndefined : Symbol(strOrUndefined, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 39))
28+
29+
str = strOrUndefined;
30+
>str : Symbol(str, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 1, 7))
31+
>strOrUndefined : Symbol(strOrUndefined, Decl(nullOrUndefinedTypeGuardIsOrderIndependent.ts, 0, 39))
32+
}
33+
}
34+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/expressions/typeGuards/nullOrUndefinedTypeGuardIsOrderIndependent.ts ===
2+
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
3+
>test : (strOrNull: string | null, strOrUndefined: string | undefined) => void
4+
>strOrNull : string | null
5+
>null : null
6+
>strOrUndefined : string | undefined
7+
8+
var str: string = "original";
9+
>str : string
10+
>"original" : string
11+
12+
var nil: null;
13+
>nil : null
14+
>null : null
15+
16+
if (null === strOrNull) {
17+
>null === strOrNull : boolean
18+
>null : null
19+
>strOrNull : string | null
20+
21+
nil = strOrNull;
22+
>nil = strOrNull : null
23+
>nil : null
24+
>strOrNull : null
25+
}
26+
else {
27+
str = strOrNull;
28+
>str = strOrNull : string
29+
>str : string
30+
>strOrNull : string
31+
}
32+
if (undefined !== strOrUndefined) {
33+
>undefined !== strOrUndefined : boolean
34+
>undefined : undefined
35+
>strOrUndefined : string | undefined
36+
37+
str = strOrUndefined;
38+
>str = strOrUndefined : string
39+
>str : string
40+
>strOrUndefined : string
41+
}
42+
}
43+

tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ var bool: boolean;
99
var func: () => void;
1010

1111
if ("string" === typeof strOrNum) {
12-
// if (typeof strOrNum === "string") {
1312
str = strOrNum;
1413
}
1514
else {
@@ -45,7 +44,6 @@ var num;
4544
var bool;
4645
var func;
4746
if ("string" === typeof strOrNum) {
48-
// if (typeof strOrNum === "string") {
4947
str = strOrNum;
5048
}
5149
else {

tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.symbols

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ var func: () => void;
2626
if ("string" === typeof strOrNum) {
2727
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
2828

29-
// if (typeof strOrNum === "string") {
3029
str = strOrNum;
3130
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
3231
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))

tests/baselines/reference/typeGuardOfFormTypeOfIsOrderIndependent.types

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ if ("string" === typeof strOrNum) {
2929
>typeof strOrNum : string
3030
>strOrNum : string | number
3131

32-
// if (typeof strOrNum === "string") {
3332
str = strOrNum;
3433
>str = strOrNum : string
3534
>str : string
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strictNullChecks: true
2+
function test(strOrNull: string | null, strOrUndefined: string | undefined) {
3+
var str: string = "original";
4+
var nil: null;
5+
if (null === strOrNull) {
6+
nil = strOrNull;
7+
}
8+
else {
9+
str = strOrNull;
10+
}
11+
if (undefined !== strOrUndefined) {
12+
str = strOrUndefined;
13+
}
14+
}

tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ var bool: boolean;
88
var func: () => void;
99

1010
if ("string" === typeof strOrNum) {
11-
// if (typeof strOrNum === "string") {
1211
str = strOrNum;
1312
}
1413
else {

0 commit comments

Comments
 (0)