Skip to content

Commit dfe7e65

Browse files
feat(types): Use typing.SupportsInt and typing.SupportsFloat and fix other typing based bugs. (#5540)
* init Signed-off-by: Michael Carlstrom <[email protected]> * remove import Signed-off-by: Michael Carlstrom <[email protected]> * remove uneeded function Signed-off-by: Michael Carlstrom <[email protected]> * style: pre-commit fixes * Add missing import Signed-off-by: Michael Carlstrom <[email protected]> * style: pre-commit fixes * Fix type behind detailed_message_enabled flag Signed-off-by: Michael Carlstrom <[email protected]> * Fix type behind detailed_message_enabled flag Signed-off-by: Michael Carlstrom <[email protected]> * Add io_name comment Signed-off-by: Michael Carlstrom <[email protected]> * Extra loops to single function Signed-off-by: Michael Carlstrom <[email protected]> * style: pre-commit fixes * Remove unneeded forward declaration Signed-off-by: Michael Carlstrom <[email protected]> * Switch variable name away from macro Signed-off-by: Michael Carlstrom <[email protected]> * Switch variable name away from macro Signed-off-by: Michael Carlstrom <[email protected]> * Switch variable name away from macro Signed-off-by: Michael Carlstrom <[email protected]> * clang-tidy Signed-off-by: Michael Carlstrom <[email protected]> * remove stack import * Fix bug in std::function Callable type Signed-off-by: Michael Carlstrom <[email protected]> * style: pre-commit fixes * remove is_annotation argument Signed-off-by: Michael Carlstrom <[email protected]> * style: pre-commit fixes * Update function name and arg names Signed-off-by: Michael Carlstrom <[email protected]> * style: pre-commit fixes --------- Signed-off-by: Michael Carlstrom <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 16b5abd commit dfe7e65

19 files changed

+324
-198
lines changed

include/pybind11/cast.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value && !is_std_char_t
241241
return PyLong_FromUnsignedLongLong((unsigned long long) src);
242242
}
243243

244-
PYBIND11_TYPE_CASTER(T, const_name<std::is_integral<T>::value>("int", "float"));
244+
PYBIND11_TYPE_CASTER(T,
245+
io_name<std::is_integral<T>::value>(
246+
"typing.SupportsInt", "int", "typing.SupportsFloat", "float"));
245247
};
246248

