diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx index e8cccc5..33364f1 100644 --- a/src/cpyrt/CPPMethod.cxx +++ b/src/cpyrt/CPPMethod.cxx @@ -581,7 +581,9 @@ int cpyrt::CPPMethod::GetPriority() { if (scope) priority += static_cast(interop::GetNumBasesLongestBranch(scope)); - if (interop::IsEnumScope(scope)) + // GetScope does not resolve enum names, so also detect enums by type + if (interop::IsEnumScope(scope) || + interop::IsEnumType(interop::GetMethodArgType(fMethod, iarg))) priority -= 100; // a couple of special cases as explained above diff --git a/src/cpyrt/Converters.cxx b/src/cpyrt/Converters.cxx index da07033..4c4510f 100644 --- a/src/cpyrt/Converters.cxx +++ b/src/cpyrt/Converters.cxx @@ -344,9 +344,20 @@ GetCppInstance(PyObject* pyobject, } //- custom helpers to check ranges ------------------------------------------- +static inline bool IsCPPEnumInstance(PyObject* pyobject) { + // cppjit enums are int subclasses created with __underlying on the class + if (!PyLong_Check(pyobject) || PyLong_CheckExact(pyobject) || + PyBool_Check(pyobject)) + return false; + return PyObject_HasAttr((PyObject*)Py_TYPE(pyobject), + cppjit::cpyrt::PyStrings::gUnderlying); +} + +// bools and enum instances match integer parameters in the implicit round only static inline bool ImplicitBool(PyObject* pyobject, cpyrt::CallContext* ctxt) { using namespace cppjit::cpyrt; - if (!AllowImplicit(ctxt) && PyBool_Check(pyobject)) { + if (!AllowImplicit(ctxt) && + (PyBool_Check(pyobject) || IsCPPEnumInstance(pyobject))) { if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; return false; @@ -1259,6 +1270,49 @@ bool cpyrt::ULLongConverter::ToMemory(PyObject* value, void* address, return true; } +//---------------------------------------------------------------------------- +cpyrt::EnumConverter::~EnumConverter() { DestroyConverter(fBase); } + +bool cpyrt::EnumConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // an instance of the same enum is an exact match; anything else, plain + // integers included, converts in the implicit round only, as in C++ + if (ctxt && !AllowImplicit(ctxt)) { + bool exact = false; + if (IsCPPEnumInstance(pyobject)) { + PyObject* pycppname = + PyObject_GetAttr((PyObject*)Py_TYPE(pyobject), PyStrings::gCppName); + if (pycppname) { + exact = (fEnumName == cpyrt_PyText_AsString(pycppname)); + Py_DECREF(pycppname); + } else + PyErr_Clear(); + } + if (!exact) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return false; + } + // the base integer converter defers enum instances, so lift the gate + CallContextRAII allow(ctxt); + return fBase->SetArg(pyobject, para, ctxt); + } + return fBase->SetArg(pyobject, para, ctxt); +} + +PyObject* cpyrt::EnumConverter::FromMemory(void* address) { + return fBase->FromMemory(address); +} + +bool cpyrt::EnumConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + return fBase->ToMemory(value, address, ctxt); +} + +std::string cpyrt::EnumConverter::GetFailureMsg() { + return fBase->GetFailureMsg(); +} + //---------------------------------------------------------------------------- bool cpyrt::CStringConverter::SetArg(PyObject* pyobject, Parameter& para, CallContext* ctxt) { @@ -3628,6 +3682,16 @@ cppjit::cpyrt::CreateConverter(interop::TCppType_t type, cdims_t dims) { interop::TCppType_t resolvedType = interop::ResolveType(type); const std::string& resolvedTypeStr = interop::GetTypeAsString(resolvedType); + // a by-value enum parameter exact-matches instances of the same enum; the + // resolved type is its underlying integer, which carries the conversion + if (interop::IsEnumType(type) && fullType != "std::byte") { + h = gConvFactories.find(resolvedTypeStr); + if (h != gConvFactories.end()) + return new EnumConverter( + (h->second)(dims), + interop::GetScopedFinalName(interop::GetScopeFromType(type))); + } + // a full, qualified matching converter is preferred if (resolvedTypeStr != fullType) { h = gConvFactories.find(resolvedTypeStr); diff --git a/src/cpyrt/DeclareConverters.h b/src/cpyrt/DeclareConverters.h index 537a8d0..b1b655f 100644 --- a/src/cpyrt/DeclareConverters.h +++ b/src/cpyrt/DeclareConverters.h @@ -136,6 +136,27 @@ CPPJIT_DECLARE_REFCONVERTER(Float); CPPJIT_DECLARE_REFCONVERTER(Double); CPPJIT_DECLARE_REFCONVERTER(LDouble); +// by-value enum parameters: exact-match instances of the same enum in the +// first resolution round, delegate storage to the underlying integer converter +class EnumConverter : public Converter { +public: + EnumConverter(Converter* base, const std::string& enum_name) + : fBase(base), fEnumName(enum_name) {} + ~EnumConverter() override; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override; + +private: + Converter* fBase; + std::string fEnumName; +}; + class VoidConverter : public Converter { public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; diff --git a/test/test_regression.py b/test/test_regression.py index c82d808..980cbf3 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -1658,3 +1658,31 @@ def test53_str_fallback_without_ostream_insertion(self): # degrade to the generic repr assert str(bare) == repr(bare) assert "Bare object at" in str(bare) + + def test54_enum_arg_overload_priority(self): + """Enum overloads resolve like C++ for both int and enum arguments""" + + import cppjit + + cppjit.cppdef("""\ + namespace EnumArgPriority { + enum Color { Red = 0, Green = 1, Blue = 2 }; + int pick(Color) { return 1; } + int pick(unsigned int) { return 2; } + int only_enum(Color c) { return 10 + (int)c; } + int only_uint(unsigned int v) { return 20 + (int)v; } + }""") + + ns = cppjit.gbl.EnumArgPriority + + # C++ has no implicit int -> enum conversion + assert ns.pick(2) == 2 + + # an enum instance is an exact match for its own enum overload + assert ns.pick(ns.Color.Green) == 1 + + # an enum parameter is only deprioritized, never unusable + assert ns.only_enum(2) == 12 + + # an enum instance still converts to an integer parameter + assert ns.only_uint(ns.Color.Blue) == 22