feat: rework of arg/return type hints to support .noconvert() (#5486)

* Added rework of arg/return typing

* Changed `Path` to `pathlib.Path` for compatibility with pybind11-stubgen

* Removed old arg/return type hint implementation

* Added noconvert support for arg/return type hints

* Added commented failing tests for Literals with special characters

* Added return_descr/arg_descr for correct typing in typing::Callable

* Fixed clang-tidy issues

* Changed io_name to have explicit return type (for C++11 support)

* style: pre-commit fixes

* Added support for nested callables

* Fixed missing include

* Fixed is_return_value constructor call

* Fixed clang-tidy issue

* Uncommented test cases for special characters in literals

* Moved literal tests to correct test case

* Added escaping of special characters in typing::Literal

* Readded mistakenly deleted bracket

* Moved sanitize_string_literal to correct namespace

* Added test for Literal with `!` and changed StringLiteral template param name

* Added test for Literal with multiple and repeated special chars

* Simplified string literal sanitization function

* Added test for `->` in literal

* Added test for `->` with io_name

* Removed unused parameter name to prevent warning

* Added escaping of `-` in literal to prevent processing of `->`

* Fixed wrong computation of sanitized string literal length

* Added cast to prevent error with MSVC

* Simplified special character check

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Tim Ohliger 2025-01-24 23:01:06 +01:00 committed by GitHub
parent 15d9dae14b
commit 1b7aa0bb66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 230 additions and 131 deletions

View File

@ -61,19 +61,16 @@ type is explicitly allowed.
template <>
struct type_caster<user_space::Point2D> {
// 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<py::sequence>(src)) {

View File

@ -34,39 +34,6 @@ PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
// Type trait checker for `descr`
template <typename>
struct is_descr : std::false_type {};
template <size_t N, typename... Ts>
struct is_descr<descr<N, Ts...>> : std::true_type {};
template <size_t N, typename... Ts>
struct is_descr<const descr<N, Ts...>> : std::true_type {};
// Use arg_name instead of name when available
template <typename T, typename SFINAE = void>
struct as_arg_type {
static constexpr auto name = T::name;
};
template <typename T>
struct as_arg_type<T, typename std::enable_if<is_descr<decltype(T::arg_name)>::value>::type> {
static constexpr auto name = T::arg_name;
};
// Use return_name instead of name when available
template <typename T, typename SFINAE = void>
struct as_return_type {
static constexpr auto name = T::name;
};
template <typename T>
struct as_return_type<T,
typename std::enable_if<is_descr<decltype(T::return_name)>::value>::type> {
static constexpr auto name = T::return_name;
};
template <typename type, typename SFINAE = void>
class type_caster : public type_caster_base<type> {};
template <typename type>
@ -1113,8 +1080,6 @@ struct pyobject_caster {
return src.inc_ref();
}
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name);
static constexpr auto arg_name = as_arg_type<handle_type_name<type>>::name;
static constexpr auto return_name = as_return_type<handle_type_name<type>>::name;
};
template <typename T>
@ -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<make_caster<Args>>::name)...);
= ::pybind11::detail::concat(type_descr(make_caster<Args>::name)...);
bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); }

View File

@ -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 <size_t N1, size_t N2>
constexpr descr<N1 + N2 + 1> 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<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {
return const_name("{") + descr + const_name("}");
}
template <size_t N, typename... Ts>
constexpr descr<N + 4, Ts...> arg_descr(const descr<N, Ts...> &descr) {
return const_name("@^") + descr + const_name("@!");
}
template <size_t N, typename... Ts>
constexpr descr<N + 4, Ts...> return_descr(const descr<N, Ts...> &descr) {
return const_name("@$") + descr + const_name("@!");
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -22,6 +22,7 @@
#include <cstring>
#include <memory>
#include <new>
#include <stack>
#include <string>
#include <utility>
#include <vector>
@ -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<cast_out>::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<bool> 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<text1, text2>).
// 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;
}
}

View File

