Skip to content

[BUG]: type_caster<std::complex<T>>'s parameter annotation omits builtin complex for Python 3.10 #6102

Description

@IvanaGyro

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

3.0.2~3.0.4

Problem description

Summary

pybind11/complex.h's type_caster<std::complex<T>> uses io_name(input, output) with two different annotation strings:

PYBIND11_TYPE_CASTER(
    std::complex<T>,
    io_name("typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex",
            "complex"));

The output (return type) annotation is correctly complex. The input (parameter type) annotation is typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex -- it does not include plain complex.

This is a real gap under strict type checking: typeshed's builtin complex type only gained __complex__ in Python >= 3.11 (see python/typeshed). Under Python 3.10 (still within pybind11's declared support window and a common requires-python floor for downstream projects), complex does not satisfy typing.SupportsComplex, so a stub generated from this caster rejects a call like f(2+1j) even though the C++ binding accepts it perfectly fine at runtime (the caster's load() uses PyComplex_AsCComplex, which handles a plain Python complex directly).

Reproduction

Any pybind11-bound function taking a std::complex<double>/std::complex<float> parameter, with a stub generated via pybind11-stubgen, produces a parameter annotation that a strict type checker (mypy/pyright under Python 3.10) rejects for a plain Python complex literal:

def f(z: typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex) -> complex: ...

f(2 + 1j)  # type-checker error under Python 3.10: complex has no __complex__

Suggested fix

Prefix the input annotation with complex |, matching the output annotation's correctness:

io_name("complex | typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex",
        "complex")

Reproducible example code

#include <complex>

#include <pybind11/complex.h>
#include <pybind11/pybind11.h>

namespace py = pybind11;

std::complex<double> ident(std::complex<double> z) {
    return z;
}

PYBIND11_MODULE(complex_issue, m) {
    m.def("ident", &ident);
}

Is this a regression? Put the last known working version here if it is.

3.0.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageNew bug, unverified

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions