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

feat(types) Support for inline generics #5268

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2534,6 +2537,11 @@ str_attr_accessor object_api<D>::doc() const {
return attr("__doc__");
}

template <typename D>
str_attr_accessor object_api<D>::type_params() const {
return attr("__type_params__");
}

template <typename D>
handle object_api<D>::get_type() const {
return type::handle_of(derived());
Expand Down
52 changes: 51 additions & 1 deletion include/pybind11/typing.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <StringLiteral>
class TypeVar : public object {
Expand All @@ -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 <typename T>
// 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<T>::name;
// attr("__constraints__") = pybind11::make_tuple();
// }
// // TypeVarObject(const char *name, py::typing::Tuple<pybind11::type, pybind11::ellipse>
// 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)
Expand Down
23 changes: 23 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -923,4 +923,27 @@ TEST_SUBMODULE(pytypes, m) {
#else
m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false;
#endif

// struct TypeVarObject {};
// py::class_<TypeVarObject>(m, "TypeVarObject").type_params()
// = py::make_tuple(py::typing::TypeVarObject<>("T"));

// struct TypeVarObjectBound {};
// py::class_<TypeVarObjectBound>(m, "TypeVarObjectBound").type_params()
// = py::make_tuple(py::typing::TypeVarObject<int>("T"));

struct ParamSpec {};
py::class_<ParamSpec>(m, "ParamSpec").type_params()
= py::make_tuple(py::typing::ParamSpec("P"));

struct TypeVarTuple {};
py::class_<TypeVarTuple>(m, "TypeVarTuple").type_params()
= py::make_tuple(py::typing::TypeVarTuple("T"));

struct NoTypeParams {};
struct TypeParams {};
py::class_<NoTypeParams>(m, "NoTypeParams");
// TODO: Use custom objects
py::class_<TypeParams>(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());
}
43 changes: 43 additions & 0 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading