From 0de8a6c7806d62ce07f33a634561f69614926173 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 30 Sep 2026 23:02:25 +0200 Subject: [PATCH] [cpyrt] Rank standard conversions above implicit construction #75 defers enum instances out of the exact round for integer parameters so that an enum overload wins over an integer one. Two cases regress: an implicit construction from the argument runs under kNoImplicit, where the deferred enum -> int step is refused (RooFit::LineColor(kRed) with a TColorNumber(Int_t) parameter), and in the implicit round a class overload constructible from an int-like object is tried before the integer one (TStyle::SetPalette(kGreyScale) picks the TString overload). Let enum instances through the integer gate under kNoImplicit, and resolve overloads in three stages as C++ ranks them: exact matches, standard conversions (kNoImplicit), implicit conversions. --- src/cpyrt/CPPOverload.cxx | 15 +++++++++++---- src/cpyrt/Converters.cxx | 7 +++++-- test/test_regression.py | 26 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/cpyrt/CPPOverload.cxx b/src/cpyrt/CPPOverload.cxx index 5639bde1..aecc7118 100644 --- a/src/cpyrt/CPPOverload.cxx +++ b/src/cpyrt/CPPOverload.cxx @@ -641,10 +641,19 @@ static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, pymeth->fMethodInfo->fFlags |= CallContext::kIsSorted; } + // three stages, as the C++ ranking: exact matches, standard conversions + // (kNoImplicit: no user-defined construction from the argument), then + // implicit conversions std::vector errors; std::vector implicit_possible(methods.size()); - for (int stage = 0; stage < 2; ++stage) { - bool bHaveImplicit = false; + bool bHaveImplicit = false; + for (int stage = 0; stage < 3; ++stage) { + if (stage == 1) + ctxt.fFlags |= CallContext::kNoImplicit; + else if (stage == 2) { + ctxt.fFlags &= ~CallContext::kNoImplicit; + ctxt.fFlags |= CallContext::kAllowImplicit; + } for (CPPOverload::Methods_t::size_type i = 0; i < nMethods; ++i) { if (stage && !implicit_possible[i]) continue; // did not set implicit conversion, so don't try again @@ -703,8 +712,6 @@ static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, // only move forward if implicit conversions are available if (!bHaveImplicit) break; - - ctxt.fFlags |= CallContext::kAllowImplicit; } // first summarize, then add details diff --git a/src/cpyrt/Converters.cxx b/src/cpyrt/Converters.cxx index cd6d6cf2..df2a8795 100644 --- a/src/cpyrt/Converters.cxx +++ b/src/cpyrt/Converters.cxx @@ -353,11 +353,14 @@ static inline bool IsCPPEnumInstance(PyObject* pyobject) { cppjit::cpyrt::PyStrings::gUnderlying); } -// bools and enum instances match integer parameters in the implicit round only +// bools and enum instances match integer parameters in the implicit round +// only; a nested call under kNoImplicit is that round (an implicit +// construction from the argument), so enum instances pass there static inline bool ImplicitBool(PyObject* pyobject, cpyrt::CallContext* ctxt) { using namespace cppjit::cpyrt; if (!AllowImplicit(ctxt) && - (PyBool_Check(pyobject) || IsCPPEnumInstance(pyobject))) { + (PyBool_Check(pyobject) || + (IsCPPEnumInstance(pyobject) && !NoImplicit(ctxt)))) { if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; return false; diff --git a/test/test_regression.py b/test/test_regression.py index 980cbf39..6aa2864a 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -1686,3 +1686,29 @@ def test54_enum_arg_overload_priority(self): # an enum instance still converts to an integer parameter assert ns.only_uint(ns.Color.Blue) == 22 + + def test55_enum_arg_standard_conversion(self): + """An enum instance converts to int before any class construction""" + + import cppjit + + cppjit.cppdef("""\ + namespace EnumArgConversion { + enum Color { Red = 0, Green = 1, Blue = 2 }; + struct FromInt { FromInt(int v) : v(v) {} int v; }; + struct FromEnum { FromEnum(Color c) : v((int)c) {} int v; }; + struct FromDouble { FromDouble(double d) : v((int)d) {} int v; }; + int via_int(FromInt f) { return f.v; } + int via_enum(FromEnum f) { return f.v; } + int pick(int i) { return i; } + int pick(FromDouble f) { return -f.v; } + }""") + + ns = cppjit.gbl.EnumArgConversion + + # implicit construction from an enum instance, through int and enum + assert ns.via_int(ns.Color.Blue) == 2 + assert ns.via_enum(ns.Color.Green) == 1 + + # a standard conversion outranks a user-defined one, as in C++ + assert ns.pick(ns.Color.Blue) == 2