@@ -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)" );
0 commit comments