From 8ef3e0ee59381b22a755e54b279c2184550fed4c Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 4 Oct 2026 20:24:01 +0100 Subject: [PATCH] An object literal with methods assigned to a class is an error (#494) `const c: C = { x: 3, f() { return 2; } }` compiled, but the instance was wrong: an object literal with a method or an accessor is boxed (an `!ts.object`), and its cast to the class was a plain ts.Cast of the pointer. No instance was built, so `c.x` read the class's field slot out of memory laid out as `{x, f}` (garbage such as 1.34e-311), and `c.f()` called through a vtable that was not there (0xC0000005). A class instance takes its methods from its class's vtable, so one object cannot have a method of its own; TypeScript, where `c.f()` would give 2, has no counterpart here. Such a literal is now a compile error, "an object literal with methods or accessors cannot be assigned to class 'C': a class instance takes its methods from its class; use 'new', or an interface type", wherever it meets a class type: a declaration, a parameter, a return, `as C`. A literal with fields only is still made into an instance (#487), and a literal with methods assigned to an interface type is unchanged. Closes #494 Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenCast.cpp | 12 ++++++++++++ tslang/test/tester/CMakeLists.txt | 13 +++++++++++++ .../test/tester/object-literal-to-class/accessor.ts | 9 +++++++++ .../test/tester/object-literal-to-class/method.ts | 11 +++++++++++ .../tester/object-literal-to-class/parameter.ts | 13 +++++++++++++ .../test/tester/object-literal-to-class/return.ts | 13 +++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 tslang/test/tester/object-literal-to-class/accessor.ts create mode 100644 tslang/test/tester/object-literal-to-class/method.ts create mode 100644 tslang/test/tester/object-literal-to-class/parameter.ts create mode 100644 tslang/test/tester/object-literal-to-class/return.ts diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 6c1892d3e..79fb3380f 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -1373,6 +1373,18 @@ namespace mlirgen return castTupleToTuple(location, unboxedTuple, srcTupleType, fields, genContext); } + + // an object literal is boxed for its methods and accessors, which a class instance cannot + // have of its own: it takes them from its class's vtable. The cast was a reinterpretation + // of the pointer, so the class read its fields and vtable out of the literal's layout - + // garbage, and a crash on a method call (#494). + if (auto classType = dyn_cast(type)) + { + emitError(location, "an object literal with methods or accessors cannot be assigned to class '") + << to_print(classType) + << "': a class instance takes its methods from its class; use 'new', or an interface type"; + return mlir::failure(); + } } return std::nullopt; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 9b306fd2d..d1a739093 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -3165,6 +3165,19 @@ set_tests_properties(test-compile-class-cast-unrelated-error PROPERTIES PASS_REGULAR_EXPRESSION "type A is not assignable to type B: 'v' is number, not string" FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") +# An object literal with methods or accessors assigned to a class is an error: a class instance +# takes its methods from its class. The cast reinterpreted the pointer, so the class read garbage +# fields and crashed on a method call (#494). +foreach(object_literal_to_class_name method accessor parameter return) + add_test(NAME test-compile-object-literal-to-class-${object_literal_to_class_name} + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/object-literal-to-class/${object_literal_to_class_name}.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/object-literal-to-class-${object_literal_to_class_name}.obj") + set_tests_properties(test-compile-object-literal-to-class-${object_literal_to_class_name} + PROPERTIES PASS_REGULAR_EXPRESSION "an object literal with methods or accessors cannot be assigned to class 'C'" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") +endforeach() + # The shared-component tier under the other two models. A shared library records the model # it was built under, so both halves of a pair are built with the same flag - which is what # these run. The file pairs are the default model's, verbatim. diff --git a/tslang/test/tester/object-literal-to-class/accessor.ts b/tslang/test/tester/object-literal-to-class/accessor.ts new file mode 100644 index 000000000..26e3c2077 --- /dev/null +++ b/tslang/test/tester/object-literal-to-class/accessor.ts @@ -0,0 +1,9 @@ +// An object literal with an accessor assigned to a class (#494). +class C { + x: number; +} + +function main() { + const c: C = { x: 3, get y() { return 2; } }; + print(c.x); +} diff --git a/tslang/test/tester/object-literal-to-class/method.ts b/tslang/test/tester/object-literal-to-class/method.ts new file mode 100644 index 000000000..64acaebf4 --- /dev/null +++ b/tslang/test/tester/object-literal-to-class/method.ts @@ -0,0 +1,11 @@ +// An object literal with a method assigned to a class (#494): the cast reinterpreted the pointer, +// so c.x read garbage and c.f() crashed. +class C { + x: number; + f() { return 1; } +} + +function main() { + const c: C = { x: 3, f() { return 2; } }; + print(c.x, c.f()); +} diff --git a/tslang/test/tester/object-literal-to-class/parameter.ts b/tslang/test/tester/object-literal-to-class/parameter.ts new file mode 100644 index 000000000..909758f2e --- /dev/null +++ b/tslang/test/tester/object-literal-to-class/parameter.ts @@ -0,0 +1,13 @@ +// An object literal with a method passed to a class parameter (#494). +class C { + x: number; + f() { return 1; } +} + +function take(c: C) { + return c.x; +} + +function main() { + print(take({ x: 3, f() { return 2; } })); +} diff --git a/tslang/test/tester/object-literal-to-class/return.ts b/tslang/test/tester/object-literal-to-class/return.ts new file mode 100644 index 000000000..602d0a156 --- /dev/null +++ b/tslang/test/tester/object-literal-to-class/return.ts @@ -0,0 +1,13 @@ +// An object literal with a method returned as a class (#494). +class C { + x: number; + f() { return 1; } +} + +function make(): C { + return { x: 3, f() { return 2; } }; +} + +function main() { + print(make().x); +}