Skip to content

Commit

Permalink
Narrow by clause expressions in switches with true condition (#53681)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist authored Sep 18, 2023
1 parent 686cb1b commit 70b7de1
Show file tree
Hide file tree
Showing 5 changed files with 366 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1680,7 +1680,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {

function bindCaseBlock(node: CaseBlock): void {
const clauses = node.clauses;
const isNarrowingSwitch = isNarrowingExpression(node.parent.expression);
const isNarrowingSwitch = node.parent.expression.kind === SyntaxKind.TrueKeyword || isNarrowingExpression(node.parent.expression);
let fallthroughFlow = unreachableFlow;
for (let i = 0; i < clauses.length; i++) {
const clauseStart = i;
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27342,6 +27342,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
}
else if (expr.kind === SyntaxKind.TrueKeyword) {
const clause = flow.switchStatement.caseBlock.clauses.find((_, index) => index === flow.clauseStart);
const clauseExpression = clause && clause.kind === SyntaxKind.CaseClause ? clause.expression : undefined;
if (clauseExpression) {
type = narrowType(type, clauseExpression, /*assumeTrue*/ true);
}
}
else {
if (strictNullChecks) {
if (optionalChainContainsReference(expr, reference)) {
Expand Down
144 changes: 144 additions & 0 deletions tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//// [tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts] ////

=== narrowByClauseExpressionInSwitchTrue.ts ===
// https://github.com/microsoft/TypeScript/issues/37178

type A = { type: "A" };
>A : Symbol(A, Decl(narrowByClauseExpressionInSwitchTrue.ts, 0, 0))
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10))

type B = { type: "B" };
>B : Symbol(B, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 23))
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))

type AorB = A | B;
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
>A : Symbol(A, Decl(narrowByClauseExpressionInSwitchTrue.ts, 0, 0))
>B : Symbol(B, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 23))

const isA = (x: AorB): x is A => x.type === "A";
>isA : Symbol(isA, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 5))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 13))
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 13))
>A : Symbol(A, Decl(narrowByClauseExpressionInSwitchTrue.ts, 0, 0))
>x.type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 13))
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))

const isB = (x: AorB): x is B => x.type === "B";
>isB : Symbol(isB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 5))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 13))
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 13))
>B : Symbol(B, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 23))
>x.type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 13))
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 2, 10), Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 10))

function test1(x: AorB) {
>test1 : Symbol(test1, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 48))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))

switch (true) {
case isA(x):
>isA : Symbol(isA, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 5))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))

x;
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))

break;
case isB(x):
>isB : Symbol(isB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 5))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))

x;
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 9, 15))

break;
}
}

function test2(x: AorB) {
>test2 : Symbol(test2, Decl(narrowByClauseExpressionInSwitchTrue.ts, 18, 1))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))
>AorB : Symbol(AorB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 3, 23))

switch (true) {
case isA(x):
>isA : Symbol(isA, Decl(narrowByClauseExpressionInSwitchTrue.ts, 6, 5))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))

x;
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))

// fallthrough
case isB(x):
>isB : Symbol(isB, Decl(narrowByClauseExpressionInSwitchTrue.ts, 7, 5))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))

x;
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 20, 15))

break;
}
}

let x: string | undefined;
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 31, 3))

switch (true) {
case typeof x !== "undefined":
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 31, 3))

x.trim();
>x.trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 31, 3))
>trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --))
}

type SomeType = { type: "SomeType" };
>SomeType : Symbol(SomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 36, 1))
>type : Symbol(type, Decl(narrowByClauseExpressionInSwitchTrue.ts, 38, 17))

declare function isSomeType(x: unknown): x is SomeType;
>isSomeType : Symbol(isSomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 38, 37))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 39, 28))
>x : Symbol(x, Decl(narrowByClauseExpressionInSwitchTrue.ts, 39, 28))
>SomeType : Symbol(SomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 36, 1))

function processInput(input: string | RegExp | SomeType) {
>processInput : Symbol(processInput, Decl(narrowByClauseExpressionInSwitchTrue.ts, 39, 55))
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>SomeType : Symbol(SomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 36, 1))

