From b358f898f160090e48742dcd1c5a9fec6c1d833b Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 11 Oct 2026 13:07:50 +0100 Subject: [PATCH] A switch's `default` clause falls through like any other clause Without a `break`, a `default` body jumped straight out of the switch instead of into the clause after it: `default: r += "q"; case 6: r += "x"` gave "q", not "qx". mlirGenSwitchCase left the body's exit pending (to be pointed at the next clause's body) for every clause except `default`. Found by docs/bugs/testFallThrough.ts. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenImpl.h | 6 +- tslang/test/tester/CMakeLists.txt | 3 + .../tests/00switch_default_fallthrough.ts | 89 +++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 tslang/test/tester/tests/00switch_default_fallthrough.ts diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 1f60a28e6..6fad39c1b 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -4635,9 +4635,11 @@ class MLIRGenImpl } } - // exit; + // exit; without a `break` it falls through into the next clause's body - from `default` + // too, wherever it stands. A branch left pending after the last clause keeps the merge + // block it was created with. auto branchOp = builder.create(location, mergeBlock); - if (!hasBreak && !isDefaultCase) + if (!hasBreak) { pendingBranches.push_back(branchOp); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 7a9a4d06e..d6f9a08e9 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -474,6 +474,7 @@ tslang_add_test(NAME test-compile-00-string-length-resize COMMAND test-runner "$ tslang_add_test(NAME test-compile-00-array-length-grow-zeroed COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_grow_zeroed.ts") tslang_add_test(NAME test-compile-00-rest-args-pack-owned COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00rest_args_pack_owned.ts") tslang_add_test(NAME test-compile-00-switch-char-string-cases COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_char_string_cases.ts") +tslang_add_test(NAME test-compile-00-switch-default-fallthrough COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_default_fallthrough.ts") tslang_add_test(NAME test-compile-00-generic-class-constructor-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_class_constructor_inference.ts") tslang_add_test(NAME test-compile-00-generator-done-value-zero COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_done_value_zero.ts") tslang_add_test(NAME test-compile-00-any-equality-undefined COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_equality_undefined.ts") @@ -991,6 +992,7 @@ tslang_add_test(NAME test-jit-00-string-length-resize COMMAND test-runner -jit " tslang_add_test(NAME test-jit-00-array-length-grow-zeroed COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_grow_zeroed.ts") tslang_add_test(NAME test-jit-00-rest-args-pack-owned COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00rest_args_pack_owned.ts") tslang_add_test(NAME test-jit-00-switch-char-string-cases COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_char_string_cases.ts") +tslang_add_test(NAME test-jit-00-switch-default-fallthrough COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_default_fallthrough.ts") tslang_add_test(NAME test-jit-00-generic-class-constructor-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_class_constructor_inference.ts") tslang_add_test(NAME test-jit-00-generator-done-value-zero COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_done_value_zero.ts") tslang_add_test(NAME test-jit-00-any-equality-undefined COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_equality_undefined.ts") @@ -1986,6 +1988,7 @@ set(TSLANG_CORPUS 00string_length_resize.ts 00strings.ts 00switch.ts + 00switch_default_fallthrough.ts 00switch_char_string_cases.ts 00symbol.ts 00throw_in_catch.ts diff --git a/tslang/test/tester/tests/00switch_default_fallthrough.ts b/tslang/test/tester/tests/00switch_default_fallthrough.ts new file mode 100644 index 000000000..c3b0c63da --- /dev/null +++ b/tslang/test/tester/tests/00switch_default_fallthrough.ts @@ -0,0 +1,89 @@ +// A `default` clause without a `break` never fell through into the clauses after it: its body +// jumped straight out of the switch, so `default: r += "q"; case 6: r += "x"` gave "q". + +function defaultFirst(x: number) { + let r = ""; + switch (x) { + default: + r += "q"; + // fallthrough + case 6: + case 7: + r += "x"; + // fallthrough + case 8: + r += "y"; + break; + case 10: + r += "z"; + break; + } + + return r; +} + +function defaultInMiddle(x: number) { + let r = ""; + switch (x) { + case 6: + r += "x"; + // fallthrough + default: + r += "q"; + // fallthrough + case 8: + r += "y"; + break; + case 9: + r += "w"; + } + + return r; +} + +function defaultLast(x: number) { + let r = ""; + switch (x) { + case 1: + r += "a"; + // fallthrough + default: + r += "d"; + } + + return r + "."; +} + +function defaultWithBreak(x: number) { + let r = ""; + switch (x) { + default: + r += "q"; + break; + case 1: + r += "a"; + } + + return r; +} + +function main() { + assert(defaultFirst(100) == "qxy", "defaultFirst(100)"); + assert(defaultFirst(6) == "xy", "defaultFirst(6)"); + assert(defaultFirst(7) == "xy", "defaultFirst(7)"); + assert(defaultFirst(8) == "y", "defaultFirst(8)"); + assert(defaultFirst(10) == "z", "defaultFirst(10)"); + + assert(defaultInMiddle(100) == "qy", "defaultInMiddle(100)"); + assert(defaultInMiddle(6) == "xqy", "defaultInMiddle(6)"); + assert(defaultInMiddle(8) == "y", "defaultInMiddle(8)"); + assert(defaultInMiddle(9) == "w", "defaultInMiddle(9)"); + + assert(defaultLast(1) == "ad.", "defaultLast(1)"); + assert(defaultLast(2) == "d.", "defaultLast(2)"); + + assert(defaultWithBreak(5) == "q", "defaultWithBreak(5)"); + assert(defaultWithBreak(1) == "a", "defaultWithBreak(1)"); + + print("done."); +}