diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 38127a8d62075..a5b7e1fa93dc2 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f4c2dd1d9094..ae21249084b72 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27298,6 +27298,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)) { diff --git a/tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.symbols b/tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.symbols new file mode 100644 index 0000000000000..6461b7c88b263 --- /dev/null +++ b/tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.symbols @@ -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; + } +} + diff --git a/tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.types b/tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.types new file mode 100644 index 0000000000000..f8a1d9bc87e42 --- /dev/null +++ b/tests/baselines/reference/narrowByClauseExpressionInSwitchTrue.types @@ -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; + } +} + diff --git a/tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts b/tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts new file mode 100644 index 0000000000000..f689b18bb1711 --- /dev/null +++ b/tests/cases/compiler/narrowByClauseExpressionInSwitchTrue.ts @@ -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; + } +}