diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index a47062f72..445feba6f 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 55d175ed1..532e49ade 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2631,6 +2631,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") + # An error inside a cast function the compiler generates (here: null in a union cast to string) is # reported where the cast is - line 6 - and not at the generated text's offset taken as a position # in the user's file, which put it inside the comment on line 1. 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"));