Repository navigation
[WIP] New Overload Resolution - #150
Draft
Vipul-Cariappa wants to merge 27 commits into
Draft
Vipul-Cariappa wants to merge 27 commits into
Vipul-Cariappa wants to merge 27 commits into
Conversation
Vipul-Cariappa
force-pushed
the
dev/overload-resolution
branch
from
October 13, 2026 06:15
51d0efa to
2ba1f3e
Compare
Vipul-Cariappa
marked this pull request as draft
October 13, 2026 07:18
Vipul-Cariappa
force-pushed
the
dev/overload-resolution
branch
4 times, most recently
from
October 13, 2026 11:29
f0aab5a to
f48cc29
Compare
`IsEquivalentTypes` checks whether two types are equivalent up to CVR qualifiers, references and pointers, or whether one type derives from the other. Through its out-parameters it reports how the types differ: - `qual`: the CVR qualifiers that differ, - `ref`: the kind of reference (lvalue/rvalue) that was stripped, - `pointer`: whether a pointer was stripped.
* Remove SelectAndForward from TemplateProxy SelectAndForward forwarded a template proxy call to one of its overload sets: tpp_vectorcall tried the non-templated overloads (implicit conversions allowed), then the known templated ones and finally the low-priority ones (both without implicit conversions). "Adopt Clang-driven overload resolution from CppInterOp" replaced that cascade with a single Clang-driven selection and its three callers went away with it; the static function has had no caller since (clang reports it with -Wunused-function). * Remove CPPDataMember_NewConstant CPPDataMember_NewConstant built a static, constant data member descriptor from a raw name and address, via the matching CPPDataMember::Set(scope, name, address) overload. Nothing calls it (it was already unused at 3df629f): every data member descriptor is created by CPPDataMember_New from a reflected variable, including enum constants. Remove the factory and the Set overload only it used. * Remove Utility::PyErr_Occurred_WithGIL It re-acquired the GIL around PyErr_Occurred() for callers that might have released it. Nothing calls it (it was already unused at 3df629f); the executors that release the GIL (GILControl in Executors.cxx) restore it before checking for errors. * remove unused call context enums based on new overload resolution * Remove the write-only kHaveImplicit call flag Converters set CallContext::kHaveImplicit when they rejected an argument that an implicit conversion could have accepted. The old overload resolution in mp_vectorcall read it through HaveImplicit() to decide whether to retry all overloads in a second round with kAllowImplicit (CPPOverload.cxx at 3df629f). "Adopt Clang-driven overload resolution from CppInterOp" removed that second round, so HaveImplicit() lost its only caller and the flag has been written but never read since. Remove the four writes in Converters.cxx, HaveImplicit() and the enumerator. * Remove the unused is_conversion_operator computation "Adopt Clang-driven overload resolution from CppInterOp" made mp_vectorcall compute whether any overload in the set is a conversion operator, but the result is never read (unlike is_operator, which is passed on to BestOverloadFunctionMatch). Remove it together with CPPMethod::IsConversionOperator, which it was the only caller of. * Drop the unused <regex> include from Converters.cxx Converters.cxx used std::regex only for s_fnptr, which recognized function-pointer spellings in CreateConverter. "Adopt Clang-driven overload resolution from CppInterOp" replaced that string matching with type queries and removed s_fnptr; nothing in the file uses <regex> any more. * Remove unreachable statements after return/throw * Remove SmartPtrConverter's never-overridden GetAddressSpecialCase SmartPtrConverter declares its own virtual GetAddressSpecialCase that always returns false, and no class derives from SmartPtrConverter, so the special-case branch in SmartPtrConverter::SetArg (meant for e.g. passing nullptr to a smart pointer reference) can never be taken. It was already dead at 3df629f. Remove the hook, the branch and the TODO that discussed it; non-instances are rejected as before. * Drop the redundant unicodeobject.h include from TemplateProxy.cxx "Adopt Clang-driven overload resolution from CppInterOp" added a direct line above) already includes; nothing else in the file needed it. * removed commented code
`BestOverloadFunctionMatch` returns the declaration that was passed in as a candidate, but `mp_vectorcall` compared it against the *canonical* declaration of each method in the overload. When a function has more than one declaration and the one in the overload is not the first (e.g. ROOT forward-declares functions of autoloaded libraries before the header is parsed), the comparison never matched and the call failed with "Overload Resolution Failed" even though Clang found a unique best match. Compare the canonical declarations on both sides. Assisted-by: claude-code:claude-opus-5-5
`AddTypeName` spells a `LowLevelView` argument with the view's `fCppTypeName`, which was set to the converter name of the element type. For (un)signed char and std::byte views that name is the internal converter alias `UCharAsInt`/`SCharAsInt`, which is not a C++ type, so the overload resolution could never match e.g. `uint8_t* arr[8]` for an array created with `cppjit.ll.array_new["uint8_t*"]`. Record the C++ spelling of the element type instead; the converter name is unchanged. Fixes test_regression.py::test37_array_of_pointers_argument. Assisted-by: claude-code:claude-opus-5-5
`AddTypeName` had no case for ctypes objects and fell back to the Python type's `__name__`, putting non-existent C++ types such as `c_void_p` or `c_int` into the prototype handed to Clang. Overload resolution then only succeeded through the argument-count fallback, and always failed for templates and for overload sets with several candidates of the same arity, e.g. `cppjit.ll.array_delete["void"](ctypes.c_void_p())` or ROOT's `TTree::Branch(name, ctypes.c_void_p(buf), leaflist)`. A ctypes object is a C object owned by Python, so spell it as an lvalue of its C type: `c_int` -> `int&`, `c_void_p` -> `void*&`, `c_char_p` -> `char*&`, `POINTER(c_int)()` -> `int*&`. An lvalue also binds to pointer parameters through the existing reference-to-pointer promotion, which keeps passing e.g. a `c_int` to an `int*` working. Arrays are spelled as a pointer to their elements and `ctypes.byref(obj)` as a pointer to `obj`. Fixes test_lowlevel.py::test17_array_delete_multidim. Assisted-by: claude-code:claude-opus-5-5
A smart pointer proxy behaves as the object it points to: its Python type is the pointee class, so the methods found on it are those of the pointee (the methods of the smart pointer itself are reached through `__smartptr__()`). The implicit object argument in the overload prototype was however spelled as the smart pointer type (e.g. `std::unique_ptr<A>&, int` for `p.get(1)`), so Clang found no viable candidate whenever the method was overloaded, and the call only worked when the argument-count fallback happened to pick a unique method. Add a `smart_as_pointee` option to `Utility::AddTypeName`, which spells a smart pointer proxy as an lvalue of its pointee, and use it for the object of bound calls of normal and templated methods, and for the first argument of unbound calls of non-static methods (e.g. `A.get(p, 1)`), which is the object as well. Fixes e.g. `RDataFrame.Define()` on the `std::unique_ptr<RDataFrame>` returned by `ROOT.RDF.FromNumpy()`, and `RFile.Put()`. Assisted-by: claude-code:claude-opus-5-5
numpy scalars and arrays fell back to the Python type's `__name__` in `AddTypeName`, i.e. the non-existent C++ types `float64` or `ndarray`, so overload resolution only succeeded through the argument-count fallback. Overloaded functions such as `TProfile::Fill(Double_t, Double_t)` vs. `Fill(const char*, Double_t)` (hsimple.py) or `TGraph(Int_t, const Double_t*, const Double_t*)` failed. Spell a numpy scalar as a value of the C type of its dtype, and an array as a pointer to its elements, the equivalent of a C array decaying into a pointer (as is already done for `array.array`). This does not allow implicit copies of arrays into e.g. a `std::vector`. numpy is only looked up if it was already imported. Assisted-by: claude-code:claude-opus-5-5
Calling a Python callable with arguments of unsuitable types raises a TypeError, and code using the bindings relies on that, e.g. ROOT's distributed RDataFrame validates its constructor arguments with `except TypeError`, as do several pythonizations that probe overloads. The new OverloadResolutionException (and OverloadAmbiguityException) derived directly from Exception, so these errors escaped such handlers. Make it a subclass of TypeError; code that catches the specific exception is unaffected. Assisted-by: claude-code:claude-opus-5-5
`AddTypeName` appended nothing for an empty Python list or tuple, so the
overload prototype became malformed (e.g. `std::string, ` for
`RDataFrame("tree", [])`) and the overload resolution failed outright.
Spell it as `std::initializer_list<__cppjit_internal::UnknownType>`: like
for a non-empty list, the overload resolution promotes this to the
`std::initializer_list<T>` parameter of a candidate.
Assisted-by: claude-code:claude-opus-5-5
`TemplateProxy.__overload__(signature)` resolved the signature through `GetMethodTemplate` without the object argument, although the candidates of a class are non-static methods that take it first. Clang therefore used the first parameter type as the object (crashing in Sema for a non-class type), only templates were considered, and when nothing was found NULL was returned without setting an exception. Add `interop::GetMethodBySignature`, which resolves the non-static methods of the set with an object of the class prepended to the signature, and static methods and functions without it (so that no candidate gets an argument of the wrong kind as its object), sharing the candidate lookup and instantiation with `GetMethodTemplate`. Use it in `__overload__`, including the non-templated overloads in the selection by signature, and raise a LookupError if no overload matches. Assisted-by: claude-code:claude-opus-5-5
When the overload resolution selects a function taking `T&&` for a proxy
of a class U that converts to T (e.g. `std::vector<Variant>::push_back`
given an `Axis`, with `Variant(const Axis&)`), C++ initializes the
parameter with a temporary T. `InstanceMoveConverter` instead treated the
U proxy as the object to move and rejected it as not being an rvalue.
Go through the implicit conversion (which creates the movable temporary)
when the proxy is not of class T or a class derived from it. Smart
pointers are still matched by the reference converter.
Fixes e.g. `RDataFrame.Hist([axis1, axis2], ...)` and
`RNTupleWriter.Recreate([("x", "float")], ...)`. It also makes
`boost::any::operator=` work for an lvalue (test_boost.py test02): the
forwarding-reference `operator=(ValueType&&)` still fails to instantiate
for it, but the move assignment `operator=(any&&)` now takes a temporary
`any` converted from it, as C++ would.
Assisted-by: claude-code:claude-opus-5-5
Data members of enum type (static or not, e.g. `std::ios_base::ate`) were
converted with the converter of the underlying integer type and thus
returned as plain Python integers, while enum constants are instances of
the Python enum class. Passed on to a function, a plain integer is spelled
as `int` in the overload prototype and doesn't convert to the enum, so
e.g. `std.ostringstream("(", std.ios_base.ate)` failed (and only worked
in other environments through the argument-count fallback).
Keep the enum type for data members, so that they get an EnumConverter,
and let its FromMemory() return an instance of the enum class, which is
looked up once in the enclosing scope.
Fixes test_templates.py::test04_variadic_function in ROOT.
Assisted-by: claude-code:claude-opus-5-5
When the overload resolution of a TemplateProxy found no candidate for the arguments, it reported "Failed to instantiate" as a generic TypeError, whereas a CPPOverload raises an OverloadResolutionException in the same situation. Whether a method set goes through a TemplateProxy depends on the presence of templated overloads, e.g. the `std::vector` constructors of the standard library in use, so the exception type differed between environments (test_stltypes.py::test16_vector_construction failed with ROOT's C++20 standard library). Raise an OverloadResolutionException (a TypeError) instead. Assisted-by: claude-code:claude-opus-5-5
`vector_iter` asks `__real_data()` for the data pointer to iterate quickly and indexes the vector if there is none. `__real_data()` can fail, though: for `std::vector<char>`, `data()` returns a `char*`, which is converted to a Python str and fails if the bytes up to the next null (the data isn't null-terminated) aren't valid UTF-8. That error was left set, so iterating such a vector randomly failed with "returned a result with an exception set". Clear the error when no data pointer could be obtained. Assisted-by: claude-code:claude-opus-5-5
`GetMethodTemplate` ignored the result of `AppendTypesSlow`. If the argument types could not be resolved, the overload was selected with fewer (or no) argument types. If an explicit template argument could not be resolved, it was replaced by `__cppjit_internal::UnknownType`, and the template was instantiated with it. E.g. `entry._GetPtr["AtlasLikeDataVector<CustomStruct>"]` in ROOT's REntry pythonization, called before the dictionary of that class was loaded, instantiated `REntry::GetPtr<UnknownType>`. That threw a type mismatch at run time instead of failing the lookup, so the pythonization's fallback to the alternative class names never kicked in. Fail the lookup if `AppendTypesSlow` fails for the argument types or the explicit template arguments, and don't replace unknown explicit template arguments. (An unknown type among the argument types is still spelled as UnknownType, which just makes the candidates non-viable.) The other callers of `AppendTypesSlow` already check its result. Assisted-by: claude-code:claude-opus-5-5
The overload resolution ignored the names of keyword arguments.
`TemplateProxy::Instantiate` built the prototype from the positional
arguments only, so a method set with templated overloads could select a
candidate with too few parameters, which then failed when called with
the keywords ("takes at most 3 arguments (6 given)"), e.g.
`df.Display(columnList=("x", "y"), nRows=15)` or
`xRooNode("RooWorkspace", name=..., title=...)`. `CPPOverload` appended
the keyword values in the order given, so `f(1, c="x")` was resolved as
`f(1, "x")`, and keywords given in another order than the parameters
selected the wrong overload or none. The call itself
(`CPPMethod::ProcessKwds`) does map the keywords by name, as cppyy did.
Pass the keyword names along with the prototype, whose last arguments
are the keyword values, to `BestOverloadFunctionMatch` and
`GetMethodTemplate`. Each candidate then gets its own argument list: the
positional arguments, then the keyword values at the positions of their
parameters, with skipped defaulted parameters in between taking an
argument of their own type. Candidates that lack a name, would get an
argument twice, or would have to skip a required parameter (or one whose
type depends on the template parameters) are not viable. Candidates with
the same argument list are resolved together; among the best candidates
of the different lists, the one that skips the fewest parameters wins.
The fallback to a candidate by argument count is only used if no list has
a match. If the names fit no candidate at all, the resolution falls back
to the keyword values in the order given, so that the call reports the
unexpected (or repeated) keyword argument as before.
Assisted-by: claude-code:claude-opus-5-5
`interop::Compile` ignored its `silent` argument and always declared the
code non-silently. Code that is compiled tentatively and may legitimately
fail, like the aggregate-initialization pseudo-constructors generated in
Pythonize (e.g. for unions or classes with enum constants), then went
through the interpreter's regular error path: besides printing compiler
errors, with ROOT/cling this unloads the failed transaction, and ROOT's
unloading callbacks (TClass::GetListOfEnums -> TListOfEnums::Load) start
a nested transaction, which aborts with assertions enabled ("Must not nest
within unloading transaction"). The CI runs with assertions, where
test_datatypes, test_lowlevel, test_templates, test_regression and
test_fragile crashed.
Pass the silent flag on to Cpp::Declare again.
Assisted-by: claude-code:claude-opus-5-5
Vipul-Cariappa
force-pushed
the
dev/overload-resolution
branch
from
October 21, 2026 13:46
425cada to
05090fc
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.