diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f26c307a8..c846f48a6 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -182,6 +182,10 @@ public: /// 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 +2538,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 b0feb9464..077eaaf46 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,41 @@ class TypeVar : public object { }; #endif + + +template +class TypeVarObject : public object { + PYBIND11_OBJECT_DEFAULT(TypeVarObject, object, PyObject_Type) + using object::object; + TypeVarObject(const char *name){ + attr("__name__") = name; + attr("__bound__") = make_caster; + attr("__constraints__") = pybind11::make_tuple(); + } + // TypeVarObject(const char *name, py::typing::Tuple tuple){ + // attr("__name__") = name; + // attr("__bound__") = py::none(); + // attr("__constraints__") = 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 ecb44939a..aa77a65b0 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -923,4 +923,21 @@ 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 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 218092b43..ff1825784 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -4,6 +4,7 @@ import contextlib import sys import types +from typing import TypeVar import pytest import env @@ -1048,3 +1049,30 @@ 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__ == int + assert type_var.__constraints__ == () + +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__ == 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): + param_spec.__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)