Files
pybind11/include/pybind11/complex.h
Ralf W. Grosse-Kunstleve 9f1187f97c 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.
2025-11-10 20:26:50 -08:00

94 lines
2.7 KiB
C++

/*
pybind11/complex.h: Complex number support
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "pybind11.h"
#include <complex>
/// glibc defines I as a macro which breaks things, e.g., boost template names
#ifdef I
# undef I
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
template <typename T>
struct format_descriptor<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> {
static constexpr const char c = format_descriptor<T>::c;
static constexpr const char value[3] = {'Z', c, '\0'};
static std::string format() { return std::string(value); }
};
#ifndef PYBIND11_CPP17
template <typename T>
constexpr const char
format_descriptor<std::complex<T>,
detail::enable_if_t<std::is_floating_point<T>::value>>::value[3];
#endif
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T>
struct is_fmt_numeric<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> {
static constexpr bool value = true;
static constexpr int index = is_fmt_numeric<T>::index + 3;
};
template <typename T>
class type_caster<std::complex<T>> {
public:
bool load(handle src, bool convert) {
if (!src) {
return false;
}
if (!convert && !PyComplex_Check(src.ptr())) {
return false;
}
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;
}
value = std::complex<T>((T) result.real, (T) result.imag);
return true;
}
static handle
cast(const std::complex<T> &src, return_value_policy /* policy */, handle /* parent */) {
return PyComplex_FromDoubles((double) src.real(), (double) src.imag());
}
PYBIND11_TYPE_CASTER(
std::complex<T>,
io_name("typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex",
"complex"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)