diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 5d97a3483..6c1892d3e 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -1294,8 +1294,10 @@ namespace mlirgen } else if (auto classType = dyn_cast(type)) { - emitError(location, "invalid cast from ") << to_print(valueType) << " to " << to_print(type); - return mlir::failure(); + // a record with a run-time field (`const p: P = { x: 1, items: [i, 2] }`) makes the + // instance from its fields, as a constant record does (#487) + fields = mlir::cast(classType.getStorageType()).getFields(); + return castTupleToClass(location, value, srcTupleType, fields, classType, genContext); } else if (auto funcType = dyn_cast(type)) { diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 328f1f392..32db49015 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -9875,6 +9875,20 @@ class MLIRGenImpl } } + // a record literal made into a class instance (`const p: P = { x: 1, items: [i, 2] }`) is + // built with the class's field types, as for a tuple: `[i, 2]` is the field's `number[]` + // itself, not an `s32[]` copied into one and left with no count under rc, and `[]` is no + // `any[]` the field cannot take (#487) + if (auto classType = dyn_cast(receiverType)) + { + auto classStorageType = mlir::cast(classType.getStorageType()); + auto index = classStorageType.getIndex(fieldName); + if (index >= 0 && index < (int)classStorageType.size()) + { + return classStorageType.getType(index); + } + } + if (auto interfaceType = dyn_cast(receiverType)) { auto interfaceInfo = getInterfaceInfoByFullName(interfaceType.getName().getValue()); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 909c83f7e..9b306fd2d 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -349,6 +349,7 @@ tslang_add_test(NAME test-compile-00-arrays6 COMMAND test-runner "${PROJECT_SOUR tslang_add_test(NAME test-compile-00-array-static-nested COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_static_nested.ts") tslang_add_test(NAME test-compile-00-array-literal-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_literal_copy.ts") tslang_add_test(NAME test-compile-00-tuple-runtime-arrays COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00tuple_runtime_arrays.ts") +tslang_add_test(NAME test-compile-00-record-to-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_to_class.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") @@ -855,6 +856,7 @@ tslang_add_test(NAME test-jit-00-arrays6 COMMAND test-runner -jit "${PROJECT_SOU tslang_add_test(NAME test-jit-00-array-static-nested COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_static_nested.ts") tslang_add_test(NAME test-jit-00-array-literal-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_literal_copy.ts") tslang_add_test(NAME test-jit-00-tuple-runtime-arrays COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00tuple_runtime_arrays.ts") +tslang_add_test(NAME test-jit-00-record-to-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_to_class.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") @@ -1690,6 +1692,7 @@ set(TSLANG_CORPUS 00array_static_nested.ts 00array_literal_copy.ts 00tuple_runtime_arrays.ts + 00record_to_class.ts 00const_record_owned_fields.ts 00array_length_valid.ts 00string_view_copy.ts diff --git a/tslang/test/tester/tests/00record_to_class.ts b/tslang/test/tester/tests/00record_to_class.ts new file mode 100644 index 000000000..f907eb7ac --- /dev/null +++ b/tslang/test/tester/tests/00record_to_class.ts @@ -0,0 +1,51 @@ +// A record literal with a run-time field can be assigned to a class type (#487): the instance is +// made from the record's fields, as it is from a constant record's, where the cast used to be an +// "invalid cast" error. A literal assigned to a class is built with the class's field types, so +// `[i, 2]` is the field's `number[]` itself (an `s32[]` copied into one was never freed under rc) +// and `[]` is no `any[]` the field cannot take. +class P { + x: number; + items: number[]; + name: string; +} + +function take(p: P) { + return p.items.length + p.name.length; +} + +function make(i: number): P { + return { x: i, items: [i, i + 1, i + 2], name: "n" + i }; +} + +function main() { + let i = 7; + const p: P = { x: 1, items: [i, 2], name: "a" }; + assert(p.items.length == 2 && p.items[0] == 7 && p.x == 1, "const of a run-time record"); + p.items.push(3); + assert(p.items.length == 3, "its array can be changed"); + + const c: P = { x: 2, items: [1, 2], name: "c" }; + assert(c.items.length == 2 && c.items[1] == 2, "a constant record"); + const e: P = { x: i, items: [], name: "e" }; + e.items.push(i); + assert(e.items.length == 1 && e.items[0] == 7, "an empty array"); + + let n: number = 5; + let r = { x: n, items: [n, 2], name: "b" + n }; + const q: P = r; + q.items.push(9); + assert(r.items.length == 3 && q.name == "b5", "a record variable, sharing its array"); + + assert(take({ x: 2, items: [i], name: "cc" + i }) == 4, "to a class parameter"); + const m = make(i); + assert(m.x == 7 && m.items.length == 3 && m.name == "n7", "returned as a class"); + + let total = 0; + for (let k = 0; k < 1000; k++) { + const z: P = { x: k, items: [k, k], name: "z" + k }; + total += z.items.length + z.name.length; + } + assert(total == 5890, "a fresh instance each pass"); + + print("done."); +}