Narrow by clause expressions in switches with true condition (#53681)

This commit is contained in:
Mateusz Burzyński 2023-09-18 21:07:17 +02:00 committed by GitHub
parent 686cb1b63c
commit 70b7de11fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 366 additions and 1 deletions

View File

@ -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;

View File

@ -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)) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}