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
9 changes: 6 additions & 3 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,12 @@ struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value && !is_std_char_t
return PyLong_FromUnsignedLongLong((unsigned long long) src);
}

PYBIND11_TYPE_CASTER(T,
io_name<std::is_integral<T>::value>(
"typing.SupportsInt", "int", "typing.SupportsFloat", "float"));
PYBIND11_TYPE_CASTER(
T,
io_name<std::is_integral<T>::value>("typing.SupportsInt | typing.SupportsIndex",
"int",
"typing.SupportsFloat | typing.SupportsIndex",
"float"));
};

template <typename T>
Expand Down
23 changes: 21 additions & 2 deletions include/pybind11/complex.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,23 @@ class type_caster<std::complex<T>> {
if (!convert && !PyComplex_Check(src.ptr())) {
return false;
}
Py_complex result = PyComplex_AsCComplex(src.ptr());
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())) {
index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
if (!index) {
PyErr_Clear();
if (!convert)
return false;
} else {
src_or_index = index;
}
}
#endif
Py_complex result = PyComplex_AsCComplex(src_or_index.ptr());
if (result.real == -1.0 && PyErr_Occurred()) {
PyErr_Clear();
return false;
Expand All @@ -68,7 +84,10 @@ class type_caster<std::complex<T>> {
return PyComplex_FromDoubles((double) src.real(), (double) src.imag());
}

PYBIND11_TYPE_CASTER(std::complex<T>, const_name("complex"));
PYBIND11_TYPE_CASTER(
std::complex<T>,
io_name("typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex",
"complex"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
7 changes: 7 additions & 0 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@
#define PYBIND11_BYTES_AS_STRING PyBytes_AsString
#define PYBIND11_BYTES_SIZE PyBytes_Size
#define PYBIND11_LONG_CHECK(o) PyLong_Check(o)
// In PyPy 7.3.3, `PyIndex_Check` is implemented by calling `__index__`,
// while CPython only considers the existence of `nb_index`/`__index__`.
#if !defined(PYPY_VERSION)
# define PYBIND11_INDEX_CHECK(o) PyIndex_Check(o)
#else
# define PYBIND11_INDEX_CHECK(o) hasattr(o, "__index__")
#endif
#define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o)
#define PYBIND11_LONG_FROM_SIGNED(o) PyLong_FromSsize_t((ssize_t) (o))
#define PYBIND11_LONG_FROM_UNSIGNED(o) PyLong_FromSize_t((size_t) (o))
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 @@ -363,6 +363,8 @@ TEST_SUBMODULE(builtin_casters, m) {
m.def("complex_cast", [](float x) { return "{}"_s.format(x); });
m.def("complex_cast",
[](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); });
m.def("complex_convert", [](std::complex<float> x) { return x; });
m.def("complex_noconvert", [](std::complex<float> x) { return x; }, py::arg{}.noconvert());

// test int vs. long (Python 2)
m.def("int_cast", []() { return (int) 42; });
Expand Down
81 changes: 77 additions & 4 deletions tests/test_builtin_casters.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,10 @@ def __int__(self):

convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert

assert doc(convert) == "int_passthrough(arg0: typing.SupportsInt) -> int"
assert (
doc(convert)
== "int_passthrough(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
)
assert doc(noconvert) == "int_passthrough_noconvert(arg0: int) -> int"

def requires_conversion(v):
Expand Down Expand Up @@ -322,19 +325,39 @@ def cant_convert(v):


def test_float_convert(doc):
class Int:
def __int__(self):
return -5

class Index:
def __index__(self) -> int:
return -7

class Float:
def __float__(self):
return 41.45

convert, noconvert = m.float_passthrough, m.float_passthrough_noconvert
assert doc(convert) == "float_passthrough(arg0: typing.SupportsFloat) -> float"
assert (
doc(convert)
== "float_passthrough(arg0: typing.SupportsFloat | typing.SupportsIndex) -> float"
)
assert doc(noconvert) == "float_passthrough_noconvert(arg0: float) -> float"

def requires_conversion(v):
pytest.raises(TypeError, noconvert, v)

def cant_convert(v):
pytest.raises(TypeError, convert, v)

requires_conversion(Float())
requires_conversion(Index())
assert pytest.approx(convert(Float())) == 41.45
assert pytest.approx(convert(Index())) == -7.0
assert isinstance(convert(Float()), float)
assert pytest.approx(convert(3)) == 3.0
requires_conversion(3)
cant_convert(Int())


def test_numpy_int_convert():
Expand Down Expand Up @@ -381,7 +404,7 @@ def test_tuple(doc):
assert (
doc(m.tuple_passthrough)
== """
tuple_passthrough(arg0: tuple[bool, str, typing.SupportsInt]) -> tuple[int, str, bool]
tuple_passthrough(arg0: tuple[bool, str, typing.SupportsInt | typing.SupportsIndex]) -> tuple[int, str, bool]

Return a triple in reversed order
"""
Expand Down Expand Up @@ -458,11 +481,61 @@ def test_reference_wrapper():
assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10]


def test_complex_cast():
def test_complex_cast(doc):
"""std::complex casts"""

class Complex:
def __complex__(self) -> complex:
return complex(5, 4)

class Float:
def __float__(self) -> float:
return 5.0

class Int:
def __int__(self) -> int:
return 3

class Index:
def __index__(self) -> int:
return 1

assert m.complex_cast(1) == "1.0"
assert m.complex_cast(1.0) == "1.0"
assert m.complex_cast(Complex()) == "(5.0, 4.0)"
assert m.complex_cast(2j) == "(0.0, 2.0)"

convert, noconvert = m.complex_convert, m.complex_noconvert

def requires_conversion(v):
pytest.raises(TypeError, noconvert, v)

def cant_convert(v):
pytest.raises(TypeError, convert, v)

assert (
doc(convert)
== "complex_convert(arg0: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex) -> complex"
)
assert doc(noconvert) == "complex_noconvert(arg0: complex) -> complex"

assert convert(1) == 1.0
assert convert(2.0) == 2.0
assert convert(1 + 5j) == 1.0 + 5.0j
assert convert(Complex()) == 5.0 + 4j
assert convert(Float()) == 5.0
assert isinstance(convert(Float()), complex)
cant_convert(Int())
assert convert(Index()) == 1
assert isinstance(convert(Index()), complex)

requires_conversion(1)
requires_conversion(2.0)
assert noconvert(1 + 5j) == 1.0 + 5.0j
requires_conversion(Complex())
requires_conversion(Float())
requires_conversion(Index())


def test_bool_caster():
"""Test bool caster implicit conversions."""
Expand Down
4 changes: 2 additions & 2 deletions tests/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ def test_cpp_function_roundtrip():
def test_function_signatures(doc):
assert (
doc(m.test_callback3)
== "test_callback3(arg0: collections.abc.Callable[[typing.SupportsInt], int]) -> str"
== "test_callback3(arg0: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex], int]) -> str"
)
assert (
doc(m.test_callback4)
== "test_callback4() -> collections.abc.Callable[[typing.SupportsInt], int]"
== "test_callback4() -> collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex], int]"
)


Expand Down
4 changes: 2 additions & 2 deletions tests/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ def test_qualname(doc):
assert (
doc(m.NestBase.Nested.fn)
== """
fn(self: m.class_.NestBase.Nested, arg0: typing.SupportsInt, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None
fn(self: m.class_.NestBase.Nested, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None
"""
)
assert (
doc(m.NestBase.Nested.fa)
== """
fa(self: m.class_.NestBase.Nested, a: typing.SupportsInt, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None
fa(self: m.class_.NestBase.Nested, a: typing.SupportsInt | typing.SupportsIndex, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None
"""
)
assert m.NestBase.__module__ == "pybind11_tests.class_"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_custom_type_casters.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_noconvert_args(msg):
msg(excinfo.value)
== """
ints_preferred(): incompatible function arguments. The following argument types are supported:
1. (i: typing.SupportsInt) -> int
1. (i: typing.SupportsInt | typing.SupportsIndex) -> int

Invoked with: 4.0
"""
Expand Down
6 changes: 3 additions & 3 deletions tests/test_docstring_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ def test_docstring_options():

# options.enable_function_signatures()
assert m.test_function3.__doc__.startswith(
"test_function3(a: typing.SupportsInt, b: typing.SupportsInt) -> None"
"test_function3(a: typing.SupportsInt | typing.SupportsIndex, b: typing.SupportsInt | typing.SupportsIndex) -> None"
)

assert m.test_function4.__doc__.startswith(
"test_function4(a: typing.SupportsInt, b: typing.SupportsInt) -> None"
"test_function4(a: typing.SupportsInt | typing.SupportsIndex, b: typing.SupportsInt | typing.SupportsIndex) -> None"
)
assert m.test_function4.__doc__.endswith("A custom docstring\n")

Expand All @@ -37,7 +37,7 @@ def test_docstring_options():

# RAII destructor
assert m.test_function7.__doc__.startswith(
"test_function7(a: typing.SupportsInt, b: typing.SupportsInt) -> None"
"test_function7(a: typing.SupportsInt | typing.SupportsIndex, b: typing.SupportsInt | typing.SupportsIndex) -> None"
)
assert m.test_function7.__doc__.endswith("A custom docstring\n")

Expand Down
2 changes: 1 addition & 1 deletion tests/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def test_generated_dunder_methods_pos_only():
)
assert (
re.match(
r"^__setstate__\(self: [\w\.]+, state: [\w\.]+, /\)",
r"^__setstate__\(self: [\w\.]+, state: [\w\. \|]+, /\)",
enum_type.__setstate__.__doc__,
)
is not None
Expand Down
8 changes: 4 additions & 4 deletions tests/test_factory_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ def test_init_factory_signature(msg):
msg(excinfo.value)
== """
__init__(): incompatible constructor arguments. The following argument types are supported:
1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt)
1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt | typing.SupportsIndex)
2. m.factory_constructors.TestFactory1(arg0: str)
3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
4. m.factory_constructors.TestFactory1(arg0: object, arg1: typing.SupportsInt, arg2: object)
4. m.factory_constructors.TestFactory1(arg0: object, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: object)

Invoked with: 'invalid', 'constructor', 'arguments'
"""
Expand All @@ -93,13 +93,13 @@ def test_init_factory_signature(msg):
__init__(*args, **kwargs)
Overloaded function.

1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt) -> None
1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: typing.SupportsInt | typing.SupportsIndex) -> None

2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None

3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None

4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: typing.SupportsInt, arg2: object) -> None
4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: typing.SupportsInt | typing.SupportsIndex, arg2: object) -> None
"""
)

Expand Down
Loading
Loading