Skip to content

Commit

Permalink
Narrow by comparisons to boolean literals
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Apr 9, 2023
1 parent 68d8be4 commit e9a78f2
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ import {
isBlock,
isBlockOrCatchScoped,
IsBlockScopedContainer,
isBooleanLiteral,
isCallExpression,
isClassStaticBlockDeclaration,
isConditionalTypeNode,
Expand Down Expand Up @@ -1260,7 +1261,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) ||
isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right);
isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right) ||
(isBooleanLiteral(expr.right) && isNarrowingExpression(expr.left) || isBooleanLiteral(expr.left) && isNarrowingExpression(expr.right));
case SyntaxKind.InstanceOfKeyword:
return isNarrowableOperand(expr.left);
case SyntaxKind.InKeyword:
Expand Down Expand Up @@ -1411,6 +1413,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
currentFalseTarget = savedFalseTarget;
}

// function is

function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
Expand Down
13 changes: 13 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
BigIntLiteral,
BigIntLiteralType,
BinaryExpression,
BinaryOperator,
BinaryOperatorToken,
binarySearch,
BindableObjectDefinePropertyCall,
Expand All @@ -43,6 +44,7 @@ import {
BindingPattern,
bindSourceFile,
Block,
BooleanLiteral,
BreakOrContinueStatement,
CallChain,
CallExpression,
Expand Down Expand Up @@ -26992,6 +26994,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type;
}

function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type {
assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken);
return narrowType(type, expr, assumeTrue);
}

function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
switch (expr.operatorToken.kind) {
case SyntaxKind.EqualsToken:
Expand All @@ -27012,6 +27019,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
}
if (isBooleanLiteral(right)) {
return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue);
}
if (isBooleanLiteral(left)) {
return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue);
}
if (isMatchingReference(reference, left)) {
return narrowTypeByEquality(type, operator, right, assumeTrue);
}
Expand Down
98 changes: 98 additions & 0 deletions tests/baselines/reference/narrowByBooleanComparison.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
=== tests/cases/compiler/narrowByBooleanComparison.ts ===
type A = { type: "A" };
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10))

type B = { type: "B" };
>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23))
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 1, 10))

type C = { type: "C" };
>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23))
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 2, 10))

type MyUnion = A | B | C;
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23))
>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23))

const isA = (x: MyUnion): x is A => x.type === "A";
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0))
>x.type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13))
>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10))

function test1(x: MyUnion) {
>test1 : Symbol(test1, Decl(narrowByBooleanComparison.ts, 5, 51))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23))

if (isA(x) !== true) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (isA(x) !== false) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (isA(x) === false) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (isA(x) === true) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (isA(x) != true) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (isA(x) == true) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (true !== isA(x)) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}

if (true === isA(x)) {
>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5))
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))

x
>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15))
}
}
118 changes: 118 additions & 0 deletions tests/baselines/reference/narrowByBooleanComparison.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
=== tests/cases/compiler/narrowByBooleanComparison.ts ===
type A = { type: "A" };
>A : { type: "A"; }
>type : "A"

type B = { type: "B" };
>B : { type: "B"; }
>type : "B"

type C = { type: "C" };
>C : { type: "C"; }
>type : "C"

type MyUnion = A | B | C;
>MyUnion : A | B | C

const isA = (x: MyUnion): x is A => x.type === "A";
>isA : (x: MyUnion) => x is A
>(x: MyUnion): x is A => x.type === "A" : (x: MyUnion) => x is A
>x : MyUnion
>x.type === "A" : boolean
>x.type : "A" | "B" | "C"
>x : MyUnion
>type : "A" | "B" | "C"
>"A" : "A"

function test1(x: MyUnion) {
>test1 : (x: MyUnion) => void
>x : MyUnion

if (isA(x) !== true) {
>isA(x) !== true : boolean
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion
>true : true

x
>x : B | C
}

if (isA(x) !== false) {
>isA(x) !== false : boolean
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion
>false : false

x
>x : A
}

if (isA(x) === false) {
>isA(x) === false : boolean
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion
>false : false

x
>x : B | C
}

if (isA(x) === true) {
>isA(x) === true : boolean
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion
>true : true

x
>x : A
}

if (isA(x) != true) {
>isA(x) != true : boolean
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion
>true : true

x
>x : B | C
}

if (isA(x) == true) {
>isA(x) == true : boolean
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion
>true : true

x
>x : A
}

if (true !== isA(x)) {
>true !== isA(x) : boolean
>true : true
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion

x
>x : B | C
}

if (true === isA(x)) {
>true === isA(x) : boolean
>true : true
>isA(x) : boolean
>isA : (x: MyUnion) => x is A
>x : MyUnion

x
>x : A
}
}
43 changes: 43 additions & 0 deletions tests/cases/compiler/narrowByBooleanComparison.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @strict: true
// @noEmit: true

type A = { type: "A" };
type B = { type: "B" };
type C = { type: "C" };
type MyUnion = A | B | C;

const isA = (x: MyUnion): x is A => x.type === "A";

function test1(x: MyUnion) {
if (isA(x) !== true) {
x
}

if (isA(x) !== false) {
x
}

if (isA(x) === false) {
x
}

if (isA(x) === true) {
x
}

if (isA(x) != true) {
x
}

if (isA(x) == true) {
x
}

if (true !== isA(x)) {
x
}

if (true === isA(x)) {
x
}
}

0 comments on commit e9a78f2

Please sign in to comment.