diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 8d91c51d9..fb37f0021 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -83,8 +83,13 @@ class Optional : public object { #if defined(__cpp_nontype_template_parameter_class) template struct StringLiteral { - constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); } - char value[N]; + constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); } + char name[N]; +}; + +template +class Literal : public object { + PYBIND11_OBJECT_DEFAULT(Literal, object, PyObject_Type) }; // Example syntax for creating a TypeVar. @@ -172,9 +177,15 @@ struct handle_type_name> { }; #if defined(__cpp_nontype_template_parameter_class) +template +struct handle_type_name> { + static constexpr auto name = const_name("Literal[") + + pybind11::detail::concat(const_name(Literals.name)...) + + const_name("]"); +}; template struct handle_type_name> { - static constexpr auto name = const_name(StrLit.value); + static constexpr auto name = const_name(StrLit.name); }; #endif diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index ce003e092..952b5af6e 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -110,6 +110,19 @@ void m_defs(py::module_ &m) { } // namespace handle_from_move_only_type_with_operator_PyObject #if defined(__cpp_nontype_template_parameter_class) +namespace literals { +enum Color { RED = 0, BLUE = 1 }; + +typedef py::typing::Literal<"26", + "0x1A", + "\"hello world\"", + "b\"hello world\"", + "u\"hello world\"", + "True", + "Color.RED", + "None"> + LiteralFoo; +} // namespace literals namespace typevar { typedef py::typing::TypeVar<"T"> TypeVarT; typedef py::typing::TypeVar<"V"> TypeVarV; @@ -851,6 +864,7 @@ TEST_SUBMODULE(pytypes, m) { m.def("annotate_iterator_int", [](const py::typing::Iterator &) {}); m.def("annotate_fn", [](const py::typing::Callable, py::str)> &) {}); + m.def("annotate_type", [](const py::typing::Type &t) -> py::type { return t; }); m.def("annotate_union", @@ -881,6 +895,11 @@ TEST_SUBMODULE(pytypes, m) { [](py::typing::Optional &o) -> py::object { return o; }); #if defined(__cpp_nontype_template_parameter_class) + py::enum_(m, "Color") + .value("RED", literals::Color::RED) + .value("BLUE", literals::Color::BLUE); + + m.def("annotate_literal", [](literals::LiteralFoo &o) -> py::object { return o; }); m.def("annotate_generic_containers", [](const py::typing::List &l) -> py::typing::List { return l; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 19e002de9..b265512c8 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -998,6 +998,17 @@ def test_optional_object_annotations(doc): ) +@pytest.mark.skipif( + not m.if_defined__cpp_nontype_template_parameter_class, + reason="C++20 feature not available.", +) +def test_literal(doc): + assert ( + doc(m.annotate_literal) + == 'annotate_literal(arg0: Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]) -> object' + ) + + @pytest.mark.skipif( not m.if_defined__cpp_nontype_template_parameter_class, reason="C++20 feature not available.",