diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 71960b803d..5c82d1f34c 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -361,3 +361,30 @@ before they are used as a parameter or return type of a function: pyFoo.def(py::init()); pyBar.def(py::init()); } + +Setting inner type hints in docstrings +====================================== + +When you use pybind11 wrappers for ``list``, ``dict``, and other generic python +types, the docstring will just display the generic type. You can convey the +inner types in the docstring by using a special 'typed' version of the generic +type. + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + m.def("pass_list_of_str", [](py::List arg) { + // arg can be used just like py::list + )); + } + +The following special types are available: + +* ``py::Tuple`` +* ``py::Dict`` +* ``py::List`` +* ``py::Set`` +* ``py::Callable`` + +.. warning:: Just like in python, these are merely hints. They don't actually + enforce the types of their contents at runtime or compile time. diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b89e4946ad..24c5ea9f0c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -906,6 +906,36 @@ struct handle_type_name { static constexpr auto name = const_name("**kwargs"); }; +template +struct handle_type_name> { + static constexpr auto name + = const_name("Tuple[") + concat(make_caster::name...) + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("Dict[") + make_caster::name + const_name(", ") + + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("List[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("Set[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + using retval_type = conditional_t::value, void_type, Return>; + static constexpr auto name = const_name("Callable[[") + concat(make_caster::name...) + + const_name("], ") + make_caster::name + + const_name("]"); +}; + template struct pyobject_caster { template ::value, int> = 0> diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 37fc49a0e1..1f0f573563 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1937,6 +1937,11 @@ class tuple : public object { detail::tuple_iterator end() const { return {*this, PyTuple_GET_SIZE(m_ptr)}; } }; +template +class Tuple : public tuple { + using tuple::tuple; +}; + // We need to put this into a separate function because the Intel compiler // fails to compile enable_if_t...>::value> part below // (tested with ICC 2021.1 Beta 20200827). @@ -1984,6 +1989,11 @@ class dict : public object { } }; +template +class Dict : public dict { + using dict::dict; +}; + class sequence : public object { public: PYBIND11_OBJECT_DEFAULT(sequence, object, PySequence_Check) @@ -2043,6 +2053,11 @@ class list : public object { } }; +template +class List : public list { + using list::list; +}; + class args : public tuple { PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check) }; @@ -2080,6 +2095,11 @@ class set : public anyset { void clear() /* py-non-const */ { PySet_Clear(m_ptr); } }; +template +class Set : public set { + using set::set; +}; + class frozenset : public anyset { public: PYBIND11_OBJECT_CVT(frozenset, anyset, PyFrozenSet_Check, PyFrozenSet_New) @@ -2098,6 +2118,14 @@ class function : public object { bool is_cpp_function() const { return (bool) cpp_function(); } }; +template +class Callable; + +template +class Callable : public function { + using function::function; +}; + class staticmethod : public object { public: PYBIND11_OBJECT_CVT(staticmethod, object, detail::PyStaticMethod_Check, PyStaticMethod_New) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 99237ccef7..5cdf01a6c1 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -804,4 +804,10 @@ TEST_SUBMODULE(pytypes, m) { a >>= b; return a; }); + + m.def("annotate_tuple_float_str", [](const py::Tuple&) {}); + m.def("annotate_dict_str_int", [](const py::Dict&) {}); + m.def("annotate_list_int", [](const py::List&) {}); + m.def("annotate_set_str", [](const py::Set&) {}); + m.def("annotate_fn", [](const py::Callable, py::str)>&) {}); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 3ed7b9c946..16264a1285 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -863,3 +863,32 @@ def test_inplace_lshift(a, b): def test_inplace_rshift(a, b): expected = a >> b assert m.inplace_rshift(a, b) == expected + + +def test_tuple_annotations(doc): + assert ( + doc(m.annotate_tuple_float_str) + == "annotate_tuple_float_str(arg0: Tuple[float, str]) -> None" + ) + + +def test_dict_annotations(doc): + assert ( + doc(m.annotate_dict_str_int) + == "annotate_dict_str_int(arg0: Dict[str, int]) -> None" + ) + + +def test_list_annotations(doc): + assert doc(m.annotate_list_int) == "annotate_list_int(arg0: List[int]) -> None" + + +def test_set_annotations(doc): + assert doc(m.annotate_set_str) == "annotate_set_str(arg0: Set[str]) -> None" + + +def test_fn_annotations(doc): + assert ( + doc(m.annotate_fn) + == "annotate_fn(arg0: Callable[[List[str], str], int]) -> None" + )