Skip to content
Open
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
12 changes: 5 additions & 7 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,22 +259,20 @@ struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value && !is_std_char_t
return false;
} else {
handle src_or_index = src;
// PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls.
#if defined(PYPY_VERSION)
object index;
// If not a PyLong, we need to call PyNumber_Index explicitly on PyPy.
// When convert is false, we only reach here if PYBIND11_INDEX_CHECK passed above.
if (!PYBIND11_LONG_CHECK(src.ptr())) {
// PyLong_AsUnsignedLong[Long] never calls __index__, so call PyNumber_Index here.
if (std::is_unsigned<py_type>::value && !PYBIND11_LONG_CHECK(src.ptr())
&& PYBIND11_INDEX_CHECK(src.ptr())) {
index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
if (!index) {
PyErr_Clear();
if (!convert)
if (!convert) {
return false;
}
} else {
src_or_index = index;
}
}
#endif
if (std::is_unsigned<py_type>::value) {
py_value = as_unsigned<py_type>(src_or_index.ptr());
} else { // signed integer:
Expand Down
1 change: 0 additions & 1 deletion include/pybind11/complex.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class type_caster<std::complex<T>> {
}
handle src_or_index = src;
// PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls.
// The same logic is used in numeric_caster for ints and floats
#if defined(PYPY_VERSION)
object index;
if (PYBIND11_INDEX_CHECK(src.ptr())) {
Expand Down
2 changes: 2 additions & 0 deletions tests/test_builtin_casters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ TEST_SUBMODULE(builtin_casters, m) {
// test_int_convert
m.def("int_passthrough", [](int arg) { return arg; });
m.def("int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert());
m.def("uint_passthrough", [](unsigned arg) { return arg; });
m.def("uint_passthrough_noconvert", [](unsigned arg) { return arg; }, py::arg{}.noconvert());

// test_float_convert
m.def("float_passthrough", [](float arg) { return arg; });
Expand Down
20 changes: 12 additions & 8 deletions tests/test_builtin_casters.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ def test_integer_casting():
assert "incompatible function arguments" in str(excinfo.value)


def test_int_convert(doc):
@pytest.mark.parametrize("prefix", ["int", "uint"])
def test_int_convert(doc, prefix):
class Int:
def __int__(self):
return 42
Expand Down Expand Up @@ -284,13 +285,14 @@ def __index__(self):
def __int__(self):
return 42

convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
convert = getattr(m, f"{prefix}_passthrough")
noconvert = getattr(m, f"{prefix}_passthrough_noconvert")

assert (
doc(convert)
== "int_passthrough(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
== f"{prefix}_passthrough(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
)
assert doc(noconvert) == "int_passthrough_noconvert(arg0: int) -> int"
assert doc(noconvert) == f"{prefix}_passthrough_noconvert(arg0: int) -> int"

def requires_conversion(v):
pytest.raises(TypeError, noconvert, v)
Expand All @@ -302,7 +304,7 @@ def cant_convert(v):
assert noconvert(7) == 7
cant_convert(3.14159)
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
if sys.version_info < (3, 10) and env.CPYTHON:
if sys.version_info < (3, 10) and env.CPYTHON and prefix == "int":
with pytest.deprecated_call():
assert convert(Int()) == 42
else:
Expand Down Expand Up @@ -402,10 +404,12 @@ def cant_convert(v):
cant_convert(Int())


def test_numpy_int_convert():
@pytest.mark.parametrize("prefix", ["int", "uint"])
def test_numpy_int_convert(prefix):
np = pytest.importorskip("numpy")

convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
convert = getattr(m, f"{prefix}_passthrough")
noconvert = getattr(m, f"{prefix}_passthrough_noconvert")

def require_implicit(v):
pytest.raises(TypeError, noconvert, v)
Expand All @@ -417,7 +421,7 @@ def require_implicit(v):
# The implicit conversion from np.float32 is undesirable but currently accepted.
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
# https://github.com/pybind/pybind11/issues/3408
if sys.version_info < (3, 10) and env.CPYTHON:
if sys.version_info < (3, 10) and env.CPYTHON and prefix == "int":
with pytest.deprecated_call():
assert convert(np.float32(3.14159)) == 3
else:
Expand Down
6 changes: 6 additions & 0 deletions tests/test_enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ TEST_SUBMODULE(enums, m) {
.value("ESecondMode", ClassWithUnscopedEnum::ESecondMode)
.export_values();

// test_scalar_comparison_with_index_object
enum UnscopedInt32Enum : std::int32_t { I32Three = 3 };
py::enum_<UnscopedInt32Enum>(m, "UnscopedInt32Enum").value("Three", I32Three);
enum UnscopedUInt32Enum : std::uint32_t { U32Three = 3 };
py::enum_<UnscopedUInt32Enum>(m, "UnscopedUInt32Enum").value("Three", U32Three);

// test_enum_to_int
m.def("test_enum_to_int", [](int) {});
m.def("test_enum_to_uint", [](uint32_t) {});
Expand Down
20 changes: 20 additions & 0 deletions tests/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,26 @@ def test_enum_to_int():
m.test_enum_to_long_long(m.ScopedBoolEnum.TRUE)


@pytest.mark.parametrize("enum_name", ["UnscopedInt32Enum", "UnscopedUInt32Enum"])
def test_scalar_comparison_with_index_object(enum_name):
# An unsigned underlying type must compare like a signed one against objects
# that only implement __index__ (e.g. numpy integers).
class Index:
def __index__(self):
return 3

three = getattr(m, enum_name).Three
assert three == 3
assert three == Index()
assert not three != Index()
assert three != 4
np = pytest.importorskip("numpy")
assert three == np.int32(3)
assert three == np.uint64(3)
assert not three != np.int64(3)
assert three != np.int32(4)


def test_duplicate_enum_name():
with pytest.raises(ValueError) as excinfo:
m.register_bad_enum()
Expand Down
Loading