From f4308038425387f05fc1c93e2d9c197d8bc2298a Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:28:08 -0400 Subject: [PATCH 01/19] fix future type hints Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 8 +- include/pybind11/detail/common.h | 25 ++ include/pybind11/detail/descr.h | 18 ++ include/pybind11/stl/filesystem.h | 6 +- include/pybind11/typing.h | 33 ++- tests/test_opaque_types.py | 31 ++- tests/test_pytypes.cpp | 7 +- tests/test_pytypes.py | 435 +++++++++++++++++++++--------- tests/test_stl.py | 89 ++++-- 9 files changed, 475 insertions(+), 177 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0cc63b0b4a..3f0f743b3e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -404,7 +404,7 @@ class type_caster : public type_caster { template 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; @@ -1359,7 +1359,11 @@ struct handle_type_name { }; template <> struct handle_type_name { +#if PYBIND11_USE_NEW_UNIONS + static constexpr auto name = const_name("set | frozenset"); +#else static constexpr auto name = const_name("typing.Union[set, frozenset]"); +#endif }; template <> struct handle_type_name { @@ -1439,7 +1443,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("types.CapsuleType"); + static constexpr auto name = const_name(PYBIND11_CAPSULE_TYPE_TYPE_HINT); }; template <> struct handle_type_name { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 6a9440b60d..5c668eb1dd 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -261,6 +261,15 @@ # 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" @@ -268,6 +277,22 @@ # 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_USE_NEW_UNIONS true +# define PYBIND11_TYPE_GUARD_TYPE_HINT "typing.TypeGuard" +#else +# define PYBIND11_USE_NEW_UNIONS false +# 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 diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index e5f829d2eb..90fa6da456 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -174,12 +174,30 @@ template constexpr auto concat(const descr &d, const Args &...args) { return (d, ..., args); } + +template +constexpr descr operator|(const descr &a, + const descr &b) { + return a + const_name(" | ") + b; +} + +template +constexpr auto union_concat(const descr &d, const Args &...args) { + return (d | ... | args); +} + #else template constexpr auto concat(const descr &d, const Args &...args) -> decltype(std::declval>() + concat(args...)) { return d + const_name(", ") + concat(args...); } + +template +constexpr auto union_concat(const descr &d, const Args &...args) + -> decltype(std::declval>() + union_concat(args...)) { + return d + const_name(" | ") + union_concat(args...); +} #endif template diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index de64193ac4..a9ce8c1e7c 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -96,7 +96,11 @@ struct path_caster { return true; } - PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); + #if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); + #else + PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); + #endif }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 57b2c087d1..48c93064f4 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -216,18 +216,39 @@ struct handle_type_name> { static constexpr auto name = const_name("type[") + make_caster::name + const_name("]"); }; +#if PYBIND11_USE_NEW_UNIONS +template +constexpr auto union_helper() { + return ::pybind11::detail::union_concat(make_caster::name...); +} +#else +template +constexpr auto union_helper() { + return const_name("typing.Union[") + + ::pybind11::detail::concat(make_caster::name...) + + const_name("]"); +} +#endif + template struct handle_type_name> { - static constexpr auto name = const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); + static constexpr auto name = union_helper(); }; + +#if PYBIND11_USE_NEW_UNIONS +template +struct handle_type_name> { + static constexpr auto name + = union_helper(); +}; +#else template struct handle_type_name> { static constexpr auto name = const_name("typing.Optional[") + make_caster::name + const_name("]"); }; +#endif template struct handle_type_name> { @@ -245,13 +266,13 @@ struct handle_type_name> { template struct handle_type_name> { static constexpr auto name - = const_name("typing.TypeGuard[") + make_caster::name + const_name("]"); + = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name - = const_name("typing.TypeIs[") + make_caster::name + const_name("]"); + = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); }; template <> @@ -261,7 +282,7 @@ struct handle_type_name { template <> struct handle_type_name { - 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) diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 498b6e02b2..6790b89d25 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import pytest import env @@ -37,15 +38,27 @@ 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) - == """ - get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: types.CapsuleType) -> int - - Invoked with: [1, 2, 3] - """ - ) + + if sys.version_info >= (3, 13): + assert ( + msg(excinfo.value) + == """ + get_void_ptr_value(): incompatible function arguments. The following argument types are supported: + 1. (arg0: types.CapsuleType) -> int + + Invoked with: [1, 2, 3] + """ + ) + else: + assert ( + msg(excinfo.value) + == """ + get_void_ptr_value(): incompatible function arguments. The following argument types are supported: + 1. (arg0: typing_extensions.CapsuleType) -> int + + Invoked with: [1, 2, 3] + """ + ) assert m.return_null_str() is None assert m.get_null_str_value(m.return_null_str()) is not None diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 657480a005..99b38c5a1d 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -155,7 +155,11 @@ namespace detail { template <> struct type_caster { - PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); + #if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); + #else + PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); + #endif static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); @@ -172,6 +176,7 @@ struct type_caster { value.value = src.cast(); return true; } + // static void f/ }; } // namespace detail diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 545ad5b97f..8db016cd4b 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -130,10 +130,16 @@ def test_set(capture, doc): assert m.anyset_contains({"foo"}, "foo") assert doc(m.get_set) == "get_set() -> set" - assert ( - doc(m.print_anyset) - == "print_anyset(arg0: typing.Union[set, frozenset]) -> None" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.print_anyset) + == "print_anyset(arg0: set | frozenset) -> None" + ) + else: + assert ( + doc(m.print_anyset) + == "print_anyset(arg0: typing.Union[set, frozenset]) -> None" + ) def test_frozenset(capture, doc): @@ -990,45 +996,81 @@ def test_type_annotation(doc): def test_union_annotations(doc): - assert ( - doc(m.annotate_union) - == "annotate_union(arg0: list[typing.Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[typing.Union[str, int, object]]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_union) + == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" + ) + else: + assert ( + doc(m.annotate_union) + == "annotate_union(arg0: list[typing.Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[typing.Union[str, int, object]]" + ) def test_union_typing_only(doc): - assert ( - doc(m.union_typing_only) - == "union_typing_only(arg0: list[typing.Union[str]]) -> list[typing.Union[int]]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.union_typing_only) + == "union_typing_only(arg0: list[str]) -> list[int]" + ) + else: + assert ( + doc(m.union_typing_only) + == "union_typing_only(arg0: list[typing.Union[str]]) -> list[typing.Union[int]]" + ) def test_union_object_annotations(doc): - assert ( - doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: typing.Union[typing.SupportsInt, str]) -> object" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_union_to_object) + == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" + ) + else: + assert ( + doc(m.annotate_union_to_object) + == "annotate_union_to_object(arg0: typing.Union[typing.SupportsInt, str]) -> object" + ) def test_optional_annotations(doc): - assert ( - doc(m.annotate_optional) - == "annotate_optional(arg0: list) -> list[typing.Optional[str]]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_optional) + == "annotate_optional(arg0: list) -> list[str | None]" + ) + else: + assert ( + doc(m.annotate_optional) + == "annotate_optional(arg0: list) -> list[typing.Optional[str]]" + ) def test_type_guard_annotations(doc): - assert ( - doc(m.annotate_type_guard) - == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.annotate_type_guard) + == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" + ) + else: + assert ( + doc(m.annotate_type_guard) + == "annotate_type_guard(arg0: object) -> typing_extensions.TypeGuard[str]" + ) def test_type_is_annotations(doc): - assert ( - doc(m.annotate_type_is) - == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" - ) + if sys.version_info >= (3, 13): + assert ( + doc(m.annotate_type_is) + == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" + ) + else: + assert ( + doc(m.annotate_type_is) + == "annotate_type_is(arg0: object) -> typing_extensions.TypeIs[str]" + ) def test_no_return_annotation(doc): @@ -1036,14 +1078,23 @@ def test_no_return_annotation(doc): def test_never_annotation(doc): - assert doc(m.annotate_never) == "annotate_never() -> typing.Never" + if sys.version_info >= (3, 11): + assert doc(m.annotate_never) == "annotate_never() -> typing.Never" + else: + assert doc(m.annotate_never) == "annotate_never() -> typing_extensions.Never" def test_optional_object_annotations(doc): - assert ( + if sys.version_info >= (3, 10): + assert ( doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.Optional[typing.SupportsInt]) -> object" + == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" ) + else: + assert ( + doc(m.annotate_optional_to_object) + == "annotate_optional_to_object(arg0: typing.Optional[typing.SupportsInt]) -> object" + ) @pytest.mark.skipif( @@ -1076,10 +1127,16 @@ def test_literal(doc): doc(m.identity_literal_curly_close) == 'identity_literal_curly_close(arg0: typing.Literal["}"]) -> typing.Literal["}"]' ) - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' + ) + else: + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' + ) assert ( doc(m.identity_literal_arrow_with_callable) == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' @@ -1168,10 +1225,17 @@ def test_module_attribute_types() -> None: assert module_annotations["list_int"] == "list[typing.SupportsInt]" assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" - assert ( - module_annotations["foo_union"] - == "typing.Union[pybind11_tests.pytypes.foo, pybind11_tests.pytypes.foo2, pybind11_tests.pytypes.foo3]" - ) + + if sys.version_info >= (3,10): + assert ( + module_annotations["foo_union"] + == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" + ) + else: + assert ( + module_annotations["foo_union"] + == "typing.Union[pybind11_tests.pytypes.foo, pybind11_tests.pytypes.foo2, pybind11_tests.pytypes.foo3]" + ) @pytest.mark.skipif( @@ -1250,14 +1314,24 @@ def test_final_annotation() -> None: def test_arg_return_type_hints(doc): - assert ( - doc(m.half_of_number) - == "half_of_number(arg0: typing.Union[float, int]) -> float" - ) - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: typing.Union[float, int]) -> float" - ) + if sys.version_info >= (3, 10): + assert ( + doc(m.half_of_number) + == "half_of_number(arg0: float | int) -> float" + ) + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: float | int) -> float" + ) + else: + assert ( + doc(m.half_of_number) + == "half_of_number(arg0: typing.Union[float, int]) -> float" + ) + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: typing.Union[float, int]) -> float" + ) assert ( doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" ) @@ -1266,90 +1340,183 @@ def test_arg_return_type_hints(doc): assert m.half_of_number(0) == 0 assert isinstance(m.half_of_number(0), float) assert not isinstance(m.half_of_number(0), int) - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: collections.abc.Sequence[typing.Union[float, int]]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[typing.Union[float, int], typing.Union[float, int]]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[typing.Union[float, int], ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, typing.Union[float, int]]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[typing.Union[float, int]]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[typing.Union[float, int]]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) - == "identity_set(arg0: set[typing.Union[float, int]]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: collections.abc.Iterable[typing.Union[float, int]]) -> collections.abc.Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: collections.abc.Iterator[typing.Union[float, int]]) -> collections.abc.Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: collections.abc.Callable[[typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Union[float, int]], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]) -> collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: typing.Union[float, int], arg1: collections.abc.Callable[[typing.Union[float, int]], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: typing.Union[float, int], arg1: collections.abc.Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: typing.Union[typing.Union[float, int], str]) -> typing.Union[float, str]" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" - ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" - ) - # TypeIs - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing.TypeIs[float]" + + if sys.version_info >= (3, 10): + # std::vector + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[float | int]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" + ) + # Set + assert ( + doc(m.identity_set) + == "identity_set(arg0: set[float | int]) -> set[float]" + ) + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" + ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: float | int | str) -> float | str" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: float | int | None) -> float | None" + ) + # TypeGuard + assert ( + doc(m.check_type_guard) + == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" + ) + # TypeIs + if sys.version_info >= (3, 13): + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing.TypeIs[float]" + else: + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + else: + # std::vector + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: collections.abc.Sequence[typing.Union[float, int]]) -> list[float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[typing.Union[float, int], typing.Union[float, int]]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[typing.Union[float, int], ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, typing.Union[float, int]]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[typing.Union[float, int]]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[typing.Union[float, int]]]) -> list[list[float]]" + ) + # Set + assert ( + doc(m.identity_set) + == "identity_set(arg0: set[typing.Union[float, int]]) -> set[float]" + ) + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: collections.abc.Iterable[typing.Union[float, int]]) -> collections.abc.Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: collections.abc.Iterator[typing.Union[float, int]]) -> collections.abc.Iterator[float]" + ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: collections.abc.Callable[[typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Union[float, int]], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]) -> collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: typing.Union[float, int], arg1: collections.abc.Callable[[typing.Union[float, int]], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: typing.Union[float, int], arg1: collections.abc.Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: typing.Union[typing.Union[float, int], str]) -> typing.Union[float, str]" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" + ) + # TypeGuard + assert ( + doc(m.check_type_guard) + == "check_type_guard(arg0: list[object]) -> typing_extensions.TypeGuard[list[float]]" + ) + # TypeIs + assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" diff --git a/tests/test_stl.py b/tests/test_stl.py index c952d034fe..839e6d6207 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import pytest import env # noqa: F401 @@ -274,37 +275,17 @@ def __fspath__(self): assert m.parent_path(b"foo/bar") == Path("foo") assert m.parent_path(PseudoStrPath()) == Path("foo") assert m.parent_path(PseudoBytesPath()) == Path("foo") - assert ( - doc(m.parent_path) - == "parent_path(arg0: typing.Union[os.PathLike, str, bytes]) -> pathlib.Path" - ) # std::vector assert m.parent_paths(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: collections.abc.Sequence[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) # py::typing::List assert m.parent_paths_list(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) # Nested py::typing::List assert m.parent_paths_nested_list([["foo/bar"], ["foo/baz", "foo/buzz"]]) == [ [Path("foo")], [Path("foo"), Path("foo")], ] - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[typing.Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" - ) # py::typing::Tuple assert m.parent_paths_tuple(("foo/bar", "foo/baz")) == (Path("foo"), Path("foo")) - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[typing.Union[os.PathLike, str, bytes], typing.Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" - ) # py::typing::Dict assert m.parent_paths_dict( { @@ -317,11 +298,71 @@ def __fspath__(self): "key2": Path("foo"), "key3": Path("foo"), } - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" - ) +@pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") +def test_path_typing(doc): + if sys.version_info >= (3, 10): + # Single argument + assert ( + doc(m.parent_path) + == "parent_path(arg0: os.PathLike | str | bytes) -> pathlib.Path" + ) + # std::vector + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: collections.abc.Sequence[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # py::typing::List + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # Nested py::typing::List + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[os.PathLike | str | bytes]]) -> list[list[pathlib.Path]]" + ) + # py::typing::Tuple + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[os.PathLike | str | bytes, os.PathLike | str | bytes]) -> tuple[pathlib.Path, pathlib.Path]" + ) + # py::typing::Dict + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, os.PathLike | str | bytes]) -> dict[str, pathlib.Path]" + ) + else: + # Single argument + assert ( + doc(m.parent_path) + == "parent_path(arg0: typing.Union[os.PathLike, str, bytes]) -> pathlib.Path" + ) + # std::vector + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: collections.abc.Sequence[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" + ) + # py::typing::List + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" + ) + # Nested py::typing::List + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[typing.Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" + ) + # py::typing::Tuple + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[typing.Union[os.PathLike, str, bytes], typing.Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" + ) + # py::typing::Dict + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" + ) @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") def test_variant(doc): From e374a2f9d816f3dad1cb035e1f17e25699911d78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 May 2025 15:32:17 +0000 Subject: [PATCH 02/19] style: pre-commit fixes --- include/pybind11/stl/filesystem.h | 10 ++++---- include/pybind11/typing.h | 17 ++++++-------- tests/test_opaque_types.py | 3 ++- tests/test_pytypes.cpp | 10 ++++---- tests/test_pytypes.py | 38 ++++++++++++++++--------------- tests/test_stl.py | 3 +++ 6 files changed, 42 insertions(+), 39 deletions(-) diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index a9ce8c1e7c..aed17a1232 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -96,11 +96,11 @@ struct path_caster { return true; } - #if PYBIND11_USE_NEW_UNIONS - PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); - #else - PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); - #endif +# if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); +# else + PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); +# endif }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 48c93064f4..35ca762a73 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -224,9 +224,8 @@ constexpr auto union_helper() { #else template constexpr auto union_helper() { - return const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); + return const_name("typing.Union[") + ::pybind11::detail::concat(make_caster::name...) + + const_name("]"); } #endif @@ -235,12 +234,10 @@ struct handle_type_name> { static constexpr auto name = union_helper(); }; - #if PYBIND11_USE_NEW_UNIONS template struct handle_type_name> { - static constexpr auto name - = union_helper(); + static constexpr auto name = union_helper(); }; #else template @@ -265,14 +262,14 @@ struct handle_type_name> { template struct handle_type_name> { - static constexpr auto name - = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); + static constexpr auto name = const_name(PYBIND11_TYPE_GUARD_TYPE_HINT) + const_name("[") + + make_caster::name + const_name("]"); }; template struct handle_type_name> { - static constexpr auto name - = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[") + make_caster::name + const_name("]"); + static constexpr auto name = const_name(PYBIND11_TYPE_IS_TYPE_HINT) + const_name("[") + + make_caster::name + const_name("]"); }; template <> diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 6790b89d25..9854924901 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys + import pytest import env @@ -38,7 +39,7 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - + if sys.version_info >= (3, 13): assert ( msg(excinfo.value) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 99b38c5a1d..cbb85f0e1e 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -155,11 +155,11 @@ namespace detail { template <> struct type_caster { - #if PYBIND11_USE_NEW_UNIONS - PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); - #else - PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); - #endif +#if PYBIND11_USE_NEW_UNIONS + PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); +#else + PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); +#endif static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 8db016cd4b..928b24c7fe 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -131,10 +131,7 @@ def test_set(capture, doc): assert doc(m.get_set) == "get_set() -> set" if sys.version_info >= (3, 10): - assert ( - doc(m.print_anyset) - == "print_anyset(arg0: set | frozenset) -> None" - ) + assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" else: assert ( doc(m.print_anyset) @@ -1087,9 +1084,9 @@ def test_never_annotation(doc): def test_optional_object_annotations(doc): if sys.version_info >= (3, 10): assert ( - doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" - ) + doc(m.annotate_optional_to_object) + == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" + ) else: assert ( doc(m.annotate_optional_to_object) @@ -1226,7 +1223,7 @@ def test_module_attribute_types() -> None: assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" - if sys.version_info >= (3,10): + if sys.version_info >= (3, 10): assert ( module_annotations["foo_union"] == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" @@ -1315,10 +1312,7 @@ def test_final_annotation() -> None: def test_arg_return_type_hints(doc): if sys.version_info >= (3, 10): - assert ( - doc(m.half_of_number) - == "half_of_number(arg0: float | int) -> float" - ) + assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" assert ( doc(m.half_of_number_convert) == "half_of_number_convert(x: float | int) -> float" @@ -1374,8 +1368,7 @@ def test_arg_return_type_hints(doc): ) # Set assert ( - doc(m.identity_set) - == "identity_set(arg0: set[float | int]) -> set[float]" + doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" ) # Iterable assert ( @@ -1429,10 +1422,16 @@ def test_arg_return_type_hints(doc): ) # TypeIs if sys.version_info >= (3, 13): - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing.TypeIs[float]" + assert ( + doc(m.check_type_is) + == "check_type_is(arg0: object) -> typing.TypeIs[float]" + ) else: - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" - else: + assert ( + doc(m.check_type_is) + == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + ) + else: # std::vector assert ( doc(m.half_of_number_vector) @@ -1519,4 +1518,7 @@ def test_arg_return_type_hints(doc): == "check_type_guard(arg0: list[object]) -> typing_extensions.TypeGuard[list[float]]" ) # TypeIs - assert doc(m.check_type_is) == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + assert ( + doc(m.check_type_is) + == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" + ) diff --git a/tests/test_stl.py b/tests/test_stl.py index 839e6d6207..ec814c1fd5 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys + import pytest import env # noqa: F401 @@ -299,6 +300,7 @@ def __fspath__(self): "key3": Path("foo"), } + @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") def test_path_typing(doc): if sys.version_info >= (3, 10): @@ -364,6 +366,7 @@ def test_path_typing(doc): == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" ) + @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") def test_variant(doc): assert m.load_variant(1) == "int" From 325f080cb0e63edc969f6c7643275c819f87bcfd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:34:22 -0400 Subject: [PATCH 03/19] remove unused var Signed-off-by: Michael Carlstrom --- tests/test_stl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stl.py b/tests/test_stl.py index ec814c1fd5..1b4eb8c9a1 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -259,7 +259,7 @@ def test_reference_sensitive_optional(): @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") -def test_fs_path(doc): +def test_fs_path(): from pathlib import Path class PseudoStrPath: From 6dcc1d92be02e20b9f63baabfb84fcd62f1e7dd4 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:47:36 -0400 Subject: [PATCH 04/19] remove union_helper Signed-off-by: Michael Carlstrom --- include/pybind11/typing.h | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 35ca762a73..f5c11a1ead 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -216,36 +216,27 @@ struct handle_type_name> { static constexpr auto name = const_name("type[") + make_caster::name + const_name("]"); }; +template +struct handle_type_name> { #if PYBIND11_USE_NEW_UNIONS -template -constexpr auto union_helper() { - return ::pybind11::detail::union_concat(make_caster::name...); -} + static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name...); #else -template -constexpr auto union_helper() { - return const_name("typing.Union[") + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); -} + static constexpr auto name = const_name("typing.Union[") + + ::pybind11::detail::concat(make_caster::name...) + + const_name("]"); #endif - -template -struct handle_type_name> { - static constexpr auto name = union_helper(); }; -#if PYBIND11_USE_NEW_UNIONS template struct handle_type_name> { - static constexpr auto name = union_helper(); -}; +#if PYBIND11_USE_NEW_UNIONS + static constexpr auto name + = ::pybind11::detail::union_concat(make_caster::name, make_caster::name); #else -template -struct handle_type_name> { static constexpr auto name = const_name("typing.Optional[") + make_caster::name + const_name("]"); -}; #endif +}; template struct handle_type_name> { From 8570b25325fb1f20104041fd28c85b43df82ef19 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 11:49:07 -0400 Subject: [PATCH 05/19] fix speelling error Signed-off-by: Michael Carlstrom --- include/pybind11/typing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index f5c11a1ead..50c92fd7f8 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -222,7 +222,7 @@ struct handle_type_name> { static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name...); #else static constexpr auto name = const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) + + ::pybind11::detail::concat(make_caster::name...) + const_name("]"); #endif }; From 5ea93d46d27a1bcd2f1e6cbcb8e7608e72b59105 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 12:00:27 -0400 Subject: [PATCH 06/19] base case for union_concat Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 90fa6da456..e198b1b00f 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -157,6 +157,8 @@ constexpr descr<1, Type> _() { #endif // #ifndef _ constexpr descr<0> concat() { return {}; } +constexpr descr<0> union_concat() { return {}; } + template constexpr descr concat(const descr &descr) { From f51488611483a54c8ae8777a338306f3f8616764 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 May 2025 16:00:50 +0000 Subject: [PATCH 07/19] style: pre-commit fixes --- include/pybind11/detail/descr.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index e198b1b00f..62ce1509d4 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -159,7 +159,6 @@ constexpr descr<1, Type> _() { constexpr descr<0> concat() { return {}; } constexpr descr<0> union_concat() { return {}; } - template constexpr descr concat(const descr &descr) { return descr; From 0d2de7a2b624693a9e0a7eed19b176df34ef89c2 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 12:13:35 -0400 Subject: [PATCH 08/19] add case for one descr Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 62ce1509d4..51bfcfa9c9 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -164,6 +164,11 @@ constexpr descr concat(const descr &descr) { return descr; } +template +constexpr descr union_concat(const descr &descr) { + return descr; +} + #ifdef __cpp_fold_expressions template constexpr descr operator,(const descr &a, From c489b95f1189080a01ee52d6e19e3f1339512bbf Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 25 May 2025 17:50:36 -0400 Subject: [PATCH 09/19] weakref and final test Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 2 +- tests/test_pytypes.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index cec9117a94..eb4d344c8e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1453,7 +1453,7 @@ struct handle_type_name { }; template <> struct handle_type_name { - static constexpr auto name = const_name("weakref"); + static constexpr auto name = const_name("weakref.ReferenceType"); }; template <> struct handle_type_name { diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 928b24c7fe..31f4b05855 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1129,15 +1129,19 @@ def test_literal(doc): doc(m.identity_literal_arrow_with_io_name) == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' + ) else: assert ( doc(m.identity_literal_arrow_with_io_name) == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' - ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' + ) assert ( doc(m.identity_literal_all_special_chars) == 'identity_literal_all_special_chars(arg0: typing.Literal["!@!!->{%}"]) -> typing.Literal["!@!!->{%}"]' From 41e2bbe4167773bc2b869bc671e3df28a292b853 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Mon, 26 May 2025 16:48:37 -0400 Subject: [PATCH 10/19] Add acrpss_version_type_hint_checker Signed-off-by: Michael Carlstrom --- tests/conftest.py | 26 ++++++++++++++ tests/test_opaque_types.py | 24 ++++--------- tests/test_pytypes.cpp | 1 - tests/test_pytypes.py | 70 +++++++++++--------------------------- 4 files changed, 52 insertions(+), 69 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8f5352a1ff..577dda30f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -242,3 +242,29 @@ def pytest_report_header(): lines.append("free-threaded Python build") return lines + + +UNION_HINT = "typing.Union" + + +def replace_unions(expected: str) -> str: + return expected + + +def across_version_type_hint_checker(doc: str, expected: str) -> None: + if sys.version_info < (3, 13): + expected = expected.replace("typing.TypeIs", "typing_extensions.TypeIs") + expected = expected.replace( + "types.CapsuleType", "typing_extensions.CapsuleType" + ) + if sys.version_info < (3, 12): + expected = expected.replace( + "collections.abc.Buffer", "typing_extensions.Buffer" + ) + if sys.version_info < (3, 11): + expected = expected.replace("typing.Never", "typing_extensions.Never") + if sys.version_info < (3, 10): + expected = expected.replace("typing.TypeGuard", "typing_extensions.TypeGuard") + expected = replace_unions(expected) + + assert doc == expected diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index 9854924901..e386fd3c78 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,8 +1,7 @@ from __future__ import annotations -import sys - import pytest +from conftest import across_version_type_hint_checker import env from pybind11_tests import ConstructorStats, UserType @@ -40,26 +39,15 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - if sys.version_info >= (3, 13): - assert ( - msg(excinfo.value) - == """ - get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: types.CapsuleType) -> int - - Invoked with: [1, 2, 3] + across_version_type_hint_checker( + msg(excinfo.value), """ - ) - else: - assert ( - msg(excinfo.value) - == """ get_void_ptr_value(): incompatible function arguments. The following argument types are supported: - 1. (arg0: typing_extensions.CapsuleType) -> int + 1. (arg0: types.CapsuleType) -> int Invoked with: [1, 2, 3] - """ - ) + """, + ) assert m.return_null_str() is None assert m.get_null_str_value(m.return_null_str()) is not None diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index cbb85f0e1e..e74fe6ab6a 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -176,7 +176,6 @@ struct type_caster { value.value = src.cast(); return true; } - // static void f/ }; } // namespace detail diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 31f4b05855..1edfeb8101 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -5,6 +5,7 @@ import types import pytest +from conftest import across_version_type_hint_checker import env from pybind11_tests import detailed_error_messages_enabled @@ -1045,29 +1046,16 @@ def test_optional_annotations(doc): def test_type_guard_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_type_guard) - == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" - ) - else: - assert ( - doc(m.annotate_type_guard) - == "annotate_type_guard(arg0: object) -> typing_extensions.TypeGuard[str]" - ) + across_version_type_hint_checker( + doc(m.annotate_type_guard), + "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]", + ) def test_type_is_annotations(doc): - if sys.version_info >= (3, 13): - assert ( - doc(m.annotate_type_is) - == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" - ) - else: - assert ( - doc(m.annotate_type_is) - == "annotate_type_is(arg0: object) -> typing_extensions.TypeIs[str]" - ) + across_version_type_hint_checker( + doc(m.annotate_type_is), "annotate_type_is(arg0: object) -> typing.TypeIs[str]" + ) def test_no_return_annotation(doc): @@ -1075,10 +1063,9 @@ def test_no_return_annotation(doc): def test_never_annotation(doc): - if sys.version_info >= (3, 11): - assert doc(m.annotate_never) == "annotate_never() -> typing.Never" - else: - assert doc(m.annotate_never) == "annotate_never() -> typing_extensions.Never" + across_version_type_hint_checker( + doc(m.annotate_never), "annotate_never() -> typing.Never" + ) def test_optional_object_annotations(doc): @@ -1419,22 +1406,6 @@ def test_arg_return_type_hints(doc): doc(m.identity_optional) == "identity_optional(arg0: float | int | None) -> float | None" ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" - ) - # TypeIs - if sys.version_info >= (3, 13): - assert ( - doc(m.check_type_is) - == "check_type_is(arg0: object) -> typing.TypeIs[float]" - ) - else: - assert ( - doc(m.check_type_is) - == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" - ) else: # std::vector assert ( @@ -1516,13 +1487,12 @@ def test_arg_return_type_hints(doc): doc(m.identity_optional) == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" ) - # TypeGuard - assert ( - doc(m.check_type_guard) - == "check_type_guard(arg0: list[object]) -> typing_extensions.TypeGuard[list[float]]" - ) - # TypeIs - assert ( - doc(m.check_type_is) - == "check_type_is(arg0: object) -> typing_extensions.TypeIs[float]" - ) + + # TypeIs + across_version_type_hint_checker( + doc(m.check_type_is), "check_type_is(arg0: object) -> typing.TypeIs[float]" + ) + across_version_type_hint_checker( + doc(m.check_type_guard), + "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]", + ) From bd41651c62f91377d6d5369753acd19cba4268aa Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Mon, 26 May 2025 17:04:00 -0400 Subject: [PATCH 11/19] cleanup Signed-off-by: Michael Carlstrom --- test.pyi | 2 ++ tests/conftest.py | 8 -------- 2 files changed, 2 insertions(+), 8 deletions(-) create mode 100644 test.pyi diff --git a/test.pyi b/test.pyi new file mode 100644 index 0000000000..ef1cd2f3f4 --- /dev/null +++ b/test.pyi @@ -0,0 +1,2 @@ +def foo() -> int | str: ... + diff --git a/tests/conftest.py b/tests/conftest.py index 577dda30f1..081303e649 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -244,13 +244,6 @@ def pytest_report_header(): return lines -UNION_HINT = "typing.Union" - - -def replace_unions(expected: str) -> str: - return expected - - def across_version_type_hint_checker(doc: str, expected: str) -> None: if sys.version_info < (3, 13): expected = expected.replace("typing.TypeIs", "typing_extensions.TypeIs") @@ -265,6 +258,5 @@ def across_version_type_hint_checker(doc: str, expected: str) -> None: expected = expected.replace("typing.Never", "typing_extensions.Never") if sys.version_info < (3, 10): expected = expected.replace("typing.TypeGuard", "typing_extensions.TypeGuard") - expected = replace_unions(expected) assert doc == expected From 3d4185ac25773a703d4a631666196ce67ec58f73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 21:04:23 +0000 Subject: [PATCH 12/19] style: pre-commit fixes --- test.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/test.pyi b/test.pyi index ef1cd2f3f4..5a40fac3ee 100644 --- a/test.pyi +++ b/test.pyi @@ -1,2 +1 @@ def foo() -> int | str: ... - From df38f9979ad5f05823e687b691cef5d69c04b4f8 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Mon, 26 May 2025 17:04:49 -0400 Subject: [PATCH 13/19] remove test.pyi Signed-off-by: Michael Carlstrom --- test.pyi | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test.pyi diff --git a/test.pyi b/test.pyi deleted file mode 100644 index 5a40fac3ee..0000000000 --- a/test.pyi +++ /dev/null @@ -1 +0,0 @@ -def foo() -> int | str: ... From 8746f749fc49d415e7ce89fbaade8f8aefd61705 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 02:58:51 -0400 Subject: [PATCH 14/19] use new unions and add fixture Signed-off-by: Michael Carlstrom --- include/pybind11/cast.h | 4 - include/pybind11/detail/common.h | 2 - include/pybind11/stl.h | 8 +- include/pybind11/stl/filesystem.h | 4 - include/pybind11/typing.h | 11 - tests/conftest.py | 29 ++- tests/test_buffers.py | 12 +- tests/test_opaque_types.py | 11 +- tests/test_pytypes.cpp | 4 - tests/test_pytypes.py | 409 ++++++++++-------------------- tests/test_stl.py | 98 +++---- 11 files changed, 193 insertions(+), 399 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index eb4d344c8e..7a6edf25b7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1361,11 +1361,7 @@ struct handle_type_name { }; template <> struct handle_type_name { -#if PYBIND11_USE_NEW_UNIONS static constexpr auto name = const_name("set | frozenset"); -#else - static constexpr auto name = const_name("typing.Union[set, frozenset]"); -#endif }; template <> struct handle_type_name { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 8b14c9311b..7f9748853e 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -286,10 +286,8 @@ // 3.10 Compatibility #if 0x030A0000 <= PY_VERSION_HEX -# define PYBIND11_USE_NEW_UNIONS true # define PYBIND11_TYPE_GUARD_TYPE_HINT "typing.TypeGuard" #else -# define PYBIND11_USE_NEW_UNIONS false # define PYBIND11_TYPE_GUARD_TYPE_HINT "typing_extensions.TypeGuard" #endif diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 31cf9b20cb..0f84264105 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -558,7 +558,8 @@ struct optional_caster { } PYBIND11_TYPE_CASTER(Type, - const_name("typing.Optional[") + value_conv::name + const_name("]")); + ::pybind11::detail::union_concat(make_caster::name, + make_caster::name)); }; #if defined(PYBIND11_HAS_OPTIONAL) @@ -642,10 +643,7 @@ struct variant_caster> { } using Type = V; - PYBIND11_TYPE_CASTER(Type, - const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]")); + PYBIND11_TYPE_CASTER(Type, ::pybind11::detail::union_concat(make_caster::name...)); }; #if defined(PYBIND11_HAS_VARIANT) diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index aed17a1232..1c0b18c1ef 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -96,11 +96,7 @@ struct path_caster { return true; } -# if PYBIND11_USE_NEW_UNIONS PYBIND11_TYPE_CASTER(T, io_name("os.PathLike | str | bytes", "pathlib.Path")); -# else - PYBIND11_TYPE_CASTER(T, io_name("typing.Union[os.PathLike, str, bytes]", "pathlib.Path")); -# endif }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 50c92fd7f8..333f40101e 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -218,24 +218,13 @@ struct handle_type_name> { template struct handle_type_name> { -#if PYBIND11_USE_NEW_UNIONS static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name...); -#else - static constexpr auto name = const_name("typing.Union[") - + ::pybind11::detail::concat(make_caster::name...) - + const_name("]"); -#endif }; template struct handle_type_name> { -#if PYBIND11_USE_NEW_UNIONS static constexpr auto name = ::pybind11::detail::union_concat(make_caster::name, make_caster::name); -#else - static constexpr auto name - = const_name("typing.Optional[") + make_caster::name + const_name("]"); -#endif }; template diff --git a/tests/conftest.py b/tests/conftest.py index 081303e649..22d41f8881 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ import sysconfig import textwrap import traceback +from typing import Callable import pytest @@ -244,19 +245,25 @@ def pytest_report_header(): return lines -def across_version_type_hint_checker(doc: str, expected: str) -> None: +@pytest.fixture +def backport_typehints() -> Callable[[SanitizedString], SanitizedString]: + d = {} if sys.version_info < (3, 13): - expected = expected.replace("typing.TypeIs", "typing_extensions.TypeIs") - expected = expected.replace( - "types.CapsuleType", "typing_extensions.CapsuleType" - ) + d["typing_extensions.TypeIs"] = "typing.TypeIs" + d["typing_extensions.CapsuleType"] = "types.CapsuleType" if sys.version_info < (3, 12): - expected = expected.replace( - "collections.abc.Buffer", "typing_extensions.Buffer" - ) + d["typing_extensions.Buffer"] = "collections.abc.Buffer" if sys.version_info < (3, 11): - expected = expected.replace("typing.Never", "typing_extensions.Never") + d["typing_extensions.Never"] = "typing.Never" if sys.version_info < (3, 10): - expected = expected.replace("typing.TypeGuard", "typing_extensions.TypeGuard") + d["typing_extensions.TypeGuard"] = "typing.TypeGuard" + + def backport(sanatized_string: SanitizedString) -> SanitizedString: + text = sanatized_string.string + for old, new in d.items(): + text = text.replace(old, new) + + sanatized_string.string = text + return sanatized_string - assert doc == expected + return backport diff --git a/tests/test_buffers.py b/tests/test_buffers.py index a712f2bda4..f666df5bad 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -3,7 +3,6 @@ import ctypes import io import struct -import sys import pytest @@ -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(): diff --git a/tests/test_opaque_types.py b/tests/test_opaque_types.py index e386fd3c78..7a4d7a43da 100644 --- a/tests/test_opaque_types.py +++ b/tests/test_opaque_types.py @@ -1,7 +1,6 @@ from __future__ import annotations import pytest -from conftest import across_version_type_hint_checker import env from pybind11_tests import ConstructorStats, UserType @@ -28,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 @@ -39,14 +38,14 @@ def test_pointers(msg): with pytest.raises(TypeError) as excinfo: m.get_void_ptr_value([1, 2, 3]) # This should not work - across_version_type_hint_checker( - msg(excinfo.value), - """ + assert ( + backport_typehints(msg(excinfo.value)) + == """ get_void_ptr_value(): incompatible function arguments. The following argument types are supported: 1. (arg0: types.CapsuleType) -> int Invoked with: [1, 2, 3] - """, + """ ) assert m.return_null_str() is None diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index e74fe6ab6a..32d635f403 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -155,11 +155,7 @@ namespace detail { template <> struct type_caster { -#if PYBIND11_USE_NEW_UNIONS PYBIND11_TYPE_CASTER(RealNumber, io_name("float | int", "float")); -#else - PYBIND11_TYPE_CASTER(RealNumber, io_name("typing.Union[float, int]", "float")); -#endif static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 1edfeb8101..a199d72f0a 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -5,7 +5,6 @@ import types import pytest -from conftest import across_version_type_hint_checker import env from pybind11_tests import detailed_error_messages_enabled @@ -131,13 +130,7 @@ def test_set(capture, doc): assert m.anyset_contains({"foo"}, "foo") assert doc(m.get_set) == "get_set() -> set" - if sys.version_info >= (3, 10): - assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" - else: - assert ( - doc(m.print_anyset) - == "print_anyset(arg0: typing.Union[set, frozenset]) -> None" - ) + assert doc(m.print_anyset) == "print_anyset(arg0: set | frozenset) -> None" def test_frozenset(capture, doc): @@ -994,67 +987,40 @@ def test_type_annotation(doc): def test_union_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_union) - == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" - ) - else: - assert ( - doc(m.annotate_union) - == "annotate_union(arg0: list[typing.Union[str, typing.SupportsInt, object]], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[typing.Union[str, int, object]]" - ) + assert ( + doc(m.annotate_union) + == "annotate_union(arg0: list[str | typing.SupportsInt | object], arg1: str, arg2: typing.SupportsInt, arg3: object) -> list[str | int | object]" + ) def test_union_typing_only(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.union_typing_only) - == "union_typing_only(arg0: list[str]) -> list[int]" - ) - else: - assert ( - doc(m.union_typing_only) - == "union_typing_only(arg0: list[typing.Union[str]]) -> list[typing.Union[int]]" - ) + assert doc(m.union_typing_only) == "union_typing_only(arg0: list[str]) -> list[int]" def test_union_object_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" - ) - else: - assert ( - doc(m.annotate_union_to_object) - == "annotate_union_to_object(arg0: typing.Union[typing.SupportsInt, str]) -> object" - ) + assert ( + doc(m.annotate_union_to_object) + == "annotate_union_to_object(arg0: typing.SupportsInt | str) -> object" + ) def test_optional_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_optional) - == "annotate_optional(arg0: list) -> list[str | None]" - ) - else: - assert ( - doc(m.annotate_optional) - == "annotate_optional(arg0: list) -> list[typing.Optional[str]]" - ) + assert ( + doc(m.annotate_optional) == "annotate_optional(arg0: list) -> list[str | None]" + ) -def test_type_guard_annotations(doc): - across_version_type_hint_checker( - doc(m.annotate_type_guard), - "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]", +def test_type_guard_annotations(doc, backport_typehints): + assert ( + backport_typehints(doc(m.annotate_type_guard)) + == "annotate_type_guard(arg0: object) -> typing.TypeGuard[str]" ) -def test_type_is_annotations(doc): - across_version_type_hint_checker( - doc(m.annotate_type_is), "annotate_type_is(arg0: object) -> typing.TypeIs[str]" +def test_type_is_annotations(doc, backport_typehints): + assert ( + backport_typehints(doc(m.annotate_type_is)) + == "annotate_type_is(arg0: object) -> typing.TypeIs[str]" ) @@ -1062,23 +1028,17 @@ def test_no_return_annotation(doc): assert doc(m.annotate_no_return) == "annotate_no_return() -> typing.NoReturn" -def test_never_annotation(doc): - across_version_type_hint_checker( - doc(m.annotate_never), "annotate_never() -> typing.Never" +def test_never_annotation(doc, backport_typehints): + assert ( + backport_typehints(doc(m.annotate_never)) == "annotate_never() -> typing.Never" ) def test_optional_object_annotations(doc): - if sys.version_info >= (3, 10): - assert ( - doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" - ) - else: - assert ( - doc(m.annotate_optional_to_object) - == "annotate_optional_to_object(arg0: typing.Optional[typing.SupportsInt]) -> object" - ) + assert ( + doc(m.annotate_optional_to_object) + == "annotate_optional_to_object(arg0: typing.SupportsInt | None) -> object" + ) @pytest.mark.skipif( @@ -1111,24 +1071,14 @@ def test_literal(doc): doc(m.identity_literal_curly_close) == 'identity_literal_curly_close(arg0: typing.Literal["}"]) -> typing.Literal["}"]' ) - if sys.version_info >= (3, 10): - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' - ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' - ) - else: - assert ( - doc(m.identity_literal_arrow_with_io_name) - == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: typing.Union[float, int]) -> typing.Literal["->"]' - ) - assert ( - doc(m.identity_literal_arrow_with_callable) - == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Literal["->"], typing.Union[float, int]], float]' - ) + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: typing.Literal["->"], arg1: float | int) -> typing.Literal["->"]' + ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: collections.abc.Callable[[typing.Literal["->"], float | int], float]) -> collections.abc.Callable[[typing.Literal["->"], float | int], float]' + ) assert ( doc(m.identity_literal_all_special_chars) == 'identity_literal_all_special_chars(arg0: typing.Literal["!@!!->{%}"]) -> typing.Literal["!@!!->{%}"]' @@ -1214,16 +1164,10 @@ def test_module_attribute_types() -> None: assert module_annotations["set_str"] == "set[str]" assert module_annotations["foo"] == "pybind11_tests.pytypes.foo" - if sys.version_info >= (3, 10): - assert ( - module_annotations["foo_union"] - == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" - ) - else: - assert ( - module_annotations["foo_union"] - == "typing.Union[pybind11_tests.pytypes.foo, pybind11_tests.pytypes.foo2, pybind11_tests.pytypes.foo3]" - ) + assert ( + module_annotations["foo_union"] + == "pybind11_tests.pytypes.foo | pybind11_tests.pytypes.foo2 | pybind11_tests.pytypes.foo3" + ) @pytest.mark.skipif( @@ -1301,22 +1245,12 @@ def test_final_annotation() -> None: assert module_annotations["CONST_INT"] == "typing.Final[int]" -def test_arg_return_type_hints(doc): - if sys.version_info >= (3, 10): - assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: float | int) -> float" - ) - else: - assert ( - doc(m.half_of_number) - == "half_of_number(arg0: typing.Union[float, int]) -> float" - ) - assert ( - doc(m.half_of_number_convert) - == "half_of_number_convert(x: typing.Union[float, int]) -> float" - ) +def test_arg_return_type_hints(doc, backport_typehints): + assert doc(m.half_of_number) == "half_of_number(arg0: float | int) -> float" + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: float | int) -> float" + ) assert ( doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" ) @@ -1326,173 +1260,90 @@ def test_arg_return_type_hints(doc): assert isinstance(m.half_of_number(0), float) assert not isinstance(m.half_of_number(0), int) - if sys.version_info >= (3, 10): - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[float | int]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: float | int | str) -> float | str" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: float | int | None) -> float | None" - ) - else: - # std::vector - assert ( - doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: collections.abc.Sequence[typing.Union[float, int]]) -> list[float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple) - == "half_of_number_tuple(arg0: tuple[typing.Union[float, int], typing.Union[float, int]]) -> tuple[float, float]" - ) - # Tuple - assert ( - doc(m.half_of_number_tuple_ellipsis) - == "half_of_number_tuple_ellipsis(arg0: tuple[typing.Union[float, int], ...]) -> tuple[float, ...]" - ) - # Dict - assert ( - doc(m.half_of_number_dict) - == "half_of_number_dict(arg0: dict[str, typing.Union[float, int]]) -> dict[str, float]" - ) - # List - assert ( - doc(m.half_of_number_list) - == "half_of_number_list(arg0: list[typing.Union[float, int]]) -> list[float]" - ) - # List> - assert ( - doc(m.half_of_number_nested_list) - == "half_of_number_nested_list(arg0: list[list[typing.Union[float, int]]]) -> list[list[float]]" - ) - # Set - assert ( - doc(m.identity_set) - == "identity_set(arg0: set[typing.Union[float, int]]) -> set[float]" - ) - # Iterable - assert ( - doc(m.identity_iterable) - == "identity_iterable(arg0: collections.abc.Iterable[typing.Union[float, int]]) -> collections.abc.Iterable[float]" - ) - # Iterator - assert ( - doc(m.identity_iterator) - == "identity_iterator(arg0: collections.abc.Iterator[typing.Union[float, int]]) -> collections.abc.Iterator[float]" - ) - # Callable identity - assert ( - doc(m.identity_callable) - == "identity_callable(arg0: collections.abc.Callable[[typing.Union[float, int]], float]) -> collections.abc.Callable[[typing.Union[float, int]], float]" - ) - # Callable identity - assert ( - doc(m.identity_callable_ellipsis) - == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" - ) - # Nested Callable identity - assert ( - doc(m.identity_nested_callable) - == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]) -> collections.abc.Callable[[collections.abc.Callable[[typing.Union[float, int]], float]], collections.abc.Callable[[typing.Union[float, int]], float]]" - ) - # Callable - assert ( - doc(m.apply_callable) - == "apply_callable(arg0: typing.Union[float, int], arg1: collections.abc.Callable[[typing.Union[float, int]], float]) -> float" - ) - # Callable - assert ( - doc(m.apply_callable_ellipsis) - == "apply_callable_ellipsis(arg0: typing.Union[float, int], arg1: collections.abc.Callable[..., float]) -> float" - ) - # Union - assert ( - doc(m.identity_union) - == "identity_union(arg0: typing.Union[typing.Union[float, int], str]) -> typing.Union[float, str]" - ) - # Optional - assert ( - doc(m.identity_optional) - == "identity_optional(arg0: typing.Optional[typing.Union[float, int]]) -> typing.Optional[float]" - ) - + # std::vector + assert ( + doc(m.half_of_number_vector) + == "half_of_number_vector(arg0: collections.abc.Sequence[float | int]) -> list[float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple) + == "half_of_number_tuple(arg0: tuple[float | int, float | int]) -> tuple[float, float]" + ) + # Tuple + assert ( + doc(m.half_of_number_tuple_ellipsis) + == "half_of_number_tuple_ellipsis(arg0: tuple[float | int, ...]) -> tuple[float, ...]" + ) + # Dict + assert ( + doc(m.half_of_number_dict) + == "half_of_number_dict(arg0: dict[str, float | int]) -> dict[str, float]" + ) + # List + assert ( + doc(m.half_of_number_list) + == "half_of_number_list(arg0: list[float | int]) -> list[float]" + ) + # List> + assert ( + doc(m.half_of_number_nested_list) + == "half_of_number_nested_list(arg0: list[list[float | int]]) -> list[list[float]]" + ) + # Set + assert doc(m.identity_set) == "identity_set(arg0: set[float | int]) -> set[float]" + # Iterable + assert ( + doc(m.identity_iterable) + == "identity_iterable(arg0: collections.abc.Iterable[float | int]) -> collections.abc.Iterable[float]" + ) + # Iterator + assert ( + doc(m.identity_iterator) + == "identity_iterator(arg0: collections.abc.Iterator[float | int]) -> collections.abc.Iterator[float]" + ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: collections.abc.Callable[[float | int], float]) -> collections.abc.Callable[[float | int], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: collections.abc.Callable[..., float]) -> collections.abc.Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]) -> collections.abc.Callable[[collections.abc.Callable[[float | int], float]], collections.abc.Callable[[float | int], float]]" + ) + # Callable + assert ( + doc(m.apply_callable) + == "apply_callable(arg0: float | int, arg1: collections.abc.Callable[[float | int], float]) -> float" + ) + # Callable + assert ( + doc(m.apply_callable_ellipsis) + == "apply_callable_ellipsis(arg0: float | int, arg1: collections.abc.Callable[..., float]) -> float" + ) + # Union + assert ( + doc(m.identity_union) + == "identity_union(arg0: float | int | str) -> float | str" + ) + # Optional + assert ( + doc(m.identity_optional) + == "identity_optional(arg0: float | int | None) -> float | None" + ) # TypeIs - across_version_type_hint_checker( - doc(m.check_type_is), "check_type_is(arg0: object) -> typing.TypeIs[float]" + assert ( + backport_typehints(doc(m.check_type_is)) + == "check_type_is(arg0: object) -> typing.TypeIs[float]" ) - across_version_type_hint_checker( - doc(m.check_type_guard), - "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]", + # TypeGuard + assert ( + backport_typehints(doc(m.check_type_guard)) + == "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]" ) diff --git a/tests/test_stl.py b/tests/test_stl.py index 1b4eb8c9a1..66523780a7 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - import pytest import env # noqa: F401 @@ -303,68 +301,36 @@ def __fspath__(self): @pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no ") def test_path_typing(doc): - if sys.version_info >= (3, 10): - # Single argument - assert ( - doc(m.parent_path) - == "parent_path(arg0: os.PathLike | str | bytes) -> pathlib.Path" - ) - # std::vector - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: collections.abc.Sequence[os.PathLike | str | bytes]) -> list[pathlib.Path]" - ) - # py::typing::List - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[os.PathLike | str | bytes]) -> list[pathlib.Path]" - ) - # Nested py::typing::List - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[os.PathLike | str | bytes]]) -> list[list[pathlib.Path]]" - ) - # py::typing::Tuple - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[os.PathLike | str | bytes, os.PathLike | str | bytes]) -> tuple[pathlib.Path, pathlib.Path]" - ) - # py::typing::Dict - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, os.PathLike | str | bytes]) -> dict[str, pathlib.Path]" - ) - else: - # Single argument - assert ( - doc(m.parent_path) - == "parent_path(arg0: typing.Union[os.PathLike, str, bytes]) -> pathlib.Path" - ) - # std::vector - assert ( - doc(m.parent_paths) - == "parent_paths(arg0: collections.abc.Sequence[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) - # py::typing::List - assert ( - doc(m.parent_paths_list) - == "parent_paths_list(arg0: list[typing.Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" - ) - # Nested py::typing::List - assert ( - doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[typing.Union[os.PathLike, str, bytes]]]) -> list[list[pathlib.Path]]" - ) - # py::typing::Tuple - assert ( - doc(m.parent_paths_tuple) - == "parent_paths_tuple(arg0: tuple[typing.Union[os.PathLike, str, bytes], typing.Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" - ) - # py::typing::Dict - assert ( - doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, typing.Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" - ) + # Single argument + assert ( + doc(m.parent_path) + == "parent_path(arg0: os.PathLike | str | bytes) -> pathlib.Path" + ) + # std::vector + assert ( + doc(m.parent_paths) + == "parent_paths(arg0: collections.abc.Sequence[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # py::typing::List + assert ( + doc(m.parent_paths_list) + == "parent_paths_list(arg0: list[os.PathLike | str | bytes]) -> list[pathlib.Path]" + ) + # Nested py::typing::List + assert ( + doc(m.parent_paths_nested_list) + == "parent_paths_nested_list(arg0: list[list[os.PathLike | str | bytes]]) -> list[list[pathlib.Path]]" + ) + # py::typing::Tuple + assert ( + doc(m.parent_paths_tuple) + == "parent_paths_tuple(arg0: tuple[os.PathLike | str | bytes, os.PathLike | str | bytes]) -> tuple[pathlib.Path, pathlib.Path]" + ) + # py::typing::Dict + assert ( + doc(m.parent_paths_dict) + == "parent_paths_dict(arg0: dict[str, os.PathLike | str | bytes]) -> dict[str, pathlib.Path]" + ) @pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no ") @@ -381,7 +347,7 @@ def test_variant(doc): assert ( doc(m.load_variant) - == "load_variant(arg0: typing.Union[typing.SupportsInt, str, typing.SupportsFloat, None]) -> str" + == "load_variant(arg0: typing.SupportsInt | str | typing.SupportsFloat | None) -> str" ) @@ -397,7 +363,7 @@ def test_variant_monostate(doc): assert ( doc(m.load_monostate_variant) - == "load_monostate_variant(arg0: typing.Union[None, typing.SupportsInt, str]) -> str" + == "load_monostate_variant(arg0: None | typing.SupportsInt | str) -> str" ) From f733d21a20a8a7baf3e1493d4ac3b4ee5c831af5 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 15:59:40 -0700 Subject: [PATCH 15/19] timohl suggested cleanup Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 6 ++++++ include/pybind11/stl.h | 3 +-- include/pybind11/typing.h | 2 +- tests/conftest.py | 4 +--- tests/test_stl.py | 4 +++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 51bfcfa9c9..be3793c1b6 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -204,6 +204,12 @@ constexpr auto union_concat(const descr &d, const Args &...args) -> decltype(std::declval>() + union_concat(args...)) { return d + const_name(" | ") + union_concat(args...); } + +template +constexpr operator|(const Args &...args) + -> decltype(std::declval>() + union_concat(args...)) { + return union_concat(args...); +} #endif template diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 0f84264105..bd0f152b6e 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -558,8 +558,7 @@ struct optional_caster { } PYBIND11_TYPE_CASTER(Type, - ::pybind11::detail::union_concat(make_caster::name, - make_caster::name)); + value_conv::name | make_caster::name); }; #if defined(PYBIND11_HAS_OPTIONAL) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 333f40101e..ea6a080046 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -224,7 +224,7 @@ struct handle_type_name> { template struct handle_type_name> { static constexpr auto name - = ::pybind11::detail::union_concat(make_caster::name, make_caster::name); + = make_caster::name | make_caster::name; }; template diff --git a/tests/conftest.py b/tests/conftest.py index 22d41f8881..b8338d46f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -259,11 +259,9 @@ def backport_typehints() -> Callable[[SanitizedString], SanitizedString]: d["typing_extensions.TypeGuard"] = "typing.TypeGuard" def backport(sanatized_string: SanitizedString) -> SanitizedString: - text = sanatized_string.string for old, new in d.items(): - text = text.replace(old, new) + sanatized_string.string = sanatized_string.string.replace(old, new) - sanatized_string.string = text return sanatized_string return backport diff --git a/tests/test_stl.py b/tests/test_stl.py index 66523780a7..f01b7840e4 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -227,11 +227,13 @@ def test_boost_optional(): assert int(props.access_by_copy) == 42 -def test_reference_sensitive_optional(): +def test_reference_sensitive_optional(doc): assert m.double_or_zero_refsensitive(None) == 0 assert m.double_or_zero_refsensitive(42) == 84 pytest.raises(TypeError, m.double_or_zero_refsensitive, "foo") + assert doc(m.double_or_zero_refsensitive) == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int" + assert m.half_or_none_refsensitive(0) is None assert m.half_or_none_refsensitive(42) == 21 pytest.raises(TypeError, m.half_or_none_refsensitive, "foo") From 62a2ee638359c2f48bdd5df8bfbad70d34ad04a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 23:00:04 +0000 Subject: [PATCH 16/19] style: pre-commit fixes --- include/pybind11/detail/descr.h | 2 +- include/pybind11/stl.h | 3 +-- include/pybind11/typing.h | 3 +-- tests/test_stl.py | 5 ++++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index be3793c1b6..df59189252 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -207,7 +207,7 @@ constexpr auto union_concat(const descr &d, const Args &...args) template constexpr operator|(const Args &...args) - -> decltype(std::declval>() + union_concat(args...)) { + ->decltype(std::declval>() + union_concat(args...)) { return union_concat(args...); } #endif diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index bd0f152b6e..01be0b47c6 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -557,8 +557,7 @@ struct optional_caster { return true; } - PYBIND11_TYPE_CASTER(Type, - value_conv::name | make_caster::name); + PYBIND11_TYPE_CASTER(Type, value_conv::name | make_caster::name); }; #if defined(PYBIND11_HAS_OPTIONAL) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index ea6a080046..1715026efa 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -223,8 +223,7 @@ struct handle_type_name> { template struct handle_type_name> { - static constexpr auto name - = make_caster::name | make_caster::name; + static constexpr auto name = make_caster::name | make_caster::name; }; template diff --git a/tests/test_stl.py b/tests/test_stl.py index f01b7840e4..4a57635e27 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -232,7 +232,10 @@ def test_reference_sensitive_optional(doc): assert m.double_or_zero_refsensitive(42) == 84 pytest.raises(TypeError, m.double_or_zero_refsensitive, "foo") - assert doc(m.double_or_zero_refsensitive) == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int" + assert ( + doc(m.double_or_zero_refsensitive) + == "double_or_zero_refsensitive(arg0: typing.SupportsInt | None) -> int" + ) assert m.half_or_none_refsensitive(0) is None assert m.half_or_none_refsensitive(42) == 21 From 06a418831db7e2767c930f4888c1b178fcfc402c Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 16:02:58 -0700 Subject: [PATCH 17/19] add missing auto Signed-off-by: Michael Carlstrom --- include/pybind11/detail/descr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index df59189252..0aade83b9f 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -206,7 +206,7 @@ constexpr auto union_concat(const descr &d, const Args &...args) } template -constexpr operator|(const Args &...args) +constexpr auto operator|(const Args &...args) ->decltype(std::declval>() + union_concat(args...)) { return union_concat(args...); } From c287018dd2dbfb444f4af34a25cac12b2202cf18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 23:03:39 +0000 Subject: [PATCH 18/19] style: pre-commit fixes --- include/pybind11/detail/descr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 0aade83b9f..86e7cd1841 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -207,7 +207,7 @@ constexpr auto union_concat(const descr &d, const Args &...args) template constexpr auto operator|(const Args &...args) - ->decltype(std::declval>() + union_concat(args...)) { + -> decltype(std::declval>() + union_concat(args...)) { return union_concat(args...); } #endif From 08ff0d3f9ba8403c3593a56a55e3ed3575fa9bbb Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Tue, 27 May 2025 16:25:46 -0700 Subject: [PATCH 19/19] move operator| def --- include/pybind11/detail/descr.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 86e7cd1841..701662c4cf 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -169,6 +169,12 @@ constexpr descr union_concat(const descr &descr) { return descr; } +template +constexpr descr operator|(const descr &a, + const descr &b) { + return a + const_name(" | ") + b; +} + #ifdef __cpp_fold_expressions template constexpr descr operator,(const descr &a, @@ -181,12 +187,6 @@ constexpr auto concat(const descr &d, const Args &...args) { return (d, ..., args); } -template -constexpr descr operator|(const descr &a, - const descr &b) { - return a + const_name(" | ") + b; -} - template constexpr auto union_concat(const descr &d, const Args &...args) { return (d | ... | args); @@ -205,11 +205,6 @@ constexpr auto union_concat(const descr &d, const Args &...args) return d + const_name(" | ") + union_concat(args...); } -template -constexpr auto operator|(const Args &...args) - -> decltype(std::declval>() + union_concat(args...)) { - return union_concat(args...); -} #endif template