Fix cross-module generic class export/import (4 co-operating bugs)#280
Merged
Merged
Conversation
A generic class's bare template declaration never reached the module's
declaration-export text at all: mlirGen(ClassLikeDeclaration) returns
early into registerGenericClass whenever processing the template (vs.
a concrete instantiation), before ever calling
addClassDeclarationToExport. Fixed by exporting the generic's own
ORIGINAL source text (not a signature-only declaration like concrete
classes get, since a generic has no compiled body anywhere for an
importer to link against - it needs the real, recompilable source)
via a new addGenericClassDeclarationToExport/printGenericClass.
That exposed two more bugs once generics started actually reaching
the reimport path:
- The existing declaration-reimport mechanism parses with a
"partial.d.ts" filename, which implicitly marks everything ambient/
external regardless of any per-declaration @dllimport marker - fine
for every other declaration kind (bodies already compiled elsewhere)
but fatal for a generic's real body. Fixed by giving generic
declarations their own separate embedded global
("__decls_generic_<file>_<hash>"), reparsed with a plain ".ts"
filename instead.
- Reimporting the generic's own text re-declares it a second time
inside the importer, which re-attempts the export call with a
stale source-file/AST-position pairing, throwing an unhandled
std::out_of_range (surfaced as a blocked "Microsoft Visual C++
Runtime Library" dialog - near-zero CPU, looked like a hang from
the outside). Fixed with a bounds-check that skips the redundant
re-export instead of crashing.
Also fixes a related, separately-triggered bug: a concrete generic
instantiation (e.g. Pair<number,string>) was inheriting its
template's `export` modifier, and the mangled name's comma broke the
linker's /EXPORT: directive syntax outright. Fixed by forcing
isExport/isPublic false on any class reached via a generic
specialization's codegen path.
Re-enables all 3 tiers (compile / -shared / -jit-shared) of
export_class_generic.ts/import_class_generic.ts, previously disabled.
Full suite: 789/789, no regressions.
This was referenced Jul 23, 2026
ASDAlexander77
added a commit
that referenced
this pull request
Jul 23, 2026
Last remaining item from decls-cross-module-declaration-mechanism.md's unverified list (after generic classes/PR #280, generic functions/PR #285, generic interfaces/PR #286): genericTypeAliasMap's registration path in mlirGen(TypeAliasDeclaration) never called any export function for the generic branch - only the non-generic else-branch calls addTypeDeclarationToExport. Confirmed with a cross-module repro: a generic type alias failed cross-module resolution under -shared. Unlike the other three generic declaration kinds, there is no dedicated GenericTypeAliasInfo struct tracking sourceFile/ elementNamespace for a generic type alias - genericTypeAliasMap's value is just {typeParams, typeNode}, since resolution (getTypeByTypeReference / resolveGenericTypeInNamespace) is pure compile-time type substitution on the stored TypeNode, never re-invoking mlirGen on the whole declaration the way a class/function/ interface instantiation does. So instead of adding a new Info struct, addGenericTypeAliasDeclarationToExport takes the AST node and namespace directly and is called inline from the one registration site, gated on `stage == Stages::SourceGeneration` (a given top-level TypeAliasDeclaration is visited exactly once per stage, so this is sufficient dedup - no "already registered" early-return branch exists here to retry from, unlike the class/function/interface fixes). Tested with both a single- and two-type-param alias (Box<T>, Pair<A,B>) to check for the classes/functions' export-bleed-on- instantiation bug; neither triggered it, matching generic interfaces' outcome - a type alias has no compiled body or runtime entity at all, so there's nothing for an instantiation to wrongly dllexport in the first place. All 3 tiers passed on the first attempt, no follow-up fix needed - the simplest of the four generic-declaration-kind fixes. New export_type_alias_generic.ts/import_type_alias_generic.ts cross-module test pair (compile, -shared compile, -jit -shared tiers). 802/802 green. This closes out the generic cross-module export arc. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A generic class's bare template declaration (
export class Box<T> {...}) never reached the exporting module's declaration-export text at all:mlirGen(ClassLikeDeclaration)returns early intoregisterGenericClasswhenever processing the template (as opposed to a concrete instantiation), before ever callingaddClassDeclarationToExport. This left namespaces containing only generic classes completely unresolvable from an importing module once a compiled.dll/.libalready existed ("can't resolve name: M").Four co-operating bugs fixed:
addGenericClassDeclarationToExport/printGenericClass..d.ts-mode ambient bleed - the existing declaration-reimport mechanism parses with a"partial.d.ts"filename, which implicitly marks everything ambient/external regardless of any per-declaration@dllimportmarker - fine for every other declaration kind (bodies already compiled elsewhere) but fatal for a generic's real body. Fixed by giving generic declarations their own separate embedded global ("__decls_generic_<file>_<hash>"), reparsed with a plain.tsfilename instead.std::out_of_range. This surfaced as a blocked "Microsoft Visual C++ Runtime Library" dialog - near-zero CPU usage, looked exactly like a hang from the outside (confirmed viaEnumWindows+ a ProcDump capture rather than assumed). Fixed with a bounds-check that skips the redundant re-export instead of crashing.isExportbleeding into instantiations - a concrete generic instantiation (e.g.Pair<number,string>) was inheriting its template'sexportmodifier, and the mangled name's comma broke the linker's/EXPORT:directive syntax outright (lld: invalid /export:). Fixed by forcingisExport/isPublicfalse on any class reached via a generic specialization's codegen path.Re-enables all 3 tiers (compile /
-shared/-jit -shared) ofexport_class_generic.ts/import_class_generic.ts, previously disabled inCMakeLists.txt.Test plan
ctest -C Debug -R "class-generic"- all tiers passctest -C Debug -j8- 789/789 passed, no regressions