247249
template <typename T>
@@ -1231,7 +1233,7 @@ struct handle_type_name<buffer> {
12311233
};
12321234
template <>
12331235
struct handle_type_name<int_> {
1234-
static constexpr auto name = const_name("int");
1236+
static constexpr auto name = io_name("typing.SupportsInt", "int");
12351237
};
12361238
template <>
12371239
struct handle_type_name<iterable> {
@@ -1243,7 +1245,7 @@ struct handle_type_name<iterator> {
12431245
};
12441246
template <>
12451247
struct handle_type_name<float_> {
1246-
static constexpr auto name = const_name("float");
1248+
static constexpr auto name = io_name("typing.SupportsFloat", "float");
12471249
};
12481250
template <>
12491251
struct handle_type_name<function> {
@@ -1604,6 +1606,16 @@ inline void object::cast() && {
16041606

16051607
PYBIND11_NAMESPACE_BEGIN(detail)
16061608

1609+
// forward declaration (definition in attr.h)
1610+
struct function_record;
1611+
1612+
// forward declaration (definition in pybind11.h)
1613+
std::string generate_function_signature(const char *type_caster_name_field,
1614+
function_record *func_rec,
1615+
const std::type_info *const *types,
1616+
size_t &type_index,
1617+
size_t &arg_index);
1618+
16071619
// Declared in pytypes.h:
16081620
template <typename T, enable_if_t<!is_pyobject<T>::value, int>>
16091621
object object_or_cast(T &&o) {
@@ -1624,7 +1636,11 @@ str_attr_accessor object_api<D>::attr_with_type_hint(const char *key) const {
16241636
if (ann.contains(key)) {
16251637
throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already.");
16261638
}
1627-
ann[key] = make_caster<T>::name.text;
1639+
1640+
const char *text = make_caster<T>::name.text;
1641+
1642+
size_t unused = 0;
1643+
ann[key] = generate_function_signature(text, nullptr, nullptr, unused, unused);
16281644
return {derived(), key};
16291645
}
16301646

include/pybind11/detail/descr.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ constexpr descr<N1 + N2 + 1> io_name(char const (&text1)[N1], char const (&text2
106106
+ const_name("@");
107107
}
108108

109+
// Ternary description for io_name (like the numeric type_caster)
110+
template <bool B, size_t N1, size_t N2, size_t N3, size_t N4>
111+
constexpr enable_if_t<B, descr<N1 + N2 + 1>>
112+
io_name(char const (&text1)[N1], char const (&text2)[N2], char const (&)[N3], char const (&)[N4]) {
113+
return io_name(text1, text2);
114+
}
115+
116+
template <bool B, size_t N1, size_t N2, size_t N3, size_t N4>
117+
constexpr enable_if_t<!B, descr<N3 + N4 + 1>>
118+
io_name(char const (&)[N1], char const (&)[N2], char const (&text3)[N3], char const (&text4)[N4]) {
119+
return io_name(text3, text4);
120+
}
121+
109122
// If "_" is defined as a macro, py::detail::_ cannot be provided.
110123
// It is therefore best to use py::detail::const_name universally.
111124
// This block is for backward compatibility only.

include/pybind11/functional.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,12 @@ struct type_caster<std::function<Return(Args...)>> {
138138
return cpp_function(std::forward<Func>(f_), policy).release();
139139
}
140140

141-
PYBIND11_TYPE_CASTER(type,
142-
const_name("Callable[[")
143-
+ ::pybind11::detail::concat(make_caster<Args>::name...)
144-
+ const_name("], ") + make_caster<retval_type>::name
145-
+ const_name("]"));
141+
PYBIND11_TYPE_CASTER(
142+
type,
143+
const_name("Callable[[")
144+
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
145+
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
146+
+ const_name("]"));
146147
};
147148

148149
PYBIND11_NAMESPACE_END(detail)

include/pybind11/pybind11.h

Lines changed: 130 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,134 @@ inline std::string replace_newlines_and_squash(const char *text) {
104104
return result.substr(str_begin, str_range);
105105
}
106106

107+
/* Generate a proper function signature */
108+
inline std::string generate_function_signature(const char *type_caster_name_field,
109+
detail::function_record *func_rec,
110+
const std::type_info *const *types,
111+
size_t &type_index,
112+
size_t &arg_index) {
113+
std::string signature;
114+
bool is_starred = false;
115+
bool is_annotation = func_rec == nullptr;
116+
// `is_return_value.top()` is true if we are currently inside the return type of the
117+
// signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops
118+
// back to the previous state.
119+
std::stack<bool> is_return_value({false});
120+
// The following characters have special meaning in the signature parsing. Literals
121+
// containing these are escaped with `!`.
122+
std::string special_chars("!@%{}-");
123+
for (const auto *pc = type_caster_name_field; *pc != '\0'; ++pc) {
124+
const auto c = *pc;
125+
if (c == '{') {
126+
// Write arg name for everything except *args and **kwargs.
127+
is_starred = *(pc + 1) == '*';
128+
if (is_starred) {
129+
continue;
130+
}
131+
// Separator for keyword-only arguments, placed before the kw
132+
// arguments start (unless we are already putting an *args)
133+
if (!func_rec->has_args && arg_index == func_rec->nargs_pos) {
134+
signature += "*, ";
135+
}
136+
if (arg_index < func_rec->args.size() && func_rec->args[arg_index].name) {
137+
signature += func_rec->args[arg_index].name;
138+
} else if (arg_index == 0 && func_rec->is_method) {
139+
signature += "self";
140+
} else {
141+
signature += "arg" + std::to_string(arg_index - (func_rec->is_method ? 1 : 0));
142+
}
143+
signature += ": ";
144+
} else if (c == '}') {
145+
// Write default value if available.
146+
if (!is_starred && arg_index < func_rec->args.size()
147+
&& func_rec->args[arg_index].descr) {
148+
signature += " = ";
149+
signature += detail::replace_newlines_and_squash(func_rec->args[arg_index].descr);
150+
}
151+
// Separator for positional-only arguments (placed after the
152+
// argument, rather than before like *
153+
if (func_rec->nargs_pos_only > 0 && (arg_index + 1) == func_rec->nargs_pos_only) {
154+
signature += ", /";
155+
}
156+
if (!is_starred) {
157+
arg_index++;
158+
}
159+
} else if (c == '%') {
160+
const std::type_info *t = types[type_index++];
161+
if (!t) {
162+
pybind11_fail("Internal error while parsing type signature (1)");
163+
}
164+
if (auto *tinfo = detail::get_type_info(*t)) {
165+
handle th((PyObject *) tinfo->type);
166+
signature += th.attr("__module__").cast<std::string>() + "."
167+
+ th.attr("__qualname__").cast<std::string>();
168+
} else if (func_rec->is_new_style_constructor && arg_index == 0) {
169+
// A new-style `__init__` takes `self` as `value_and_holder`.
170+
// Rewrite it to the proper class type.
171+
signature += func_rec->scope.attr("__module__").cast<std::string>() + "."
172+
+ func_rec->scope.attr("__qualname__").cast<std::string>();
173+
} else {
174+
signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
175+
}
176+
} else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) {
177+
// typing::Literal escapes special characters with !
178+
signature += *++pc;
179+
} else if (c == '@') {
180+
// `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see
181+
// typing::Callable/detail::arg_descr/detail::return_descr)
182+
if (*(pc + 1) == '^') {
183+
is_return_value.emplace(false);
184+
++pc;
185+
continue;
186+
}
187+
if (*(pc + 1) == '$') {
188+
is_return_value.emplace(true);
189+
++pc;
190+
continue;
191+
}
192+
if (*(pc + 1) == '!') {
193+
is_return_value.pop();
194+
++pc;
195+
continue;
196+
}
197+
// Handle types that differ depending on whether they appear
198+
// in an argument or a return value position (see io_name<text1, text2>).
199+
// For named arguments (py::arg()) with noconvert set, return value type is used.
200+
++pc;
201+
if (!is_return_value.top()
202+
&& (is_annotation
203+
|| !(arg_index < func_rec->args.size()
204+
&& !func_rec->args[arg_index].convert))) {
205+
while (*pc != '\0' && *pc != '@') {
206+
signature += *pc++;
207+
}
208+
if (*pc == '@') {
209+
++pc;
210+
}
211+
while (*pc != '\0' && *pc != '@') {
212+
++pc;
213+
}
214+
} else {
215+
while (*pc != '\0' && *pc != '@') {
216+
++pc;
217+
}
218+
if (*pc == '@') {
219+
++pc;
220+
}
221+
while (*pc != '\0' && *pc != '@') {
222+
signature += *pc++;
223+
}
224+
}
225+
} else {
226+
if (c == '-' && *(pc + 1) == '>') {
227+
is_return_value.emplace(true);
228+
}
229+
signature += c;
230+
}
231+
}
232+
return signature;
233+
}
234+
107235
#if defined(_MSC_VER)
108236
# define PYBIND11_COMPAT_STRDUP _strdup
109237
#else
@@ -439,124 +567,9 @@ class cpp_function : public function {
439567
}
440568
#endif
441569

442-
/* Generate a proper function signature */
443-
std::string signature;
444570
size_t type_index = 0, arg_index = 0;
445-
bool is_starred = false;
446-
// `is_return_value.top()` is true if we are currently inside the return type of the
447-
// signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops
448-
// back to the previous state.
449-
std::stack<bool> is_return_value({false});
450-
// The following characters have special meaning in the signature parsing. Literals
451-
// containing these are escaped with `!`.
452-
std::string special_chars("!@%{}-");
453-
for (const auto *pc = text; *pc != '\0'; ++pc) {
454-
const auto c = *pc;
455-
456-
if (c == '{') {
457-
// Write arg name for everything except *args and **kwargs.
458-
is_starred = *(pc + 1) == '*';
459-
if (is_starred) {
460-
continue;
461-
}
462-
// Separator for keyword-only arguments, placed before the kw
463-
// arguments start (unless we are already putting an *args)
464-
if (!rec->has_args && arg_index == rec->nargs_pos) {
465-
signature += "*, ";
466-
}
467-
if (arg_index < rec->args.size() && rec->args[arg_index].name) {
468-
signature += rec->args[arg_index].name;
469-
} else if (arg_index == 0 && rec->is_method) {
470-
signature += "self";
471-
} else {
472-
signature += "arg" + std::to_string(arg_index - (rec->is_method ? 1 : 0));
473-
}
474-
signature += ": ";
475-
} else if (c == '}') {
476-
// Write default value if available.
477-
if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) {
478-
signature += " = ";
479-
signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr);
480-
}
481-
// Separator for positional-only arguments (placed after the
482-
// argument, rather than before like *
483-
if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) {
484-
signature += ", /";
485-
}
486-
if (!is_starred) {
487-
arg_index++;
488-
}
489-
} else if (c == '%') {
490-
const std::type_info *t = types[type_index++];
491-
if (!t) {
492-
pybind11_fail("Internal error while parsing type signature (1)");
493-
}
494-
if (auto *tinfo = detail::get_type_info(*t)) {
495-
handle th((PyObject *) tinfo->type);
496-
signature += th.attr("__module__").cast<std::string>() + "."
497-
+ th.attr("__qualname__").cast<std::string>();
498-
} else if (rec->is_new_style_constructor && arg_index == 0) {
499-
// A new-style `__init__` takes `self` as `value_and_holder`.
500-
// Rewrite it to the proper class type.
501-
signature += rec->scope.attr("__module__").cast<std::string>() + "."
502-
+ rec->scope.attr("__qualname__").cast<std::string>();
503-
} else {
504-
signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
505-
}
506-
} else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) {
507-
// typing::Literal escapes special characters with !
508-
signature += *++pc;
509-
} else if (c == '@') {
510-
// `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see
511-
// typing::Callable/detail::arg_descr/detail::return_descr)
512-
if (*(pc + 1) == '^') {
513-
is_return_value.emplace(false);
514-
++pc;
515-
continue;
516-
}
517-
if (*(pc + 1) == '$') {
518-
is_return_value.emplace(true);
519-
++pc;
520-
continue;
521-
}
522-
if (*(pc + 1) == '!') {
523-
is_return_value.pop();
524-
++pc;
525-
continue;
526-
}
527-
// Handle types that differ depending on whether they appear
528-
// in an argument or a return value position (see io_name<text1, text2>).
529-
// For named arguments (py::arg()) with noconvert set, return value type is used.
530-
++pc;
531-
if (!is_return_value.top()
532-
&& !(arg_index < rec->args.size() && !rec->args[arg_index].convert)) {
533-
while (*pc != '\0' && *pc != '@') {
534-
signature += *pc++;
535-
}
536-
if (*pc == '@') {
537-
++pc;
538-
}
539-
while (*pc != '\0' && *pc != '@') {
540-
++pc;
541-
}
542-
} else {
543-
while (*pc != '\0' && *pc != '@') {
544-
++pc;
545-
}
546-
if (*pc == '@') {
547-
++pc;
548-
}
549-
while (*pc != '\0' && *pc != '@') {
550-
signature += *pc++;
551-
}
552-
}
553-
} else {
554-
if (c == '-' && *(pc + 1) == '>') {
555-
is_return_value.emplace(true);
556-
}
557-
signature += c;
558-
}
559-
}
571+
std::string signature
572+
= detail::generate_function_signature(text, rec, types, type_index, arg_index);
560573

561574
if (arg_index != args - rec->has_args - rec->has_kwargs || types[type_index] != nullptr) {
562575
pybind11_fail("Internal error while parsing type signature (2)");

include/pybind11/typing.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ struct handle_type_name<typing::Optional<T>> {
228228

229229
template <typename T>
230230
struct handle_type_name<typing::Final<T>> {
231-
static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]");
231+
static constexpr auto name = const_name("Final[")
232+
+ ::pybind11::detail::return_descr(make_caster<T>::name)
233+
+ const_name("]");
232234
};
233235

234236
template <typename T>

tests/test_builtin_casters.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ TEST_SUBMODULE(builtin_casters, m) {
236236
m.def("int_passthrough", [](int arg) { return arg; });
237237
m.def("int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert());
238238

239+
// test_float_convert
240+
m.def("float_passthrough", [](float arg) { return arg; });
241+
m.def("float_passthrough_noconvert", [](float arg) { return arg; }, py::arg{}.noconvert());
242+
239243
// test_tuple
240244
m.def(
241245
"pair_passthrough",

0 commit comments

Comments
 (0)