Skip to content

Commit eb67639

Browse files
committed
init
Signed-off-by: Michael Carlstrom <[email protected]>
1 parent 03d8f48 commit eb67639

File tree

4 files changed

+181
-11
lines changed

4 files changed

+181
-11
lines changed

include/pybind11/attr.h

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,62 @@ struct argument_record {
191191
: name(name), descr(descr), value(value), convert(convert), none(none) {}
192192
};
193193

194+
#if defined(PYBIND11_HAS_OPTIONAL)
195+
/// Internal data structure which holds metadata about a typevar argument
196+
struct typevar_record {
197+
const char *name; ///< TypeVar name
198+
std::optional<std::string> bound; ///< Upper Bound of the TypeVar
199+
std::optional<std::string> default_; ///< Default value of the TypeVar
200+
std::vector<std::string> constraints; ///< Constraints (mutually exclusive with bound)
201+
202+
typevar_record(const char *name,
203+
std::optional<std::string> bound,
204+
std::optional<std::string> default_,
205+
std::vector<std::string> constraints)
206+
: name(name), bound(std::move(bound)), default_(std::move(default_)),
207+
constraints(std::move(constraints)) {}
208+
209+
static typevar_record from_name(const char *name) {
210+
return typevar_record(name, std::nullopt, std::nullopt, {});
211+
}
212+
213+
template <typename Bound>
214+
static typevar_record with_bound(const char *name) {
215+
return typevar_record(name, generate_type_signature<Bound>(), std::nullopt, {});
216+
}
217+
218+
template <typename... Constraints>
219+
static typevar_record with_constraints(const char *name) {
220+
return typevar_record(
221+
name, std::nullopt, std::nullopt, {generate_type_signature<Constraints>()...});
222+
}
223+
224+
template <typename Default>
225+
static typevar_record with_default(const char *name) {
226+
return typevar_record(name, std::nullopt, generate_type_signature<Default>(), {});
227+
}
228+
229+
template <typename Default, typename Bound>
230+
static typevar_record with_default_and_bound(const char *name) {
231+
return typevar_record(
232+
name, generate_type_signature<Bound>(), generate_type_signature<Default>(), {});
233+
}
234+
235+
template <typename Default, typename... Constraints>
236+
static typevar_record with_default_and_constraints(const char *name) {
237+
return typevar_record(name,
238+
std::nullopt,
239+
generate_type_signature<Default>(),
240+
{generate_type_signature<Constraints>()...});
241+
}
242+
};
243+
#else
244+
struct typevar_record {};
245+
#endif
246+
194247
/// Internal data structure which holds metadata about a bound function (signature, overloads,
195248
/// etc.)
196-
#define PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID "v1" // PLEASE UPDATE if the struct is changed.
249+
#define PYBIND11_DETAIL_FUNCTION_RECORD_ABI_ID "v2" // PLEASE UPDATE if the struct is changed.
197250
struct function_record {
198251
function_record()
199252
: is_constructor(false), is_new_style_constructor(false), is_stateless(false),
@@ -251,6 +304,9 @@ struct function_record {
251304
/// True if this function is to be inserted at the beginning of the overload resolution chain
252305
bool prepend : 1;
253306

307+
/// List of registered typevar arguments
308+
std::vector<typevar_record> type_vars;
309+
254310
/// Number of arguments (including py::args and/or py::kwargs, if present)
255311
std::uint16_t nargs;
256312

@@ -482,6 +538,12 @@ struct process_attribute<is_new_style_constructor>
482538
}
483539
};
484540