@ -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)

View File

@ -16,6 +16,13 @@
#include <algorithm>
#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L
# define PYBIND11_TYPING_H_HAS_STRING_LITERAL
# include <numeric>
# include <ranges>
# include <string_view>
#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 <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); }
@ -143,13 +149,6 @@ struct handle_type_name<typing::Tuple<Types...>> {
static constexpr auto name = const_name("tuple[")
+ ::pybind11::detail::concat(make_caster<Types>::name...)
+ const_name("]");
static constexpr auto arg_name
= const_name("tuple[")
+ ::pybind11::detail::concat(as_arg_type<make_caster<Types>>::name...) + const_name("]");
static constexpr auto return_name
= const_name("tuple[")
+ ::pybind11::detail::concat(as_return_type<make_caster<Types>>::name...)
+ const_name("]");
};
template <>
@ -163,58 +162,32 @@ struct handle_type_name<typing::Tuple<T, ellipsis>> {
// PEP 484 specifies this syntax for a variable-length tuple
static constexpr auto name
= const_name("tuple[") + make_caster<T>::name + const_name(", ...]");
static constexpr auto arg_name
= const_name("tuple[") + as_arg_type<make_caster<T>>::name + const_name(", ...]");
static constexpr auto return_name
= const_name("tuple[") + as_return_type<make_caster<T>>::name + const_name(", ...]");
};
template <typename K, typename V>
struct handle_type_name<typing::Dict<K, V>> {
static constexpr auto name = const_name("dict[") + make_caster<K>::name + const_name(", ")
+ make_caster<V>::name + const_name("]");
static constexpr auto arg_name = const_name("dict[") + as_arg_type<make_caster<K>>::name
+ const_name(", ") + as_arg_type<make_caster<V>>::name
+ const_name("]");
static constexpr auto return_name = const_name("dict[") + as_return_type<make_caster<K>>::name
+ const_name(", ") + as_return_type<make_caster<V>>::name
+ const_name("]");
};
template <typename T>
struct handle_type_name<typing::List<T>> {
static constexpr auto name = const_name("list[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("list[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("list[") + as_return_type<make_caster<T>>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Set<T>> {
static constexpr auto name = const_name("set[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("set[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("set[") + as_return_type<make_caster<T>>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Iterable<T>> {
static constexpr auto name = const_name("Iterable[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("Iterable[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("Iterable[") + as_return_type<make_caster<T>>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::Iterator<T>> {
static constexpr auto name = const_name("Iterator[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("Iterator[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("Iterator[") + as_return_type<make_caster<T>>::name + const_name("]");
};
template <typename Return, typename... Args>
@ -222,8 +195,9 @@ struct handle_type_name<typing::Callable<Return(Args...)>> {
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
static constexpr auto name
= const_name("Callable[[")
+ ::pybind11::detail::concat(as_arg_type<make_caster<Args>>::name...) + const_name("], ")
+ as_return_type<make_caster<retval_type>>::name + const_name("]");
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
+ const_name("]");
};
template <typename Return>
@ -231,7 +205,7 @@ struct handle_type_name<typing::Callable<Return(ellipsis)>> {
// PEP 484 specifies this syntax for defining only return types of callables
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
static constexpr auto name = const_name("Callable[..., ")
+ as_return_type<make_caster<retval_type>>::name
+ ::pybind11::detail::return_descr(make_caster<retval_type>::name)
+ const_name("]");
};
@ -245,22 +219,11 @@ struct handle_type_name<typing::Union<Types...>> {
static constexpr auto name = const_name("Union[")
+ ::pybind11::detail::concat(make_caster<Types>::name...)
+ const_name("]");
static constexpr auto arg_name
= const_name("Union[")
+ ::pybind11::detail::concat(as_arg_type<make_caster<Types>>::name...) + const_name("]");
static constexpr auto return_name
= const_name("Union[")
+ ::pybind11::detail::concat(as_return_type<make_caster<Types>>::name...)
+ const_name("]");
};
template <typename T>
struct handle_type_name<typing::Optional<T>> {
static constexpr auto name = const_name("Optional[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("Optional[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("Optional[") + as_return_type<make_caster<T>>::name + const_name("]");
};
template <typename T>
@ -273,19 +236,14 @@ struct handle_type_name<typing::ClassVar<T>> {
static constexpr auto name = const_name("ClassVar[") + make_caster<T>::name + const_name("]");
};
// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually
// the narrower type.
template <typename T>
struct handle_type_name<typing::TypeGuard<T>> {
static constexpr auto name
= const_name("TypeGuard[") + as_return_type<make_caster<T>>::name + const_name("]");
static constexpr auto name = const_name("TypeGuard[") + make_caster<T>::name + const_name("]");
};
template <typename T>
struct handle_type_name<typing::TypeIs<T>> {
static constexpr auto name
= const_name("TypeIs[") + as_return_type<make_caster<T>>::name + const_name("]");
static constexpr auto name = const_name("TypeIs[") + make_caster<T>::name + const_name("]");
};
template <>
@ -299,15 +257,35 @@ struct handle_type_name<typing::Never> {
};
#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL)
template <typing::StringLiteral StrLit>
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 <typing::StringLiteral... Literals>
struct handle_type_name<typing::Literal<Literals...>> {
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<Literals>().name)...)
+ const_name("]");
};
template <typing::StringLiteral StrLit>
struct handle_type_name<typing::TypeVar<StrLit>> {
static constexpr auto name = const_name(StrLit.name);
static constexpr auto name = const_name(sanitize_string_literal<StrLit>().name);
};
#endif

View File

@ -20,19 +20,16 @@ namespace detail {
template <>
struct type_caster<user_space::Point2D> {
// 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<user_space::Point2D> {
// 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<py::sequence>(src)) {

View File

@ -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<RealNumber> {
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<py::float_>(src)) {
return false;
}
if (!py::isinstance<py::float_>(src) && !py::isinstance<py::int_>(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<RealNumber(const py::typing::Literal<"\"->\""> &,
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<typevar::TypeVarT> &l) -> py::typing::List<typevar::TypeVarV> {
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<T>
m.def("half_of_number_vector", [](const std::vector<RealNumber> &x) {
std::vector<RealNumber> result;
@ -1130,6 +1152,16 @@ TEST_SUBMODULE(pytypes, m) {
m.def("identity_iterable", [](const py::typing::Iterable<RealNumber> &x) { return x; });
// Iterator<T>
m.def("identity_iterator", [](const py::typing::Iterator<RealNumber> &x) { return x; });
// Callable<R(A)> identity
m.def("identity_callable",
[](const py::typing::Callable<RealNumber(const RealNumber &)> &x) { return x; });
// Callable<R(...)> identity
m.def("identity_callable_ellipsis",
[](const py::typing::Callable<RealNumber(py::ellipsis)> &x) { return x; });
// Nested Callable<R(A)> identity
m.def("identity_nested_callable",
[](const py::typing::Callable<py::typing::Callable<RealNumber(const RealNumber &)>(
py::typing::Callable<RealNumber(const RealNumber &)>)> &x) { return x; });
// Callable<R(A)>
m.def("apply_callable",
[](const RealNumber &x, const py::typing::Callable<RealNumber(const RealNumber &)> &f) {

View File

@ -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<T> should use fallback type (complex is not really useful but just used for testing)
# std::vector<T>
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<T, T>
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<R(A)> identity
assert (
doc(m.identity_callable)
== "identity_callable(arg0: Callable[[Union[float, int]], float]) -> Callable[[Union[float, int]], float]"
)
# Callable<R(...)> identity
assert (
doc(m.identity_callable_ellipsis)
== "identity_callable_ellipsis(arg0: Callable[..., float]) -> Callable[..., float]"
)
# Nested Callable<R(A)> 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<R(A)>
assert (
doc(m.apply_callable)

View File

@ -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]"
)