diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index 5e50064e6..b250c8257 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -348,7 +348,7 @@ struct InterfaceInfo for (auto &field : fields) { - tupleFields.push_back({field.id, field.type, false, mlir_ts::AccessLevel::Public}); + tupleFields.push_back({field.id, field.type, field.isConditional, mlir_ts::AccessLevel::Public}); } return mlir::success(); diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index d78553977..43cb19960 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -40,6 +40,22 @@ namespace mlirgen if (std::find_if(fields.begin(), fields.end(), [&](auto &item) { return item.id == interfaceField.id; }) == fields.end()) { + // the source has no field of this name at all - only reachable for a + // conditional (`?`) interface member (getVirtualTable's resolveField + // already rejects a missing non-conditional one, failing + // canCastTupleToInterface before this clone path even runs). Match the + // convention InterfaceSymbolRefOp's `optional` slot-count check already + // relies on for the non-clone path (see 00interface_optional_cast_order.ts): + // the field is simply ABSENT from the storage tuple, not present with a + // placeholder value - so skip it here rather than appending it. Appending it + // with any value/type would desync the clone's field COUNT from what + // interfaceVTableNameForObject's slot-index math and the interface's runtime + // "is this slot within the actual object's tuple size" check expect. + if (interfaceField.isConditional) + { + continue; + } + fields.push_back(interfaceField); } } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 8fa42a214..fd7352fb3 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -292,6 +292,7 @@ add_test(NAME test-compile-00-object-annotated-method-params COMMAND test-runner add_test(NAME test-compile-00-object-annotated-method-interleaved COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_interleaved.ts") add_test(NAME test-compile-00-object-annotated-method-extends-interface COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_extends_interface.ts") add_test(NAME test-compile-00-object-annotated-method-extends-interface-multilevel COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_extends_interface_multilevel.ts") +add_test(NAME test-compile-00-interface-optional-extends COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_optional_extends.ts") add_test(NAME test-compile-00-interface-object-array COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object_array.ts") add_test(NAME test-compile-00-interface-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts") add_test(NAME test-compile-00-interface-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts") @@ -651,6 +652,7 @@ add_test(NAME test-jit-00-object-annotated-method-params COMMAND test-runner -ji add_test(NAME test-jit-00-object-annotated-method-interleaved COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_interleaved.ts") add_test(NAME test-jit-00-object-annotated-method-extends-interface COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_extends_interface.ts") add_test(NAME test-jit-00-object-annotated-method-extends-interface-multilevel COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_extends_interface_multilevel.ts") +add_test(NAME test-jit-00-interface-optional-extends COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_optional_extends.ts") add_test(NAME test-jit-00-interface-object-array COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object_array.ts") add_test(NAME test-jit-00-interface-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts") add_test(NAME test-jit-00-interface-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts") diff --git a/tslang/test/tester/tests/00interface_optional_extends.ts b/tslang/test/tester/tests/00interface_optional_extends.ts new file mode 100644 index 000000000..fad05dd7d --- /dev/null +++ b/tslang/test/tester/tests/00interface_optional_extends.ts @@ -0,0 +1,38 @@ +// Combines two previously-separate axes of interface coverage that had never +// been tested together: an optional member (00interface_optional_cast_order.ts +// - optional member's virtualIndex can get clobbered to -1 on the SHARED +// InterfaceInfo by whichever cast ran most recently) and `extends` +// (00object_annotated_method_extends_interface*.ts - inherited members need +// vtableOffset added to their own-interface-relative virtualIndex). The +// optional member here is declared on the BASE interface, so resolving it +// through the DERIVED interface's combined vtable exercises both the +// extends-offset math and the optional-member vtable-patch path at once. + +function main() { + interface Base { + base: number; + opt?: number; + } + + interface Derived extends Base { + derived: number; + } + + let present: Derived = { base: 1, opt: 5, derived: 10 }; + let missing: Derived = { base: 2, derived: 20 }; + + assert(present.base == 1); + assert(present.opt == 5); + assert(present.derived == 10); + + assert(missing.base == 2); + assert(missing.opt == undefined); + assert(missing.derived == 20); + + // re-read the providing object after the non-providing one was cast, to + // catch the shared-virtualIndex-clobber bug class if it resurfaces here + print(present.opt); + assert(present.opt == 5); + + print("done."); +}