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

Experimenting: Annotated[Any, pybind11.CppType("cpp_namespace::UserType")] #4888

Draft
wants to merge 49 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7780fbc
Try `Annotated[Any, "cpp_namespace::UserType"]` unconditionally.
Oct 19, 2023
76b4a34
Add `struct handle_type_name<...>` specializations for `object`, `lis…
Oct 19, 2023
794d97e
Revert "Add `struct handle_type_name<...>` specializations for `objec…
Oct 19, 2023
2cafdab
Add `cpp_name_needs_typing_annotated()`
Oct 19, 2023
f6ae40b
clang-tidy compatibility
Oct 20, 2023
ea00323
Merge branch 'master' into annotated_any
Oct 20, 2023
272152e
`Annotated[Any, CppTypePybind11("cpp_namespace::UserType")]` based on…
Oct 21, 2023
90b3912
Merge branch 'master' into annotated_any
Oct 21, 2023
bb8709a
Remove `cpp_name_needs_typing_annotated()`. Preparation for bringing …
Oct 21, 2023
199532e
Reapply "Add `struct handle_type_name<...>` specializations for `obje…
Oct 21, 2023
7280380
Fix `handle_type_name<anyset>` as suggested by @sizmailov:
Oct 21, 2023
70a510c
Adjust test_numpy_dtypes test_signature to new behavior.
Oct 21, 2023
ace70b0
Render `anyset` as `set | frozenset` as suggested by @sizmailov:
Oct 21, 2023
7c8991a
Use `Union[set, frozenset]` for internal consistency.
Oct 21, 2023
63a4881
Add missing `handle_type_name<slice>` specialization (discovered only…
Oct 22, 2023
3c20944
Add missing `handle_type_name<>` specializations for `bytearray`, `me…
Oct 22, 2023
74817b7
Add missing `handle_type_name<module_>`.
Oct 22, 2023
6a3a954
Add missing `handle_type_name<dtype>`.
Oct 22, 2023
169b0e5
Add test returning `py::exception<void>` (fails at runtime).
Oct 22, 2023
acfd646
Merge branch 'master' into annotated_any
Oct 23, 2023
9548fa5
Merge branch 'master' into annotated_any
Nov 11, 2023
0b98433
Remove `CppTypePybind11()` wrapping for now.
Nov 14, 2023
781304e
Add test_cases_for_stubgen
Nov 14, 2023
61ee34e
Pull in `Annotated[list[int], FixedSize(2)]` from test_stl
Nov 14, 2023
1104076
Add `Annotated[Any, "..."]` wrapping in `type_info_description()`
Nov 14, 2023
e5f210e
Rename `user_type` to `UserType`
Nov 14, 2023
d1694d9
Use locally defined bindings to avoid dependency on test_stl.
Nov 15, 2023
1b4fa71
Unmodified copy of https://github.com/python/mypy/blob/c6cb3c6282003d…
Nov 15, 2023
644d150
Minimal changes to make the basics code build.
Nov 15, 2023
1fa0065
pre-commit clang-format, NO manual changes.
Nov 15, 2023
69dac46
Add `m.basics` tests in ntest_cases_for_stubgen.py
Nov 15, 2023
1a2e8a6
C++11 compatibility.
Nov 15, 2023
79f6bdc
Replace `.stl_binders.` with `.cases_for_stubgen.`
Nov 15, 2023
2376f6e
Use py::handle instead of py::object to avoid clang-tidy errors.
Nov 15, 2023
542438f
Merge branch 'master' into annotated_any
Nov 16, 2023
bdbb10d
Add some deeply nested test cases.
Nov 16, 2023
429a1f8
Make test_cases_for_stubgen.py much more compact, and the `pytest -v`…
Nov 16, 2023
2b2ffeb
Introduce `detail::annotated_any()` helper.
Nov 16, 2023
65661fe
Change `detail::annotated_any()` to produce `pybind11.CppType(...)`
Nov 16, 2023
6b771d5
Merge branch 'master' into annotated_any
Dec 17, 2023
813660c
Change `annotated_any()` to `quote_cpp_type_name()`
Dec 17, 2023
66ee131
Test changes to adjust to previous commit (Change `annotated_any()` t…
Dec 17, 2023
d14d91e
Remove `handle_type_name` default implementation, add explicit specia…
Dec 17, 2023
1e6bea2
Merge branch 'master' into annotated_any
Mar 27, 2024
bf6077a
style: pre-commit fixes
pre-commit-ci[bot] Mar 27, 2024
b02767d
Merge branch 'master' into annotated_any
Mar 27, 2024
67c41cc
Merge branch 'master' into annotated_any
Apr 1, 2024
8c5bb07
Merge branch 'master' into annotated_any
Apr 3, 2024
d57ed51
Adjustments related to pybind/pybind11#4985
Apr 3, 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
2 changes: 1 addition & 1 deletion include/pybind11/detail/type_caster_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -1202,7 +1202,7 @@ class type_caster_base : public type_caster_generic {
};

inline std::string quote_cpp_type_name(const std::string &cpp_type_name) {
return cpp_type_name; // No-op for now. See PR #4888
return "`" + cpp_type_name + "`"; // See PR #4888
}

PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ set(PYBIND11_TEST_FILES
test_builtin_casters
test_call_policies
test_callbacks
test_cases_for_stubgen
test_chrono
test_class
test_const_name
Expand Down
222 changes: 222 additions & 0 deletions tests/test_cases_for_stubgen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#include "pybind11/stl.h"
#include "pybind11/stl_bind.h"
#include "pybind11_tests.h"

#include <array>
#include <map>
#include <vector>

namespace test_cases_for_stubgen {

// The `basics` code was copied from here (to have all test cases for stubgen in one place):
// https://github.com/python/mypy/blob/c6cb3c6282003dd3dadcf028735f9ba6190a0c84/test-data/pybind11_mypy_demo/src/main.cpp
// Copyright (c) 2016 The Pybind Development Team, All rights reserved.

namespace basics {

int answer() { return 42; }

int sum(int a, int b) { return a + b; }

double midpoint(double left, double right) { return left + (right - left) / 2; }

double weighted_midpoint(double left, double right, double alpha = 0.5) {
return left + (right - left) * alpha;
}

struct Point {

enum class LengthUnit { mm = 0, pixel, inch };

enum class AngleUnit { radian = 0, degree };

Point() : Point(0, 0) {}
Point(double x, double y) : x(x), y(y) {}

static const Point origin;
static const Point x_axis;
static const Point y_axis;

static LengthUnit length_unit;
static AngleUnit angle_unit;

double length() const { return std::sqrt(x * x + y * y); }

double distance_to(double other_x, double other_y) const {
double dx = x - other_x;
double dy = y - other_y;
return std::sqrt(dx * dx + dy * dy);
}

double distance_to(const Point &other) const { return distance_to(other.x, other.y); }

double x, y;
};

const Point Point::origin = Point(0, 0);
const Point Point::x_axis = Point(1, 0);
const Point Point::y_axis = Point(0, 1);

Point::LengthUnit Point::length_unit = Point::LengthUnit::mm;
Point::AngleUnit Point::angle_unit = Point::AngleUnit::radian;

} // namespace basics

void bind_basics(py::module &basics) {

using namespace basics;

// Functions
basics.def(
"answer", &answer, "answer docstring, with end quote\""); // tests explicit docstrings
basics.def("sum", &sum, "multiline docstring test, edge case quotes \"\"\"'''");
basics.def("midpoint", &midpoint, py::arg("left"), py::arg("right"));
basics.def("weighted_midpoint",
weighted_midpoint,
py::arg("left"),
py::arg("right"),
py::arg("alpha") = 0.5);

// Classes
py::class_<Point> pyPoint(basics, "Point");
py::enum_<Point::LengthUnit> pyLengthUnit(pyPoint, "LengthUnit");
py::enum_<Point::AngleUnit> pyAngleUnit(pyPoint, "AngleUnit");

pyPoint.def(py::init<>())
.def(py::init<double, double>(), py::arg("x"), py::arg("y"))
#ifdef PYBIND11_CPP14
.def("distance_to",
py::overload_cast<double, double>(&Point::distance_to, py::const_),
py::arg("x"),
py::arg("y"))
.def("distance_to",
py::overload_cast<const Point &>(&Point::distance_to, py::const_),
py::arg("other"))
#else
.def("distance_to",
static_cast<double (Point::*)(double, double) const>(&Point::distance_to),
py::arg("x"),
py::arg("y"))
.def("distance_to",
static_cast<double (Point::*)(const Point &) const>(&Point::distance_to),
py::arg("other"))
#endif
.def_readwrite("x", &Point::x)
.def_property(
"y",
[](Point &self) { return self.y; },
[](Point &self, double value) { self.y = value; })
.def_property_readonly("length", &Point::length)
.def_property_readonly_static("x_axis", [](py::handle /*cls*/) { return Point::x_axis; })
.def_property_readonly_static("y_axis", [](py::handle /*cls*/) { return Point::y_axis; })
.def_readwrite_static("length_unit", &Point::length_unit)
.def_property_static(
"angle_unit",
[](py::handle /*cls*/) { return Point::angle_unit; },
[](py::handle /*cls*/, Point::AngleUnit value) { Point::angle_unit = value; });

pyPoint.attr("origin") = Point::origin;

pyLengthUnit.value("mm", Point::LengthUnit::mm)
.value("pixel", Point::LengthUnit::pixel)
.value("inch", Point::LengthUnit::inch);

pyAngleUnit.value("radian", Point::AngleUnit::radian)
.value("degree", Point::AngleUnit::degree);

// Module-level attributes
basics.attr("PI") = std::acos(-1);
basics.attr("__version__") = "0.0.1";
}

struct UserType {
bool operator<(const UserType &) const { return false; }
};

struct minimal_caster {
static constexpr auto name = py::detail::const_name<UserType>();

static py::handle
cast(UserType const & /*src*/, py::return_value_policy /*policy*/, py::handle /*parent*/) {
return py::none().release();
}

// Maximizing simplicity. This will go terribly wrong for other arg types.
template <typename>
using cast_op_type = const UserType &;

// NOLINTNEXTLINE(google-explicit-constructor)
operator UserType const &() {
static UserType obj;
return obj;
}

bool load(py::handle /*src*/, bool /*convert*/) { return false; }
};

} // namespace test_cases_for_stubgen

namespace pybind11 {
namespace detail {

template <>
struct type_caster<test_cases_for_stubgen::UserType> : test_cases_for_stubgen::minimal_caster {};

} // namespace detail
} // namespace pybind11

PYBIND11_MAKE_OPAQUE(std::map<int, test_cases_for_stubgen::UserType>);
PYBIND11_MAKE_OPAQUE(std::map<test_cases_for_stubgen::UserType, int>);
PYBIND11_MAKE_OPAQUE(std::map<float, test_cases_for_stubgen::UserType>);
PYBIND11_MAKE_OPAQUE(std::map<test_cases_for_stubgen::UserType, float>);

TEST_SUBMODULE(cases_for_stubgen, m) {
auto basics = m.def_submodule("basics");
test_cases_for_stubgen::bind_basics(basics);

using UserType = test_cases_for_stubgen::UserType;

m.def("pass_user_type", [](const UserType &) {});
m.def("return_user_type", []() { return UserType(); });

py::bind_map<std::map<int, UserType>>(m, "MapIntUserType");
py::bind_map<std::map<UserType, int>>(m, "MapUserTypeInt");

#define LOCAL_HELPER(MapTypePythonName, ...) \
py::class_<__VA_ARGS__>(m, MapTypePythonName) \
.def( \
"keys", \
[](const __VA_ARGS__ &v) { return py::make_key_iterator(v); }, \
py::keep_alive<0, 1>()) \
.def( \
"values", \
[](const __VA_ARGS__ &v) { return py::make_value_iterator(v); }, \
py::keep_alive<0, 1>()) \
.def( \
"__iter__", \
[](const __VA_ARGS__ &v) { return py::make_iterator(v.begin(), v.end()); }, \
py::keep_alive<0, 1>())

LOCAL_HELPER("MapFloatUserType", std::map<float, UserType>);
LOCAL_HELPER("MapUserTypeFloat", std::map<UserType, float>);
#undef LOCAL_HELPER

m.def("pass_std_array_int_2", [](const std::array<int, 2> &) {});
m.def("return_std_array_int_3", []() { return std::array<int, 3>{{1, 2, 3}}; });

// Rather arbitrary, meant to be a torture test for recursive processing.
using nested_case_01a = std::vector<std::array<int, 2>>;
using nested_case_02a = std::vector<UserType>;
using nested_case_03a = std::map<std::array<int, 2>, UserType>;
using nested_case_04a = std::map<nested_case_01a, nested_case_02a>;
using nested_case_05a = std::vector<nested_case_04a>;
using nested_case_06a = std::map<nested_case_04a, nested_case_05a>;
#define LOCAL_HELPER(name) m.def(#name, [](const name &) {})
LOCAL_HELPER(nested_case_01a);
LOCAL_HELPER(nested_case_02a);
LOCAL_HELPER(nested_case_03a);
LOCAL_HELPER(nested_case_04a);
LOCAL_HELPER(nested_case_05a);
LOCAL_HELPER(nested_case_06a);
#undef LOCAL_HELPER
}
45 changes: 45 additions & 0 deletions tests/test_cases_for_stubgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest

from pybind11_tests import cases_for_stubgen as m

TEST_CASES = {
"m.basics.answer.__doc__": 'answer() -> int\n\nanswer docstring, with end quote"\n',
"m.basics.sum.__doc__": "sum(arg0: int, arg1: int) -> int\n\nmultiline docstring test, edge case quotes \"\"\"'''\n",
"m.basics.midpoint.__doc__": "midpoint(left: float, right: float) -> float\n",
"m.basics.weighted_midpoint.__doc__": "weighted_midpoint(left: float, right: float, alpha: float = 0.5) -> float\n",
"m.basics.Point.__init__.__doc__": "__init__(*args, **kwargs)\nOverloaded function.\n\n1. __init__(self: pybind11_tests.cases_for_stubgen.basics.Point) -> None\n\n2. __init__(self: pybind11_tests.cases_for_stubgen.basics.Point, x: float, y: float) -> None\n",
"m.basics.Point.distance_to.__doc__": "distance_to(*args, **kwargs)\nOverloaded function.\n\n1. distance_to(self: pybind11_tests.cases_for_stubgen.basics.Point, x: float, y: float) -> float\n\n2. distance_to(self: pybind11_tests.cases_for_stubgen.basics.Point, other: pybind11_tests.cases_for_stubgen.basics.Point) -> float\n",
"m.basics.Point.length_unit.__doc__": "Members:\n\n mm\n\n pixel\n\n inch",
"m.basics.Point.angle_unit.__doc__": "Members:\n\n radian\n\n degree",
"m.pass_user_type.__doc__": "pass_user_type(arg0: `test_cases_for_stubgen::UserType`) -> None\n",
"m.return_user_type.__doc__": "return_user_type() -> `test_cases_for_stubgen::UserType`\n",
"m.MapIntUserType.keys.__doc__": "keys(self: pybind11_tests.cases_for_stubgen.MapIntUserType) -> pybind11_tests.cases_for_stubgen.KeysView\n",
"m.MapIntUserType.values.__doc__": "values(self: pybind11_tests.cases_for_stubgen.MapIntUserType) -> pybind11_tests.cases_for_stubgen.ValuesView\n",
"m.MapIntUserType.items.__doc__": "items(self: pybind11_tests.cases_for_stubgen.MapIntUserType) -> pybind11_tests.cases_for_stubgen.ItemsView\n",
"m.MapUserTypeInt.keys.__doc__": "keys(self: pybind11_tests.cases_for_stubgen.MapUserTypeInt) -> pybind11_tests.cases_for_stubgen.KeysView\n",
"m.MapUserTypeInt.values.__doc__": "values(self: pybind11_tests.cases_for_stubgen.MapUserTypeInt) -> pybind11_tests.cases_for_stubgen.ValuesView\n",
"m.MapUserTypeInt.items.__doc__": "items(self: pybind11_tests.cases_for_stubgen.MapUserTypeInt) -> pybind11_tests.cases_for_stubgen.ItemsView\n",
"m.MapFloatUserType.keys.__doc__": "keys(self: pybind11_tests.cases_for_stubgen.MapFloatUserType) -> Iterator[float]\n",
"m.MapFloatUserType.values.__doc__": "values(self: pybind11_tests.cases_for_stubgen.MapFloatUserType) -> Iterator[`test_cases_for_stubgen::UserType`]\n",
"m.MapFloatUserType.__iter__.__doc__": "__iter__(self: pybind11_tests.cases_for_stubgen.MapFloatUserType) -> Iterator[tuple[float, `test_cases_for_stubgen::UserType`]]\n",
"m.MapUserTypeFloat.keys.__doc__": "keys(self: pybind11_tests.cases_for_stubgen.MapUserTypeFloat) -> Iterator[`test_cases_for_stubgen::UserType`]\n",
"m.MapUserTypeFloat.values.__doc__": "values(self: pybind11_tests.cases_for_stubgen.MapUserTypeFloat) -> Iterator[float]\n",
"m.MapUserTypeFloat.__iter__.__doc__": "__iter__(self: pybind11_tests.cases_for_stubgen.MapUserTypeFloat) -> Iterator[tuple[`test_cases_for_stubgen::UserType`, float]]\n",
"m.pass_std_array_int_2.__doc__": "pass_std_array_int_2(arg0: Annotated[list[int], FixedSize(2)]) -> None\n",
"m.return_std_array_int_3.__doc__": "return_std_array_int_3() -> Annotated[list[int], FixedSize(3)]\n",
"m.nested_case_01a.__doc__": "nested_case_01a(arg0: list[Annotated[list[int], FixedSize(2)]]) -> None\n",
"m.nested_case_02a.__doc__": "nested_case_02a(arg0: list[`test_cases_for_stubgen::UserType`]) -> None\n",
"m.nested_case_03a.__doc__": "nested_case_03a(arg0: dict[Annotated[list[int], FixedSize(2)], `test_cases_for_stubgen::UserType`]) -> None\n",
"m.nested_case_04a.__doc__": "nested_case_04a(arg0: dict[list[Annotated[list[int], FixedSize(2)]], list[`test_cases_for_stubgen::UserType`]]) -> None\n",
"m.nested_case_05a.__doc__": "nested_case_05a(arg0: list[dict[list[Annotated[list[int], FixedSize(2)]], list[`test_cases_for_stubgen::UserType`]]]) -> None\n",
"m.nested_case_06a.__doc__": "nested_case_06a(arg0: dict[dict[list[Annotated[list[int], FixedSize(2)]], list[`test_cases_for_stubgen::UserType`]], list[dict[list[Annotated[list[int], FixedSize(2)]], list[`test_cases_for_stubgen::UserType`]]]]) -> None\n",
}


@pytest.mark.parametrize("test_case", TEST_CASES.keys())
def test_docstring(test_case):
assert dir(m) # Only direct use of m, to stop tooling from removing the import.
# On some platforms the stl_binders module name prevails for KeysView, ValuesView, ItemsView.
docstring = eval(test_case).replace(".stl_binders.", ".cases_for_stubgen.")
expected = TEST_CASES[test_case]
assert docstring == expected
4 changes: 2 additions & 2 deletions tests/test_numpy_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ def test_complex_array():

def test_signature(doc):
assert (
doc(m.create_rec_nested)
== "create_rec_nested(arg0: int) -> numpy.ndarray[NestedStruct]"
doc(m.create_rec_nested) == "create_rec_nested(arg0: int) "
"-> numpy.ndarray[`NestedStruct`]"
)


Expand Down
Loading