mirror of
https://github.com/pybind/pybind11.git
synced 2025-01-30 14:52:41 +00:00
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:
parent
15d9dae14b
commit
1b7aa0bb66
@ -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)) {
|
||||
|
@ -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{}); }
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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]"
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user