diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index c18ba8c00..2cc81e10a 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -344,9 +344,32 @@ namespace mlirgen funcProto->setHasExtraFields(existLocalVarsInThisContextMap(funcProto->getName())); } + // a concrete instantiation of a generic function (e.g. identity) reprocesses + // the SAME AST node as the bare template (`export function identity(...)`), so + // processFunctionAttributes's own getExportModifier check below would otherwise mark + // this LOCAL, per-instantiation specialization as exported too - wrong for the same + // two reasons the class-generic fix (class-generic-declaration-export-fix) suppresses + // isExport on a specialized ClassInfo: (1) a specialization materialized by whichever + // module instantiates it is local to that module, not a re-export of it (the bare + // template's own export is handled separately via registerGenericFunctionLike / + // addGenericFunctionDeclarationToExport); (2) a multi-type-param instantiation's + // mangled name contains a raw comma (e.g. M.pair), a + // metacharacter in the linker's `/EXPORT:name[,option]` directive syntax - lld rejects + // it outright ("invalid /export:"). NOTE: can't key this off + // genContext.instantiateSpecializedFunction - mlirGenFunctionLikeDeclaration + // deliberately clears that flag on funcDeclGenContext before calling + // mlirGenFunctionPrototype (to stop nested generics being instantiated by mistake), + // so by the time we get here it always reads false. typeParamsWithArgs is the + // signal that actually survives - same one the class fix uses. Must be passed INTO + // processFunctionAttributes (not applied to its return value afterward) because it + // pushes the "export" attribute into `attrs` itself before returning. + auto suppressExportForGenericInstantiation = + functionLikeDeclarationBaseAST->typeParameters.size() > 0 && !genContext.typeParamsWithArgs.empty(); + SmallVector attrs; - auto dllExport = processFunctionAttributes(location, fullName, functionLikeDeclarationBaseAST, attrs, funcProtoGenContext); - + auto dllExport = processFunctionAttributes(location, fullName, functionLikeDeclarationBaseAST, attrs, funcProtoGenContext, + suppressExportForGenericInstantiation); + if (funcType) { auto it = getCaptureVarsMap().find(funcProto->getName()); diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index e3b7cdf29..5785c6537 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1778,7 +1778,8 @@ class MLIRGenImpl } bool processFunctionAttributes(mlir::Location location, StringRef fullName, - FunctionLikeDeclarationBase functionLikeDeclarationBaseAST, SmallVector &attrs, const GenContext &genContext) + FunctionLikeDeclarationBase functionLikeDeclarationBaseAST, SmallVector &attrs, const GenContext &genContext, + bool suppressExport = false) { #ifdef ADD_GC_ATTRIBUTE attrs.push_back({builder.getIdentifier(TS_GC_ATTRIBUTE), mlir::UnitAttr::get(builder.getContext())}); @@ -1802,8 +1803,9 @@ class MLIRGenImpl }); // add modifiers - auto dllExport = getExportModifier(functionLikeDeclarationBaseAST) - || ((functionLikeDeclarationBaseAST->internalFlags & InternalFlags::DllExport) == InternalFlags::DllExport); + auto dllExport = !suppressExport + && (getExportModifier(functionLikeDeclarationBaseAST) + || ((functionLikeDeclarationBaseAST->internalFlags & InternalFlags::DllExport) == InternalFlags::DllExport)); if (dllExport) { attrs.push_back({mlir::StringAttr::get(builder.getContext(), "export"), mlir::UnitAttr::get(builder.getContext())}); @@ -1861,6 +1863,17 @@ class MLIRGenImpl if (existGenericFunctionMap(name)) { + // already registered - but the registration itself typically happens during + // Stages::Discovering (before addGenericFunctionDeclarationToExport's + // isAddedToExport gate, which only actually emits once stage == + // Stages::SourceGeneration, will do anything) - retry the export step alone + // using the existing GenericFunctionInfo rather than skipping it entirely. + // Mirrors registerGenericClass's identical gotcha-3 fix. + if (getExportModifier(functionLikeDeclarationBaseAST)) + { + addGenericFunctionDeclarationToExport(lookupGenericFunctionMap(name)); + } + return {mlir::success(), name}; } @@ -1909,6 +1922,18 @@ class MLIRGenImpl getGenericFunctionMap().insert({namePtr, newGenericFunctionPtr}); fullNameGenericFunctionsMap.insert(fullNamePtr, newGenericFunctionPtr); + // support dynamic loading: a generic function is never instantiated in this + // module if nothing here uses it concretely, so mlirGenFunctionPrototype's own + // addFunctionDeclarationToExport call never runs for the bare template (it only + // fires for a SPECIALIZED instantiation) - the bare template needs to be + // exported here instead, the one place every generic function declaration + // passes through regardless of whether it is ever instantiated locally. Mirrors + // registerGenericClass. + if (getExportModifier(functionLikeDeclarationBaseAST)) + { + addGenericFunctionDeclarationToExport(newGenericFunctionPtr); + } + return {mlir::success(), name}; } @@ -10322,6 +10347,52 @@ class MLIRGenImpl declExports << ss.str().str(); } + void addGenericFunctionDeclarationToExport(GenericFunctionInfo::TypePtr genericFunctionInfo) + { + // funcType can be null when registered with ignoreFunctionArgsDetection=true + // (see registerGenericFunctionLike) - nothing to key isAddedToExport's dedup on + // in that case, so just fall through and re-print (matches + // addFunctionDeclarationToExport's own "TODO: add distinct declaration" gap for + // the non-generic case). + if (genericFunctionInfo->funcType && isAddedToExport(genericFunctionInfo->funcType)) + { + // already added + return; + } + + // same bounds-check as addGenericClassDeclarationToExport - a generic function + // re-declared while re-importing another module's embedded declarations still + // carries its own `export` keyword verbatim, but at that point `sourceFile` is + // the ambient/outer file, not the "partial" buffer parsePartialStatements + // actually parsed this declaration from. + auto declEnd = static_cast(genericFunctionInfo->functionDeclaration->_end); + if (declEnd > genericFunctionInfo->sourceFile->text.length()) + { + return; + } + + if (genericFunctionInfo->funcType) + { + exportedTypes.insert(genericFunctionInfo->funcType); + } + + // like a generic class, a generic function has no compiled body for any given + // instantiation: each importing module instantiates it locally, on demand, + // exactly like a same-file usage would. So the FULL original source - type + // parameters and body intact, no @dllimport marker - must be re-exported + // verbatim for parsePartialStatements to recompile per instantiation in the + // importer. + auto declText = convertWideToUTF8(getTextOfNodeFromSourceText( + genericFunctionInfo->sourceFile->text, genericFunctionInfo->functionDeclaration.as(), true)); + + SmallVector out; + llvm::raw_svector_ostream ss(out); + MLIRDeclarationPrinter dp(ss); + dp.printGenericClass(genericFunctionInfo->elementNamespace, declText); + + genericDeclExports << ss.str().str(); + } + void addGenericClassDeclarationToExport(GenericClassInfo::TypePtr genericClassInfo) { if (isAddedToExport(genericClassInfo->classType)) diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 9ebc986af..922371247 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -884,6 +884,7 @@ add_test(NAME test-compile-export-import-class-static COMMAND test-runner "${PRO # declaration's own re-export attempt hit a stale source-file/AST-position mismatch). # See class-generic-declaration-export-fix memory for the full account. add_test(NAME test-compile-export-import-class-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_generic.ts") +add_test(NAME test-compile-export-import-function-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function_generic.ts") add_test(NAME test-compile-export-import-class-accessor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_accessor.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_accessor.ts") add_test(NAME test-compile-export-import-class-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_indexer.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_indexer.ts") add_test(NAME test-compile-export-import-interface-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_interface_indexer.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_interface_indexer.ts") @@ -955,6 +956,7 @@ add_test(NAME test-compile-shared-export-import-class-structural-interface COMMA # add_test(NAME test-compile-shared-export-import-class-implements-interface-abstract COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_abstract.ts") # FIXED: see the matching test-compile-export-import-class-generic comment above (2026-07-22). add_test(NAME test-compile-shared-export-import-class-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_generic.ts") +add_test(NAME test-compile-shared-export-import-function-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function_generic.ts") # FIXED (2026-07-22): the -shared declaration-reconstruction path never printed real # get/set syntax for a class's accessors, only the underlying get_x/set_x funcOps as # ordinary methods - so property-style access ("Class member 'celsius' can't be found") @@ -1001,6 +1003,7 @@ add_test(NAME test-jit-shared-export-import-class-structural-interface COMMAND t # add_test(NAME test-jit-shared-export-import-class-implements-interface-abstract COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_abstract.ts") # FIXED: see the matching test-compile-export-import-class-generic comment above (2026-07-22). add_test(NAME test-jit-shared-export-import-class-generic COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_generic.ts") +add_test(NAME test-jit-shared-export-import-function-generic COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_function_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_function_generic.ts") # FIXED: see the matching test-compile-shared-export-import-class-accessor comment above (2026-07-22). add_test(NAME test-jit-shared-export-import-class-accessor COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_accessor.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_accessor.ts") add_test(NAME test-jit-shared-export-import-class-indexer COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_indexer.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_indexer.ts") diff --git a/tslang/test/tester/tests/export_function_generic.ts b/tslang/test/tester/tests/export_function_generic.ts new file mode 100644 index 000000000..bc156f20c --- /dev/null +++ b/tslang/test/tester/tests/export_function_generic.ts @@ -0,0 +1,18 @@ +namespace M { + + // Cross-module counterpart to 00funcs_generic.ts. GenericFunctionInfo + // (MLIRGenStore.h) is structurally analogous to GenericClassInfo, whose + // cross-module export gap took 4 fixes to close (PR #280, + // class-generic-declaration-export-fix) - per + // decls-cross-module-declaration-mechanism memory, generic + // functions/interfaces/type-aliases were flagged as plausible, + // unverified instances of the exact same gap. + + export function identity(x: T): T { + return x; + } + + export function pair(a: A, b: B): string { + return `${a}-${b}`; + } +} diff --git a/tslang/test/tester/tests/import_function_generic.ts b/tslang/test/tester/tests/import_function_generic.ts new file mode 100644 index 000000000..28e40a949 --- /dev/null +++ b/tslang/test/tester/tests/import_function_generic.ts @@ -0,0 +1,9 @@ +import './export_function_generic' + +function main() { + assert(M.identity(42) == 42); + assert(M.identity("hi") == "hi"); + assert(M.pair(1, "one") == "1-one"); + + print("done."); +}