Add typing.SupportsIndex to int/float/complex type hints (#5891)

* Add typing.SupportsIndex to int/float/complex type hints

This corrects a mistake where these types were supported but the type
hint was not updated to reflect that SupportsIndex objects are accepted.

To track the resulting test failures:

The output of

"$(cat PYROOT)"/bin/python3 $HOME/clone/pybind11_scons/run_tests.py $HOME/forked/pybind11 -v

is in

~/logs/pybind11_pr5879_scons_run_tests_v_log_2025-11-10+122217.txt

* Cursor auto-fixes (partial) plus pre-commit cleanup. 7 test failures left to do.

* Fix remaining test failures, partially done by cursor, partially manually.

* Cursor-generated commit: Added the Index() tests from PR 5879.

Summary:

  Changes Made

  1. **C++ Bindings** (`tests/test_builtin_casters.cpp`)

  • Added complex_convert and complex_noconvert functions needed for the tests

  2. **Python Tests** (`tests/test_builtin_casters.py`)

  `test_float_convert`:
  • Added Index class with __index__ returning -7
  • Added Int class with __int__ returning -5
  • Added test showing Index() works with convert mode: assert pytest.approx(convert(Index())) == -7.0
  • Added test showing Index() doesn't work with noconvert mode: requires_conversion(Index())
  • Added additional assertions for int literals and Int() class

  `test_complex_cast`:
  • Expanded the test to include convert and noconvert functionality
  • Added Index, Complex, Float, and Int classes
  • Added test showing Index() works with convert mode: assert convert(Index()) == 1 and assert isinstance(convert(Index()), complex)
  • Added test showing Index() doesn't work with noconvert mode: requires_conversion(Index())
  • Added type hint assertions matching the SupportsIndex additions

  These tests demonstrate that custom __index__ objects work with float and complex in convert mode, matching the typing.SupportsIndex type hint added in PR
  5891.

* Reflect behavior changes going back from PR 5879 to master. This diff will have to be reapplied under PR 5879.

* Add PyPy-specific __index__ handling for complex caster

Extract PyPy-specific __index__ backporting from PR 5879 to fix PyPy 3.10
test failures in PR 5891. This adds:

1. PYBIND11_INDEX_CHECK macro in detail/common.h:
   - Uses PyIndex_Check on CPython
   - Uses hasattr check on PyPy (workaround for PyPy 7.3.3 behavior)

2. PyPy-specific __index__ handling in complex.h:
   - Handles __index__ objects on PyPy 7.3.7's 3.8 which doesn't
     implement PyLong_*'s __index__ calls
   - Mirrors the logic used in numeric_caster for ints and floats

This backports __index__ handling for PyPy, matching the approach
used in PR 5879's expand-float-strict branch.
This commit is contained in:
Ralf W. Grosse-Kunstleve
2025-11-10 20:26:50 -08:00
committed by GitHub
parent 73da78c3e4
commit 9f1187f97c
18 changed files with 204 additions and 80 deletions

View File

@@ -347,9 +347,12 @@ public:
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>

View File

@@ -54,7 +54,23 @@ public:
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;
@@ -68,7 +84,10 @@ public:
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)

View File

@@ -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))

View File

@@ -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; });

View File

@@ -286,7 +286,10 @@ def test_int_convert(doc):
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):
@@ -322,19 +325,39 @@ def test_int_convert(doc):
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():
@@ -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
"""
@@ -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."""

View File

@@ -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]"
)

View File

@@ -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_"

View File

