Skip to content

fix(types): type hints from future python versions #5693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jun 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
8 changes: 4 additions & 4 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ class type_caster<void> : public type_caster<void_type> {
template <typename T>
using cast_op_type = void *&;
explicit operator void *&() { return value; }
static constexpr auto name = const_name("types.CapsuleType");
static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_TYPE_HINT);

private:
void *value = nullptr;
Expand Down Expand Up @@ -1361,7 +1361,7 @@ struct handle_type_name<dict> {
};
template <>
struct handle_type_name<anyset> {
static constexpr auto name = const_name("typing.Union[set, frozenset]");
static constexpr auto name = const_name("set | frozenset");
};
template <>
struct handle_type_name<set> {
Expand Down Expand Up @@ -1441,15 +1441,15 @@ struct handle_type_name<type> {
};
template <>
struct handle_type_name<capsule> {
static constexpr auto name = const_name("types.CapsuleType");
static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_TYPE_HINT);
};
template <>
struct handle_type_name<ellipsis> {
static constexpr auto name = const_name("ellipsis");
};
template <>
struct handle_type_name<weakref> {
static constexpr auto name = const_name("weakref");
static constexpr auto name = const_name("weakref.ReferenceType");
};
template <>
struct handle_type_name<args> {
Expand Down
23 changes: 23 additions & 0 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,36 @@
# define PYBIND11_HAS_SUBINTERPRETER_SUPPORT
#endif

// 3.13 Compatibility
#if 0x030D0000 <= PY_VERSION_HEX
# define PYBIND11_TYPE_IS_TYPE_HINT "typing.TypeIs"
# define PYBIND11_CAPSULE_TYPE_TYPE_HINT "types.CapsuleType"
#else
# define PYBIND11_TYPE_IS_TYPE_HINT "typing_extensions.TypeIs"
# define PYBIND11_CAPSULE_TYPE_TYPE_HINT "typing_extensions.CapsuleType"
#endif

// 3.12 Compatibility
#if 0x030C0000 <= PY_VERSION_HEX
# define PYBIND11_BUFFER_TYPE_HINT "collections.abc.Buffer"
#else
# define PYBIND11_BUFFER_TYPE_HINT "typing_extensions.Buffer"
#endif

// 3.11 Compatibility
#if 0x030B0000 <= PY_VERSION_HEX
# define PYBIND11_NEVER_TYPE_HINT "typing.Never"
#else
# define PYBIND11_NEVER_TYPE_HINT "typing_extensions.Never"
#endif

// 3.10 Compatibility
#if 0x030A0000 <= PY_VERSION_HEX
# define PYBIND11_TYPE_GUARD_TYPE_HINT "typing.TypeGuard"
#else
# define PYBIND11_TYPE_GUARD_TYPE_HINT "typing_extensions.TypeGuard"
#endif

// #define PYBIND11_STR_LEGACY_PERMISSIVE
// If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject
// (probably surprising and never documented, but this was the
Expand Down
25 changes: 25 additions & 0 deletions include/pybind11/detail/descr.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,24 @@ constexpr descr<1, Type> _() {
#endif // #ifndef _

constexpr descr<0> concat() { return {}; }
constexpr descr<0> union_concat() { return {}; }

template <size_t N, typename... Ts>
constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
return descr;
}

template <size_t N, typename... Ts>
constexpr descr<N, Ts...> union_concat(const descr<N, Ts...> &descr) {
return descr;
}

template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 3, Ts1..., Ts2...> operator|(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return a + const_name(" | ") + b;
}

#ifdef __cpp_fold_expressions
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2 + 2, Ts1..., Ts2...> operator,(const descr<N1, Ts1...> &a,
Expand All @@ -174,12 +186,25 @@ template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
return (d, ..., args);
}

template <size_t N, typename... Ts, typename... Args>
constexpr auto union_concat(const descr<N, Ts...> &d, const Args &...args) {
return (d | ... | args);
}

#else
template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
return d + const_name(", ") + concat(args...);
}

template <size_t N, typename... Ts, typename... Args>
constexpr auto union_concat(const descr<N, Ts...> &d, const Args &...args)
-> decltype(std::declval<descr<N + 3, Ts...>>() + union_concat(args...)) {
return d + const_name(" | ") + union_concat(args...);
}

#endif

template <size_t N, typename... Ts>
Expand Down
8 changes: 2 additions & 6 deletions include/pybind11/stl.h
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,7 @@ struct optional_caster {
return true;
}

PYBIND11_TYPE_CASTER(Type,
const_name("typing.Optional[") + value_conv::name + const_name("]"));
PYBIND11_TYPE_CASTER(Type, value_conv::name | make_caster<none>::name);
};

#if defined(PYBIND11_HAS_OPTIONAL)
Expand Down Expand Up @@ -642,10 +641,7 @@ struct variant_caster<V<Ts...>> {
}

using Type = V<Ts...>;
PYBIND11_TYPE_CASTER(Type,
const_name("typing.Union[")
+ ::pybind11::detail::concat(make_caster<Ts>::name...)
+ const_name("]"));
PYBIND11_TYPE_CASTER(Type, ::pybind11::detail::union_concat(make_caster<Ts>::name...));
};

