diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 31148d7f9..656d5beff 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -600,6 +600,11 @@ namespace mlirgen return *result; } + if (auto result = castConstArrayToArray(location, type, value, valueType, genContext)) + { + return *result; + } + if (auto result = castToOptionalType(location, type, value, valueType, genContext)) { return *result; @@ -909,6 +914,42 @@ namespace mlirgen return std::nullopt; } + // A constant array keeps the element type its literal was built with: `const c = [1, 2]` is a + // const_array, and so is `[1, 2]` meeting a union such as `number[] | string`, where no single + // array type guides the literal. Lowering turns a const array into an array by copying its data as it + // is, so for another element type (`number[]`) the elements were read with the wrong layout - garbage, + // with only a warning. Such an array is built here from its elements, each cast to the element type. + std::optional MLIRGenImpl::castConstArrayToArray(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext) + { + auto constArrayType = dyn_cast(valueType); + auto arrayType = dyn_cast(type); + if (!constArrayType || !arrayType || constArrayType.getElementType() == arrayType.getElementType()) + { + return std::nullopt; + } + + auto constOp = value.getDefiningOp(); + auto elementAttrs = constOp ? dyn_cast(constOp.getValue()) : mlir::ArrayAttr(); + if (!elementAttrs || llvm::any_of(elementAttrs, [](mlir::Attribute attr) { return isa(attr); })) + { + // not a literal, or nested arrays: left to the cast below + return std::nullopt; + } + + SmallVector elements; + for (auto elementAttr : elementAttrs) + { + auto element = builder.create(location, constArrayType.getElementType(), elementAttr); + CAST_A(castedElement, location, arrayType.getElementType(), element, genContext); + elements.push_back(castedElement); + } + + // the data block about to be filled releases every element when it dies + mlirGenRetainCaptured(location, elements); + + return V(builder.create(location, arrayType, elements)); + } + std::optional MLIRGenImpl::castTupleLikeVariants(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext) { // const tuple to tuple diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index c183761b0..8256525ee 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -10912,6 +10912,9 @@ class MLIRGenImpl // casts between tuple-like types (tuple, const tuple, class storage, interface fields) std::optional castTupleLikeVariants(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext); + // constant array literal to an array of another element type, element by element + std::optional castConstArrayToArray(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext); + // optional // TODO: it is in CastLogic as well, review usage and remove from here // but if optional points to interface then it will not work diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index b9e469c0d..39df3dbf4 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -174,6 +174,7 @@ add_test(NAME test-compile-00-typeof-static-fold COMMAND test-runner "${PROJECT_ add_test(NAME test-compile-00-typeof-function-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_function_narrowing.ts") add_test(NAME test-compile-00-typeof-static-fold-conditions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts") add_test(NAME test-compile-00-typeof-union-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_union_narrowing.ts") +add_test(NAME test-compile-00-const-array-to-array-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_to_array_elements.ts") add_test(NAME test-compile-00-array-move-wide-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts") add_test(NAME test-compile-00-funcs-expression-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_generic.ts") add_test(NAME test-compile-00-funcs-expression-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_iterator.ts") @@ -594,6 +595,7 @@ add_test(NAME test-jit-00-typeof-static-fold COMMAND test-runner -jit "${PROJECT add_test(NAME test-jit-00-typeof-function-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_function_narrowing.ts") add_test(NAME test-jit-00-typeof-static-fold-conditions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts") add_test(NAME test-jit-00-typeof-union-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_union_narrowing.ts") +add_test(NAME test-jit-00-const-array-to-array-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_to_array_elements.ts") add_test(NAME test-jit-00-array-move-wide-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts") add_test(NAME test-jit-00-funcs-expression-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_generic.ts") add_test(NAME test-jit-00-funcs-expression-iterator COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_iterator.ts") @@ -1322,6 +1324,7 @@ add_test(NAME test-jit-none-strings COMMAND test-runner -jit -mm=none "${PROJECT set(TSLANG_CORPUS 00add_promotes_both_operands.ts 00conditional_owned_result.ts + 00const_array_to_array_elements.ts 00owned_array_splice.ts 00alloc_in_catch.ts 00any_compare.ts diff --git a/tslang/test/tester/tests/00const_array_to_array_elements.ts b/tslang/test/tester/tests/00const_array_to_array_elements.ts new file mode 100644 index 000000000..312033441 --- /dev/null +++ b/tslang/test/tester/tests/00const_array_to_array_elements.ts @@ -0,0 +1,38 @@ +// An integer array literal that is not built for one array type keeps its `s32` elements: a const +// variable, or a literal assigned to a union like `number[] | string`. Casting it to `number[]` copied +// the elements as they were, so `number` elements were read out of `s32` data - garbage values and +// a garbage length, with only a warning. +function lengthOf(u: number[] | string) { + return typeof u === "array" ? u.length : -1; +} + +function main() { + const c = [1, 2, 3]; + let fromConst: number[] = c; + assert(fromConst.length == 3, "const variable: length"); + assert(fromConst[2] == 3, "const variable: element"); + + let u: number[] | string = [1, 2]; + if (typeof u === "array") { + assert(u.length == 2, "union initializer: length"); + assert(u[1] == 2, "union initializer: element"); + } else { + assert(false, "union initializer: wrong branch"); + } + + let v: number[] | string; + v = [4, 5, 6]; + if (typeof v === "array") { + assert(v[0] + v[1] + v[2] == 15, "union assignment"); + } else { + assert(false, "union assignment: wrong branch"); + } + + assert(lengthOf([7, 8]) == 2, "union parameter"); + + // the elements are real `number`s now: fractional arithmetic works on them + const halves: number[] = c; + assert(halves[0] / 2 == 0.5, "number arithmetic on converted element"); + + print("done."); +}