diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h b/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h index 986d8b274..7bdef2c86 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h @@ -36,6 +36,7 @@ namespace typescript void printFunction(StringRef, ArrayRef, mlir::Type); void printMethod(bool, StringRef, ArrayRef, mlir::Type, mlir::Type); void printAccessor(bool, StringRef, StringRef, mlir_ts::AccessLevel, ArrayRef, mlir::Type, mlir::Type); + void printIndexer(mlir::Type, mlir::Type); void printNamespaceBegin(NamespaceInfo::TypePtr); void printNamespaceEnd(NamespaceInfo::TypePtr); void print(mlir::Type); diff --git a/tslang/lib/TypeScript/DeclarationPrinter.cpp b/tslang/lib/TypeScript/DeclarationPrinter.cpp index cde0e14bc..3fb0a84a0 100644 --- a/tslang/lib/TypeScript/DeclarationPrinter.cpp +++ b/tslang/lib/TypeScript/DeclarationPrinter.cpp @@ -287,7 +287,20 @@ namespace typescript newline(); } - void MLIRDeclarationPrinter::printTypeDeclaration(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir::Type type) + void MLIRDeclarationPrinter::printIndexer(mlir::Type indexType, mlir::Type resultType) + { + os.indent(4); + + os << "[p0: "; + print(indexType); + os << "]: "; + print(resultType); + os << ";"; + + newline(); + } + + void MLIRDeclarationPrinter::printTypeDeclaration(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir::Type type) { printNamespaceBegin(elementNamespace); @@ -493,6 +506,21 @@ namespace typescript newline(); } + // index signature ([x: T]: U;) - not stored in storageType's fields, + // and (unlike accessors) its get/set methods are ordinary named + // "get"/"set" methods already covered by the methods loop below; only + // the signature declaration itself is missing from a plain method + // reprint, and without it a reimporting module's ClassInfo::indexes + // stays empty so `obj[i]`/`super[i]` can't resolve (see + // super-index-access-gap-fix memory). + for (auto indexInfo : classType->indexes) + { + if (!indexInfo.indexSignature || indexInfo.indexSignature.getNumResults() == 0) + continue; + + printIndexer(indexInfo.indexSignature.getInput(0), indexInfo.indexSignature.getResult(0)); + } + // methods (including static) for (auto method : classType->methods) { diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index c1051e2be..2e363b982 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -715,6 +715,76 @@ namespace mlirgen // same as it always did for `super.method()`/`super.`. auto effectiveThisValue = getThisRefOfClass(location, classInfo->classType, thisValue, isSuperClass, genContext); + if (classInfo->isDynamicImport) + { + // same reasoning as ClassAccessorAccess's isDynamicImport branch: a plain + // FlatSymbolRefAttr lowers to a call the static linker must resolve, but a + // -shared importer never links against the exporter's .lib (see + // cross-module-instanceof-investigation.md) - resolve get/set to VALUEs + // (direct ref / dlsym-style global / inline SearchForAddressOfSymbolOp) and + // drive the call through ThisIndirectIndexAccessorOp instead. + auto resolveIndexFunc = [&](FunctionEntry funcEntry) -> mlir::Value { + if (!funcEntry) + { + return mlir::Value(); + } + + StringRef funcName = funcEntry.name; + auto effectiveFuncType = funcEntry.funcType; + + if (auto funcOp = theModule.lookupSymbol(funcName)) + { + if (!funcOp.getBody().empty()) + { + return builder.create( + location, effectiveFuncType, mlir::FlatSymbolRefAttr::get(builder.getContext(), funcName)); + } + } + + auto globalFuncVar = resolveFullNameIdentifier(location, funcName, false, genContext); + if (!globalFuncVar) + { + auto symbolNameValue = V(mlirGenStringValue(location, funcName.str(), true)); + auto referenceToFuncOpaque = builder.create( + location, getOpaqueType(), symbolNameValue); + auto castResult = cast(location, effectiveFuncType, referenceToFuncOpaque, genContext); + if (castResult.failed_or_no_value()) + { + emitError(location, "Class member '") << funcName << "' can't be resolved (dynamic import)"; + return mlir::Value(); + } + + globalFuncVar = V(castResult); + } + + return globalFuncVar; + }; + + auto getterValue = resolveIndexFunc(getFunc); + auto setterValue = resolveIndexFunc(setFunc); + if ((getFunc && !getterValue) || (setFunc && !setterValue)) + { + return mlir::Value(); + } + + auto opaqueThisType = mlir_ts::OpaqueType::get(builder.getContext()); + if (!getterValue) + { + getterValue = builder.create( + location, mlir_ts::FunctionType::get(builder.getContext(), {opaqueThisType, argumentType}, {indexResultType}, false)); + } + + if (!setterValue) + { + setterValue = builder.create( + location, mlir_ts::FunctionType::get(builder.getContext(), {opaqueThisType, argumentType, indexResultType}, {}, false)); + } + + auto thisIndirectIndexAccessorOp = builder.create( + location, indexResultType, effectiveThisValue, V(result), getterValue, setterValue, mlir::Value()); + return thisIndirectIndexAccessorOp.getResult(0); + } + auto thisIndexAccessorOp = builder.create( location, indexResultType, effectiveThisValue, V(result), getFunc ? mlir::FlatSymbolRefAttr::get(builder.getContext(), getFunc.name) diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 9f9c04aa4..ff4a28e74 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -885,6 +885,7 @@ add_test(NAME test-compile-export-import-class-static COMMAND test-runner "${PRO # 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-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-class-implements-interface-multilevel COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_multilevel.ts") add_test(NAME test-compile-export-import-class-implements-interface-optional COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_optional.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_optional.ts") add_test(NAME test-compile-export-import-class-implements-interface-abstract COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_implements_interface_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_implements_interface_abstract.ts") @@ -959,6 +960,7 @@ add_test(NAME test-compile-shared-export-import-class-generic COMMAND test-runne # never populated the reimported class's accessors list. See print(ClassInfo::TypePtr) # in DeclarationPrinter.cpp. add_test(NAME test-compile-shared-export-import-class-accessor COMMAND test-runner -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-compile-shared-export-import-class-indexer COMMAND test-runner -shared "${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-shared-export-import-object-literal-with-interface COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_with_interface.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_with_interface.ts") add_test(NAME test-compile-shared-export-import-object-literal-untyped COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_untyped.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_untyped.ts") add_test(NAME test-compile-shared-export-import-object-literal-untyped-multi-method COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_untyped_multi_method.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_untyped_multi_method.ts") @@ -999,6 +1001,7 @@ add_test(NAME test-jit-shared-export-import-class-structural-interface COMMAND t 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") # 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") add_test(NAME test-jit-shared-export-import-object-literal-with-interface COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_with_interface.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_with_interface.ts") add_test(NAME test-jit-shared-export-import-object-literal-untyped COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_untyped.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_untyped.ts") add_test(NAME test-jit-shared-export-import-object-literal-untyped-multi-method COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_untyped_multi_method.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_untyped_multi_method.ts") diff --git a/tslang/test/tester/tests/export_class_indexer.ts b/tslang/test/tester/tests/export_class_indexer.ts new file mode 100644 index 000000000..80729fa3d --- /dev/null +++ b/tslang/test/tester/tests/export_class_indexer.ts @@ -0,0 +1,21 @@ +namespace M { + + // A get/set indexer pair defined in the exporting module, overridden in + // an importer subclass which calls back into the base indexer via + // `super[index]`. Cross-module counterpart to 00class_indexer_super.ts + // (same-file) - see super-index-access-gap-fix memory. + + export class Storage { + protected _val: string = ""; + + [index: number]: string; + + get(index: number): string { + return this._val; + } + + set(index: number, value: string) { + this._val = value; + } + } +} diff --git a/tslang/test/tester/tests/import_class_indexer.ts b/tslang/test/tester/tests/import_class_indexer.ts new file mode 100644 index 000000000..c776529ca --- /dev/null +++ b/tslang/test/tester/tests/import_class_indexer.ts @@ -0,0 +1,21 @@ +import './export_class_indexer' + +class UpperStorage extends M.Storage { + [index: number]: string; + + get(index: number): string { + return super[index] + "!"; + } + + set(index: number, value: string) { + super[index] = "U:" + value; + } +} + +function main() { + const s = new UpperStorage(); + s[0] = "hi"; + assert(s[0] == "U:hi!"); + + print("done."); +}