@@ -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
"""

View File

@@ -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")
@@ -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")

View File

@@ -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

View File

@@ -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'
"""
@@ -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
"""
)

View File

@@ -9,28 +9,28 @@ from pybind11_tests import kwargs_and_defaults as m
def test_function_signatures(doc):
assert (
doc(m.kw_func0)
== "kw_func0(arg0: typing.SupportsInt, arg1: typing.SupportsInt) -> str"
== "kw_func0(arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex) -> str"
)
assert (
doc(m.kw_func1)
== "kw_func1(x: typing.SupportsInt, y: typing.SupportsInt) -> str"
== "kw_func1(x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsInt | typing.SupportsIndex) -> str"
)
assert (
doc(m.kw_func2)
== "kw_func2(x: typing.SupportsInt = 100, y: typing.SupportsInt = 200) -> str"
== "kw_func2(x: typing.SupportsInt | typing.SupportsIndex = 100, y: typing.SupportsInt | typing.SupportsIndex = 200) -> str"
)
assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None"
assert (
doc(m.kw_func4)
== "kw_func4(myList: collections.abc.Sequence[typing.SupportsInt] = [13, 17]) -> str"
== "kw_func4(myList: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex] = [13, 17]) -> str"
)
assert (
doc(m.kw_func_udl)
== "kw_func_udl(x: typing.SupportsInt, y: typing.SupportsInt = 300) -> str"
== "kw_func_udl(x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsInt | typing.SupportsIndex = 300) -> str"
)
assert (
doc(m.kw_func_udl_z)
== "kw_func_udl_z(x: typing.SupportsInt, y: typing.SupportsInt = 0) -> str"
== "kw_func_udl_z(x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsInt | typing.SupportsIndex = 0) -> str"
)
assert doc(m.args_function) == "args_function(*args) -> tuple"
assert (
@@ -42,11 +42,11 @@ def test_function_signatures(doc):
)
assert (
doc(m.KWClass.foo0)
== "foo0(self: m.kwargs_and_defaults.KWClass, arg0: typing.SupportsInt, arg1: typing.SupportsFloat) -> None"
== "foo0(self: m.kwargs_and_defaults.KWClass, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsFloat | typing.SupportsIndex) -> None"
)
assert (
doc(m.KWClass.foo1)
== "foo1(self: m.kwargs_and_defaults.KWClass, x: typing.SupportsInt, y: typing.SupportsFloat) -> None"
== "foo1(self: m.kwargs_and_defaults.KWClass, x: typing.SupportsInt | typing.SupportsIndex, y: typing.SupportsFloat | typing.SupportsIndex) -> None"
)
assert (
doc(m.kw_lb_func0)
@@ -138,7 +138,7 @@ def test_mixed_args_and_kwargs(msg):
msg(excinfo.value)
== """
mixed_plus_args(): incompatible function arguments. The following argument types are supported:
1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple
1. (arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsFloat | typing.SupportsIndex, *args) -> tuple
Invoked with: 1
"""
@@ -149,7 +149,7 @@ def test_mixed_args_and_kwargs(msg):
msg(excinfo.value)
== """
mixed_plus_args(): incompatible function arguments. The following argument types are supported:
1. (arg0: typing.SupportsInt, arg1: typing.SupportsFloat, *args) -> tuple
1. (arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsFloat | typing.SupportsIndex, *args) -> tuple
Invoked with:
"""
@@ -183,7 +183,7 @@ def test_mixed_args_and_kwargs(msg):
msg(excinfo.value)
== """
mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple
1. (i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, **kwargs) -> tuple
Invoked with: 1; kwargs: i=1
"""
@@ -194,7 +194,7 @@ def test_mixed_args_and_kwargs(msg):
msg(excinfo.value)
== """
mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
1. (i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, **kwargs) -> tuple
1. (i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, **kwargs) -> tuple
Invoked with: 1, 2; kwargs: j=1
"""
@@ -211,7 +211,7 @@ def test_mixed_args_and_kwargs(msg):
msg(excinfo.value)
== """
args_kwonly(): incompatible function arguments. The following argument types are supported:
1. (i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt) -> tuple
1. (i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsFloat | typing.SupportsIndex, *args, z: typing.SupportsInt | typing.SupportsIndex) -> tuple
Invoked with: 2, 2.5, 22
"""
@@ -233,12 +233,12 @@ def test_mixed_args_and_kwargs(msg):
)
assert (
m.args_kwonly_kwargs.__doc__
== "args_kwonly_kwargs(i: typing.SupportsInt, j: typing.SupportsFloat, *args, z: typing.SupportsInt, **kwargs) -> tuple\n"
== "args_kwonly_kwargs(i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsFloat | typing.SupportsIndex, *args, z: typing.SupportsInt | typing.SupportsIndex, **kwargs) -> tuple\n"
)
assert (
m.args_kwonly_kwargs_defaults.__doc__
== "args_kwonly_kwargs_defaults(i: typing.SupportsInt = 1, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n"
== "args_kwonly_kwargs_defaults(i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, z: typing.SupportsInt | typing.SupportsIndex = 42, **kwargs) -> tuple\n"
)
assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {})
assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {})
@@ -294,11 +294,11 @@ def test_keyword_only_args(msg):
x.method(i=1, j=2)
assert (
m.first_arg_kw_only.__init__.__doc__
== "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt = 0) -> None\n"
== "__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt | typing.SupportsIndex = 0) -> None\n"
)
assert (
m.first_arg_kw_only.method.__doc__
== "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt = 1, j: typing.SupportsInt = 2) -> None\n"
== "method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: typing.SupportsInt | typing.SupportsIndex = 1, j: typing.SupportsInt | typing.SupportsIndex = 2) -> None\n"
)
@@ -344,7 +344,7 @@ def test_positional_only_args():
# Mix it with args and kwargs:
assert (
m.args_kwonly_full_monty.__doc__
== "args_kwonly_full_monty(arg0: typing.SupportsInt = 1, arg1: typing.SupportsInt = 2, /, j: typing.SupportsFloat = 3.14159, *args, z: typing.SupportsInt = 42, **kwargs) -> tuple\n"
== "args_kwonly_full_monty(arg0: typing.SupportsInt | typing.SupportsIndex = 1, arg1: typing.SupportsInt | typing.SupportsIndex = 2, /, j: typing.SupportsFloat | typing.SupportsIndex = 3.14159, *args, z: typing.SupportsInt | typing.SupportsIndex = 42, **kwargs) -> tuple\n"
)
assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {})
@@ -387,30 +387,30 @@ def test_positional_only_args():
# https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
assert (
m.first_arg_kw_only.pos_only.__doc__
== "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: typing.SupportsInt, j: typing.SupportsInt) -> None\n"
== "pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsInt | typing.SupportsIndex) -> None\n"
)
def test_signatures():
assert (
m.kw_only_all.__doc__
== "kw_only_all(*, i: typing.SupportsInt, j: typing.SupportsInt) -> tuple\n"
== "kw_only_all(*, i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsInt | typing.SupportsIndex) -> tuple\n"
)
assert (
m.kw_only_mixed.__doc__
== "kw_only_mixed(i: typing.SupportsInt, *, j: typing.SupportsInt) -> tuple\n"
== "kw_only_mixed(i: typing.SupportsInt | typing.SupportsIndex, *, j: typing.SupportsInt | typing.SupportsIndex) -> tuple\n"
)
assert (
m.pos_only_all.__doc__
== "pos_only_all(i: typing.SupportsInt, j: typing.SupportsInt, /) -> tuple\n"
== "pos_only_all(i: typing.SupportsInt | typing.SupportsIndex, j: typing.SupportsInt | typing.SupportsIndex, /) -> tuple\n"
)
assert (
m.pos_only_mix.__doc__
== "pos_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt) -> tuple\n"
== "pos_only_mix(i: typing.SupportsInt | typing.SupportsIndex, /, j: typing.SupportsInt | typing.SupportsIndex) -> tuple\n"
)
assert (
m.pos_kw_only_mix.__doc__
== "pos_kw_only_mix(i: typing.SupportsInt, /, j: typing.SupportsInt, *, k: typing.SupportsInt) -> tuple\n"
== "pos_kw_only_mix(i: typing.SupportsInt | typing.SupportsIndex, /, j: typing.SupportsInt | typing.SupportsIndex, *, k: typing.SupportsInt | typing.SupportsIndex) -> tuple\n"
)

View File

@@ -251,7 +251,7 @@ def test_no_mixed_overloads():
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
if not detailed_error_messages_enabled
else "error while attempting to bind static method ExampleMandA.overload_mixed1"
"(arg0: typing.SupportsFloat) -> str"
"(arg0: typing.SupportsFloat | typing.SupportsIndex) -> str"
)
)
@@ -264,7 +264,7 @@ def test_no_mixed_overloads():
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
if not detailed_error_messages_enabled
else "error while attempting to bind instance method ExampleMandA.overload_mixed2"
"(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: typing.SupportsInt, arg1: typing.SupportsInt)"
"(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: typing.SupportsInt | typing.SupportsIndex, arg1: typing.SupportsInt | typing.SupportsIndex)"
" -> str"
)
)
@@ -491,7 +491,7 @@ def test_str_issue(msg):
msg(excinfo.value)
== """
__init__(): incompatible constructor arguments. The following argument types are supported:
1. m.methods_and_attributes.StrIssue(arg0: typing.SupportsInt)
1. m.methods_and_attributes.StrIssue(arg0: typing.SupportsInt | typing.SupportsIndex)
2. m.methods_and_attributes.StrIssue()
Invoked with: 'no', 'such', 'constructor'
@@ -534,21 +534,27 @@ def test_overload_ordering():
assert m.overload_order(0) == 4
assert (
"1. overload_order(arg0: typing.SupportsInt) -> int" in m.overload_order.__doc__
"1. overload_order(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
in m.overload_order.__doc__
)
assert "2. overload_order(arg0: str) -> int" in m.overload_order.__doc__
assert "3. overload_order(arg0: str) -> int" in m.overload_order.__doc__
assert (
"4. overload_order(arg0: typing.SupportsInt) -> int" in m.overload_order.__doc__
"4. overload_order(arg0: typing.SupportsInt | typing.SupportsIndex) -> int"
in m.overload_order.__doc__
)
with pytest.raises(TypeError) as err:
m.overload_order(1.1)
assert "1. (arg0: typing.SupportsInt) -> int" in str(err.value)
assert "1. (arg0: typing.SupportsInt | typing.SupportsIndex) -> int" in str(
err.value
)
assert "2. (arg0: str) -> int" in str(err.value)
assert "3. (arg0: str) -> int" in str(err.value)
assert "4. (arg0: typing.SupportsInt) -> int" in str(err.value)
assert "4. (arg0: typing.SupportsInt | typing.SupportsIndex) -> int" in str(
err.value
)
def test_rvalue_ref_param():

