From 83bb6efa350a467bfd901ffeebdc93865ee9cef6 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 5 Oct 2026 17:53:58 +0100 Subject: [PATCH 1/2] An object literal field the receiver's type cannot hold is an error `let s: Sq = ...; s = { kind: "ci", r: 1 }` (Sq = { kind: "sq"; side: number }) compiled with no error and left `s` unchanged (#513). The same literal in a declaration, `let t: Sq = { kind: "ci", r: 1 }`, compiled and held garbage. addObjectFieldInfo cast each field to the receiver's field type and took the result as a value. The cast of "ci" to the literal type "sq" failed and reported it, but a failed cast is a null value: the field was queued with no value, the literal came out with no value at all, and an assignment of no value stores nothing and still succeeds. MLIRGen postpones its messages and shows them only if the module fails to compile. Nothing failed, so the cast's error was never shown. A field whose cast fails now fails the literal, so its error is shown. As an argument the literal was already an error, with a misleading "Expected 1 arguments, but got 0" next to the real one; that message is gone. literal-field-mismatch/: an assignment, a declaration, an argument and a return of `{ kind: "ci", r: 1 }` where an Sq is expected. Each must report the cast error, and no arity error. On main the assignment and the declaration compile with no error. positive.ts assigns, declares, passes and returns matching literals, and a union receiver still picks its member. Closes #513 Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenImpl.h | 11 +++++++- tslang/test/tester/CMakeLists.txt | 15 +++++++++++ .../tester/literal-field-mismatch/argument.ts | 10 +++++++ .../tester/literal-field-mismatch/assign.ts | 8 ++++++ .../tester/literal-field-mismatch/declare.ts | 7 +++++ .../tester/literal-field-mismatch/positive.ts | 26 +++++++++++++++++++ .../tester/literal-field-mismatch/return.ts | 10 +++++++ 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tslang/test/tester/literal-field-mismatch/argument.ts create mode 100644 tslang/test/tester/literal-field-mismatch/assign.ts create mode 100644 tslang/test/tester/literal-field-mismatch/declare.ts create mode 100644 tslang/test/tester/literal-field-mismatch/positive.ts create mode 100644 tslang/test/tester/literal-field-mismatch/return.ts diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 8e6b9693f..693573256 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -10010,7 +10010,16 @@ class MLIRGenImpl if (type != receiverElementType) { value = builder.getUnitAttr(); - itemValue = cast(location, receiverElementType, itemValue, genContext); + // a field that cannot be the receiver's (`kind: "ci"` for a `kind: "sq"`) is an error: its + // failed cast was a null value, the literal came out with no value at all, and an assignment + // of it stored nothing - with the cast's error postponed, and so never shown (#513) + auto castResult = cast(location, receiverElementType, itemValue, genContext); + if (castResult.failed_or_no_value()) + { + return mlir::failure(); + } + + itemValue = V(castResult); isConstValue = false; } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index d0f0a113b..84dd2b892 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2608,6 +2608,21 @@ endforeach() tslang_add_test(NAME test-jit-type-alias-not-circular COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/type-alias-circular/positive.ts") tslang_add_test(NAME test-compile-type-alias-not-circular COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/type-alias-circular/positive.ts") +# An object literal with a field the receiver's type cannot hold (`kind: "ci"` for `kind: "sq"`) is an +# error (issue #513). The field's failed cast was dropped: the literal had no value, an assignment of it +# stored nothing, and the cast's error, postponed like every message, was never shown. +foreach(literal_field_mismatch_name assign declare argument return) + add_test(NAME test-compile-literal-field-mismatch-${literal_field_mismatch_name} + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/literal-field-mismatch/${literal_field_mismatch_name}.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/literal-field-mismatch-${literal_field_mismatch_name}.obj") + set_tests_properties(test-compile-literal-field-mismatch-${literal_field_mismatch_name} + PROPERTIES PASS_REGULAR_EXPRESSION "can't cast from literal type: '\"ci\"' to '\"sq\"'" + FAIL_REGULAR_EXPRESSION "Expected 1 arguments|Stack dump|Assertion failed") +endforeach() +tslang_add_test(NAME test-jit-literal-field-match COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/literal-field-mismatch/positive.ts") +tslang_add_test(NAME test-compile-literal-field-match COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/literal-field-mismatch/positive.ts") + # An error reported by a pass that still returns success - a lowering pattern that cannot build a # value, says so and carries on - fails the compile. It used to be printed and then ignored: the object # file was written, and under --emit=jit the program ran and crashed. Two tests because one diff --git a/tslang/test/tester/literal-field-mismatch/argument.ts b/tslang/test/tester/literal-field-mismatch/argument.ts new file mode 100644 index 000000000..1cfb57b30 --- /dev/null +++ b/tslang/test/tester/literal-field-mismatch/argument.ts @@ -0,0 +1,10 @@ +// reported "Expected 1 arguments, but got 0" +type Sq = { kind: "sq"; side: number }; + +function f(s: Sq) { + return s.kind; +} + +function main() { + print(f({ kind: "ci", r: 1 })); +} diff --git a/tslang/test/tester/literal-field-mismatch/assign.ts b/tslang/test/tester/literal-field-mismatch/assign.ts new file mode 100644 index 000000000..59a844034 --- /dev/null +++ b/tslang/test/tester/literal-field-mismatch/assign.ts @@ -0,0 +1,8 @@ +// #513: compiled with no error, and `s` kept "sq" +type Sq = { kind: "sq"; side: number }; + +function main() { + let s: Sq = { kind: "sq", side: 4 }; + s = { kind: "ci", r: 1 }; + print(s.kind, s.side); +} diff --git a/tslang/test/tester/literal-field-mismatch/declare.ts b/tslang/test/tester/literal-field-mismatch/declare.ts new file mode 100644 index 000000000..239dc4abd --- /dev/null +++ b/tslang/test/tester/literal-field-mismatch/declare.ts @@ -0,0 +1,7 @@ +// compiled with no error, and `t` held garbage +type Sq = { kind: "sq"; side: number }; + +function main() { + let t: Sq = { kind: "ci", r: 1 }; + print(t.kind); +} diff --git a/tslang/test/tester/literal-field-mismatch/positive.ts b/tslang/test/tester/literal-field-mismatch/positive.ts new file mode 100644 index 000000000..20a36bff6 --- /dev/null +++ b/tslang/test/tester/literal-field-mismatch/positive.ts @@ -0,0 +1,26 @@ +// literals that are the receiver's type still assign, declare, pass and return +type Sq = { kind: "sq"; side: number }; +type Ci = { kind: "ci"; r: number }; + +function f(s: Sq) { + return s.side; +} + +function g(): Sq { + return { kind: "sq", side: 3 }; +} + +function main() { + let u: Sq | Ci = { kind: "ci", r: 1 }; + assert(u.kind == "ci", "union, declared"); + u = { kind: "sq", side: 2 }; + assert(u.kind == "sq", "union, assigned"); + + let s: Sq = { kind: "sq", side: 4 }; + s = { kind: "sq", side: 6 }; + assert(s.side == 6, "assigned"); + assert(f({ kind: "sq", side: 5 }) == 5, "argument"); + assert(g().side == 3, "returned"); + + print("done."); +} diff --git a/tslang/test/tester/literal-field-mismatch/return.ts b/tslang/test/tester/literal-field-mismatch/return.ts new file mode 100644 index 000000000..390c9d31e --- /dev/null +++ b/tslang/test/tester/literal-field-mismatch/return.ts @@ -0,0 +1,10 @@ +// a returned literal of the other member +type Sq = { kind: "sq"; side: number }; + +function g(): Sq { + return { kind: "ci", r: 1 }; +} + +function main() { + print(g().kind); +} From bb6164cf243ec13e68dc3ec7c6462c1fee75bf49 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 5 Oct 2026 17:56:19 +0100 Subject: [PATCH 2/2] export_object_literal_with_class_types: compile without strict null checks `{ left: null }` for a `left: Point` is an error under strict null checks, which are on by default: null is not a Point, as in `let p: Point = null`. The test compiled only because the field's failed cast was dropped (#513), and the field read null by default. With that cast now an error, the file says what it relies on. The test is about a cross-module object literal with class-typed fields, not null checking. Co-Authored-By: Claude Opus 5.5 --- .../test/tester/tests/export_object_literal_with_class_types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tslang/test/tester/tests/export_object_literal_with_class_types.ts b/tslang/test/tester/tests/export_object_literal_with_class_types.ts index 1f7cd60db..e4d768564 100644 --- a/tslang/test/tester/tests/export_object_literal_with_class_types.ts +++ b/tslang/test/tester/tests/export_object_literal_with_class_types.ts @@ -1,3 +1,5 @@ +// @strict-null false +// `left: null` for a Point: null is not a Point under strict null checks namespace A { class Point {