Skip to content

Commit 8b09312

Browse files
authored
Merge pull request #9036 from Microsoft/primitive-type-guards-are-order-independent
Primitive type guards are order independent
2 parents 6f63799 + 86a6959 commit 8b09312

10 files changed

+423
-14
lines changed

src/compiler/binder.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -610,10 +610,11 @@ namespace ts {
610610
case SyntaxKind.ExclamationEqualsToken:
611611
case SyntaxKind.EqualsEqualsEqualsToken:
612612
case SyntaxKind.ExclamationEqualsEqualsToken:
613-
if (isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) {
613+
if ((isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) ||
614+
(isNarrowingExpression(expr.right) && (expr.left.kind === SyntaxKind.NullKeyword || expr.left.kind === SyntaxKind.Identifier))) {
614615
return true;
615616
}
616-
if (expr.left.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) {
617+
if (isTypeOfNarrowingBinaryExpression(expr)) {
617618
return true;
618619
}
619620
return false;
@@ -625,6 +626,20 @@ namespace ts {
625626
return false;
626627
}
627628

629+
function isTypeOfNarrowingBinaryExpression(expr: BinaryExpression) {
630+
let typeOf: Expression;
631+
if (expr.left.kind === SyntaxKind.StringLiteral) {
632+
typeOf = expr.right;
633+
}
634+
else if (expr.right.kind === SyntaxKind.StringLiteral) {
635+
typeOf = expr.left;
636+
}
637+
else {
638+
typeOf = undefined;
639+
}
640+
return typeOf && typeOf.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>typeOf).expression);
641+
}
642+
628643
function createBranchLabel(): FlowLabel {
629644
return {
630645
flags: FlowFlags.BranchLabel,

src/compiler/checker.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -7895,10 +7895,11 @@ namespace ts {
78957895
case SyntaxKind.ExclamationEqualsToken:
78967896
case SyntaxKind.EqualsEqualsEqualsToken:
78977897
case SyntaxKind.ExclamationEqualsEqualsToken:
7898-
if (isNullOrUndefinedLiteral(expr.right)) {
7898+
if (isNullOrUndefinedLiteral(expr.left) || isNullOrUndefinedLiteral(expr.right)) {
78997899
return narrowTypeByNullCheck(type, expr, assumeTrue);
79007900
}
7901-
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) {
7901+
if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral ||
7902+
expr.left.kind === SyntaxKind.StringLiteral && expr.right.kind === SyntaxKind.TypeOfExpression) {
79027903
return narrowTypeByTypeof(type, expr, assumeTrue);
79037904
}
79047905
break;
@@ -7911,18 +7912,20 @@ namespace ts {
79117912
}
79127913

79137914
function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
7914-
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on the right
7915+
// We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on one side
79157916
const operator = expr.operatorToken.kind;
7917+
const nullLike = isNullOrUndefinedLiteral(expr.left) ? expr.left : expr.right;
7918+
const narrowed = isNullOrUndefinedLiteral(expr.left) ? expr.right : expr.left;
79167919
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
79177920
assumeTrue = !assumeTrue;
79187921
}
7919-
if (!strictNullChecks || !isMatchingReference(reference, getReferenceFromExpression(expr.left))) {
7922+
if (!strictNullChecks || !isMatchingReference(reference, getReferenceFromExpression(narrowed))) {
79207923
return type;
79217924
}
79227925
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
79237926
const facts = doubleEquals ?
79247927
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
7925-
expr.right.kind === SyntaxKind.NullKeyword ?
7928+
nullLike.kind === SyntaxKind.NullKeyword ?
79267929
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
79277930
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
79287931
return getTypeWithFacts(type, facts);
@@ -7931,12 +7934,12 @@ namespace ts {
79317934
function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
79327935
// We have '==', '!=', '====', or !==' operator with 'typeof xxx' on the left
79337936
// and string literal on the right
7934-
const left = getReferenceFromExpression((<TypeOfExpression>expr.left).expression);
7935-
const right = <LiteralExpression>expr.right;
7936-
if (!isMatchingReference(reference, left)) {
7937+
const narrowed = getReferenceFromExpression((<TypeOfExpression>(expr.left.kind === SyntaxKind.TypeOfExpression ? expr.left : expr.right)).expression);
7938+
const literal = <LiteralExpression>(expr.right.kind === SyntaxKind.StringLiteral ? expr.right : expr.left);
7939+
if (!isMatchingReference(reference, narrowed)) {
79377940
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
79387941
// narrowed type of 'y' to its declared type.
7939-
if (containsMatchingReference(reference, left)) {
7942+
if (containsMatchingReference(reference, narrowed)) {
79407943
return declaredType;
79417944
}
79427945
return type;
@@ -7949,14 +7952,14 @@ namespace ts {
79497952
// We narrow a non-union type to an exact primitive type if the non-union type
79507953
// is a supertype of that primtive type. For example, type 'any' can be narrowed
79517954
// to one of the primitive types.
7952-
const targetType = getProperty(typeofTypesByName, right.text);
7955+
const targetType = getProperty(typeofTypesByName, literal.text);
79537956
if (targetType && isTypeSubtypeOf(targetType, type)) {
79547957
return targetType;
79557958
}
79567959
}
79577960
const facts = assumeTrue ?
7958-
getProperty(typeofEQFacts, right.text) || TypeFacts.TypeofEQHostObject :
7959-
getProperty(typeofNEFacts, right.text) || TypeFacts.TypeofNEHostObject;
7961+
getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject :
7962+
getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject;
79607963
return getTypeWithFacts(type, facts);
79617964
}
79627965

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+
}
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+
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//// [typeGuardOfFormTypeOfIsOrderIndependent.ts]
2+
var strOrNum: string | number;
3+
var strOrBool: string | boolean;
4+
var strOrFunc: string | (() => void);
5+
var numOrBool: number | boolean
6+
var str: string;
7+
var num: number;
8+
var bool: boolean;
9+
var func: () => void;
10+
11+
if ("string" === typeof strOrNum) {
12+
str = strOrNum;
13+
}
14+
else {
15+
num = strOrNum;
16+
}
17+
if ("function" === typeof strOrFunc) {
18+
func = strOrFunc;
19+
}
20+
else {
21+
str = strOrFunc;
22+
}
23+
if ("number" === typeof numOrBool) {
24+
num = numOrBool;
25+
}
26+
else {
27+
bool = numOrBool;
28+
}
29+
if ("boolean" === typeof strOrBool) {
30+
bool = strOrBool;
31+
}
32+
else {
33+
str = strOrBool;
34+
}
35+
36+
37+
//// [typeGuardOfFormTypeOfIsOrderIndependent.js]
38+
var strOrNum;
39+
var strOrBool;
40+
var strOrFunc;
41+
var numOrBool;
42+
var str;
43+
var num;
44+
var bool;
45+
var func;
46+
if ("string" === typeof strOrNum) {
47+
str = strOrNum;
48+
}
49+
else {
50+
num = strOrNum;
51+
}
52+
if ("function" === typeof strOrFunc) {
53+
func = strOrFunc;
54+
}
55+
else {
56+
str = strOrFunc;
57+
}
58+
if ("number" === typeof numOrBool) {
59+
num = numOrBool;
60+
}
61+
else {
62+
bool = numOrBool;
63+
}
64+
if ("boolean" === typeof strOrBool) {
65+
bool = strOrBool;
66+
}
67+
else {
68+
str = strOrBool;
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfIsOrderIndependent.ts ===
2+
var strOrNum: string | number;
3+
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
4+
5+
var strOrBool: string | boolean;
6+
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
7+
8+
var strOrFunc: string | (() => void);
9+
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
10+
11+
var numOrBool: number | boolean
12+
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
13+
14+
var str: string;
15+
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
16+
17+
var num: number;
18+
>num : Symbol(num, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 5, 3))
19+
20+
var bool: boolean;
21+
>bool : Symbol(bool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 6, 3))
22+
23+
var func: () => void;
24+
>func : Symbol(func, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 7, 3))
25+
26+
if ("string" === typeof strOrNum) {
27+
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
28+
29+
str = strOrNum;
30+
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
31+
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
32+
}
33+
else {
34+
num = strOrNum;
35+
>num : Symbol(num, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 5, 3))
36+
>strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 0, 3))
37+
}
38+
if ("function" === typeof strOrFunc) {
39+
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
40+
41+
func = strOrFunc;
42+
>func : Symbol(func, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 7, 3))
43+
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
44+
}
45+
else {
46+
str = strOrFunc;
47+
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
48+
>strOrFunc : Symbol(strOrFunc, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 2, 3))
49+
}
50+
if ("number" === typeof numOrBool) {
51+
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
52+
53+
num = numOrBool;
54+
>num : Symbol(num, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 5, 3))
55+
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
56+
}
57+
else {
58+
bool = numOrBool;
59+
>bool : Symbol(bool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 6, 3))
60+
>numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 3, 3))
61+
}
62+
if ("boolean" === typeof strOrBool) {
63+
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
64+
65+
bool = strOrBool;
66+
>bool : Symbol(bool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 6, 3))
67+
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
68+
}
69+
else {
70+
str = strOrBool;
71+
>str : Symbol(str, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 4, 3))
72+
>strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfIsOrderIndependent.ts, 1, 3))
73+
}
74+

0 commit comments

Comments
 (0)