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

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