Skip to content

Commit e154af6

Browse files
Andaristsnovader
authored andcommitted
Narrow by comparisons to boolean literals (microsoft#53714)
1 parent 30414de commit e154af6

File tree

5 files changed

+701
-1
lines changed

5 files changed

+701
-1
lines changed

src/compiler/binder.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ import {
136136
isBlock,
137137
isBlockOrCatchScoped,
138138
IsBlockScopedContainer,
139+
isBooleanLiteral,
139140
isCallExpression,
140141
isClassStaticBlockDeclaration,
141142
isConditionalTypeNode,
@@ -1279,7 +1280,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
12791280
case SyntaxKind.EqualsEqualsEqualsToken:
12801281
case SyntaxKind.ExclamationEqualsEqualsToken:
12811282
return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) ||
1282-
isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right);
1283+
isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right) ||
1284+
(isBooleanLiteral(expr.right) && isNarrowingExpression(expr.left) || isBooleanLiteral(expr.left) && isNarrowingExpression(expr.right));
12831285
case SyntaxKind.InstanceOfKeyword:
12841286
return isNarrowableOperand(expr.left);
12851287
case SyntaxKind.InKeyword:

src/compiler/checker.ts

+13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
BigIntLiteral,
3535
BigIntLiteralType,
3636
BinaryExpression,
37+
BinaryOperator,
3738
BinaryOperatorToken,
3839
binarySearch,
3940
BindableObjectDefinePropertyCall,
@@ -44,6 +45,7 @@ import {
4445
BindingPattern,
4546
bindSourceFile,
4647
Block,
48+
BooleanLiteral,
4749
BreakOrContinueStatement,
4850
CallChain,
4951
CallExpression,
@@ -27661,6 +27663,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2766127663
return type;
2766227664
}
2766327665

