diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f26c307a87..c70773ad06 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -182,6 +182,9 @@ class object_api : public pyobject_tag { /// Get or set the object's docstring, i.e. ``obj.__doc__``. str_attr_accessor doc() const; + /// Get or set the object's type_params, i.e. ``obj.__type_params__``. + str_attr_accessor type_params() const; + /// Return the object's current reference count ssize_t ref_count() const { #ifdef PYPY_VERSION @@ -2534,6 +2537,11 @@ str_attr_accessor object_api::doc() const { return attr("__doc__"); } +template +str_attr_accessor object_api::type_params() const { + return attr("__type_params__"); +} + template handle object_api::get_type() const { return type::handle_of(derived()); diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index b0feb9464a..683535e755 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -115,7 +115,7 @@ class Literal : public object { PYBIND11_OBJECT_DEFAULT(Literal, object, PyObject_Type) }; -// Example syntax for creating a TypeVar. +// Example syntax for creating a type annotation of a TypeVar, ParamSpec, and TypeVarTuple. // typedef typing::TypeVar<"T"> TypeVarT; template class TypeVar : public object { @@ -124,6 +124,56 @@ class TypeVar : public object { }; #endif +// class NameWrapper : public object { +// PYBIND11_OBJECT_DEFAULT(NameWrapper, object, PyObject_Type) +// using object::object; +// NameWrapper(const char *name) { attr("__name__") = name; } +// }; + +// template +// class TypeVarObject : public object { +// PYBIND11_OBJECT_DEFAULT(TypeVarObject, object, PyObject_Type) +// using object::object; +// TypeVarObject(const char *name) { +// attr("__name__") = name; +// attr("__bound__") = object(); +// attr("__bound__").attr("__name__") = pybind11::detail::make_caster::name; +// attr("__constraints__") = pybind11::make_tuple(); +// } +// // TypeVarObject(const char *name, py::typing::Tuple +// tuple){ +// // attr("__name__") = name; +// // attr("__bound__") = py::none(); +// // attr("__constraints__") = tuple; +// // } +// }; + +// template <> +// class TypeVarObject : public object { +// PYBIND11_OBJECT_DEFAULT(TypeVarObject, object, PyObject_Type) +// using object::object; +// TypeVarObject(const char *name) { +// attr("__name__") = name; +// attr("__bound__") = py::none(); +// attr("__constraints__") = pybind11::make_tuple(); +// } +// }; + +class ParamSpec : public object { + PYBIND11_OBJECT_DEFAULT(ParamSpec, object, PyObject_Type) + using object::object; + ParamSpec(const char *name) { + attr("__name__") = name; + attr("__bound__") = pybind11::none(); + } +}; + +class TypeVarTuple : public object { + PYBIND11_OBJECT_DEFAULT(TypeVarTuple, object, PyObject_Type) + using object::object; + TypeVarTuple(const char *name) { attr("__name__") = name; } +}; + PYBIND11_NAMESPACE_END(typing) PYBIND11_NAMESPACE_BEGIN(detail) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index ecb44939aa..9c480be1ef 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -923,4 +923,27 @@ TEST_SUBMODULE(pytypes, m) { #else m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false; #endif + + // struct TypeVarObject {}; + // py::class_(m, "TypeVarObject").type_params() + // = py::make_tuple(py::typing::TypeVarObject<>("T")); + + // struct TypeVarObjectBound {}; + // py::class_(m, "TypeVarObjectBound").type_params() + // = py::make_tuple(py::typing::TypeVarObject("T")); + + struct ParamSpec {}; + py::class_(m, "ParamSpec").type_params() + = py::make_tuple(py::typing::ParamSpec("P")); + + struct TypeVarTuple {}; + py::class_(m, "TypeVarTuple").type_params() + = py::make_tuple(py::typing::TypeVarTuple("T")); + + struct NoTypeParams {}; + struct TypeParams {}; + py::class_(m, "NoTypeParams"); + // TODO: Use custom objects + py::class_(m, "TypeParams").type_params() = py::make_tuple("foo", 3, py::none()); + m.def("type_params", []() -> void {}).type_params() = py::make_tuple("foo", 3, py::none()); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 218092b434..cd19bb4bec 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1048,3 +1048,46 @@ def test_typevar(doc): assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T" assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T" + + +def test_typevar_object(): + assert len(m.TypeVarObject.__type_params__) == 1 + type_var = m.TypeVarObject.__type_params__[0] + assert type_var.__name__ == "T" + assert type_var.__bound__ is None + assert type_var.__constraints__ == () + + assert len(m.TypeVarObjectBound.__type_params__) == 1 + type_var = m.TypeVarObjectBound.__type_params__[0] + assert type_var.__name__ == "T" + assert type_var.__bound__ == int + assert type_var.__constraints__ == () + + assert len(m.TypeVarObjectConstraints.__type_params__) == 1 + type_var = m.TypeVarObjectConstraints.__type_params__[0] + assert type_var.__name__ == "T" + assert type_var.__bound__ is None + assert type_var.__constraints__ == ("hi", 3) + + +def test_param_spec(): + assert len(m.ParamSpec.__type_params__) == 1 + param_spec = m.ParamSpec.__type_params__[0] + + assert param_spec.__name__ == "P" + assert param_spec.__bound__ is None + + +def test_type_var_tuple(): + assert len(m.TypeVarTuple.__type_params__) == 1 + type_var_tuple = m.TypeVarTuple.__type_params__[0] + + assert type_var_tuple.__name__ == "T" + with pytest.raises(AttributeError): + print(type_var_tuple.__bound__) + + +def test_type_params(): + assert m.NoTypeParams.__type_params__ == () + assert m.TypeParams.__type_params__ == ("foo", 3, None) + assert m.type_params.__type_params__ == ("foo", 3, None)