View File

@@ -367,7 +367,7 @@ def test_complex_array():
def test_signature(doc):
assert (
doc(m.create_rec_nested)
== "create_rec_nested(arg0: typing.SupportsInt) -> numpy.typing.NDArray[NestedStruct]"
== "create_rec_nested(arg0: typing.SupportsInt | typing.SupportsIndex) -> numpy.typing.NDArray[NestedStruct]"
)

View File

@@ -211,11 +211,11 @@ def test_passthrough_arguments(doc):
"vec_passthrough("
+ ", ".join(
[
"arg0: typing.SupportsFloat",
"arg0: typing.SupportsFloat | typing.SupportsIndex",
"arg1: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]",
"arg2: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]",
"arg3: typing.Annotated[numpy.typing.ArrayLike, numpy.int32]",
"arg4: typing.SupportsInt",
"arg4: typing.SupportsInt | typing.SupportsIndex",
"arg5: m.numpy_vectorize.NonPODClass",
"arg6: typing.Annotated[numpy.typing.ArrayLike, numpy.float64]",
]

View File

@@ -944,14 +944,14 @@ def test_tuple_variable_length_annotations(doc):
def test_dict_annotations(doc):
assert (
doc(m.annotate_dict_str_int)
== "annotate_dict_str_int(arg0: dict[str, typing.SupportsInt]) -> None"
== "annotate_dict_str_int(arg0: dict[str, typing.SupportsInt | typing.SupportsIndex]) -> None"
)
def test_list_annotations(doc):
assert (
doc(m.annotate_list_int)
== "annotate_list_int(arg0: list[typing.SupportsInt]) -> None"
== "annotate_list_int(arg0: list[typing.SupportsInt | typing.SupportsIndex]) -> None"
)
@@ -969,7 +969,7 @@ def test_iterable_annotations(doc):
def test_iterator_annotations(doc):
assert (
doc(m.annotate_iterator_int)
== "annotate_iterator_int(arg0: collections.abc.Iterator[typing.SupportsInt]) -> None"
== "annotate_iterator_int(arg0: collections.abc.Iterator[typing.SupportsInt | typing.SupportsIndex]) -> None"
)
@@ -989,7 +989,8 @@ def test_fn_return_only(doc):
def test_type_annotation(doc):
assert (
doc(m.annotate_type) == "annotate_type(arg0: type[typing.SupportsInt]) -> type"
doc(m.annotate_type)
== "annotate_type(arg0: type[typing.SupportsInt | typing.SupportsIndex]) -> type"
)
@@ -1007,7 +1008,7 @@ def test_union_typing_only(doc):
def test_union_object_annotations(doc):
assert (
doc(m.annotate_union_to_object)
== "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object"
== "annotate_union_to_object(arg0: typing.SupportsInt | typing.SupportsIndex | str) -> object"
)
@@ -1044,7 +1045,7 @@ def test_never_annotation(doc, backport_typehints):
def test_optional_object_annotations(doc):
assert (
doc(m.annotate_optional_to_object)
== "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object"
== "annotate_optional_to_object(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> object"
)
@@ -1167,7 +1168,10 @@ def get_annotations_helper(o):
def test_module_attribute_types() -> None:
module_annotations = get_annotations_helper(m)
assert module_annotations["list_int"] == "list[typing.SupportsInt]"
assert (
module_annotations["list_int"]
== "list[typing.SupportsInt | typing.SupportsIndex]"
)
assert module_annotations["set_str"] == "set[str]"
assert module_annotations["foo"] == "pybind11_tests.pytypes.foo"
@@ -1190,7 +1194,10 @@ def test_get_annotations_compliance() -> None:
module_annotations = get_annotations(m)
assert module_annotations["list_int"] == "list[typing.SupportsInt]"
assert (
module_annotations["list_int"]
== "list[typing.SupportsInt | typing.SupportsIndex]"
)
assert module_annotations["set_str"] == "set[str]"
@@ -1204,10 +1211,13 @@ def test_class_attribute_types() -> None:
instance_annotations = get_annotations_helper(m.Instance)
assert empty_annotations is None
assert static_annotations["x"] == "typing.ClassVar[typing.SupportsFloat]"
assert (
static_annotations["x"]
== "typing.ClassVar[typing.SupportsFloat | typing.SupportsIndex]"
)
assert (
static_annotations["dict_str_int"]
== "typing.ClassVar[dict[str, typing.SupportsInt]]"
== "typing.ClassVar[dict[str, typing.SupportsInt | typing.SupportsIndex]]"
)
assert m.Static.x == 1.0
@@ -1219,7 +1229,7 @@ def test_class_attribute_types() -> None:
static.dict_str_int["hi"] = 3
assert m.Static().dict_str_int == {"hi": 3}
assert instance_annotations["y"] == "typing.SupportsFloat"
assert instance_annotations["y"] == "typing.SupportsFloat | typing.SupportsIndex"
instance1 = m.Instance()
instance1.y = 4.0
@@ -1236,7 +1246,10 @@ def test_class_attribute_types() -> None:
def test_redeclaration_attr_with_type_hint() -> None:
obj = m.Instance()
m.attr_with_type_hint_float_x(obj)
assert get_annotations_helper(obj)["x"] == "typing.SupportsFloat"
assert (
get_annotations_helper(obj)["x"]
== "typing.SupportsFloat | typing.SupportsIndex"
)
with pytest.raises(
RuntimeError, match=r'^__annotations__\["x"\] was set already\.$'
):

View File

@@ -22,7 +22,7 @@ def test_vector(doc):
assert doc(m.cast_vector) == "cast_vector() -> list[int]"
assert (
doc(m.load_vector)
== "load_vector(arg0: collections.abc.Sequence[typing.SupportsInt]) -> bool"
== "load_vector(arg0: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> bool"
)
# Test regression caused by 936: pointers to stl containers weren't castable
@@ -51,7 +51,7 @@ def test_array(doc):
)
assert (
doc(m.load_array)
== 'load_array(arg0: typing.Annotated[collections.abc.Sequence[typing.SupportsInt], "FixedSize(2)"]) -> bool'
== 'load_array(arg0: typing.Annotated[collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], "FixedSize(2)"]) -> bool'
)
@@ -72,7 +72,7 @@ def test_valarray(doc):
assert doc(m.cast_valarray) == "cast_valarray() -> list[int]"
assert (
doc(m.load_valarray)
== "load_valarray(arg0: collections.abc.Sequence[typing.SupportsInt]) -> bool"
== "load_valarray(arg0: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> bool"
)
@@ -234,7 +234,7 @@ def test_reference_sensitive_optional(doc):
assert (
doc(m.double_or_zero_refsensitive)
== "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int"
== "double_or_zero_refsensitive(arg0: typing.SupportsInt | typing.SupportsIndex | None) -> int"
)
assert m.half_or_none_refsensitive(0) is None
@@ -352,7 +352,7 @@ def test_variant(doc):
assert (
doc(m.load_variant)
== "load_variant(arg0: typing.SupportsInt | str | typing.SupportsFloat | None) -> str"
== "load_variant(arg0: typing.SupportsInt | typing.SupportsIndex | str | typing.SupportsFloat | typing.SupportsIndex | None) -> str"
)
@@ -368,7 +368,7 @@ def test_variant_monostate(doc):
assert (
doc(m.load_monostate_variant)
== "load_monostate_variant(arg0: None | typing.SupportsInt | str) -> str"
== "load_monostate_variant(arg0: None | typing.SupportsInt | typing.SupportsIndex | str) -> str"
)
@@ -388,7 +388,7 @@ def test_stl_pass_by_pointer(msg):
msg(excinfo.value)
== """
stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported:
1. (v: collections.abc.Sequence[typing.SupportsInt] = None) -> list[int]
1. (v: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex] = None) -> list[int]
Invoked with:
"""
@@ -400,7 +400,7 @@ def test_stl_pass_by_pointer(msg):
msg(excinfo.value)
== """
stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported:
1. (v: collections.abc.Sequence[typing.SupportsInt] = None) -> list[int]
1. (v: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex] = None) -> list[int]
Invoked with: None
"""
@@ -615,7 +615,7 @@ def test_sequence_caster_protocol(doc):
# convert mode
assert (
doc(m.roundtrip_std_vector_int)
== "roundtrip_std_vector_int(arg0: collections.abc.Sequence[typing.SupportsInt]) -> list[int]"
== "roundtrip_std_vector_int(arg0: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> list[int]"
)
assert m.roundtrip_std_vector_int([1, 2, 3]) == [1, 2, 3]
assert m.roundtrip_std_vector_int((1, 2, 3)) == [1, 2, 3]
@@ -668,7 +668,7 @@ def test_mapping_caster_protocol(doc):
# convert mode
assert (
doc(m.roundtrip_std_map_str_int)
== "roundtrip_std_map_str_int(arg0: collections.abc.Mapping[str, typing.SupportsInt]) -> dict[str, int]"
== "roundtrip_std_map_str_int(arg0: collections.abc.Mapping[str, typing.SupportsInt | typing.SupportsIndex]) -> dict[str, int]"
)
assert m.roundtrip_std_map_str_int(a1b2c3) == a1b2c3
assert m.roundtrip_std_map_str_int(FormalMappingLike(**a1b2c3)) == a1b2c3
@@ -714,7 +714,7 @@ def test_set_caster_protocol(doc):
# convert mode
assert (
doc(m.roundtrip_std_set_int)
== "roundtrip_std_set_int(arg0: collections.abc.Set[typing.SupportsInt]) -> set[int]"
== "roundtrip_std_set_int(arg0: collections.abc.Set[typing.SupportsInt | typing.SupportsIndex]) -> set[int]"
)
assert m.roundtrip_std_set_int({1, 2, 3}) == {1, 2, 3}
assert m.roundtrip_std_set_int(FormalSetLike(1, 2, 3)) == {1, 2, 3}

View File

@@ -103,7 +103,8 @@ def test_return_list_pyobject_ptr_reference():
def test_type_caster_name_via_incompatible_function_arguments_type_error():
with pytest.raises(
TypeError, match=r"1\. \(arg0: object, arg1: typing.SupportsInt\) -> None"
TypeError,
match=r"1\. \(arg0: object, arg1: typing.SupportsInt \| typing.SupportsIndex\) -> None",
):
m.pass_pyobject_ptr_and_int(ValueHolder(101), ValueHolder(202))