diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index dd88a4fc2b..376ae50df0 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1328,6 +1328,20 @@ inline void object::cast() && { PYBIND11_NAMESPACE_BEGIN(detail) +// forward declaration (definition in attr.h) +struct function_record; +struct argument_record; + +// forward declaration (definition in pybind11.h) +std::string generate_signature( + const char *text, + function_record *rec, + const std::type_info *const *types, + size_t &type_index, + size_t &arg_index, + const bool is_annotation +); + // Declared in pytypes.h: template ::value, int>> object object_or_cast(T &&o) { @@ -1351,70 +1365,8 @@ str_attr_accessor object_api::attr_with_type_hint(const char *key) const { const char *text = make_caster::name.text; - std::string signature; - // `is_return_value.top()` is true if we are currently inside the return type of the - // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops - // back to the previous state. - std::stack is_return_value({false}); - // The following characters have special meaning in the signature parsing. Literals - // containing these are escaped with `!`. - std::string special_chars("!@%{}-"); - - // Simplified version of similar parser in cpp_function - for (const auto *pc = text; *pc != '\0'; ++pc) { - const auto c = *pc; - if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { - // typing::Literal escapes special characters with ! - signature += *++pc; - } else if (c == '@') { - // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see - // typing::Callable/detail::arg_descr/detail::return_descr) - if (*(pc + 1) == '^') { - is_return_value.emplace(false); - ++pc; - continue; - } - if (*(pc + 1) == '$') { - is_return_value.emplace(true); - ++pc; - continue; - } - if (*(pc + 1) == '!') { - is_return_value.pop(); - ++pc; - continue; - } - // Handle types that differ depending on whether they appear - // in an argument or a return value position (see io_name). - // For named arguments (py::arg()) with noconvert set, return value type is used. - ++pc; - if (!is_return_value.top()) { - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - ++pc; - } - } else { - while (*pc != '\0' && *pc != '@') { - ++pc; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - } - } else { - signature += c; - } - } - - ann[key] = signature; + size_t _ = 0; + ann[key] = generate_signature(text,0,0,_,_, true); return {derived(), key}; } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 0b8a7bcf7c..592c17084a 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -102,6 +102,134 @@ inline std::string replace_newlines_and_squash(const char *text) { return result.substr(str_begin, str_range); } + +inline std::string generate_signature( + const char *text, + detail::function_record *rec, + const std::type_info *const *types, + size_t &type_index, + size_t &arg_index, + const bool is_annotation = false +){ + /* Generate a proper function signature */ + std::string signature; + bool is_starred = false; + // `is_return_value.top()` is true if we are currently inside the return type of the + // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops + // back to the previous state. + std::stack is_return_value({false}); + // The following characters have special meaning in the signature parsing. Literals + // containing these are escaped with `!`. + std::string special_chars("!@%{}-"); + for (const auto *pc = text; *pc != '\0'; ++pc) { + const auto c = *pc; + if (c == '{') { + // Write arg name for everything except *args and **kwargs. + is_starred = *(pc + 1) == '*'; + if (is_starred) { + continue; + } + // Separator for keyword-only arguments, placed before the kw + // arguments start (unless we are already putting an *args) + if (!rec->has_args && arg_index == rec->nargs_pos) { + signature += "*, "; + } + if (arg_index < rec->args.size() && rec->args[arg_index].name) { + signature += rec->args[arg_index].name; + } else if (arg_index == 0 && rec->is_method) { + signature += "self"; + } else { + signature += "arg" + std::to_string(arg_index - (rec->is_method ? 1 : 0)); + } + signature += ": "; + } else if (c == '}') { + // Write default value if available. + if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) { + signature += " = "; + signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr); + } + // Separator for positional-only arguments (placed after the + // argument, rather than before like * + if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) { + signature += ", /"; + } + if (!is_starred) { + arg_index++; + } + } else if (c == '%') { + const std::type_info *t = types[type_index++]; + if (!t) { + pybind11_fail("Internal error while parsing type signature (1)"); + } + if (auto *tinfo = detail::get_type_info(*t)) { + handle th((PyObject *) tinfo->type); + signature += th.attr("__module__").cast() + "." + + th.attr("__qualname__").cast(); + } else if (rec->is_new_style_constructor && arg_index == 0) { + // A new-style `__init__` takes `self` as `value_and_holder`. + // Rewrite it to the proper class type. + signature += rec->scope.attr("__module__").cast() + "." + + rec->scope.attr("__qualname__").cast(); + } else { + signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); + } + } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { + // typing::Literal escapes special characters with ! + signature += *++pc; + } else if (c == '@') { + // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see + // typing::Callable/detail::arg_descr/detail::return_descr) + if (*(pc + 1) == '^') { + is_return_value.emplace(false); + ++pc; + continue; + } + if (*(pc + 1) == '$') { + is_return_value.emplace(true); + ++pc; + continue; + } + if (*(pc + 1) == '!') { + is_return_value.pop(); + ++pc; + continue; + } + // Handle types that differ depending on whether they appear + // in an argument or a return value position (see io_name). + // For named arguments (py::arg()) with noconvert set, return value type is used. + ++pc; + if (!is_return_value.top() + && (is_annotation || !(arg_index < rec->args.size() && !rec->args[arg_index].convert))) { + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + ++pc; + } + } else { + while (*pc != '\0' && *pc != '@') { + ++pc; + } + if (*pc == '@') { + ++pc; + } + while (*pc != '\0' && *pc != '@') { + signature += *pc++; + } + } + } else { + if (c == '-' && *(pc + 1) == '>') { + is_return_value.emplace(true); + } + signature += c; + } + } + return signature; +} + #if defined(_MSC_VER) # define PYBIND11_COMPAT_STRDUP _strdup #else @@ -437,123 +565,8 @@ class cpp_function : public function { } #endif - /* Generate a proper function signature */ - std::string signature; size_t type_index = 0, arg_index = 0; - bool is_starred = false; - // `is_return_value.top()` is true if we are currently inside the return type of the - // signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops - // back to the previous state. - std::stack is_return_value({false}); - // The following characters have special meaning in the signature parsing. Literals - // containing these are escaped with `!`. - std::string special_chars("!@%{}-"); - for (const auto *pc = text; *pc != '\0'; ++pc) { - const auto c = *pc; - if (c == '{') { - // Write arg name for everything except *args and **kwargs. - is_starred = *(pc + 1) == '*'; - if (is_starred) { - continue; - } - // Separator for keyword-only arguments, placed before the kw - // arguments start (unless we are already putting an *args) - if (!rec->has_args && arg_index == rec->nargs_pos) { - signature += "*, "; - } - if (arg_index < rec->args.size() && rec->args[arg_index].name) { - signature += rec->args[arg_index].name; - } else if (arg_index == 0 && rec->is_method) { - signature += "self"; - } else { - signature += "arg" + std::to_string(arg_index - (rec->is_method ? 1 : 0)); - } - signature += ": "; - } else if (c == '}') { - // Write default value if available. - if (!is_starred && arg_index < rec->args.size() && rec->args[arg_index].descr) { - signature += " = "; - signature += detail::replace_newlines_and_squash(rec->args[arg_index].descr); - } - // Separator for positional-only arguments (placed after the - // argument, rather than before like * - if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) { - signature += ", /"; - } - if (!is_starred) { - arg_index++; - } - } else if (c == '%') { - const std::type_info *t = types[type_index++]; - if (!t) { - pybind11_fail("Internal error while parsing type signature (1)"); - } - if (auto *tinfo = detail::get_type_info(*t)) { - handle th((PyObject *) tinfo->type); - signature += th.attr("__module__").cast() + "." - + th.attr("__qualname__").cast(); - } else if (rec->is_new_style_constructor && arg_index == 0) { - // A new-style `__init__` takes `self` as `value_and_holder`. - // Rewrite it to the proper class type. - signature += rec->scope.attr("__module__").cast() + "." - + rec->scope.attr("__qualname__").cast(); - } else { - signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name())); - } - } else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) { - // typing::Literal escapes special characters with ! - signature += *++pc; - } else if (c == '@') { - // `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see - // typing::Callable/detail::arg_descr/detail::return_descr) - if (*(pc + 1) == '^') { - is_return_value.emplace(false); - ++pc; - continue; - } - if (*(pc + 1) == '$') { - is_return_value.emplace(true); - ++pc; - continue; - } - if (*(pc + 1) == '!') { - is_return_value.pop(); - ++pc; - continue; - } - // Handle types that differ depending on whether they appear - // in an argument or a return value position (see io_name). - // For named arguments (py::arg()) with noconvert set, return value type is used. - ++pc; - if (!is_return_value.top() - && !(arg_index < rec->args.size() && !rec->args[arg_index].convert)) { - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - ++pc; - } - } else { - while (*pc != '\0' && *pc != '@') { - ++pc; - } - if (*pc == '@') { - ++pc; - } - while (*pc != '\0' && *pc != '@') { - signature += *pc++; - } - } - } else { - if (c == '-' && *(pc + 1) == '>') { - is_return_value.emplace(true); - } - signature += c; - } - } + std::string signature = detail::generate_signature(text, rec, types, type_index, arg_index); if (arg_index != args - rec->has_args - rec->has_kwargs || types[type_index] != nullptr) { pybind11_fail("Internal error while parsing type signature (2)");