Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/cpyrt/CPPMethod.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,9 @@ int cpyrt::CPPMethod::GetPriority() {
if (scope)
priority += static_cast<int>(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
Expand Down
66 changes: 65 additions & 1 deletion src/cpyrt/Converters.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<CallContext::kAllowImplicit> 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) {
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions src/cpyrt/DeclareConverters.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions test/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading