From 7d032a6646f778dd08b4b111d3aa37e0d28f8d63 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 18 Oct 2026 00:03:10 +0100 Subject: [PATCH] A @cpp method called through ?. picks among its overloads `obj?.mix(1, 2, 3)` called the first overload, `mix(a, b)`, with three arguments: the member access is made in a conditional of its own, and the call saw only the value of its optional result, so resolveCppOverload could not tell which method it was, and the call was not a C++ call (ts.cpp_call). The method is now accessed again from the object the callee is bound to, inside the call's own region. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenAccessCall.cpp | 72 ++++++++++++++++++++- tslang/lib/TypeScript/MLIRGenImpl.h | 3 + tslang/test/tester/tests/00cpp_overloads.ts | 27 ++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index 66474778f..c56ca9c57 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -1667,8 +1667,15 @@ namespace mlirgen // value if true - auto innerFuncRef = + mlir::Value innerFuncRef = builder.create(location, optFuncRef.getElementType(), actualFuncRefValue); + innerFuncRef = cppMethodAccessAgain(location, actualFuncRefValue, innerFuncRef, genContext); + if (!innerFuncRef) + { + builder.setInsertionPoint(ifOp); + ifOp.erase(); + return mlir::failure(); + } auto result = mlirGenCallExpression(location, innerFuncRef, typeArguments, operands, genContext); // a failed call (e.g. too few arguments) must fail the expression, not leave an @@ -1983,6 +1990,69 @@ namespace mlirgen return resolved; } + // `obj?.method(...)`: the member access is made in a conditional of its own, and its callee comes + // to the call as the value of that conditional's optional result, which no longer says which + // method it is - resolveCppOverload kept the first overload (`mix(a, b)` took `mix(1, 2, 3)`), and + // the call wasn't a C++ call. A @cpp method is accessed again here, from the object the callee is + // bound to, so the call sees the member access itself. funcRefValue for any other callee; null, + // with an error, when the access fails. + mlir::Value MLIRGenImpl::cppMethodAccessAgain(mlir::Location location, mlir::Value optFuncRefValue, + mlir::Value funcRefValue, const GenContext &genContext) + { + auto ifOp = optFuncRefValue.getDefiningOp(); + if (!ifOp || ifOp.getThenRegion().empty()) + { + return funcRefValue; + } + + auto resultOp = dyn_cast(ifOp.getThenRegion().front().getTerminator()); + if (!resultOp) + { + return funcRefValue; + } + + auto optionalValueOp = resultOp.getOperand(mlir::cast(optFuncRefValue).getResultNumber()) + .getDefiningOp(); + if (!optionalValueOp || !isCppVTableMethod(optionalValueOp.getIn())) + { + return funcRefValue; + } + + mlir::Value thisValue; + StringRef identifier; + if (auto virtualRef = optionalValueOp.getIn().getDefiningOp()) + { + thisValue = virtualRef.getThisVal(); + identifier = virtualRef.getIdentifier(); + } + else if (auto symbolRef = optionalValueOp.getIn().getDefiningOp()) + { + thisValue = symbolRef.getThisVal(); + identifier = symbolRef.getIdentifier(); + } + + // the object's own class, of which a method found in a base class took the object cast to + // that class: the overloads are those of the object's class (resolveCppOverload) + auto thisType = thisValue.getType(); + if (auto castOp = thisValue.getDefiningOp()) + { + if (isa(castOp.getIn().getType())) + { + thisType = castOp.getIn().getType(); + } + } + + auto objectValue = builder.create(location, thisType, funcRefValue); + auto result = mlirGenPropertyAccessExpression(location, objectValue, cppOverloadedName(identifier.rsplit('.').second), + genContext); + if (result.failed_or_no_value()) + { + return mlir::Value(); + } + + return V(result); + } + ValueOrLogicalResult MLIRGenImpl::NewClassInstanceOnStack(mlir::Location location, mlir_ts::ClassType classType, SmallVector &operands, const GenContext &genContext) { diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 1f55d5c62..bdc88533d 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -11665,6 +11665,9 @@ class MLIRGenImpl mlir::Value resolveCppOverload(mlir::Location location, mlir::Value funcRefValue, mlir::ArrayRef operands, const GenContext &genContext); + mlir::Value cppMethodAccessAgain(mlir::Location location, mlir::Value optFuncRefValue, mlir::Value funcRefValue, + const GenContext &genContext); + bool isCppVirtualMethod(ClassInfo::TypePtr classInfo, ClassElement classMember, const GenContext &genContext); bool isCppVTableImplementation(ClassLikeDeclaration classDeclarationAST, ClassInfo::TypePtr classInfo, diff --git a/tslang/test/tester/tests/00cpp_overloads.ts b/tslang/test/tester/tests/00cpp_overloads.ts index 3ed4cee02..f49296705 100644 --- a/tslang/test/tester/tests/00cpp_overloads.ts +++ b/tslang/test/tester/tests/00cpp_overloads.ts @@ -37,6 +37,13 @@ class Canvas { @slot(4) fill(shape: Shape): s32; } +// overloads told apart by their number of parameters alone +@cpp +class Mixer { + @slot(1) mix(a: s32, b: s32): s32; + @slot(2) mix(a: s32, b: s32, c: s32): s32; +} + type ObjectData = [vtable: Opaque, value: s32]; function slot0(self: Opaque): s32 { return -1; } @@ -51,6 +58,9 @@ function canvasDrawShape(self: Opaque, shape: Opaque): s32 { return 2000; } function canvasFillSquare(self: Opaque, shape: Opaque): s32 { return 3000; } function canvasFillShape(self: Opaque, shape: Opaque): s32 { return 4000; } +function mix2(self: Opaque, a: s32, b: s32): s32 { return 10 * a + b; } +function mix3(self: Opaque, a: s32, b: s32, c: s32): s32 { return 100 * a + 10 * b + c; } + function main() { let vtable: Opaque[] = [slot0, slot1, slot2, slot3, slot4, slot5]; let data: ObjectData = [Ref(vtable[0]), 7]; @@ -85,5 +95,22 @@ function main() { assert(canvas.fill(square) == 3000, "a Square: fill(Square) over fill(Shape)"); assert(canvas.fill(shape) == 4000, "a Shape: fill(Shape)"); + let mixerVtable: Opaque[] = [slot0, mix2, mix3]; + let mixerData: ObjectData = [Ref(mixerVtable[0]), 0]; + const mixer = Ref(mixerData); + assert(mixer.mix(1, 2) == 12, "two arguments: mix(s32, s32)"); + assert(mixer.mix(1, 2, 3) == 123, "three arguments: mix(s32, s32, s32)"); + + // `?.`: the call picks among the overloads as a direct call does + const maybeMixer: Mixer | null = mixer; + assert(maybeMixer?.mix(4, 5) == 45, "?. with two arguments: mix(s32, s32)"); + assert(maybeMixer?.mix(4, 5, 6) == 456, "?. with three arguments: mix(s32, s32, s32)"); + const maybeDerived: Derived | null = derived; + assert(maybeDerived?.write(2.5) == 225, "?. on a derived class: its override"); + assert(maybeDerived?.write("abcd") == 504, "?. on a derived class: its own overload"); + assert(maybeDerived?.write(2, 3) == 306, "?. on a derived class: a base overload"); + const noMixer: Mixer | null = null; + assert(noMixer?.mix(1, 2, 3) === undefined, "?. on null calls nothing"); + print("done."); }