541+
/// Process an attribute which adds a typevariable
542+
template <>
543+
struct process_attribute<typevar_record> : process_attribute_default<typevar_record> {
544+
static void init(const typevar_record t, function_record *r) { r->type_vars.push_back(t); }
545+
};
546+
485547
inline void check_kw_only_arg(const arg &a, function_record *r) {
486548
if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) {
487549
pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or "

include/pybind11/pybind11.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,50 @@ inline std::string replace_newlines_and_squash(const char *text) {
101101
return result.substr(str_begin, str_range);
102102
}
103103

104+
#if defined(PYBIND11_HAS_OPTIONAL)
105+
/* Generate the pep695 typevariable part of function signatures */
106+
inline std::string generate_typevariable_component(std::vector<detail::typevar_record> type_vars) {
107+
std::string signature;
108+
109+
for (auto it = type_vars.begin(); it != type_vars.end(); ++it) {
110+
const auto &type_var = *it;
111+
signature += type_var.name;
112+
113+
if (type_var.bound != std::nullopt) {
114+
signature += ": ";
115+
signature += *type_var.bound;
116+
}
117+
118+
auto constraints = type_var.constraints;
119+
if (!constraints.empty()) {
120+
signature += ": (";
121+
for (size_t j = 0; j < constraints.size(); ++j) {
122+
signature += constraints[j];
123+
if (j != constraints.size() - 1) {
124+
signature += ", ";
125+
}
126+
}
127+
signature += ")";
128+
}
129+
130+
if (type_var.default_ != std::nullopt) {
131+
signature += " = ";
132+
signature += *type_var.default_;
133+
}
134+
135+
if (std::next(it) != type_vars.end()) {
136+
signature += ", ";
137+
}
138+
}
139+
140+
return "[" + signature + "]";
141+
}
142+
#else
143+
inline std::string generate_typevariable_component(std::vector<detail::typevar_record> &) {
144+
return "";
145+
}
146+
#endif
147+
104148
/* Generate a proper function signature */
105149
inline std::string generate_function_signature(const char *type_caster_name_field,
106150
detail::function_record *func_rec,
@@ -116,6 +160,10 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel
116160
// The following characters have special meaning in the signature parsing. Literals
117161
// containing these are escaped with `!`.
118162
std::string special_chars("!@%{}-");
163+
164+
if (!func_rec->type_vars.empty()) {
165+
signature += generate_typevariable_component(func_rec->type_vars);
166+
}
119167
for (const auto *pc = type_caster_name_field; *pc != '\0'; ++pc) {
120168
const auto c = *pc;
121169
if (c == '{') {

tests/test_pytypes.cpp

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -986,14 +986,49 @@ TEST_SUBMODULE(pytypes, m) {
986986
const RealNumber &)> &x) { return x; });
987987
m.def("identity_literal_all_special_chars",
988988
[](const py::typing::Literal<"\"!@!!->{%}\""> &x) { return x; });
989-
m.def("annotate_generic_containers",
990-
[](const py::typing::List<typevar::TypeVarT> &l) -> py::typing::List<typevar::TypeVarV> {
991-
return l;
992-
});
989+
m.def(
990+
"annotate_generic_containers",
991+
[](const py::typing::List<typevar::TypeVarT> &l) -> py::typing::List<typevar::TypeVarV> {
992+
return l;
993+
},
994+
pybind11::detail::typevar_record::from_name("T"),
995+
pybind11::detail::typevar_record::from_name("V"));
996+
997+
m.def(
998+
"annotate_listT_to_T",
999+
[](const py::typing::List<typevar::TypeVarT> &l) -> typevar::TypeVarT { return l[0]; },
1000+
pybind11::detail::typevar_record::from_name("T"));
1001+
m.def(
1002+
"annotate_object_to_T",
1003+
[](const py::object &o) -> typevar::TypeVarT { return o; },
1004+
pybind11::detail::typevar_record::from_name("T"));
1005+
1006+
m.def(
1007+
"typevar_bound_int",
1008+
[](const typevar::TypeVarT &) -> void {},
1009+
pybind11::detail::typevar_record::with_bound<int>("T"));
1010+
1011+
m.def(
1012+
"typevar_constraints_int_str",
1013+
[](const typevar::TypeVarT &) -> void {},
1014+
pybind11::detail::typevar_record::with_constraints<py::int_, py::str>("T"));
1015+
1016+
m.def(
1017+
"typevar_default_int",
1018+
[](const typevar::TypeVarT &) -> void {},
1019+
pybind11::detail::typevar_record::with_default<int>("T"));
1020+
1021+
m.def(
1022+
"typevar_bound_and_default_int",
1023+
[](const typevar::TypeVarT &) -> void {},
1024+
pybind11::detail::typevar_record::with_default_and_bound<int, int>("T"));
1025+
1026+
m.def(
1027+
"typevar_constraints_and_default",
1028+
[](const typevar::TypeVarT &) -> void {},
1029+
pybind11::detail::typevar_record::
1030+
with_default_and_constraints<py::str, py::typing::List<int>, py::str>("T"));
9931031

994-
m.def("annotate_listT_to_T",
995-
[](const py::typing::List<typevar::TypeVarT> &l) -> typevar::TypeVarT { return l[0]; });
996-
m.def("annotate_object_to_T", [](const py::object &o) -> typevar::TypeVarT { return o; });
9971032
m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = true;
9981033
#else
9991034
m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false;

tests/test_pytypes.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,12 +1092,37 @@ def test_literal(doc):
10921092
def test_typevar(doc):
10931093
assert (
10941094
doc(m.annotate_generic_containers)
1095-
== "annotate_generic_containers(arg0: list[T]) -> list[V]"
1095+
== "annotate_generic_containers[T, V](arg0: list[T]) -> list[V]"
10961096
)
10971097

1098-
assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T"
1098+
assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T[T](arg0: list[T]) -> T"
10991099

1100-
assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T"
1100+
assert doc(m.annotate_object_to_T) == "annotate_object_to_T[T](arg0: object) -> T"
1101+
1102+
assert (
1103+
doc(m.typevar_bound_int)
1104+
== "typevar_bound_int[T: typing.SupportsInt](arg0: T) -> None"
1105+
)
1106+
1107+
assert (
1108+
doc(m.typevar_constraints_int_str)
1109+
== "typevar_constraints_int_str[T: (typing.SupportsInt, str)](arg0: T) -> None"
1110+
)
1111+
1112+
assert (
1113+
doc(m.typevar_default_int)
1114+
== "typevar_default_int[T = typing.SupportsInt](arg0: T) -> None"
1115+
)
1116+
1117+
assert (
1118+
doc(m.typevar_bound_and_default_int)
1119+
== "typevar_bound_and_default_int[T: typing.SupportsInt = typing.SupportsInt](arg0: T) -> None"
1120+
)
1121+
1122+
assert (
1123+
doc(m.typevar_constraints_and_default)
1124+
== "typevar_constraints_and_default[T: (list[typing.SupportsInt], str) = str](arg0: T) -> None"
1125+
)
11011126

11021127

11031128
@pytest.mark.skipif(

0 commit comments

Comments
 (0)