27666+
function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type {
27667+
assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken);
27668+
return narrowType(type, expr, assumeTrue);
27669+
}
27670+
2766427671
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
2766527672
switch (expr.operatorToken.kind) {
2766627673
case SyntaxKind.EqualsToken:
@@ -27709,6 +27716,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2770927716
if (isMatchingConstructorReference(right)) {
2771027717
return narrowTypeByConstructor(type, operator, left, assumeTrue);
2771127718
}
27719+
if (isBooleanLiteral(right)) {
27720+
return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue);
27721+
}
27722+
if (isBooleanLiteral(left)) {
27723+
return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue);
27724+
}
2771227725
break;
2771327726
case SyntaxKind.InstanceOfKeyword:
2771427727
return narrowTypeByInstanceof(type, expr, assumeTrue);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
//// [tests/cases/compiler/narrowByBooleanComparison.ts] ////
2+
3+
=== narrowByBooleanComparison.ts ===
4+
type A = { type: "A" };
5+
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
6+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10))
7+
8+
type B = { type: "B" };
9+
>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23))
10+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 1, 10))
11+
12+
type C = { type: "C" };
13+
>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23))
14+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 2, 10))
15+
16+
type MyUnion = A | B | C;
17+
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
18+
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
19+
>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23))
20+
>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23))
21+
22+
const isA = (x: MyUnion): x is A => x.type === "A";
23+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
24+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
25+
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
26+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
27+
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
28+
>x.type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10))
29+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
30+
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10))
31+
32+
function test1(x: MyUnion) {
33+
>test1 : Symbol(test1, Decl(narrowByBooleanComparison.ts, 5, 51))
34+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
35+
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
36+
37+
if (isA(x) !== true) {
38+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
39+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
40+
41+
x;
42+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
43+
}
44+
45+
if (isA(x) !== false) {
46+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
47+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
48+
49+
x;
50+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
51+
}
52+
53+
if (isA(x) === false) {
54+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
55+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
56+
57+
x;
58+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
59+
}
60+
61+
if (isA(x) === true) {
62+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
63+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
64+
65+
x;
66+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
67+
}
68+
69+
if (isA(x) != true) {
70+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
71+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
72+
73+
x;
74+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
75+
}
76+
77+
if (isA(x) == true) {
78+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
79+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
80+
81+
x;
82+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
83+
}
84+
85+
if (true !== isA(x)) {
86+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
87+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
88+
89+
x;
90+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
91+
}
92+
93+
if (true === isA(x)) {
94+
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
95+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
96+
97+
x;
98+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
99+
}
100+
}
101+
102+
// https://github.com/microsoft/TypeScript/issues/53093
103+
function test2(x: unknown) {
104+
>test2 : Symbol(test2, Decl(narrowByBooleanComparison.ts, 39, 1))
105+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 42, 15))
106+
107+
if (x instanceof Error === false) {
108+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 42, 15))
109+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
110+
111+
return;
112+
}
113+
x;
114+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 42, 15))
115+
}
116+
117+
// https://github.com/microsoft/TypeScript/issues/50712
118+
function test3(foo: unknown) {
119+
>test3 : Symbol(test3, Decl(narrowByBooleanComparison.ts, 47, 1))
120+
>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15))
121+
122+
if (typeof foo !== 'string' && Array.isArray(foo) === false) {
123+
>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15))
124+
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
125+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
126+
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
127+
>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15))
128+
129+
throw new Error('Not a string or an array');
130+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
131+
}
132+
foo;
133+
>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15))
134+
}
135+
136+
// https://github.com/microsoft/TypeScript/issues/55395
137+
class WebError extends URIError {
138+
>WebError : Symbol(WebError, Decl(narrowByBooleanComparison.ts, 55, 1))
139+
>URIError : Symbol(URIError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
140+
141+
status?: number;
142+
>status : Symbol(WebError.status, Decl(narrowByBooleanComparison.ts, 58, 33))
143+
}
144+
function test4() {
145+
>test4 : Symbol(test4, Decl(narrowByBooleanComparison.ts, 60, 1))
146+
147+
try {
148+
// make a request
149+
} catch (err) {
150+
>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13))
151+
152+
if (err instanceof WebError === false || err.status != 401) {
153+
>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13))
154+
>WebError : Symbol(WebError, Decl(narrowByBooleanComparison.ts, 55, 1))
155+
>err.status : Symbol(WebError.status, Decl(narrowByBooleanComparison.ts, 58, 33))
156+
>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13))
157+
>status : Symbol(WebError.status, Decl(narrowByBooleanComparison.ts, 58, 33))
158+
159+
console.error(err);
160+
>console.error : Symbol(Console.error, Decl(lib.dom.d.ts, --, --))
161+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
162+
>error : Symbol(Console.error, Decl(lib.dom.d.ts, --, --))
163+
>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13))
164+
}
165+
}
166+
}
167+
168+
// https://github.com/microsoft/TypeScript/issues/44366
169+
interface Entity {
170+
>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1))
171+
172+
type: string;
173+
>type : Symbol(Entity.type, Decl(narrowByBooleanComparison.ts, 72, 18))
174+
}
175+
const ACTOR_TYPE = "actor";
176+
>ACTOR_TYPE : Symbol(ACTOR_TYPE, Decl(narrowByBooleanComparison.ts, 75, 5))
177+
178+
interface Actor extends Entity {
179+
>Actor : Symbol(Actor, Decl(narrowByBooleanComparison.ts, 75, 27))
180+
>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1))
181+
182+
type: typeof ACTOR_TYPE;
183+
>type : Symbol(Actor.type, Decl(narrowByBooleanComparison.ts, 76, 32))
184+
>ACTOR_TYPE : Symbol(ACTOR_TYPE, Decl(narrowByBooleanComparison.ts, 75, 5))
185+
}
186+
function isActor(entity: Entity): entity is Actor {
187+
>isActor : Symbol(isActor, Decl(narrowByBooleanComparison.ts, 78, 1))
188+
>entity : Symbol(entity, Decl(narrowByBooleanComparison.ts, 79, 17))
189+
>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1))
190+
>entity : Symbol(entity, Decl(narrowByBooleanComparison.ts, 79, 17))
191+
>Actor : Symbol(Actor, Decl(narrowByBooleanComparison.ts, 75, 27))
192+
193+
return entity.type === ACTOR_TYPE;
194+
>entity.type : Symbol(Entity.type, Decl(narrowByBooleanComparison.ts, 72, 18))
195+
>entity : Symbol(entity, Decl(narrowByBooleanComparison.ts, 79, 17))
196+
>type : Symbol(Entity.type, Decl(narrowByBooleanComparison.ts, 72, 18))
197+
>ACTOR_TYPE : Symbol(ACTOR_TYPE, Decl(narrowByBooleanComparison.ts, 75, 5))
198+
}
199+
function test5(bin: Entity) {
200+
>test5 : Symbol(test5, Decl(narrowByBooleanComparison.ts, 81, 1))
201+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15))
202+
>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1))
203+
204+
if (isActor(bin) === false) {
205+
>isActor : Symbol(isActor, Decl(narrowByBooleanComparison.ts, 78, 1))
206+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15))
207+
208+
bin;
209+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15))
210+
211+
} else {
212+
bin;
213+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15))
214+
}
215+
}
216+
function test6(bin: Entity) {
217+
>test6 : Symbol(test6, Decl(narrowByBooleanComparison.ts, 88, 1))
218+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15))
219+
>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1))
220+
221+
if (isActor(bin) == false) {
222+
>isActor : Symbol(isActor, Decl(narrowByBooleanComparison.ts, 78, 1))
223+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15))
224+
225+
bin;
226+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15))
227+
228+
} else {
229+
bin;
230+
>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15))
231+
}
232+
}
233+
234+
// https://github.com/microsoft/TypeScript/issues/53005
235+
function isFunction(x: unknown): x is Function {
236+
>isFunction : Symbol(isFunction, Decl(narrowByBooleanComparison.ts, 95, 1))
237+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 98, 20))
238+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 98, 20))
239+
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
240+
241+
return typeof x === "function";
242+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 98, 20))
243+
}
244+
245+
function test7(x: unknown) {
246+
>test7 : Symbol(test7, Decl(narrowByBooleanComparison.ts, 100, 1))
247+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15))
248+
249+
if (isFunction(x) !== false) {
250+
>isFunction : Symbol(isFunction, Decl(narrowByBooleanComparison.ts, 95, 1))
251+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15))
252+
253+
x;
254+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15))
255+
}
256+
if (isFunction(x) === true) {
257+
>isFunction : Symbol(isFunction, Decl(narrowByBooleanComparison.ts, 95, 1))
258+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15))
259+
260+
x;
261+
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15))
262+
}
263+
}
264+

0 commit comments

Comments
 (0)