#if defined(PYBIND11_HAS_VARIANT)
Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/stl/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ struct path_caster {
return true;
}

PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path"));
PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path"));
};

#endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
Expand Down
17 changes: 7 additions & 10 deletions include/pybind11/typing.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,12 @@ struct handle_type_name<typing::Type<T>> {

template <typename... Types>
struct handle_type_name<typing::Union<Types...>> {
static constexpr auto name = const_name("typing.Union[")
+ ::pybind11::detail::concat(make_caster<Types>::name...)
+ const_name("]");
static constexpr auto name = ::pybind11::detail::union_concat(make_caster<Types>::name...);
};

template <typename T>
struct handle_type_name<typing::Optional<T>> {
static constexpr auto name
= const_name("typing.Optional[") + make_caster<T>::name + const_name("]");
static constexpr auto name = make_caster<T>::name | make_caster<none>::name;
};

template <typename T>
Expand All @@ -244,14 +241,14 @@ struct handle_type_name<typing::ClassVar<T>> {

template <typename T>
struct handle_type_name<typing::TypeGuard<T>> {
static constexpr auto name
= const_name("typing.TypeGuard[") + make_caster<T>::name + const_name("]");
static constexpr auto name = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[")
+ make_caster<T>::name + const_name("]");
};

template <typename T>
struct handle_type_name<typing::TypeIs<T>> {
static constexpr auto name
= const_name("typing.TypeIs[") + make_caster<T>::name + const_name("]");
static constexpr auto name = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[")
+ make_caster<T>::name + const_name("]");
};

template <>
Expand All @@ -261,7 +258,7 @@ struct handle_type_name<typing::NoReturn> {

template <>
struct handle_type_name<typing::Never> {
static constexpr auto name = const_name("typing.Never");
static constexpr auto name = const_name(PYBIND11_NEVER_TYPE_HINT);
};

#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL)
Expand Down
23 changes: 23 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import sysconfig
import textwrap
import traceback
from typing import Callable

import pytest

Expand Down Expand Up @@ -242,3 +243,25 @@ def pytest_report_header():
lines.append("free-threaded Python build")

return lines


@pytest.fixture
def backport_typehints() -> Callable[[SanitizedString], SanitizedString]:
d = {}
if sys.version_info < (3, 13):
d["typing_extensions.TypeIs"] = "typing.TypeIs"
d["typing_extensions.CapsuleType"] = "types.CapsuleType"
if sys.version_info < (3, 12):
d["typing_extensions.Buffer"] = "collections.abc.Buffer"
if sys.version_info < (3, 11):
d["typing_extensions.Never"] = "typing.Never"
if sys.version_info < (3, 10):
d["typing_extensions.TypeGuard"] = "typing.TypeGuard"

def backport(sanatized_string: SanitizedString) -> SanitizedString:
for old, new in d.items():
sanatized_string.string = sanatized_string.string.replace(old, new)

return sanatized_string

return backport
12 changes: 5 additions & 7 deletions tests/test_buffers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import ctypes
import io
import struct
import sys

import pytest

Expand Down Expand Up @@ -228,12 +227,11 @@ def test_ctypes_from_buffer():
assert not cinfo.readonly


def test_buffer_docstring():
if sys.version_info >= (3, 12):
docstring = "get_buffer_info(arg0: collections.abc.Buffer) -> pybind11_tests.buffers.buffer_info"
else:
docstring = "get_buffer_info(arg0: typing_extensions.Buffer) -> pybind11_tests.buffers.buffer_info"
assert m.get_buffer_info.__doc__.strip() == docstring
def test_buffer_docstring(doc, backport_typehints):
assert (
backport_typehints(doc(m.get_buffer_info))
== "get_buffer_info(arg0: collections.abc.Buffer) -> m.buffers.buffer_info"
)


def test_buffer_exception():
Expand Down
13 changes: 7 additions & 6 deletions tests/test_opaque_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_string_list():
assert m.print_opaque_list(cvp.stringList) == "Opaque list: [Element 1, Element 3]"


def test_pointers(msg):
def test_pointers(msg, backport_typehints):
living_before = ConstructorStats.get(UserType).alive()
assert m.get_void_ptr_value(m.return_void_ptr()) == 0x1234
assert m.get_void_ptr_value(UserType()) # Should also work for other C++ types
Expand All @@ -37,14 +37,15 @@ def test_pointers(msg):

with pytest.raises(TypeError) as excinfo:
m.get_void_ptr_value([1, 2, 3]) # This should not work

assert (
msg(excinfo.value)
backport_typehints(msg(excinfo.value))
== """
get_void_ptr_value(): incompatible function arguments. The following argument types are supported:
1. (arg0: types.CapsuleType) -> int
get_void_ptr_value(): incompatible function arguments. The following argument types are supported:
1. (arg0: types.CapsuleType) -> int

Invoked with: [1, 2, 3]
"""
Invoked with: [1, 2, 3]
"""
)

assert m.return_null_str() is None
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ namespace detail {

template <>
struct type_caster<RealNumber> {
PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float"));
PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float"));

static handle cast(const RealNumber &number, return_value_policy, handle) {
return py::float_(number.value).release();
Expand Down
Loading