From 7110f1f2756e13565206f9f347b2b551c909787d Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 28 Sep 2026 11:04:25 +0100 Subject: [PATCH 1/2] A -shared library's class can hold a specialization of its own generic An importer of a -shared library whose exported class has a field typed by a specialization of a generic from the same module (`children: Box`) failed: "generic type Box can't be found". Two things: - The specialization was declared on its own in __decls, as `class Box>`, and fields named it that way; neither parses. It is no longer declared: the importer makes it from the generic, as a use in its own source would. The declaration printer names it `Box` (MLIRPrinter::getClassSpecialization, supplied by MLIRGen, which has the generic registry), and the generic - this module's own, even when not marked `export` - and the type arguments are exported in its place. - __decls_generic_ sorted after __decls_, so the field was read before the generic existed. The importer loads generics first. Co-Authored-By: Claude Opus 5.5 --- .../MLIRLogic/MLIRDeclarationPrinter.h | 8 +- .../TypeScript/MLIRLogic/MLIRPrinter.h | 27 ++++++ tslang/lib/TypeScript/DeclarationPrinter.cpp | 1 + tslang/lib/TypeScript/MLIRGenImpl.h | 86 ++++++++++++++++--- tslang/lib/TypeScript/MLIRGenModule.cpp | 8 ++ tslang/test/tester/CMakeLists.txt | 7 +- 6 files changed, 123 insertions(+), 14 deletions(-) diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h b/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h index 5a4cf46b7..a307cf45c 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h @@ -14,7 +14,11 @@ namespace typescript raw_ostream &os; public: - MLIRDeclarationPrinter(raw_ostream &os) : os(os) {}; + // see MLIRPrinter::getClassSpecialization + using ClassSpecializationFn = std::function &)>; + + MLIRDeclarationPrinter(raw_ostream &os, ClassSpecializationFn getClassSpecialization = {}) + : os(os), getClassSpecialization(std::move(getClassSpecialization)) {}; void printTypeDeclaration(StringRef, NamespaceInfo::TypePtr, mlir::Type); void printEnum(StringRef, NamespaceInfo::TypePtr, mlir::DictionaryAttr); @@ -25,6 +29,8 @@ namespace typescript void printGenericClass(NamespaceInfo::TypePtr, StringRef); protected: + ClassSpecializationFn getClassSpecialization; + void newline(); void printBeforeDeclaration(); void printAsFieldName(mlir::Attribute); diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h b/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h index 4c32b201d..f308d9c94 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h @@ -35,6 +35,12 @@ class MLIRPrinter // the whole declaration. Off for diagnostics. bool quoteNonIdentifierFieldNames = false; + // a specialization of a generic class printed the way it is written, `Box`, rather + // than by its internal name `Box>`, which does not parse back. Gives + // the generic's name and the type arguments in order; the printer has no class registry, so + // declaration text (__decls) supplies it. Unset for diagnostics. + std::function &)> getClassSpecialization; + template void printFieldName(T &out, mlir::Attribute id) { @@ -357,6 +363,27 @@ class MLIRPrinter printType(out, t.getElementType()); }) .template Case([&](auto t) { + std::string genericName; + SmallVector typeArgs; + if (getClassSpecialization && getClassSpecialization(t, genericName, typeArgs)) + { + out << genericName.c_str() << "<"; + auto first = true; + for (auto typeArg : typeArgs) + { + if (!first) + { + out << ", "; + } + + first = false; + printType(out, typeArg); + } + + out << ">"; + return; + } + out << t.getName().getValue().str().c_str(); }) .template Case([&](auto t) { diff --git a/tslang/lib/TypeScript/DeclarationPrinter.cpp b/tslang/lib/TypeScript/DeclarationPrinter.cpp index d02d56b4a..0bfeb5bcf 100644 --- a/tslang/lib/TypeScript/DeclarationPrinter.cpp +++ b/tslang/lib/TypeScript/DeclarationPrinter.cpp @@ -27,6 +27,7 @@ namespace typescript MLIRPrinter mp{}; mp.printBoxedObjectTypes = true; mp.quoteNonIdentifierFieldNames = true; + mp.getClassSpecialization = getClassSpecialization; mp.printType(os, type); } diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 034bc0acc..8b910b502 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -11879,7 +11879,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printTypeDeclaration(name, elementNamespace, type); declExports << ss.str().str(); @@ -11899,7 +11899,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.print(interfaceInfo); declExports << ss.str().str(); @@ -11919,7 +11919,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printEnum(name, elementNamespace, enumType.getValues()); declExports << ss.str().str(); @@ -11934,7 +11934,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printVariableDeclaration(name, elementNamespace, type, isConst, dllName); declExports << ss.str().str(); @@ -11949,12 +11949,56 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.print(funcProto->getNameWithoutNamespace(), elementNamespace, funcProto->getFuncType(), dllName); declExports << ss.str().str(); } + // A specialization of a generic class - its generic and type arguments, in order. + GenericClassInfo::TypePtr getClassSpecializationOf(ClassInfo::TypePtr classInfo, SmallVectorImpl &typeArgs) + { + if (!classInfo || !classInfo->originClassType || classInfo->originClassType == classInfo->classType) + { + return nullptr; + } + + auto genericClassInfo = getGenericClassInfoByFullName(classInfo->originClassType.getName().getValue()); + if (!genericClassInfo) + { + return nullptr; + } + + for (auto &typeParam : genericClassInfo->typeParams) + { + auto found = classInfo->typeParamsWithArgs.find(typeParam->getName()); + if (found == classInfo->typeParamsWithArgs.end()) + { + return nullptr; + } + + typeArgs.push_back(found->getValue().second); + } + + return genericClassInfo; + } + + // see MLIRPrinter::getClassSpecialization + MLIRDeclarationPrinter::ClassSpecializationFn classSpecializationNamer() + { + return [this](mlir_ts::ClassType classType, std::string &genericName, SmallVectorImpl &typeArgs) { + auto genericClassInfo = getClassSpecializationOf(getClassInfoByFullName(classType.getName().getValue()), typeArgs); + if (!genericClassInfo) + { + typeArgs.clear(); + return false; + } + + genericName = genericClassInfo->fullName.str(); + return true; + }; + } + void addClassDeclarationToExport(ClassInfo::TypePtr newClassPtr) { if (isAddedToExport(newClassPtr->classType)) @@ -11965,11 +12009,33 @@ class MLIRGenImpl exportedTypes.insert(newClassPtr->classType); + // A specialization (Box, the type of an exported class's field) is not declared on + // its own: the importer makes it from the generic, as a use in its own source would, and + // names it `Box` (classSpecializationNamer). Declared, it came out as + // `class Box>`, which does not parse. What it needs is the generic + // - exported here when it is this module's own, even if not marked `export` - and its + // type arguments. + SmallVector typeArgs; + if (auto genericClassInfo = getClassSpecializationOf(newClassPtr, typeArgs)) + { + if (genericClassInfo->fileName == mainSourceFileName) + { + addGenericClassDeclarationToExport(genericClassInfo); + } + + for (auto typeArg : typeArgs) + { + addDependancyTypesToExport(typeArg); + } + + return; + } + addDependancyTypesToExport(newClassPtr->classType); SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.print(newClassPtr); declExports << ss.str().str(); @@ -12015,7 +12081,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printGenericClass(genericFunctionInfo->elementNamespace, declText); genericDeclExports << ss.str().str(); @@ -12060,7 +12126,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printGenericClass(genericClassInfo->elementNamespace, declText); genericDeclExports << ss.str().str(); @@ -12101,7 +12167,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printGenericClass(genericInterfaceInfo->elementNamespace, declText); genericDeclExports << ss.str().str(); @@ -12131,7 +12197,7 @@ class MLIRGenImpl SmallVector out; llvm::raw_svector_ostream ss(out); - MLIRDeclarationPrinter dp(ss); + MLIRDeclarationPrinter dp(ss, classSpecializationNamer()); dp.printGenericClass(elementNamespace, declText); genericDeclExports << ss.str().str(); diff --git a/tslang/lib/TypeScript/MLIRGenModule.cpp b/tslang/lib/TypeScript/MLIRGenModule.cpp index fa9bb43b8..b1b1d4125 100644 --- a/tslang/lib/TypeScript/MLIRGenModule.cpp +++ b/tslang/lib/TypeScript/MLIRGenModule.cpp @@ -1343,6 +1343,14 @@ namespace mlirgen addGlobalConstructor(location, fullInitGlobalFuncName); } + // Generics first: a declaration elsewhere names their specializations (a field typed + // `Box`), and the symbols come sorted, which put __decls_ before + // __decls_generic_ - "generic type Box can't be found". Registering a generic + // only records it, so one naming a class declared after it is fine. + std::stable_partition(symbols.begin(), symbols.end(), [](StringRef symbol) { + return symbol.starts_with(std::string(SHARED_LIB_DECLARATIONS_2UNDERSCORE) + "_generic_"); + }); + // A library can hold several modules (test-runner -shared links them all into one), each // with its own __decls_. A module already in this module - imported as source, // or through another library - is not declared again; see mlirGenInclude. diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index dabe5728a..fc53e2842 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2471,9 +2471,10 @@ tslang_add_import_tests(import-bindings-namespace "${import_bindings_dir}" impor # A generic class specialized while its module was imported lost its members (#231). set(import_specialization_dir "${PROJECT_SOURCE_DIR}/test/tester/import-specialization") -# Static linking only: through a -shared library the importer cannot name Box yet ("generic type -# Box can't be found"), a separate gap. -tslang_add_test(NAME test-compile-import-specialization COMMAND test-runner "${import_specialization_dir}/import_specialization.ts" "${import_specialization_dir}/box_module.ts") +# Through a -shared library the specialization was declared by its internal name +# (`class Box>`), and before the generic it is made from ("generic type Box +# can't be found"). +tslang_add_import_tests(import-specialization "${import_specialization_dir}" import_specialization box_module) # The same for a shared library: imported directly and through a source module - see # shared-import-reimport.cmake. From 3cbff1e520a7dc6348521ca17273aff7ddf01285 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Mon, 28 Sep 2026 15:49:42 +0100 Subject: [PATCH 2/2] A specialization's name has no `@`: ld reads it as a symbol version A class type argument printed as a symbol reference, so a specialization was named Box>, and so were its symbols. In an ELF symbol name `@` separates the version, and ld failed to link a -shared library that exports one ("version node not found for symbol Box<...>..vtbl"). The `@` before a symbol reference is now left out of the name, and one inside a quoted literal type is written as MLIR's escape \40. Windows linked such names; they change there too, so a prebuilt DefaultLib must be rebuilt. Co-Authored-By: Claude Opus 5.5 --- .../TypeScript/MLIRLogic/MLIRPrinter.h | 2 +- tslang/lib/TypeScript/MLIRGenImpl.h | 48 +++++++++++++++++-- tslang/test/tester/CMakeLists.txt | 4 +- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h b/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h index f308d9c94..0f877d05a 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h @@ -36,7 +36,7 @@ class MLIRPrinter bool quoteNonIdentifierFieldNames = false; // a specialization of a generic class printed the way it is written, `Box`, rather - // than by its internal name `Box>`, which does not parse back. Gives + // than by its internal name `Box>`, which does not parse back. Gives // the generic's name and the type arguments in order; the printer has no class registry, so // declaration text (__decls) supplies it. Unset for diagnostics. std::function &)> getClassSpecialization; diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 8b910b502..e697bac20 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -9983,6 +9983,46 @@ class MLIRGenImpl std::pair mlirGen(ClassLikeDeclaration classDeclarationAST, const GenContext &genContext); + // A type argument in a specialization's name (Box>), which becomes the name + // of its symbols. MLIR prints a class or interface as a symbol reference, `@Tree`, but an `@` + // in an ELF symbol name is read as its version (`name@VERSION`), and ld fails to link a + // shared library that exports it. The `@` before a symbol reference is left out; one inside a + // quoted string (a literal type) is written as MLIR's escape `\40`, so no two names meet. + static void appendTypeToSymbolName(std::string &name, mlir::Type type) + { + std::string printed; + llvm::raw_string_ostream s(printed); + s << type; + + auto quoted = false; + for (size_t i = 0; i < printed.size(); i++) + { + auto c = printed[i]; + if (quoted && c == '\\' && i + 1 < printed.size()) + { + name.push_back(c); + name.push_back(printed[++i]); + continue; + } + + if (c == '"') + { + quoted = !quoted; + } + else if (c == '@') + { + if (quoted) + { + name.append("\\40"); + } + + continue; + } + + name.push_back(c); + } + } + void appendSpecializedTypeNames(std::string &name, llvm::SmallVector &typeParams, const GenContext &genContext) { @@ -9998,8 +10038,7 @@ class MLIRGenImpl auto type = getResolveTypeParameter(typeParam->getName(), false, genContext); if (type) { - llvm::raw_string_ostream s(name); - s << type; + appendTypeToSymbolName(name, type); } else { @@ -10751,8 +10790,7 @@ class MLIRGenImpl auto type = getType(typeParam, genContext); if (type) { - llvm::raw_string_ostream s(name); - s << type; + appendTypeToSymbolName(name, type); } else { @@ -12012,7 +12050,7 @@ class MLIRGenImpl // A specialization (Box, the type of an exported class's field) is not declared on // its own: the importer makes it from the generic, as a use in its own source would, and // names it `Box` (classSpecializationNamer). Declared, it came out as - // `class Box>`, which does not parse. What it needs is the generic + // `class Box>`, which does not parse. What it needs is the generic // - exported here when it is this module's own, even if not marked `export` - and its // type arguments. SmallVector typeArgs; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index fc53e2842..235bf450b 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2472,8 +2472,8 @@ tslang_add_import_tests(import-bindings-namespace "${import_bindings_dir}" impor # A generic class specialized while its module was imported lost its members (#231). set(import_specialization_dir "${PROJECT_SOURCE_DIR}/test/tester/import-specialization") # Through a -shared library the specialization was declared by its internal name -# (`class Box>`), and before the generic it is made from ("generic type Box -# can't be found"). +# (`class Box>`), and before the generic it is made from ("generic type Box +# can't be found"). On Linux the name had an `@` (`@Tree`), which ld reads as a symbol version. tslang_add_import_tests(import-specialization "${import_specialization_dir}" import_specialization box_module) # The same for a shared library: imported directly and through a source module - see