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
10 changed files with 230 additions and 131 deletions

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