From 68a8cd9888f8d54ad92ec14627c5121f491c2096 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 5 Oct 2026 15:44:48 +0100 Subject: [PATCH] A for condition that tests an optional value is made a boolean `for (let node = head; node; node = node.next)` with `node: Node | undefined` failed module verification under every memory model: 'ts.Condition' op operand #0 must be , but got '!ts.optional<...>'. mlirGen(ForStatement) handed the condition to ts.Condition as it was, while mlirGen(WhileStatement), mlirGen(DoStatement) and mlirGen(IfStatement) cast a non-boolean condition to boolean first. The for condition now goes through the same cast. `node !== undefined` was already boolean, which is why it worked. 00for_optional_class_condition.ts: a for over an optional class (in main and in a function), a nullable (`| null`) class and an optional string, next to the same test in while and if. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenStatements.cpp | 6 ++ tslang/test/tester/CMakeLists.txt | 3 + .../tests/00for_optional_class_condition.ts | 85 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 tslang/test/tester/tests/00for_optional_class_condition.ts diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index 38d6de5c7..ca05ff994 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -840,6 +840,12 @@ namespace mlirgen auto conditionValue = V(result); if (conditionValue) { + // a truthiness test (`for (let n = head; n; n = n.next)`) is made a boolean as in mlirGen(WhileStatement) + if (conditionValue.getType() != getBooleanType()) + { + CAST(conditionValue, location, getBooleanType(), conditionValue, loopGenContext); + } + builder.create(location, conditionValue, mlir::ValueRange{}); } else diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 0a750c9da..640c95710 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -355,6 +355,7 @@ tslang_add_test(NAME test-compile-00-array-insert-functions COMMAND test-runner tslang_add_test(NAME test-compile-00-await-void COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00await_void.ts") tslang_add_test(NAME test-compile-00-for-await-sequential COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts") tslang_add_test(NAME test-compile-00-for-await-control COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts") +tslang_add_test(NAME test-compile-00-for-optional-class-condition COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_optional_class_condition.ts") tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts") tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts") tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts") @@ -867,6 +868,7 @@ tslang_add_test(NAME test-jit-00-array-insert-functions COMMAND test-runner -jit tslang_add_test(NAME test-jit-00-await-void COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00await_void.ts") tslang_add_test(NAME test-jit-00-for-await-sequential COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts") tslang_add_test(NAME test-jit-00-for-await-control COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts") +tslang_add_test(NAME test-jit-00-for-optional-class-condition COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_optional_class_condition.ts") tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts") tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts") tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts") @@ -1708,6 +1710,7 @@ set(TSLANG_CORPUS 00await_void.ts 00for_await_sequential.ts 00for_await_control.ts + 00for_optional_class_condition.ts 00const_record_owned_fields.ts 00array_length_valid.ts 00string_view_copy.ts diff --git a/tslang/test/tester/tests/00for_optional_class_condition.ts b/tslang/test/tester/tests/00for_optional_class_condition.ts new file mode 100644 index 000000000..3c9c0fea2 --- /dev/null +++ b/tslang/test/tester/tests/00for_optional_class_condition.ts @@ -0,0 +1,85 @@ +// a `for` condition that is a bare truthiness test of an optional value (`for (let n = head; n; n = n.next)`) +// went to ts.Condition unconverted and failed module verification; `while` and `if` made it a boolean first +class Node { + constructor(public value: number, public next: Node | undefined) {} +} + +class NullNode { + constructor(public value: number, public next: NullNode | null) {} +} + +function sumFor(head: Node | undefined) { + let sum: number = 0; + for (let node = head; node; node = node.next) { + sum += node.value; + } + + return sum; +} + +function sumWhile(head: Node | undefined) { + let sum: number = 0; + let node = head; + while (node) { + sum += node.value; + node = node.next; + } + + return sum; +} + +function sumIf(head: Node | undefined) { + let sum: number = 0; + if (head) { + sum += head.value; + } + + return sum; +} + +function sumForNull(head: NullNode | null) { + let sum: number = 0; + for (let node = head; node; node = node.next) { + sum += node.value; + } + + return sum; +} + +function countForString(s: string | undefined) { + let count = 0; + for (let t = s; t; t = undefined) { + count++; + } + + return count; +} + +function main() { + let head: Node | undefined = new Node(1, new Node(2, undefined)); + let sum: number = 0; + for (let node = head; node; node = node.next) { + sum += node.value; + } + + print(sum); + assert(sum == 3, "for over an optional class, at the call site"); + + assert(sumFor(head) == 3, "for over an optional class"); + assert(sumFor(new Node(5, undefined)) == 5, "for over one node"); + assert(sumFor(undefined) == 0, "for over undefined"); + + assert(sumWhile(head) == 3, "while over an optional class"); + assert(sumWhile(undefined) == 0, "while over undefined"); + + assert(sumIf(head) == 1, "if on an optional class"); + assert(sumIf(undefined) == 0, "if on undefined"); + + assert(sumForNull(new NullNode(4, new NullNode(6, null))) == 10, "for over a nullable class"); + assert(sumForNull(null) == 0, "for over null"); + + assert(countForString("a") == 1, "for over an optional string"); + assert(countForString(undefined) == 0, "for over undefined string"); + + print("done."); +}