Skip to content

Commit 183059f

Browse files
feat(types): add support for typing.Literal type (#5192)
* 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 * start string literal * start int literal * func declare * commit clean * use contextlib.suppres * resolve stash * more literal type * fix annotation name * stash * request changes * lint * Add comments * style: pre-commit fixes * Add support for unions and optionals to be compatible with object * lint * remove comment * Create Literal Type implementation * clean up * Update comment * remove incorrect comment * rerun CI * rerun CI * fix extra line * lint * move if defined block * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent aebcd70 commit 183059f

File tree

3 files changed

+44
-3
lines changed

3 files changed

+44
-3
lines changed

include/pybind11/typing.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,13 @@ class Optional : public object {
8383
#if defined(__cpp_nontype_template_parameter_class)
8484
template <size_t N>
8585
struct StringLiteral {
86-
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); }
87-
char value[N];
86+
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); }
87+
char name[N];
88+
};
89+
90+
template <StringLiteral... StrLits>
91+
class Literal : public object {
92+
PYBIND11_OBJECT_DEFAULT(Literal, object, PyObject_Type)
8893
};
8994

9095
// Example syntax for creating a TypeVar.
@@ -172,9 +177,15 @@ struct handle_type_name<typing::Optional<T>> {
172177
};
173178

174179
#if defined(__cpp_nontype_template_parameter_class)
180+
template <typing::StringLiteral... Literals>
181+
struct handle_type_name<typing::Literal<Literals...>> {
182+
static constexpr auto name = const_name("Literal[")
183+
+ pybind11::detail::concat(const_name(Literals.name)...)
184+
+ const_name("]");
185+
};
175186
template <typing::StringLiteral StrLit>
176187
struct handle_type_name<typing::TypeVar<StrLit>> {
177-
static constexpr auto name = const_name(StrLit.value);
188+
static constexpr auto name = const_name(StrLit.name);
178189
};
179190
#endif
180191

tests/test_pytypes.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ void m_defs(py::module_ &m) {
110110
} // namespace handle_from_move_only_type_with_operator_PyObject
111111

112112
#if defined(__cpp_nontype_template_parameter_class)
113+
namespace literals {
114+
enum Color { RED = 0, BLUE = 1 };
115+
116+
typedef py::typing::Literal<"26",
117+
"0x1A",
118+
"\"hello world\"",
119+
"b\"hello world\"",
120+
"u\"hello world\"",
121+
"True",
122+
"Color.RED",
123+
"None">
124+
LiteralFoo;
125+
} // namespace literals
113126
namespace typevar {
114127
typedef py::typing::TypeVar<"T"> TypeVarT;
115128
typedef py::typing::TypeVar<"V"> TypeVarV;
@@ -851,6 +864,7 @@ TEST_SUBMODULE(pytypes, m) {
851864
m.def("annotate_iterator_int", [](const py::typing::Iterator<int> &) {});
852865
m.def("annotate_fn",
853866
[](const py::typing::Callable<int(py::typing::List<py::str>, py::str)> &) {});
867+
854868
m.def("annotate_type", [](const py::typing::Type<int> &t) -> py::type { return t; });
855869

856870
m.def("annotate_union",
@@ -881,6 +895,11 @@ TEST_SUBMODULE(pytypes, m) {
881895
[](py::typing::Optional<int> &o) -> py::object { return o; });
882896

883897
#if defined(__cpp_nontype_template_parameter_class)
898+
py::enum_<literals::Color>(m, "Color")
899+
.value("RED", literals::Color::RED)
900+
.value("BLUE", literals::Color::BLUE);
901+
902+
m.def("annotate_literal", [](literals::LiteralFoo &o) -> py::object { return o; });
884903
m.def("annotate_generic_containers",
885904
[](const py::typing::List<typevar::TypeVarT> &l) -> py::typing::List<typevar::TypeVarV> {
886905
return l;

tests/test_pytypes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,17 @@ def test_optional_object_annotations(doc):
998998
)
999999

10001000

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_literal(doc):
1006+
assert (
1007+
doc(m.annotate_literal)
1008+
== 'annotate_literal(arg0: Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]) -> object'
1009+
)
1010+
1011+
10011012
@pytest.mark.skipif(
10021013
not m.if_defined__cpp_nontype_template_parameter_class,
10031014
reason="C++20 feature not available.",

0 commit comments

Comments
 (0)