diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index 6b6c91222..a6cbabe92 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -779,6 +779,8 @@ struct ClassInfo bool hasRTTI; ProcessingStages processingAtEvaluation; ProcessingStages processing; + // the module `processing` was reached in: a discovery module is thrown away with what it holds + mlir::Operation *processingModule = nullptr; ClassInfo() : isDeclaration(false), hasNew(false), hasConstructor(false), constructorAccessLevel(mlir_ts::AccessLevel::Public), hasInitializers(false), hasStaticConstructor(false), diff --git a/tslang/lib/TypeScript/MLIRGenClasses.cpp b/tslang/lib/TypeScript/MLIRGenClasses.cpp index 653eff3d0..559410bb1 100644 --- a/tslang/lib/TypeScript/MLIRGenClasses.cpp +++ b/tslang/lib/TypeScript/MLIRGenClasses.cpp @@ -115,12 +115,21 @@ namespace mlirgen // do not process specialized class second time; // TODO: investigate why classType is provided already for class - if (testProcessingState(newClassPtr, ProcessingStages::Processing, genContext)) + // Not across modules, though: an import met during discovery is generated in full, but + // into the discovery module, which is erased. The members of a specialization made there + // are gone, and the module that uses it has to have its own. + if (testProcessingState(newClassPtr, ProcessingStages::Processing, genContext) + && (genContext.allowPartialResolve || newClassPtr->processingModule == theModule.getOperation())) { return {mlir::success(), newClassPtr->classType.getName().getValue()}; } } + if (!genContext.allowPartialResolve) + { + newClassPtr->processingModule = theModule.getOperation(); + } + setProcessingState(newClassPtr, ProcessingStages::Processing, genContext); auto location = loc(classDeclarationAST); diff --git a/tslang/lib/TypeScript/MLIRGenGenerics.cpp b/tslang/lib/TypeScript/MLIRGenGenerics.cpp index 5b7dfd0ed..63dbf37e2 100644 --- a/tslang/lib/TypeScript/MLIRGenGenerics.cpp +++ b/tslang/lib/TypeScript/MLIRGenGenerics.cpp @@ -1141,6 +1141,10 @@ namespace mlirgen << " name: " << typeAlias.getKey() << " type: " << typeAlias.getValue(); llvm::dbgs() << "\n";); + // generated in full even inside an imported file; see the overload below + MLIRValueGuard declarationModeGuard(declarationMode); + declarationMode = false; + // create new instance of interface with TypeArguments if (mlir::failed(std::get<0>(mlirGen(genericClassInfo->classDeclaration, genericTypeGenContext)))) { @@ -1217,6 +1221,13 @@ namespace mlirgen // llvm_unreachable(s.c_str()); } + // A specialization belongs to every module that uses it (its members are linkonce_odr), + // so it is generated in full even while an imported file is only being declared: the + // module keeps this copy for its own uses too, and must not depend on the imported + // module's object happening to keep the members it needs. + MLIRValueGuard declarationModeGuard(declarationMode); + declarationMode = false; + auto res = std::get<0>(mlirGen(genericClassInfo->classDeclaration, genericTypeGenContext)); count--; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 630d58d79..9710f38bc 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2433,6 +2433,12 @@ set(import_bindings_dir "${PROJECT_SOURCE_DIR}/test/tester/import-bindings") tslang_add_import_tests(import-bindings-renamed "${import_bindings_dir}" import_renamed names_module) tslang_add_import_tests(import-bindings-namespace "${import_bindings_dir}" import_namespace names_module) +# 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") + # The same for a shared library: imported directly and through a source module - see # shared-import-reimport.cmake. if (WIN32) diff --git a/tslang/test/tester/import-specialization/box_module.ts b/tslang/test/tester/import-specialization/box_module.ts new file mode 100644 index 000000000..8094e9f49 --- /dev/null +++ b/tslang/test/tester/import-specialization/box_module.ts @@ -0,0 +1,33 @@ +// A module whose class has a field of a generic class specialized on it. + +export class Box { + items: T[] = []; + + [index: int]: T; + + get(index: int): T { + return this.items[index]; + } + + set(index: int, value: T) { + this.items[index] = value; + } + + add(v: T) { + this.items.push(v); + } + + get length() { + return this.items.length; + } +} + +export class Tree { + name: string; + children: Box; + + constructor(name: string) { + this.name = name; + this.children = new Box(); + } +} diff --git a/tslang/test/tester/import-specialization/import_specialization.ts b/tslang/test/tester/import-specialization/import_specialization.ts new file mode 100644 index 000000000..54c70007f --- /dev/null +++ b/tslang/test/tester/import-specialization/import_specialization.ts @@ -0,0 +1,34 @@ +// Box is first specialized while its module is imported, during this file's discovery pass. +// Its members went into the discovery module, which is thrown away, and the specialization was +// then taken as done: calls to them here failed to lower ("failed to legalize operation +// 'ts.SymbolCallInternal'"), found in the BrowserLib sources attached to #231. + +import { Tree } from "./box_module"; + +function find(n: Tree, name: string): Tree | null { + if (n.name == name) { + return n; + } + + for (let i = 0; i < n.children.length; i++) { + const found = find(n.children[i], name); + if (found !== null) { + return found; + } + } + + return null; +} + +function main() { + const root = new Tree("root"); + const a = new Tree("a"); + root.children.add(a); + a.children.add(new Tree("b")); + + assert(find(root, "b") !== null); + assert(find(root, "z") === null); + assert(root.children[0].name == "a"); + + print("done."); +}