Skip to content

Commit aebcd70

Browse files
Add TypeVars / method generics typing (#5167)
* typevar prototype * style: pre-commit fixes * change to NameT * style: pre-commit fixes * make string const * add missing closing bracket * style: pre-commit fixes * clean up handle_type_name * style: pre-commit fixes * add back missing < * style: pre-commit fixes * add back NameT * try fixed_string * style: pre-commit fixes * std::basic_fixed_string * test c++20 * style: pre-commit fixes * cleanup * fix object to typevar conversion * style: pre-commit fixes * And CPP20 checks * style: pre-commit fixes * add missing cpp20++ check * style: pre-commit fixes * Add C++20 check to python * Fix python if { * style: pre-commit fixes * update test name * style: pre-commit fixes * remove call on cpp_std * make field const * test nontype_template * update feature check * update name of guard * fix try except in test * fix pre commit * remove extra semi colon * except AttributeError * fix try except in test * remove const * Clean up tests * style: pre-commit fixes * use contextlib.suppres * request changes * lint * Add comments * style: pre-commit fixes * Add support for unions and optionals to be compatible with object * lint * remove comment --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9ec64e3 commit aebcd70

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

include/pybind11/typing.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,32 @@ class Type : public type {
7070

7171
template <typename... Types>
7272
class Union : public object {
73+
PYBIND11_OBJECT_DEFAULT(Union, object, PyObject_Type)
7374
using object::object;
7475
};
7576

7677
template <typename T>
7778
class Optional : public object {
79+
PYBIND11_OBJECT_DEFAULT(Optional, object, PyObject_Type)
7880
using object::object;
7981
};
8082

83+
#if defined(__cpp_nontype_template_parameter_class)
84+
template <size_t N>
85+
struct StringLiteral {
86+
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); }
87+
char value[N];
88+
};
89+
90+
// Example syntax for creating a TypeVar.
91+
// typedef typing::TypeVar<"T"> TypeVarT;
92+
template <StringLiteral>
93+
class TypeVar : public object {
94+
PYBIND11_OBJECT_DEFAULT(TypeVar, object, PyObject_Type)
95+
using object::object;
96+
};
97+
#endif
98+
8199
PYBIND11_NAMESPACE_END(typing)
82100

83101
PYBIND11_NAMESPACE_BEGIN(detail)
@@ -153,5 +171,12 @@ struct handle_type_name<typing::Optional<T>> {
153171
static constexpr auto name = const_name("Optional[") + make_caster<T>::name + const_name("]");
154172
};
155173

174+
#if defined(__cpp_nontype_template_parameter_class)
175+
template <typing::StringLiteral StrLit>
176+
struct handle_type_name<typing::TypeVar<StrLit>> {
177+
static constexpr auto name = const_name(StrLit.value);
178+
};
179+
#endif
180+
156181
PYBIND11_NAMESPACE_END(detail)
157182
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

tests/test_pytypes.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ void m_defs(py::module_ &m) {
109109

110110
} // namespace handle_from_move_only_type_with_operator_PyObject
111111

112+
#if defined(__cpp_nontype_template_parameter_class)
113+
namespace typevar {
114+
typedef py::typing::TypeVar<"T"> TypeVarT;
115+
typedef py::typing::TypeVar<"V"> TypeVarV;
116+
} // namespace typevar
117+
#endif
118+
112119
TEST_SUBMODULE(pytypes, m) {
113120
m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); });
114121

@@ -844,7 +851,7 @@ TEST_SUBMODULE(pytypes, m) {
844851
m.def("annotate_iterator_int", [](const py::typing::Iterator<int> &) {});
845852
m.def("annotate_fn",
846853
[](const py::typing::Callable<int(py::typing::List<py::str>, py::str)> &) {});
847-
m.def("annotate_type", [](const py::typing::Type<int> &) {});
854+
m.def("annotate_type", [](const py::typing::Type<int> &t) -> py::type { return t; });
848855

849856
m.def("annotate_union",
850857
[](py::typing::List<py::typing::Union<py::str, py::int_, py::object>> l,
@@ -861,10 +868,29 @@ TEST_SUBMODULE(pytypes, m) {
861868
[](py::typing::List<py::typing::Union<py::str>> &l)
862869
-> py::typing::List<py::typing::Union<py::int_>> { return l; });
863870

871+
m.def("annotate_union_to_object",
872+
[](py::typing::Union<int, py::str> &o) -> py::object { return o; });
873+
864874
m.def("annotate_optional",
865875
[](py::list &list) -> py::typing::List<py::typing::Optional<py::str>> {
866876
list.append(py::str("hi"));
867877
list.append(py::none());
868878
return list;
869879
});
880+
m.def("annotate_optional_to_object",
881+
[](py::typing::Optional<int> &o) -> py::object { return o; });
882+
883+
#if defined(__cpp_nontype_template_parameter_class)
884+
m.def("annotate_generic_containers",
885+
[](const py::typing::List<typevar::TypeVarT> &l) -> py::typing::List<typevar::TypeVarV> {
886+
return l;
887+
});
888+
889+
m.def("annotate_listT_to_T",
890+
[](const py::typing::List<typevar::TypeVarT> &l) -> typevar::TypeVarT { return l[0]; });
891+
m.def("annotate_object_to_T", [](const py::object &o) -> typevar::TypeVarT { return o; });
892+
m.attr("if_defined__cpp_nontype_template_parameter_class") = true;
893+
#else
894+
m.attr("if_defined__cpp_nontype_template_parameter_class") = false;
895+
#endif
870896
}

tests/test_pytypes.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ def test_fn_annotations(doc):
960960

961961

962962
def test_type_annotation(doc):
963-
assert doc(m.annotate_type) == "annotate_type(arg0: type[int]) -> None"
963+
assert doc(m.annotate_type) == "annotate_type(arg0: type[int]) -> type"
964964

965965

966966
def test_union_annotations(doc):
@@ -977,8 +977,37 @@ def test_union_typing_only(doc):
977977
)
978978

979979

980+
def test_union_object_annotations(doc):
981+
assert (
982+
doc(m.annotate_union_to_object)
983+
== "annotate_union_to_object(arg0: Union[int, str]) -> object"
984+
)
985+
986+
980987
def test_optional_annotations(doc):
981988
assert (
982989
doc(m.annotate_optional)
983990
== "annotate_optional(arg0: list) -> list[Optional[str]]"
984991
)
992+
993+
994+
def test_optional_object_annotations(doc):
995+
assert (
996+
doc(m.annotate_optional_to_object)
997+
== "annotate_optional_to_object(arg0: Optional[int]) -> object"
998+
)
999+
1000+
1001+
@pytest.mark.skipif(
1002+
not m.if_defined__cpp_nontype_template_parameter_class,
1003+
reason="C++20 feature not available.",
1004+
)
1005+
def test_typevar(doc):
1006+
assert (
1007+
doc(m.annotate_generic_containers)
1008+
== "annotate_generic_containers(arg0: list[T]) -> list[V]"
1009+
)
1010+
1011+
assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T"
1012+
1013+
assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T"

0 commit comments

Comments
 (0)