switch (true) {
case typeof input === "string":
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))

input;
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))

break;
case input instanceof RegExp:
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))
>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

input;
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))

break;
case isSomeType(input):
>isSomeType : Symbol(isSomeType, Decl(narrowByClauseExpressionInSwitchTrue.ts, 38, 37))
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))

input;
>input : Symbol(input, Decl(narrowByClauseExpressionInSwitchTrue.ts, 41, 22))

break;
}
}

157 changes: 157 additions & 0 deletions tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//// [tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts] ////

=== narrowByClauseExpressionInSwitchTrue.ts ===
// https://github.com/microsoft/TypeScript/issues/37178

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

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

type AorB = A | B;
>AorB : A | B

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

const isB = (x: AorB): x is B => x.type === "B";
>isB : (x: AorB) => x is B
>(x: AorB): x is B => x.type === "B" : (x: AorB) => x is B
>x : AorB
>x.type === "B" : boolean
>x.type : "A" | "B"
>x : AorB
>type : "A" | "B"
>"B" : "B"

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

switch (true) {
>true : true

case isA(x):
>isA(x) : boolean
>isA : (x: AorB) => x is A
>x : AorB

x;
>x : A

break;
case isB(x):
>isB(x) : boolean
>isB : (x: AorB) => x is B
>x : AorB

x;
>x : B

break;
}
}

function test2(x: AorB) {
>test2 : (x: AorB) => void
>x : AorB

switch (true) {
>true : true

case isA(x):
>isA(x) : boolean
>isA : (x: AorB) => x is A
>x : AorB

x;
>x : A

// fallthrough
case isB(x):
>isB(x) : boolean
>isB : (x: AorB) => x is B
>x : AorB

x;
>x : AorB

break;
}
}

let x: string | undefined;
>x : string | undefined

switch (true) {
>true : true

case typeof x !== "undefined":
>typeof x !== "undefined" : boolean
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>x : string | undefined
>"undefined" : "undefined"

x.trim();
>x.trim() : string
>x.trim : () => string
>x : string
>trim : () => string
}

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

declare function isSomeType(x: unknown): x is SomeType;
>isSomeType : (x: unknown) => x is SomeType
>x : unknown

function processInput(input: string | RegExp | SomeType) {
>processInput : (input: string | RegExp | SomeType) => void
>input : string | RegExp | SomeType

switch (true) {
>true : true

case typeof input === "string":
>typeof input === "string" : boolean
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>input : string | RegExp | SomeType
>"string" : "string"

input;
>input : string

break;
case input instanceof RegExp:
>input instanceof RegExp : boolean
>input : string | RegExp | SomeType
>RegExp : RegExpConstructor

input;
>input : RegExp

break;
case isSomeType(input):
>isSomeType(input) : boolean
>isSomeType : (x: unknown) => x is SomeType
>input : string | RegExp | SomeType

input;
>input : SomeType

break;
}
}

57 changes: 57 additions & 0 deletions tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// @strict: true
// @noEmit: true

// https://github.com/microsoft/TypeScript/issues/37178

type A = { type: "A" };
type B = { type: "B" };
type AorB = A | B;

const isA = (x: AorB): x is A => x.type === "A";
const isB = (x: AorB): x is B => x.type === "B";

function test1(x: AorB) {
switch (true) {
case isA(x):
x;
break;
case isB(x):
x;
break;
}
}

function test2(x: AorB) {
switch (true) {
case isA(x):
x;
// fallthrough
case isB(x):
x;
break;
}
}

let x: string | undefined;

switch (true) {
case typeof x !== "undefined":
x.trim();
}

type SomeType = { type: "SomeType" };
declare function isSomeType(x: unknown): x is SomeType;

function processInput(input: string | RegExp | SomeType) {
switch (true) {
case typeof input === "string":
input;
break;
case input instanceof RegExp:
input;
break;
case isSomeType(input):
input;
break;
}
}

0 comments on commit 70b7de1

Please sign in to comment.