From 86b70d490a2f16963bc01c54cf15f064a4f14279 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 11 Oct 2026 13:23:20 +0100 Subject: [PATCH] Nested arrows with same-named untyped parameters can be instantiated An untyped parameter makes its function generic in a type named TGenParam_. In const getPoint = (x, y) => { const recenterX = x => (x - screenWidth / 2.0) / screenWidth; return recenterX(x) + ...; }; the inner `x`'s type had the same name as the outer `x`'s, so instantiating getPoint bound the inner arrow's type as well. With a captured variable (screenWidth) the inner arrow then resolved as a closure over a function whose type was still generic, and the call failed: "can't instantiate function ... not all generic types can be identified". The type is now also named after the function's position. Found by docs/bugs/generic_in_generic.ts. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenImpl.h | 14 +++--- tslang/test/tester/CMakeLists.txt | 3 ++ .../tests/00nested_implicit_generics.ts | 43 +++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 tslang/test/tester/tests/00nested_implicit_generics.ts diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 1f60a28e6..bd5b82c20 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -2717,10 +2717,14 @@ class MLIRGenImpl return mlir::StringRef(ss.str()).copy(stringAllocator); } - mlir::StringRef getParameterGenericTypeName(std::string name) { - mlir::StringRef typeParamNamePtr; + // The type an untyped parameter makes its function generic in. It is named after the function + // too (its position): named after the parameter alone, the `x` of an arrow inside another arrow + // with an `x` had the outer one's type, so instantiating the outer arrow bound the inner one's + // type as well, and the inner arrow could no longer be instantiated. + mlir::StringRef getParameterGenericTypeName(std::string name, SignatureDeclarationBase signatureDeclarationBase) { + auto pos = signatureDeclarationBase->pos.textPos > 0 ? signatureDeclarationBase->pos.textPos : signatureDeclarationBase->pos.pos; std::stringstream ss; - ss << "TGenParam_" << name; + ss << "TGenParam_" << name << "_" << pos; return mlir::StringRef(ss.str()).copy(stringAllocator); } @@ -10727,7 +10731,7 @@ class MLIRGenImpl namePtr = getArgumentName(index); } - auto typeParamNamePtr = getParameterGenericTypeName(namePtr.str()); + auto typeParamNamePtr = getParameterGenericTypeName(namePtr.str(), signatureDeclarationBase); auto &typeParameters = signatureDeclarationBase->typeParameters; auto found = std::find_if(typeParameters.begin(), typeParameters.end(), [&](auto ¶mItem) { return MLIRHelper::getName( paramItem->name) == typeParamNamePtr; }); @@ -10802,7 +10806,7 @@ class MLIRGenImpl namePtr = getArgumentName(index); } - auto typeParamNamePtr = getParameterGenericTypeName(namePtr.str()); + auto typeParamNamePtr = getParameterGenericTypeName(namePtr.str(), signatureDeclarationBase); auto typeParam = std::make_shared(typeParamNamePtr.str()); auto result = zipTypeParameterWithArgument( location, genContext.typeParamsWithArgs, typeParam, type, false, genContext); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 7a9a4d06e..9b1460ed3 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -292,6 +292,7 @@ tslang_add_test(NAME test-compile-00-funcs-generic-typeof COMMAND test-runner "$ tslang_add_test(NAME test-compile-00-funcs-generic-arrow COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_generic_arrow.ts") tslang_add_test(NAME test-compile-00-funcs-generic-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_generic_iterator.ts") tslang_add_test(NAME test-compile-00-funcs-typeless-function-as-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_typeless_function_as_generic.ts") +tslang_add_test(NAME test-compile-00-nested-implicit-generics COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00nested_implicit_generics.ts") tslang_add_test(NAME test-compile-01-funcs-generic-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/01funcs_generic_iterator.ts") tslang_add_test(NAME test-compile-02-funcs-generic-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/02funcs_generic_iterator.ts") tslang_add_test(NAME test-compile-00-funcs-typed-generic-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_typed_generic_iterator.ts") @@ -810,6 +811,7 @@ tslang_add_test(NAME test-jit-01-funcs-generic COMMAND test-runner -jit "${PROJE tslang_add_test(NAME test-jit-00-funcs-generic-typeof COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_generic_with_typeof.ts") tslang_add_test(NAME test-jit-00-funcs-generic-arrow COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_generic_arrow.ts") tslang_add_test(NAME test-jit-00-funcs-typeless-function-as-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_typeless_function_as_generic.ts") +tslang_add_test(NAME test-jit-00-nested-implicit-generics COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00nested_implicit_generics.ts") tslang_add_test(NAME test-jit-00-funcs-generic-iterator COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_generic_iterator.ts") tslang_add_test(NAME test-jit-01-funcs-generic-iterator COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/01funcs_generic_iterator.ts") tslang_add_test(NAME test-jit-02-funcs-generic-iterator COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/02funcs_generic_iterator.ts") @@ -1831,6 +1833,7 @@ set(TSLANG_CORPUS 00funcs_nesting.ts 00funcs_typed_generic_iterator.ts 00funcs_typeless_function_as_generic.ts + 00nested_implicit_generics.ts 00funcs_vararg.ts 00function_overloads.ts 00gc_allocations_distinct.ts diff --git a/tslang/test/tester/tests/00nested_implicit_generics.ts b/tslang/test/tester/tests/00nested_implicit_generics.ts new file mode 100644 index 000000000..9c0d1b08f --- /dev/null +++ b/tslang/test/tester/tests/00nested_implicit_generics.ts @@ -0,0 +1,43 @@ +// An arrow with an untyped parameter inside another one with a parameter of the same name, which +// captures a variable of the function around both, failed to compile: "can't instantiate function +// ... not all generic types can be identified". Each untyped parameter makes its arrow generic in +// a type named after the parameter, so the inner `x`'s type had the same name as the outer `x`'s, +// and instantiating the outer arrow bound the inner one's type too. + +function render(screenWidth: number, screenHeight: number) { + const getPoint = (x, y) => { + const recenterX = x => (x - (screenWidth / 2.0)) / 2.0 / screenWidth; + const recenterY = y => -(y - (screenHeight / 2.0)) / 2.0 / screenHeight; + return recenterX(x) + recenterY(y); + }; + + assert(getPoint(512, 384) == 0, "getPoint(512, 384)"); + assert(getPoint(1024, 768) == 0, "getPoint(1024, 768)"); + const v = getPoint(300, 200); + assert(v - 0.016276 < 0.0001 && v - 0.016276 > -0.0001, "getPoint(300, 200)"); +} + +function untypedOuter(w, h) { + const add = (x, y) => { + const shift = x => x + w; + return shift(x) + y + h; + }; + + return add(1, 2); +} + +function sameNameNoCapture() { + const outer = (x) => { + const inner = x => x * 2; + return inner(x) + 1; + }; + + return outer(5); +} + +function main() { + render(1024, 768); + assert(untypedOuter(10, 100) == 113, "untypedOuter"); + assert(sameNameNoCapture() == 11, "sameNameNoCapture"); + print("done."); +}