Skip to content

Commit 4949eac

Browse files
Implement cross-module export for generic interfaces and add corresponding tests (#286)
1 parent fa5038f commit 4949eac

5 files changed

Lines changed: 125 additions & 0 deletions

File tree

tslang/lib/TypeScript/MLIRGenImpl.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10458,6 +10458,47 @@ class MLIRGenImpl
1045810458
genericDeclExports << ss.str().str();
1045910459
}
1046010460

10461+
void addGenericInterfaceDeclarationToExport(GenericInterfaceInfo::TypePtr genericInterfaceInfo)
10462+
{
10463+
if (genericInterfaceInfo->interfaceType && isAddedToExport(genericInterfaceInfo->interfaceType))
10464+
{
10465+
// already added
10466+
return;
10467+
}
10468+
10469+
// same bounds-check as addGenericClassDeclarationToExport - a generic interface
10470+
// re-declared while re-importing another module's embedded declarations still
10471+
// carries its own `export` keyword verbatim, but at that point `sourceFile` is
10472+
// the ambient/outer file, not the "partial" buffer parsePartialStatements
10473+
// actually parsed this declaration from.
10474+
auto declEnd = static_cast<size_t>(genericInterfaceInfo->interfaceDeclaration->_end);
10475+
if (declEnd > genericInterfaceInfo->sourceFile->text.length())
10476+
{
10477+
return;
10478+
}
10479+
10480+
if (genericInterfaceInfo->interfaceType)
10481+
{
10482+
exportedTypes.insert(genericInterfaceInfo->interfaceType);
10483+
}
10484+
10485+
// like a generic class, a generic interface has no compiled body for any given
10486+
// instantiation: each importing module instantiates it locally, on demand,
10487+
// exactly like a same-file usage would. So the FULL original source - type
10488+
// parameters and member signatures intact, no @dllimport marker - must be
10489+
// re-exported verbatim for parsePartialStatements to recompile per instantiation
10490+
// in the importer.
10491+
auto declText = convertWideToUTF8(getTextOfNodeFromSourceText(
10492+
genericInterfaceInfo->sourceFile->text, genericInterfaceInfo->interfaceDeclaration.as<Node>(), true));
10493+
10494+
SmallVector<char> out;
10495+
llvm::raw_svector_ostream ss(out);
10496+
MLIRDeclarationPrinter dp(ss);
10497+
dp.printGenericClass(genericInterfaceInfo->elementNamespace, declText);
10498+
10499+
genericDeclExports << ss.str().str();
10500+
}
10501+
1046110502
void addGenericTypeAliasDeclarationToExport(TypeAliasDeclaration typeAliasDeclarationAST, NamespaceInfo::TypePtr elementNamespace)
1046210503
{
1046310504
// same bounds-check as addGenericClassDeclarationToExport - a generic type alias

tslang/lib/TypeScript/MLIRGenInterfaces.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,17 @@ namespace mlirgen
505505
auto fullNamePtr = getFullNamespaceName(namePtr);
506506
if (fullNameGenericInterfacesMap.count(fullNamePtr))
507507
{
508+
// already registered - but the registration itself typically happens during
509+
// Stages::Discovering (before addGenericInterfaceDeclarationToExport's
510+
// isAddedToExport gate, which only actually emits once stage ==
511+
// Stages::SourceGeneration, will do anything) - retry the export step alone
512+
// using the existing GenericInterfaceInfo rather than skipping it entirely.
513+
// Mirrors registerGenericClass's identical gotcha-3 fix.
514+
if (getExportModifier(interfaceDeclarationAST))
515+
{
516+
addGenericInterfaceDeclarationToExport(fullNameGenericInterfacesMap.lookup(fullNamePtr));
517+
}
518+
508519
return mlir::success();
509520
}
510521

@@ -529,6 +540,18 @@ namespace mlirgen
529540
getGenericInterfacesMap().insert({namePtr, newGenericInterfacePtr});
530541
fullNameGenericInterfacesMap.insert(fullNamePtr, newGenericInterfacePtr);
531542

543+
// support dynamic loading: a generic interface is never instantiated in this
544+
// module if nothing here uses it concretely, so mlirGen(InterfaceDeclaration)'s
545+
// own addInterfaceDeclarationToExport call never runs for the bare template (it
546+
// only fires for a SPECIALIZED instantiation) - the bare template needs to be
547+
// exported here instead, the one place every generic interface declaration
548+
// passes through regardless of whether it is ever instantiated locally. Mirrors
549+
// registerGenericClass.
550+
if (getExportModifier(interfaceDeclarationAST))
551+
{
552+
addGenericInterfaceDeclarationToExport(newGenericInterfacePtr);
553+
}
554+
532555
return mlir::success();
533556
}
534557

tslang/test/tester/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@ add_test(NAME test-compile-export-import-class-static COMMAND test-runner "${PRO
886886
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")
887887
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")
888888
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")
889+
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")
889890
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")
890891
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")
891892
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
959960
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")
960961
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")
961962
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")
963+
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")
962964
# FIXED (2026-07-22): the -shared declaration-reconstruction path never printed real
963965
# get/set syntax for a class's accessors, only the underlying get_x/set_x funcOps as
964966
# 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
10071009
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")
10081010
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")
10091011
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")
1012+
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")
10101013
# FIXED: see the matching test-compile-shared-export-import-class-accessor comment above (2026-07-22).
10111014
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")
10121015
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")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace M {
2+
3+
// Cross-module counterpart to 00interface_generic.ts. GenericInterfaceInfo
4+
// (MLIRGenStore.h) is structurally analogous to GenericClassInfo/
5+
// GenericFunctionInfo, both of whose cross-module export gaps needed
6+
// dedicated fixes (PR #280, PR #285) - flagged as a plausible, unverified
7+
// instance of the same "never routed into declExports" gap in
8+
// decls-cross-module-declaration-mechanism / generic-function-cross-module-export-fix.
9+
10+
export interface Box<T> {
11+
value: T;
12+
get(): T;
13+
}
14+
15+
export interface Pair<A, B> {
16+
first: A;
17+
second: B;
18+
describe(): string;
19+
}
20+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import './export_interface_generic'
2+
3+
class NumberBox implements M.Box<number> {
4+
value: number;
5+
6+
constructor(v: number) {
7+
this.value = v;
8+
}
9+
10+
get(): number {
11+
return this.value;
12+
}
13+
}
14+
15+
class NumberStringPair implements M.Pair<number, string> {
16+
first: number;
17+
second: string;
18+
19+
constructor(a: number, b: string) {
20+
this.first = a;
21+
this.second = b;
22+
}
23+
24+
describe(): string {
25+
return `${this.second}-${this.first}`;
26+
}
27+
}
28+
29+
function main() {
30+
const b: M.Box<number> = new NumberBox(42);
31+
assert(b.get() == 42);
32+
assert(b.value == 42);
33+
34+
const p: M.Pair<number, string> = new NumberStringPair(1, "one");
35+
assert(p.describe() == "one-1");
36+
37+
print("done.");
38+
}

0 commit comments

Comments
 (0)