Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option for arg/return type hints and correct typing for std::filesystem::path #5450

Merged
merged 35 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2b8de82
Added arg/return type handling.
timohl Nov 24, 2024
7ba983b
Added support for nested arg/return type in py::typing::List
timohl Nov 24, 2024
b4008bb
Added support for arg/return type in stl/filesystem
timohl Nov 24, 2024
bc145b3
Added tests for arg/return type in stl/filesystem and py::typing::List
timohl Nov 24, 2024
a468d37
Added arg/return name to more py::typing classes
timohl Nov 24, 2024
f0b60cb
Added arg/return type to Callable[...]
timohl Nov 24, 2024
7f0f938
Added tests for typing container classes (also nested)
timohl Nov 24, 2024
e8d94ea
Changed typing classes to avoid using C++14 auto return type deduction.
timohl Nov 24, 2024
9042844
Fixed clang-tidy errors.
timohl Nov 24, 2024
c7bcb57
Changed Enable to SFINAE
timohl Nov 24, 2024
251aeb7
Added test for Tuple[T, ...]
timohl Nov 24, 2024
413d685
Added RealNumber with custom caster for testing typing classes.
timohl Nov 25, 2024
66e6644
Added tests for Set, Iterable, Iterator, Union, and Optional
timohl Nov 25, 2024
3199428
Merge branch 'pybind:master' into arg_return_type_hints
timohl Nov 25, 2024
2c17048
Added tests for Callable
timohl Nov 25, 2024
9eb7af9
Fixed Callable with ellipsis test
timohl Nov 25, 2024
9ad445d
Changed TypeGuard/TypeIs to use return type (being the narrower type)…
timohl Nov 25, 2024
c9dab34
Added test for use of fallback type name with stl vector
timohl Nov 25, 2024
28cfd9e
Merge branch 'arg_return_type_hints' of github.com:timohl/pybind11 in…
timohl Nov 25, 2024
eb8e5d1
Updated documentation.
timohl Nov 26, 2024
3c53525
Fixed unnecessary constructor call in test.
timohl Nov 26, 2024
a45f1bd
Fixed reference counting in example type caster.
timohl Nov 26, 2024
5ea6b69
Fixed clang-tidy issues.
timohl Nov 26, 2024
34fe273
Fix for clang-tidy
timohl Nov 26, 2024
e704703
Updated cast method to use pybind11 API rather than Python C API in c…
timohl Nov 30, 2024
fa0a7b1
Updated load to use pybind11 API rather than Python C API in custom c…
timohl Dec 1, 2024
1fe101a
Changed test of arg/return name to use pybind11 API instead of Python…
timohl Dec 1, 2024
562153d
Updated code in adcanced/cast example and improved documentation text
timohl Dec 1, 2024
eea98b2
Fixed references in custom type caster docs
timohl Dec 1, 2024
acb58b3
Fixed wrong logical and operator in test
timohl Dec 1, 2024
bab038d
Fixed wrong logical operator in doc example
timohl Dec 1, 2024
3a78cb4
Added comment to test about `float` vs `float | int`
timohl Dec 2, 2024
6ea4704
Updated std::filesystem::path docs in cast/overview section
timohl Dec 5, 2024
aa21ab5
Remove one stray dot.
rwgk Dec 8, 2024
6b332d4
Merge branch 'master' into arg_return_type_hints
rwgk Dec 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,39 @@ 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 Enable = void>
timohl marked this conversation as resolved.
Show resolved Hide resolved
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 Enable = 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>
Expand Down Expand Up @@ -1078,6 +1111,8 @@ 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>
Expand Down Expand Up @@ -1606,7 +1641,7 @@ class argument_loader {
"py::args cannot be specified more than once");

static constexpr auto arg_names
= ::pybind11::detail::concat(type_descr(make_caster<Args>::name)...);
= ::pybind11::detail::concat(type_descr(as_arg_type<make_caster<Args>>::name)...);

bool load_args(function_call &call) { return load_impl_sequence(call, indices{}); }

Expand Down
4 changes: 2 additions & 2 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,8 @@ class cpp_function : public function {

/* 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(") -> ") + cast_out::name;
static constexpr auto signature = const_name("(") + cast_in::arg_names
+ const_name(") -> ") + as_return_type<cast_out>::name;
PYBIND11_DESCR_CONSTEXPR auto types = decltype(signature)::types();

/* Register the function with Python from generic (non-templated) code */
Expand Down
2 changes: 2 additions & 0 deletions include/pybind11/stl/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ struct path_caster {
}

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");
};

#endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
Expand Down
62 changes: 58 additions & 4 deletions include/pybind11/typing.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ 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
timohl marked this conversation as resolved.
Show resolved Hide resolved
= 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 <>
Expand All @@ -144,48 +151,76 @@ 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>
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(make_caster<Args>::name...)
+ const_name("], ") + make_caster<retval_type>::name + const_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("]");
};

template <typename Return>
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[..., ") + make_caster<retval_type>::name + const_name("]");
static constexpr auto name = const_name("Callable[..., ")
+ as_return_type<make_caster<retval_type>>::name
+ const_name("]");
};

