diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index ada6f80ff..f264b4ad8 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -10458,6 +10458,47 @@ class MLIRGenImpl genericDeclExports << ss.str().str(); } + void addGenericInterfaceDeclarationToExport(GenericInterfaceInfo::TypePtr genericInterfaceInfo) + { + if (genericInterfaceInfo->interfaceType && isAddedToExport(genericInterfaceInfo->interfaceType)) + { + // already added + return; + } + + // same bounds-check as addGenericClassDeclarationToExport - a generic interface + // 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(genericInterfaceInfo->interfaceDeclaration->_end); + if (declEnd > genericInterfaceInfo->sourceFile->text.length()) + { + return; + } + + if (genericInterfaceInfo->interfaceType) + { + exportedTypes.insert(genericInterfaceInfo->interfaceType); + } + + // like a generic class, a generic interface 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 member signatures intact, no @dllimport marker - must be + // re-exported verbatim for parsePartialStatements to recompile per instantiation + // in the importer. + auto declText = convertWideToUTF8(getTextOfNodeFromSourceText( + genericInterfaceInfo->sourceFile->text, genericInterfaceInfo->interfaceDeclaration.as(), true)); + + SmallVector out; + llvm::raw_svector_ostream ss(out); + MLIRDeclarationPrinter dp(ss); + dp.printGenericClass(genericInterfaceInfo->elementNamespace, declText); + + genericDeclExports << ss.str().str(); + } + void addGenericTypeAliasDeclarationToExport(TypeAliasDeclaration typeAliasDeclarationAST, NamespaceInfo::TypePtr elementNamespace) { // same bounds-check as addGenericClassDeclarationToExport - a generic type alias diff --git a/tslang/lib/TypeScript/MLIRGenInterfaces.cpp b/tslang/lib/TypeScript/MLIRGenInterfaces.cpp index 065bc6479..b4f6598a2 100644 --- a/tslang/lib/TypeScript/MLIRGenInterfaces.cpp +++ b/tslang/lib/TypeScript/MLIRGenInterfaces.cpp @@ -505,6 +505,17 @@ namespace mlirgen auto fullNamePtr = getFullNamespaceName(namePtr); if (fullNameGenericInterfacesMap.count(fullNamePtr)) { + // already registered - but the registration itself typically happens during + // Stages::Discovering (before addGenericInterfaceDeclarationToExport's + // isAddedToExport gate, which only actually emits once stage == + // Stages::SourceGeneration, will do anything) - retry the export step alone + // using the existing GenericInterfaceInfo rather than skipping it entirely. + // Mirrors registerGenericClass's identical gotcha-3 fix. + if (getExportModifier(interfaceDeclarationAST)) + { + addGenericInterfaceDeclarationToExport(fullNameGenericInterfacesMap.lookup(fullNamePtr)); + } + return mlir::success(); } @@ -529,6 +540,18 @@ namespace mlirgen getGenericInterfacesMap().insert({namePtr, newGenericInterfacePtr}); fullNameGenericInterfacesMap.insert(fullNamePtr, newGenericInterfacePtr); + // support dynamic loading: a generic interface is never instantiated in this + // module if nothing here uses it concretely, so mlirGen(InterfaceDeclaration)'s + // own addInterfaceDeclarationToExport 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 interface declaration + // passes through regardless of whether it is ever instantiated locally. Mirrors + // registerGenericClass. + if (getExportModifier(interfaceDeclarationAST)) + { + addGenericInterfaceDeclarationToExport(newGenericInterfacePtr); + } + return mlir::success(); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 5c92827ad..0475c8849 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -886,6 +886,7 @@ add_test(NAME test-compile-export-import-class-static COMMAND test-runner "${PRO 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-type-alias-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_type_alias_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_type_alias_generic.ts") +add_test(NAME test-compile-export-import-interface-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_interface_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_interface_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") @@ -959,6 +960,7 @@ add_test(NAME test-compile-shared-export-import-class-structural-interface COMMA 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") add_test(NAME test-compile-shared-export-import-type-alias-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_type_alias_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_type_alias_generic.ts") +add_test(NAME test-compile-shared-export-import-interface-generic COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_interface_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_interface_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") @@ -1007,6 +1009,7 @@ add_test(NAME test-jit-shared-export-import-class-structural-interface COMMAND t 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") add_test(NAME test-jit-shared-export-import-type-alias-generic COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_type_alias_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_type_alias_generic.ts") +add_test(NAME test-jit-shared-export-import-interface-generic COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_interface_generic.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_interface_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_interface_generic.ts b/tslang/test/tester/tests/export_interface_generic.ts new file mode 100644 index 000000000..0c4e8ddbd --- /dev/null +++ b/tslang/test/tester/tests/export_interface_generic.ts @@ -0,0 +1,20 @@ +namespace M { + + // Cross-module counterpart to 00interface_generic.ts. GenericInterfaceInfo + // (MLIRGenStore.h) is structurally analogous to GenericClassInfo/ + // GenericFunctionInfo, both of whose cross-module export gaps needed + // dedicated fixes (PR #280, PR #285) - flagged as a plausible, unverified + // instance of the same "never routed into declExports" gap in + // decls-cross-module-declaration-mechanism / generic-function-cross-module-export-fix. + + export interface Box { + value: T; + get(): T; + } + + export interface Pair { + first: A; + second: B; + describe(): string; + } +} diff --git a/tslang/test/tester/tests/import_interface_generic.ts b/tslang/test/tester/tests/import_interface_generic.ts new file mode 100644 index 000000000..5a7f86669 --- /dev/null +++ b/tslang/test/tester/tests/import_interface_generic.ts @@ -0,0 +1,38 @@ +import './export_interface_generic' + +class NumberBox implements M.Box { + value: number; + + constructor(v: number) { + this.value = v; + } + + get(): number { + return this.value; + } +} + +class NumberStringPair implements M.Pair { + first: number; + second: string; + + constructor(a: number, b: string) { + this.first = a; + this.second = b; + } + + describe(): string { + return `${this.second}-${this.first}`; + } +} + +function main() { + const b: M.Box = new NumberBox(42); + assert(b.get() == 42); + assert(b.value == 42); + + const p: M.Pair = new NumberStringPair(1, "one"); + assert(p.describe() == "one-1"); + + print("done."); +}