From 03b95f81273a9cc73b93545796ec755cc2b21cfe Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 11 Oct 2026 13:26:48 +0100 Subject: [PATCH] A number, string, boolean or array is not an interface with members Passed where an interface with members is expected, such a value compiled without a word: castToInterfaceSpecialCases had no case for it, so it became a plain cast that lowering could not make (at most a ":0: warning: invalid cast"), and the callee read garbage. It is now an error naming the type and the interface. An interface without members still takes any value, as in TypeScript. Found by docs/bugs/array_of_ifaces_cast.ts, whose intended error (IStateToken[] passed for an IState) was never reported. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenCast.cpp | 18 ++++++++++++++++++ tslang/test/tester/CMakeLists.txt | 19 +++++++++++++++++++ tslang/test/tester/interface-cast/array.ts | 11 +++++++++++ tslang/test/tester/interface-cast/boolean.ts | 11 +++++++++++ tslang/test/tester/interface-cast/empty.ts | 9 +++++++++ tslang/test/tester/interface-cast/number.ts | 11 +++++++++++ tslang/test/tester/interface-cast/string.ts | 11 +++++++++++ 7 files changed, 90 insertions(+) create mode 100644 tslang/test/tester/interface-cast/array.ts create mode 100644 tslang/test/tester/interface-cast/boolean.ts create mode 100644 tslang/test/tester/interface-cast/empty.ts create mode 100644 tslang/test/tester/interface-cast/number.ts create mode 100644 tslang/test/tester/interface-cast/string.ts diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 79fb3380f..51fdaa675 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -1054,6 +1054,24 @@ namespace mlirgen { return castObjectToInterface(location, value, objectType, interfaceType, genContext); } + + // A number, string, boolean or array has no fields an interface's members could be read + // from: passed for an `{ kind: number }` it compiled, without a word, to a cast that + // lowering could not make, and the callee read garbage. An interface without members + // takes any value, as in TypeScript. + auto baseValueType = mth.stripLiteralType(valueType); + if (baseValueType.isIntOrIndexOrFloat() + || isa(baseValueType)) + { + auto interfaceInfo = getInterfaceInfoByFullName(interfaceType.getName().getValue()); + if (interfaceInfo && interfaceInfo->getVTableSize() > 0) + { + emitError(location) << "type '" << to_print(baseValueType) << "' can't be cast to interface '" + << to_print(interfaceType) << "': it has none of the interface's members"; + return mlir::failure(); + } + } } return std::nullopt; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 7a9a4d06e..ccb82cc40 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2613,6 +2613,25 @@ foreach(call_arity_case ${call_arity_cases}) FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") endforeach() +# A number, string, boolean or array passed where an interface with members is expected is an +# error; it compiled to a cast lowering could not make, and the callee read garbage. An interface +# without members still takes any value. +foreach(interface_cast_name number string boolean array) + add_test(NAME test-compile-interface-cast-${interface_cast_name} + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/interface-cast/${interface_cast_name}.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/interface-cast-${interface_cast_name}.obj") + set_tests_properties(test-compile-interface-cast-${interface_cast_name} + PROPERTIES PASS_REGULAR_EXPRESSION "can't be cast to interface 'IKind': it has none of the interface's members" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") +endforeach() +add_test(NAME test-compile-interface-cast-empty + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/interface-cast/empty.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/interface-cast-empty.obj") +set_tests_properties(test-compile-interface-cast-empty + PROPERTIES FAIL_REGULAR_EXPRESSION "error:|Stack dump|Assertion failed") + # A call to the collector's API (GC_gcollect, or the weak-link functions DefaultLib's WeakRef uses) # is an error under every model but gc, which alone links the collector - it used to reach the # linker as an unresolved symbol. Under gc the same file compiles. diff --git a/tslang/test/tester/interface-cast/array.ts b/tslang/test/tester/interface-cast/array.ts new file mode 100644 index 000000000..ddc15d6cc --- /dev/null +++ b/tslang/test/tester/interface-cast/array.ts @@ -0,0 +1,11 @@ +// A value with no fields of its own passed where an interface with members is expected compiled +// without a word, and the callee read garbage. It is an error, as in TypeScript. +interface IKind { + kind: number; +} + +function f(i: IKind) { + return i.kind; +} + +print(f([1, 2])); diff --git a/tslang/test/tester/interface-cast/boolean.ts b/tslang/test/tester/interface-cast/boolean.ts new file mode 100644 index 000000000..6d7cb1e86 --- /dev/null +++ b/tslang/test/tester/interface-cast/boolean.ts @@ -0,0 +1,11 @@ +// A value with no fields of its own passed where an interface with members is expected compiled +// without a word, and the callee read garbage. It is an error, as in TypeScript. +interface IKind { + kind: number; +} + +function f(i: IKind) { + return i.kind; +} + +print(f(true)); diff --git a/tslang/test/tester/interface-cast/empty.ts b/tslang/test/tester/interface-cast/empty.ts new file mode 100644 index 000000000..f3f2bf0fa --- /dev/null +++ b/tslang/test/tester/interface-cast/empty.ts @@ -0,0 +1,9 @@ +// An interface without members takes any value, as in TypeScript: this compiles. +interface IEmpty { +} + +function f(i: IEmpty) { + return 1; +} + +print(f([1, 2]) + f(5) + f("text")); diff --git a/tslang/test/tester/interface-cast/number.ts b/tslang/test/tester/interface-cast/number.ts new file mode 100644 index 000000000..6da64112a --- /dev/null +++ b/tslang/test/tester/interface-cast/number.ts @@ -0,0 +1,11 @@ +// A value with no fields of its own passed where an interface with members is expected compiled +// without a word, and the callee read garbage. It is an error, as in TypeScript. +interface IKind { + kind: number; +} + +function f(i: IKind) { + return i.kind; +} + +print(f(5)); diff --git a/tslang/test/tester/interface-cast/string.ts b/tslang/test/tester/interface-cast/string.ts new file mode 100644 index 000000000..feea537a9 --- /dev/null +++ b/tslang/test/tester/interface-cast/string.ts @@ -0,0 +1,11 @@ +// A value with no fields of its own passed where an interface with members is expected compiled +// without a word, and the callee read garbage. It is an error, as in TypeScript. +interface IKind { + kind: number; +} + +function f(i: IKind) { + return i.kind; +} + +print(f("text"));