template <typename T>
Expand All @@ -198,21 +233,40 @@ 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>
struct handle_type_name<typing::TypeGuard<T>> {
static constexpr auto name = const_name("TypeGuard[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("TypeGuard[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("TypeGuard[") + as_return_type<make_caster<T>>::name + const_name("]");
};

template <typename T>
struct handle_type_name<typing::TypeIs<T>> {
static constexpr auto name = const_name("TypeIs[") + make_caster<T>::name + const_name("]");
static constexpr auto arg_name
= const_name("TypeIs[") + as_arg_type<make_caster<T>>::name + const_name("]");
static constexpr auto return_name
= const_name("TypeIs[") + as_return_type<make_caster<T>>::name + const_name("]");
};

template <>
Expand Down
45 changes: 44 additions & 1 deletion tests/test_stl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# define PYBIND11_HAS_FILESYSTEM_IS_OPTIONAL
#endif
#include <pybind11/stl/filesystem.h>
#include <pybind11/typing.h>

#include <string>
#include <vector>
Expand Down Expand Up @@ -453,7 +454,49 @@ TEST_SUBMODULE(stl, m) {
#ifdef PYBIND11_HAS_FILESYSTEM
// test_fs_path
m.attr("has_filesystem") = true;
m.def("parent_path", [](const std::filesystem::path &p) { return p.parent_path(); });
m.def("parent_path", [](const std::filesystem::path &path) { return path.parent_path(); });
m.def("parent_paths", [](const std::vector<std::filesystem::path> &paths) {
std::vector<std::filesystem::path> result;
result.reserve(paths.size());
for (const auto &path : paths) {
result.push_back(path.parent_path());
}
return result;
});
m.def("parent_paths_list", [](const py::typing::List<std::filesystem::path> &paths) {
py::typing::List<std::filesystem::path> result;
for (auto path : paths) {
result.append(path.cast<std::filesystem::path>().parent_path());
}
return result;
});
m.def("parent_paths_nested_list",
[](const py::typing::List<py::typing::List<std::filesystem::path>> &paths_lists) {
py::typing::List<py::typing::List<std::filesystem::path>> result_lists;
for (auto paths : paths_lists) {
py::typing::List<std::filesystem::path> result;
for (auto path : paths) {
result.append(path.cast<std::filesystem::path>().parent_path());
}
result_lists.append(result);
}
return result_lists;
});
m.def("parent_paths_tuple",
[](const py::typing::Tuple<std::filesystem::path, std::filesystem::path> &paths) {
py::typing::Tuple<std::filesystem::path, std::filesystem::path> result
= py::make_tuple(paths[0].cast<std::filesystem::path>().parent_path(),
paths[1].cast<std::filesystem::path>().parent_path());
return result;
});
m.def("parent_paths_dict",
[](const py::typing::Dict<std::string, std::filesystem::path> &paths) {
py::typing::Dict<std::string, std::filesystem::path> result;
for (auto it : paths) {
result[it.first] = it.second.cast<std::filesystem::path>().parent_path();
}
return result;
});
#endif

#ifdef PYBIND11_TEST_VARIANT
Expand Down
50 changes: 49 additions & 1 deletion tests/test_stl.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def test_reference_sensitive_optional():


@pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no <filesystem>")
def test_fs_path():
def test_fs_path(doc):
from pathlib import Path

class PseudoStrPath:
Expand All @@ -257,11 +257,59 @@ class PseudoBytesPath:
def __fspath__(self):
return b"foo/bar"

# Single argument
assert m.parent_path(Path("foo/bar")) == Path("foo")
assert m.parent_path("foo/bar") == Path("foo")
assert m.parent_path(b"foo/bar") == Path("foo")
assert m.parent_path(PseudoStrPath()) == Path("foo")
assert m.parent_path(PseudoBytesPath()) == Path("foo")
assert (
doc(m.parent_path)
== "parent_path(arg0: Union[os.PathLike, str, bytes]) -> Path"
)
# std::vector should use name (for arg_name/return_name typing classes must be used)
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]"
)
# 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]"
)
# Nested py::typing::List
assert m.parent_paths_nested_list([["foo/bar"], ["foo/baz", "foo/buzz"]]) == [
[Path("foo")],
[Path("foo"), Path("foo")],
]
assert (
doc(m.parent_paths_nested_list)
== "parent_paths_nested_list(arg0: list[list[Union[os.PathLike, str, bytes]]]) -> list[list[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]"
)
# py::typing::Dict
assert m.parent_paths_dict(
{
"key1": Path("foo/bar"),
"key2": "foo/baz",
"key3": b"foo/buzz",
}
) == {
"key1": Path("foo"),
"key2": Path("foo"),
"key3": Path("foo"),
}
assert (
doc(m.parent_paths_dict)
== "parent_paths_dict(arg0: dict[str, Union[os.PathLike, str, bytes]]) -> dict[str, Path]"
)


@pytest.mark.skipif(not hasattr(m, "load_variant"), reason="no <variant>")
Expand Down