diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 16e63f266..a830e953e 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -2044,7 +2044,7 @@ namespace mlirgen } } - mlir::LogicalResult MLIRGenImpl::verifyFunctionCastRules(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext) + mlir::LogicalResult MLIRGenImpl::verifyFunctionCastRules(mlir::Location location, mlir::Type type, mlir::Value &value, mlir::Type &valueType, const GenContext &genContext) { if (mth.isAnyFunctionType(valueType) && mth.isAnyFunctionType(type)) { @@ -2053,6 +2053,17 @@ namespace mlirgen // need to instantiate generic method auto result = instantiateSpecializedFunction(location, value, type, genContext); EXIT_IF_FAILED(result); + + // An arrow that captures comes back as a new value, the closure bound over its + // captured variables; the old reference is the bare function and calling it reads + // the captures through a null pointer (`new W((v) => { seen += 1; })`). Without + // captures the reference was retyped in place. + if (auto resultValue = V(result)) + { + value = resultValue; + } + + valueType = value.getType(); } // A C variadic function is called with C's variadic convention and a TypeScript diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 0426c791e..7a0df99a9 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -14253,7 +14253,7 @@ class MLIRGenImpl // union or optional or any or opaque source type std::optional castFromSourceSpecialCases(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext); - mlir::LogicalResult verifyFunctionCastRules(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext); + mlir::LogicalResult verifyFunctionCastRules(mlir::Location location, mlir::Type type, mlir::Value &value, mlir::Type &valueType, const GenContext &genContext); // cast ext method to bound method std::optional castExtensionFunctionType(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 313d4dd21..a40651ca7 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -597,6 +597,7 @@ tslang_add_test(NAME test-compile-00-try-cleanup-then-catch COMMAND test-runner tslang_add_test(NAME test-compile-00-try-empty-catch COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_empty_catch.ts") tslang_add_test(NAME test-compile-00-global-union-from-call COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_union_from_call.ts") tslang_add_test(NAME test-compile-00-capture-in-new-arguments COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00capture_in_new_arguments.ts") +tslang_add_test(NAME test-compile-00-capture-in-new-untyped-arrow COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00capture_in_new_untyped_arrow.ts") tslang_add_test(NAME test-compile-01-class-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/01class_new.ts") tslang_add_test(NAME test-compile-00-class-stack COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_stack.ts") tslang_add_test(NAME test-compile-00-class-static COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static.ts") @@ -1202,6 +1203,7 @@ tslang_add_test(NAME test-jit-00-try-cleanup-then-catch COMMAND test-runner -jit tslang_add_test(NAME test-jit-00-try-empty-catch COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_empty_catch.ts") tslang_add_test(NAME test-jit-00-global-union-from-call COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_union_from_call.ts") tslang_add_test(NAME test-jit-00-capture-in-new-arguments COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00capture_in_new_arguments.ts") +tslang_add_test(NAME test-jit-00-capture-in-new-untyped-arrow COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00capture_in_new_untyped_arrow.ts") tslang_add_test(NAME test-jit-01-class-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/01class_new.ts") tslang_add_test(NAME test-jit-00-class-stack COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_stack.ts") tslang_add_test(NAME test-jit-00-class-static COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static.ts") @@ -2146,6 +2148,7 @@ set(TSLANG_CORPUS 00try_scope_exit_through_finally.ts 00try_jump_from_catch.ts 00capture_in_new_arguments.ts + 00capture_in_new_untyped_arrow.ts 00cast_to_f32.ts 00catch_untyped_value.ts 00catch_value.ts diff --git a/tslang/test/tester/tests/00capture_in_new_untyped_arrow.ts b/tslang/test/tester/tests/00capture_in_new_untyped_arrow.ts new file mode 100644 index 000000000..5f7cd5519 --- /dev/null +++ b/tslang/test/tester/tests/00capture_in_new_untyped_arrow.ts @@ -0,0 +1,66 @@ +// An arrow without parameter types, `(v) => ...`, is generic until something gives it a type. As a +// `new` argument it gets one when it is cast to the constructor parameter's type, and the cast +// instantiated it there - but when the arrow captured a local, the instantiation built the closure +// over the captured variables and the cast went on with the bare function instead. The constructor +// stored a function whose captures were a null pointer, and the first call crashed - under every +// memory model, so it is a front-end bug. A non-capturing arrow, or one with typed parameters, +// never went that way. + +class ParamProperty { + constructor(private f: (v: number) => void) {} + + run(v: number) { + this.f(v); + } +} + +class StoredField { + f: (v: number) => void; + + constructor(f: (v: number) => void) { + this.f = f; + } + + run(v: number) { + this.f(v); + } +} + +class Mapper { + constructor(private f: (v: number) => number) {} + + apply(v: number) { + return this.f(v); + } +} + +let globalSeen: number = 0; + +function main() { + let seen: number = 0; + const p = new ParamProperty((v) => { + seen += v; + }); + p.run(1); + p.run(2); + assert(seen == 3, "a capturing untyped arrow in a parameter property"); + + let total: number = 0; + const s = new StoredField((v) => { + total += v; + }); + s.run(5); + assert(total == 5, "a capturing untyped arrow stored in a field"); + + const k = 10; + const m = new Mapper((v) => v * k); + assert(m.apply(4) == 40, "a capturing untyped arrow returning a value"); + + const g = new ParamProperty((v) => { + globalSeen += v; + }); + g.run(7); + assert(globalSeen == 7, "a non-capturing untyped arrow"); + + print("done."); +}