Skip to content

Commit e9a78f2

Browse files
committed
Narrow by comparisons to boolean literals
1 parent 68d8be4 commit e9a78f2

File tree

5 files changed

+277
-1
lines changed

5 files changed

+277
-1
lines changed

src/compiler/binder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ import {
134134
isBlock,
135135
isBlockOrCatchScoped,
136136
IsBlockScopedContainer,
137+
isBooleanLiteral,
137138
isCallExpression,
138139
isClassStaticBlockDeclaration,
139140
isConditionalTypeNode,
@@ -1260,7 +1261,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
12601261
case SyntaxKind.EqualsEqualsEqualsToken:
12611262
case SyntaxKind.ExclamationEqualsEqualsToken:
12621263
return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) ||
1263-
isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right);
1264+
isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right) ||
1265+
(isBooleanLiteral(expr.right) && isNarrowingExpression(expr.left) || isBooleanLiteral(expr.left) && isNarrowingExpression(expr.right));
12641266
case SyntaxKind.InstanceOfKeyword:
12651267
return isNarrowableOperand(expr.left);
12661268
case SyntaxKind.InKeyword:
@@ -1411,6 +1413,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
14111413
currentFalseTarget = savedFalseTarget;
14121414
}
14131415

1416+
// function is
1417+
14141418
function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
14151419
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
14161420
if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {

src/compiler/checker.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
BigIntLiteral,
3434
BigIntLiteralType,
3535
BinaryExpression,
36+
BinaryOperator,
3637
BinaryOperatorToken,
3738
binarySearch,
3839
BindableObjectDefinePropertyCall,
@@ -43,6 +44,7 @@ import {
4344
BindingPattern,
4445
bindSourceFile,
4546
Block,
47+
BooleanLiteral,
4648
BreakOrContinueStatement,
4749
CallChain,
4850
CallExpression,
@@ -26992,6 +26994,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2699226994
return type;
2699326995
}
2699426996

26997+
function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type {
26998+
assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken);
26999+
return narrowType(type, expr, assumeTrue);
27000+
}
27001+
2699527002
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
2699627003
switch (expr.operatorToken.kind) {
2699727004
case SyntaxKind.EqualsToken:
@@ -27012,6 +27019,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2701227019
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
2701327020
return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
2701427021
}
27022+
if (isBooleanLiteral(right)) {
27023+
return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue);
27024+
}
27025+
if (isBooleanLiteral(left)) {
27026+
return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue);
27027+
}
2701527028
if (isMatchingReference(reference, left)) {
2701627029
return narrowTypeByEquality(type, operator, right, assumeTrue);
2701727030
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
=== tests/cases/compiler/narrowByBooleanComparison.ts ===
2+
type A = { type: "A" };
3+
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
4+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10))
5+
6+
type B = { type: "B" };
7+
>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23))
8+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 1, 10))
9+
10+
type C = { type: "C" };
11+
>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23))
12+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 2, 10))
13+
14+
type MyUnion = A | B | C;
15+
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
16+
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
17+
>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23))
18+
>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23))
19+
20+
const isA = (x: MyUnion): x is A => x.type === "A";
21+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
22+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
23+
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
24+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
25+
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
26+
>x.type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10))
27+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
28+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10))
29+
30+
function test1(x: MyUnion) {
31+
>test1 : Symbol(test1, Decl(narrowByBooleanComparison.ts, 5, 51))
32+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
33+
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
34+
35+
if (isA(x) !== true) {
36+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
37+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
38+
39+
x
40+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
41+
}
42+
43+
if (isA(x) !== false) {
44+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
45+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
46+
47+
x
48+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
49+
}
50+
51+
if (isA(x) === false) {
52+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
53+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
54+
55+
x
56+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
57+
}
58+
59+
if (isA(x) === true) {
60+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
61+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
62+
63+
x
64+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
65+
}
66+
67+
if (isA(x) != true) {
68+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
69+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
70+
71+
x
72+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
73+
}
74+
75+
if (isA(x) == true) {
76+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
77+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
78+
79+
x
80+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
81+
}
82+
83+
if (true !== isA(x)) {
84+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
85+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
86+
87+
x
88+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
89+
}
90+
91+
if (true === isA(x)) {
92+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
93+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
94+
95+
x
96+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
97+
}
98+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
=== tests/cases/compiler/narrowByBooleanComparison.ts ===
2+
type A = { type: "A" };
3+
>A : { type: "A"; }
4+
>type : "A"
5+
6+
type B = { type: "B" };
7+
>B : { type: "B"; }
8+
>type : "B"
9+
10+
type C = { type: "C" };
11+
>C : { type: "C"; }
12+
>type : "C"
13+
14+
type MyUnion = A | B | C;
15+
>MyUnion : A | B | C
16+
17+
const isA = (x: MyUnion): x is A => x.type === "A";
18+
>isA : (x: MyUnion) => x is A
19+
>(x: MyUnion): x is A => x.type === "A" : (x: MyUnion) => x is A
20+
>x : MyUnion
21+
>x.type === "A" : boolean
22+
>x.type : "A" | "B" | "C"
23+
>x : MyUnion
24+
>type : "A" | "B" | "C"
25+
>"A" : "A"
26+
27+
function test1(x: MyUnion) {
28+
>test1 : (x: MyUnion) => void
29+
>x : MyUnion
30+
31+
if (isA(x) !== true) {
32+
>isA(x) !== true : boolean
33+
>isA(x) : boolean
34+
>isA : (x: MyUnion) => x is A
35+
>x : MyUnion
36+
>true : true
37+
38+
x
39+
>x : B | C
40+
}
41+
42+
if (isA(x) !== false) {
43+
>isA(x) !== false : boolean
44+
>isA(x) : boolean
45+
>isA : (x: MyUnion) => x is A
46+
>x : MyUnion
47+
>false : false
48+
49+
x
50+
>x : A
51+
}
52+
53+
if (isA(x) === false) {
54+
>isA(x) === false : boolean
55+
>isA(x) : boolean
56+
>isA : (x: MyUnion) => x is A
57+
>x : MyUnion
58+
>false : false
59+
60+
x
61+
>x : B | C
62+
}
63+
64+
if (isA(x) === true) {
65+
>isA(x) === true : boolean
66+
>isA(x) : boolean
67+
>isA : (x: MyUnion) => x is A
68+
>x : MyUnion
69+
>true : true
70+
71+
x
72+
>x : A
73+
}
74+
75+
if (isA(x) != true) {
76+
>isA(x) != true : boolean
77+
>isA(x) : boolean
78+
>isA : (x: MyUnion) => x is A
79+
>x : MyUnion
80+
>true : true
81+
82+
x
83+
>x : B | C
84+
}
85+
86+
if (isA(x) == true) {
87+
>isA(x) == true : boolean
88+
>isA(x) : boolean
89+
>isA : (x: MyUnion) => x is A
90+
>x : MyUnion
91+
>true : true
92+
93+
x
94+
>x : A
95+
}
96+
97+
if (true !== isA(x)) {
98+
>true !== isA(x) : boolean
99+
>true : true
100+
>isA(x) : boolean
101+
>isA : (x: MyUnion) => x is A
102+
>x : MyUnion
103+
104+
x
105+
>x : B | C
106+
}
107+
108+
if (true === isA(x)) {
109+
>true === isA(x) : boolean
110+
>true : true
111+
>isA(x) : boolean
112+
>isA : (x: MyUnion) => x is A
113+
>x : MyUnion
114+
115+
x
116+
>x : A
117+
}
118+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type A = { type: "A" };
5+
type B = { type: "B" };
6+
type C = { type: "C" };
7+
type MyUnion = A | B | C;
8+
9+
const isA = (x: MyUnion): x is A => x.type === "A";
10+
11+
function test1(x: MyUnion) {
12+
if (isA(x) !== true) {
13+
x
14+
}
15+
16+
if (isA(x) !== false) {
17+
x
18+
}
19+
20+
if (isA(x) === false) {
21+
x
22+
}
23+
24+
if (isA(x) === true) {
25+
x
26+
}
27+
28+
if (isA(x) != true) {
29+
x
30+
}
31+
32+
if (isA(x) == true) {
33+
x
34+
}
35+
36+
if (true !== isA(x)) {
37+
x
38+
}
39+
40+
if (true === isA(x)) {
41+
x
42+
}
43+
}

0 commit comments

Comments
 (0)