From 67ad99ea968574b34a53d63589b72d76796fa2eb Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 23 Jul 2026 09:05:04 +0100 Subject: [PATCH] Implement cross-module export for generic interfaces and add corresponding tests --- tslang/lib/TypeScript/MLIRGenImpl.h | 41 +++++++++++++++++++ tslang/lib/TypeScript/MLIRGenInterfaces.cpp | 23 +++++++++++ tslang/test/tester/CMakeLists.txt | 3 ++ .../tester/tests/export_interface_generic.ts | 20 +++++++++ .../tester/tests/import_interface_generic.ts | 38 +++++++++++++++++ 5 files changed, 125 insertions(+) create mode 100644 tslang/test/tester/tests/export_interface_generic.ts create mode 100644 tslang/test/tester/tests/import_interface_generic.ts diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index e3b7cdf29..e4efe5aec 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -10367,6 +10367,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(); + } + auto getNamespaceName() -> StringRef { return currentNamespace->name; 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 9ebc986af..64033d1c6 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-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") @@ -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-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") @@ -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-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."); +}