diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index 065d09a6d..5a626f3ba 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -61,19 +61,16 @@ type is explicitly allowed. template <> struct type_caster { - // This macro inserts a lot of boilerplate code and sets the default type hint to `tuple` - PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple")); - // `arg_name` and `return_name` may optionally be used to specify type hints separately for - // arguments and return values. + // This macro inserts a lot of boilerplate code and sets the type hint. + // `io_name` is used to specify different type hints for arguments and return values. // The signature of our negate function would then look like: // `negate(Sequence[float]) -> tuple[float, float]` - static constexpr auto arg_name = const_name("Sequence[float]"); - static constexpr auto return_name = const_name("tuple[float, float]"); + PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]")); // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments // are used to indicate the return value policy and parent object (for // return_value_policy::reference_internal) and are often ignored by custom casters. - // The return value should reflect the type hint specified by `return_name`. + // The return value should reflect the type hint specified by the second argument of `io_name`. static handle cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) { return py::make_tuple(number.x, number.y).release(); @@ -81,7 +78,8 @@ type is explicitly allowed. // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The // second argument indicates whether implicit conversions should be allowed. - // The accepted types should reflect the type hint specified by `arg_name`. + // The accepted types should reflect the type hint specified by the first argument of + // `io_name`. bool load(handle src, bool /*convert*/) { // Check if handle is a Sequence if (!py::isinstance(src)) { diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 853165a49..f2c029113 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -34,39 +34,6 @@ PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) -// Type trait checker for `descr` -template -struct is_descr : std::false_type {}; - -template -struct is_descr> : std::true_type {}; - -template -struct is_descr> : std::true_type {}; - -// Use arg_name instead of name when available -template -struct as_arg_type { - static constexpr auto name = T::name; -}; - -template -struct as_arg_type::value>::type> { - static constexpr auto name = T::arg_name; -}; - -// Use return_name instead of name when available -template -struct as_return_type { - static constexpr auto name = T::name; -}; - -template -struct as_return_type::value>::type> { - static constexpr auto name = T::return_name; -}; - template class type_caster : public type_caster_base {}; template @@ -1113,8 +1080,6 @@ struct pyobject_caster { return src.inc_ref(); } PYBIND11_TYPE_CASTER(type, handle_type_name::name); - static constexpr auto arg_name = as_arg_type>::name; - static constexpr auto return_name = as_return_type>::name; }; template @@ -1668,7 +1633,7 @@ public: "py::args cannot be specified more than once"); static constexpr auto arg_names - = ::pybind11::detail::concat(type_descr(as_arg_type>::name)...); + = ::pybind11::detail::concat(type_descr(make_caster::name)...); bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); } diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 635614b0d..a5f17f869 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -99,6 +99,13 @@ constexpr descr<1, Type> const_name() { return {'%'}; } +// Use a different name based on whether the parameter is used as input or output +template +constexpr descr io_name(char const (&text1)[N1], char const (&text2)[N2]) { + return const_name("@") + const_name(text1) + const_name("@") + const_name(text2) + + const_name("@"); +} + // If "_" is defined as a macro, py::detail::_ cannot be provided. // It is therefore best to use py::detail::const_name universally. // This block is for backward compatibility only. @@ -167,5 +174,15 @@ constexpr descr type_descr(const descr &descr) { return const_name("{") + descr + const_name("}"); } +template +constexpr descr arg_descr(const descr &descr) { + return const_name("@^") + descr + const_name("@!"); +} + +template +constexpr descr return_descr(const descr &descr) { + return const_name("@$") + descr + const_name("@!"); +} + PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 4387c2754..655bec398 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -336,8 +337,8 @@ protected: /* Generate a readable signature describing the function's arguments and return value types */ - static constexpr auto signature = const_name("(") + cast_in::arg_names - + const_name(") -> ") + as_return_type::name; + static constexpr auto signature + = const_name("(") + cast_in::arg_names + const_name(") -> ") + cast_out::name; PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types(); /* Register the function with Python from generic (non-templated) code */ @@ -440,6 +441,13 @@ protected: std::string signature; size_t type_index = 0, arg_index = 0; bool is_starred = false; + // `is_return_value.top()` is true if we are currently inside the return type of the + // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops + // back to the previous state. + std::stack is_return_value({false}); + // The following characters have special meaning in the signature parsing. Literals + // containing these are escaped with `!`. + std::string special_chars("!@%{}-"); for (const auto *pc = text; *pc != '\0'; ++pc) { const auto c = *pc; @@ -493,7 +501,57 @@ protected: } else { signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); } + } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { + // typing::Literal escapes special characters with ! + signature += *++pc; + } else if (c == '@') { + // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see + // typing::Callable/detail::arg_descr/detail::return_descr) + if (*(pc + 1) == '^') { + is_return_value.emplace(false); + ++pc; + continue; + } + if (*(pc + 1) == '$') { + is_return_value.emplace(true); + ++pc; + continue; + } + if (*(pc + 1) == '!') { + is_return_value.pop(); + ++pc; + continue; + } + // Handle types that differ depending on whether they appear + // in an argument or a return value position (see io_name). + // For named arguments (py::arg()) with noconvert set, return value type is used. + ++pc; + if (!is_return_value.top() + && !(arg_index < rec->args.size() && !rec->args[arg_index].convert)) { + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + ++pc; + } + } else { + while (*pc != '\0' && *pc != '@') { + ++pc; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + } } else { + if (c == '-' && *(pc + 1) == '>') { + is_return_value.emplace(true); + } signature += c; } } diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index ecfb9cf0d..fb8164e0d 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -106,9 +106,7 @@ public: return true; } - PYBIND11_TYPE_CASTER(T, const_name("os.PathLike")); - static constexpr auto arg_name = const_name("Union[os.PathLike, str, bytes]"); - static constexpr auto return_name = const_name("Path"); + PYBIND11_TYPE_CASTER(T, io_name("Union[os.PathLike, str, bytes]", "pathlib.Path")); }; #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 005279058..c5f342aed 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -16,6 +16,13 @@ #include +#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L +# define PYBIND11_TYPING_H_HAS_STRING_LITERAL +# include +# include +# include +#endif + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(typing) @@ -112,8 +119,7 @@ class Never : public none { using none::none; }; -#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L -# define PYBIND11_TYPING_H_HAS_STRING_LITERAL +#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) template struct StringLiteral { constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); } @@ -143,13 +149,6 @@ struct handle_type_name> { static constexpr auto name = const_name("tuple[") + ::pybind11::detail::concat(make_caster::name...) + const_name("]"); - static constexpr auto arg_name - = const_name("tuple[") - + ::pybind11::detail::concat(as_arg_type>::name...) + const_name("]"); - static constexpr auto return_name - = const_name("tuple[") - + ::pybind11::detail::concat(as_return_type>::name...) - + const_name("]"); }; template <> @@ -163,58 +162,32 @@ struct handle_type_name> { // PEP 484 specifies this syntax for a variable-length tuple static constexpr auto name = const_name("tuple[") + make_caster::name + const_name(", ...]"); - static constexpr auto arg_name - = const_name("tuple[") + as_arg_type>::name + const_name(", ...]"); - static constexpr auto return_name - = const_name("tuple[") + as_return_type>::name + const_name(", ...]"); }; template struct handle_type_name> { static constexpr auto name = const_name("dict[") + make_caster::name + const_name(", ") + make_caster::name + const_name("]"); - static constexpr auto arg_name = const_name("dict[") + as_arg_type>::name - + const_name(", ") + as_arg_type>::name - + const_name("]"); - static constexpr auto return_name = const_name("dict[") + as_return_type>::name - + const_name(", ") + as_return_type>::name - + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("list[") + make_caster::name + const_name("]"); - static constexpr auto arg_name - = const_name("list[") + as_arg_type>::name + const_name("]"); - static constexpr auto return_name - = const_name("list[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("set[") + make_caster::name + const_name("]"); - static constexpr auto arg_name - = const_name("set[") + as_arg_type>::name + const_name("]"); - static constexpr auto return_name - = const_name("set[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("Iterable[") + make_caster::name + const_name("]"); - static constexpr auto arg_name - = const_name("Iterable[") + as_arg_type>::name + const_name("]"); - static constexpr auto return_name - = const_name("Iterable[") + as_return_type>::name + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("Iterator[") + make_caster::name + const_name("]"); - static constexpr auto arg_name - = const_name("Iterator[") + as_arg_type>::name + const_name("]"); - static constexpr auto return_name - = const_name("Iterator[") + as_return_type>::name + const_name("]"); }; template @@ -222,8 +195,9 @@ struct handle_type_name> { using retval_type = conditional_t::value, void_type, Return>; static constexpr auto name = const_name("Callable[[") - + ::pybind11::detail::concat(as_arg_type>::name...) + const_name("], ") - + as_return_type>::name + const_name("]"); + + ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster::name)...) + + const_name("], ") + ::pybind11::detail::return_descr(make_caster::name) + + const_name("]"); }; template @@ -231,7 +205,7 @@ struct handle_type_name> { // PEP 484 specifies this syntax for defining only return types of callables using retval_type = conditional_t::value, void_type, Return>; static constexpr auto name = const_name("Callable[..., ") - + as_return_type>::name + + ::pybind11::detail::return_descr(make_caster::name) + const_name("]"); }; @@ -245,22 +219,11 @@ struct handle_type_name> { static constexpr auto name = const_name("Union[") + ::pybind11::detail::concat(make_caster::name...) + const_name("]"); - static constexpr auto arg_name - = const_name("Union[") - + ::pybind11::detail::concat(as_arg_type>::name...) + const_name("]"); - static constexpr auto return_name - = const_name("Union[") - + ::pybind11::detail::concat(as_return_type>::name...) - + const_name("]"); }; template struct handle_type_name> { static constexpr auto name = const_name("Optional[") + make_caster::name + const_name("]"); - static constexpr auto arg_name - = const_name("Optional[") + as_arg_type>::name + const_name("]"); - static constexpr auto return_name - = const_name("Optional[") + as_return_type>::name + const_name("]"); }; template @@ -273,19 +236,14 @@ struct handle_type_name> { static constexpr auto name = const_name("ClassVar[") + make_caster::name + const_name("]"); }; -// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually -// the narrower type. - template struct handle_type_name> { - static constexpr auto name - = const_name("TypeGuard[") + as_return_type>::name + const_name("]"); + static constexpr auto name = const_name("TypeGuard[") + make_caster::name + const_name("]"); }; template struct handle_type_name> { - static constexpr auto name - = const_name("TypeIs[") + as_return_type>::name + const_name("]"); + static constexpr auto name = const_name("TypeIs[") + make_caster::name + const_name("]"); }; template <> @@ -299,15 +257,35 @@ struct handle_type_name { }; #if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL) +template +consteval auto sanitize_string_literal() { + constexpr std::string_view v(StrLit.name); + constexpr std::string_view special_chars("!@%{}-"); + constexpr auto num_special_chars = std::accumulate( + special_chars.begin(), special_chars.end(), (size_t) 0, [&v](auto acc, const char &c) { + return std::move(acc) + std::ranges::count(v, c); + }); + char result[v.size() + num_special_chars + 1]; + size_t i = 0; + for (auto c : StrLit.name) { + if (special_chars.find(c) != std::string_view::npos) { + result[i++] = '!'; + } + result[i++] = c; + } + return typing::StringLiteral(result); +} + template struct handle_type_name> { - static constexpr auto name = const_name("Literal[") - + pybind11::detail::concat(const_name(Literals.name)...) - + const_name("]"); + static constexpr auto name + = const_name("Literal[") + + pybind11::detail::concat(const_name(sanitize_string_literal().name)...) + + const_name("]"); }; template struct handle_type_name> { - static constexpr auto name = const_name(StrLit.name); + static constexpr auto name = const_name(sanitize_string_literal().name); }; #endif diff --git a/tests/test_docs_advanced_cast_custom.cpp b/tests/test_docs_advanced_cast_custom.cpp index a6f8a212e..0ec1b17ac 100644 --- a/tests/test_docs_advanced_cast_custom.cpp +++ b/tests/test_docs_advanced_cast_custom.cpp @@ -20,19 +20,16 @@ namespace detail { template <> struct type_caster { - // This macro inserts a lot of boilerplate code and sets the default type hint to `tuple` - PYBIND11_TYPE_CASTER(user_space::Point2D, const_name("tuple")); - // `arg_name` and `return_name` may optionally be used to specify type hints separately for - // arguments and return values. + // This macro inserts a lot of boilerplate code and sets the type hint. + // `io_name` is used to specify different type hints for arguments and return values. // The signature of our negate function would then look like: // `negate(Sequence[float]) -> tuple[float, float]` - static constexpr auto arg_name = const_name("Sequence[float]"); - static constexpr auto return_name = const_name("tuple[float, float]"); + PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]")); // C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments // are used to indicate the return value policy and parent object (for // return_value_policy::reference_internal) and are often ignored by custom casters. - // The return value should reflect the type hint specified by `return_name`. + // The return value should reflect the type hint specified by the second argument of `io_name`. static handle cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) { return py::make_tuple(number.x, number.y).release(); @@ -40,7 +37,8 @@ struct type_caster { // Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The // second argument indicates whether implicit conversions should be allowed. - // The accepted types should reflect the type hint specified by `arg_name`. + // The accepted types should reflect the type hint specified by the first argument of + // `io_name`. bool load(handle src, bool /*convert*/) { // Check if handle is a Sequence if (!py::isinstance(src)) { diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index b4fa99192..5160e9f40 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -142,7 +142,6 @@ typedef py::typing::TypeVar<"V"> TypeVarV; // RealNumber: // * in arguments -> float | int // * in return -> float -// * fallback -> complex // The choice of types is not really useful, but just made different for testing purposes. // According to `PEP 484 – Type Hints` annotating with `float` also allows `int`, // so using `float | int` could be replaced by just `float`. @@ -156,15 +155,17 @@ namespace detail { template <> struct type_caster { - PYBIND11_TYPE_CASTER(RealNumber, const_name("complex")); - static constexpr auto arg_name = const_name("Union[float, int]"); - static constexpr auto return_name = const_name("float"); + PYBIND11_TYPE_CASTER(RealNumber, io_name("Union[float, int]", "float")); static handle cast(const RealNumber &number, return_value_policy, handle) { return py::float_(number.value).release(); } - bool load(handle src, bool) { + bool load(handle src, bool convert) { + // If we're in no-convert mode, only load if given a float + if (!convert && !py::isinstance(src)) { + return false; + } if (!py::isinstance(src) && !py::isinstance(src)) { return false; } @@ -970,6 +971,19 @@ TEST_SUBMODULE(pytypes, m) { .value("BLUE", literals::Color::BLUE); m.def("annotate_literal", [](literals::LiteralFoo &o) -> py::object { return o; }); + // Literal with `@`, `%`, `{`, `}`, and `->` + m.def("identity_literal_exclamation", [](const py::typing::Literal<"\"!\""> &x) { return x; }); + m.def("identity_literal_at", [](const py::typing::Literal<"\"@\""> &x) { return x; }); + m.def("identity_literal_percent", [](const py::typing::Literal<"\"%\""> &x) { return x; }); + m.def("identity_literal_curly_open", [](const py::typing::Literal<"\"{\""> &x) { return x; }); + m.def("identity_literal_curly_close", [](const py::typing::Literal<"\"}\""> &x) { return x; }); + m.def("identity_literal_arrow_with_io_name", + [](const py::typing::Literal<"\"->\""> &x, const RealNumber &) { return x; }); + m.def("identity_literal_arrow_with_callable", + [](const py::typing::Callable\""> &, + const RealNumber &)> &x) { return x; }); + m.def("identity_literal_all_special_chars", + [](const py::typing::Literal<"\"!@!!->{%}\""> &x) { return x; }); m.def("annotate_generic_containers", [](const py::typing::List &l) -> py::typing::List { return l; @@ -1070,6 +1084,14 @@ TEST_SUBMODULE(pytypes, m) { m.attr("defined___cpp_inline_variables") = false; #endif m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; }); + m.def( + "half_of_number_convert", + [](const RealNumber &x) { return RealNumber{x.value / 2}; }, + py::arg("x")); + m.def( + "half_of_number_noconvert", + [](const RealNumber &x) { return RealNumber{x.value / 2}; }, + py::arg("x").noconvert()); // std::vector m.def("half_of_number_vector", [](const std::vector &x) { std::vector result; @@ -1130,6 +1152,16 @@ TEST_SUBMODULE(pytypes, m) { m.def("identity_iterable", [](const py::typing::Iterable &x) { return x; }); // Iterator m.def("identity_iterator", [](const py::typing::Iterator &x) { return x; }); + // Callable identity + m.def("identity_callable", + [](const py::typing::Callable &x) { return x; }); + // Callable identity + m.def("identity_callable_ellipsis", + [](const py::typing::Callable &x) { return x; }); + // Nested Callable identity + m.def("identity_nested_callable", + [](const py::typing::Callable( + py::typing::Callable)> &x) { return x; }); // Callable m.def("apply_callable", [](const RealNumber &x, const py::typing::Callable &f) { diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 448bfa6a8..469137cc3 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1044,6 +1044,39 @@ def test_literal(doc): doc(m.annotate_literal) == 'annotate_literal(arg0: Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]) -> object' ) + # The characters !, @, %, {, } and -> are used in the signature parser as special characters, but Literal should escape those for the parser to work. + assert ( + doc(m.identity_literal_exclamation) + == 'identity_literal_exclamation(arg0: Literal["!"]) -> Literal["!"]' + ) + assert ( + doc(m.identity_literal_at) + == 'identity_literal_at(arg0: Literal["@"]) -> Literal["@"]' + ) + assert ( + doc(m.identity_literal_percent) + == 'identity_literal_percent(arg0: Literal["%"]) -> Literal["%"]' + ) + assert ( + doc(m.identity_literal_curly_open) + == 'identity_literal_curly_open(arg0: Literal["{"]) -> Literal["{"]' + ) + assert ( + doc(m.identity_literal_curly_close) + == 'identity_literal_curly_close(arg0: Literal["}"]) -> Literal["}"]' + ) + assert ( + doc(m.identity_literal_arrow_with_io_name) + == 'identity_literal_arrow_with_io_name(arg0: Literal["->"], arg1: Union[float, int]) -> Literal["->"]' + ) + assert ( + doc(m.identity_literal_arrow_with_callable) + == 'identity_literal_arrow_with_callable(arg0: Callable[[Literal["->"], Union[float, int]], float]) -> Callable[[Literal["->"], Union[float, int]], float]' + ) + assert ( + doc(m.identity_literal_all_special_chars) + == 'identity_literal_all_special_chars(arg0: Literal["!@!!->{%}"]) -> Literal["!@!!->{%}"]' + ) @pytest.mark.skipif( @@ -1195,15 +1228,22 @@ def test_final_annotation() -> None: def test_arg_return_type_hints(doc): assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> float" + assert ( + doc(m.half_of_number_convert) + == "half_of_number_convert(x: Union[float, int]) -> float" + ) + assert ( + doc(m.half_of_number_noconvert) == "half_of_number_noconvert(x: float) -> float" + ) assert m.half_of_number(2.0) == 1.0 assert m.half_of_number(2) == 1.0 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 should use fallback type (complex is not really useful but just used for testing) + # std::vector assert ( doc(m.half_of_number_vector) - == "half_of_number_vector(arg0: list[complex]) -> list[complex]" + == "half_of_number_vector(arg0: list[Union[float, int]]) -> list[float]" ) # Tuple assert ( @@ -1245,6 +1285,21 @@ def test_arg_return_type_hints(doc): doc(m.identity_iterator) == "identity_iterator(arg0: Iterator[Union[float, int]]) -> Iterator[float]" ) + # Callable identity + assert ( + doc(m.identity_callable) + == "identity_callable(arg0: Callable[[Union[float, int]], float]) -> Callable[[Union[float, int]], float]" + ) + # Callable identity + assert ( + doc(m.identity_callable_ellipsis) + == "identity_callable_ellipsis(arg0: Callable[..., float]) -> Callable[..., float]" + ) + # Nested Callable identity + assert ( + doc(m.identity_nested_callable) + == "identity_nested_callable(arg0: Callable[[Callable[[Union[float, int]], float]], Callable[[Union[float, int]], float]]) -> Callable[[Callable[[Union[float, int]], float]], Callable[[Union[float, int]], float]]" + ) # Callable assert ( doc(m.apply_callable) diff --git a/tests/test_stl.py b/tests/test_stl.py index 14c7da312..f2ff727d9 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -265,19 +265,19 @@ def test_fs_path(doc): assert m.parent_path(PseudoBytesPath()) == Path("foo") assert ( doc(m.parent_path) - == "parent_path(arg0: Union[os.PathLike, str, bytes]) -> Path" + == "parent_path(arg0: Union[os.PathLike, str, bytes]) -> pathlib.Path" ) - # std::vector should use name (for arg_name/return_name typing classes must be used) + # std::vector assert m.parent_paths(["foo/bar", "foo/baz"]) == [Path("foo"), Path("foo")] assert ( doc(m.parent_paths) - == "parent_paths(arg0: list[os.PathLike]) -> list[os.PathLike]" + == "parent_paths(arg0: list[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[Union[os.PathLike, str, bytes]]) -> list[Path]" + == "parent_paths_list(arg0: list[Union[os.PathLike, str, bytes]]) -> list[pathlib.Path]" ) # Nested py::typing::List assert m.parent_paths_nested_list([["foo/bar"], ["foo/baz", "foo/buzz"]]) == [ @@ -286,13 +286,13 @@ def test_fs_path(doc): ] assert ( doc(m.parent_paths_nested_list) - == "parent_paths_nested_list(arg0: list[list[Union[os.PathLike, str, bytes]]]) -> list[list[Path]]" + == "parent_paths_nested_list(arg0: list[list[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[Union[os.PathLike, str, bytes], Union[os.PathLike, str, bytes]]) -> tuple[Path, Path]" + == "parent_paths_tuple(arg0: tuple[Union[os.PathLike, str, bytes], Union[os.PathLike, str, bytes]]) -> tuple[pathlib.Path, pathlib.Path]" ) # py::typing::Dict assert m.parent_paths_dict( @@ -308,7 +308,7 @@ def test_fs_path(doc): } assert ( doc(m.parent_paths_dict) - == "parent_paths_dict(arg0: dict[str, Union[os.PathLike, str, bytes]]) -> dict[str, Path]" + == "parent_paths_dict(arg0: dict[str, Union[os.PathLike, str, bytes]]) -> dict[str, pathlib.Path]" )