diff --git a/include/cppast/cpp_entity_kind.hpp b/include/cppast/cpp_entity_kind.hpp index 3a3a3f8..5c9e298 100644 --- a/include/cppast/cpp_entity_kind.hpp +++ b/include/cppast/cpp_entity_kind.hpp @@ -74,6 +74,9 @@ namespace cppast /// \notes A template template parameter is not considered a template for this function. /// \notes Template specializations are also considered templates here. bool is_template(cpp_entity_kind kind) noexcept; + + /// \returns Whether or not a given entity kind is a specialization of a C++ template. + bool is_template_specialization(cpp_entity_kind kind) noexcept; } // namespace cppast #endif // CPPAST_CPP_ENTITY_KIND_HPP_INCLUDED diff --git a/include/cppast/cpp_function_template.hpp b/include/cppast/cpp_function_template.hpp index d5d536d..53fc369 100644 --- a/include/cppast/cpp_function_template.hpp +++ b/include/cppast/cpp_function_template.hpp @@ -44,6 +44,8 @@ namespace cppast class cpp_function_template_specialization final : public cpp_template_specialization { public: + static cpp_entity_kind kind() noexcept; + /// Builder for [cppast::cpp_function_template_specialization](). class builder : public specialization_builder diff --git a/include/cppast/cpp_template.hpp b/include/cppast/cpp_template.hpp index 92cfc5d..64aba15 100644 --- a/include/cppast/cpp_template.hpp +++ b/include/cppast/cpp_template.hpp @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -94,9 +96,18 @@ namespace cppast } /// \effects Adds the next argument. + /// \requires No call to `add_unexposed_arguments()` has happened before. void add_argument(cpp_template_argument arg) { - result_->arguments_.push_back(std::move(arg)); + result_->arguments_ + .value(type_safe::variant_type>{}) + .push_back(std::move(arg)); + } + + /// \effects Adds unexposed arguments as string. + void add_unexposed_arguments(std::string arg) + { + result_->arguments_ = std::move(arg); } /// \returns The finished instantiation. @@ -117,15 +128,33 @@ namespace cppast return templ_; } - /// \returns An iteratable object iterating over the [cppast::cpp_template_argument]()s. - /// \exclude return - const std::vector& arguments() const noexcept + /// \returns Whether or not the arguments are exposed. + bool arguments_exposed() const noexcept { - return arguments_; + return arguments_.has_value( + type_safe::variant_type>{}); + } + + /// \returns An iteratable object iterating over the [cppast::cpp_template_argument]()s. + /// \requires The arguments are exposed, i.e. `arguments_exposed()` returns `true`. + type_safe::array_ref arguments() const noexcept + { + auto& vec = + arguments_.value(type_safe::variant_type>{}); + return type_safe::ref(vec.data(), vec.size()); + } + + /// \returns The unexposed arguments as string. + /// \requires The arguments are not exposed, i.e. `arguments_exposed()` returns `false`. + const std::string& unexposed_arguments() const noexcept + { + return arguments_.value(type_safe::variant_type{}); } private: - cpp_template_instantiation_type(cpp_template_ref ref) : templ_(std::move(ref)) + cpp_template_instantiation_type(cpp_template_ref ref) + : arguments_(type_safe::variant_type>{}), + templ_(std::move(ref)) { } @@ -134,8 +163,8 @@ namespace cppast return cpp_type_kind::template_instantiation; } - std::vector arguments_; - cpp_template_ref templ_; + type_safe::variant, std::string> arguments_; + cpp_template_ref templ_; }; /// Base class for all entities modelling a C++ template specialization. @@ -148,11 +177,31 @@ namespace cppast return cpp_template_ref(templ_, name()); } - /// \returns An iteratable object iterating over the [cppast::cpp_template_argument]()s. - /// \exclude return - const std::vector& arguments() const noexcept + /// \returns Whether or not the arguments are exposed. + bool arguments_exposed() const noexcept { - return arguments_; + return arguments_.has_value( + type_safe::variant_type>{}); + } + + /// \returns An iteratable object iterating over the [cppast::cpp_template_argument]()s. + /// \requires The arguments are exposed, i.e. `arguments_exposed()` returns `true`. + /// \notes For function template specializations it can be empty, + /// meaning that the arguments are not explictly given but deduced from the signature. + type_safe::array_ref arguments() const noexcept + { + auto& vec = + arguments_.value(type_safe::variant_type>{}); + return type_safe::ref(vec.data(), vec.size()); + } + + /// \returns The unexposed arguments as string. + /// \requires The arguments are not exposed, i.e. `arguments_exposed()` returns `false`. + /// \notes For function template specializations it can be empty, + /// meaning that the arguments are not explictly given but deduced from the signature. + const std::string& unexposed_arguments() const noexcept + { + return arguments_.value(type_safe::variant_type{}); } /// \returns Whether or not the specialization is a full specialization. @@ -177,11 +226,22 @@ namespace cppast } /// \effects Adds the next argument for the [cppast::cpp_template_parameter]() of the primary template. + /// \requires No call to `add_unexposed_arguments()` has happened before. void add_argument(cpp_template_argument arg) { - auto specialization = - static_cast(*this->template_entity); - specialization.arguments_.push_back(std::move(arg)); + auto& specialization = + static_cast(*this->template_entity); + specialization.arguments_ + .value(type_safe::variant_type>{}) + .push_back(std::move(arg)); + } + + /// \effects Adds unexposed arguments as string. + void add_unexposed_arguments(std::string arg) + { + auto& specialization = + static_cast(*this->template_entity); + specialization.arguments_ = std::move(arg); } protected: @@ -191,15 +251,18 @@ namespace cppast /// \effects Sets the entity that is being templated and the primary template. cpp_template_specialization(std::unique_ptr entity, const cpp_template_ref& templ) - : cpp_template(std::move(entity)), templ_(templ.id()[0u]) + : cpp_template(std::move(entity)), + arguments_(type_safe::variant_type>{}), + templ_(templ.id()[0u]) { - DEBUG_ASSERT(!templ.is_overloaded() && templ.name() == entity->name(), + DEBUG_ASSERT(!templ.is_overloaded() + && (templ.name().empty() || templ.name() == begin()->name()), detail::precondition_error_handler{}, "invalid name of template ref"); } private: - std::vector arguments_; - cpp_entity_id templ_; + type_safe::variant, std::string> arguments_; + cpp_entity_id templ_; }; } // namespace cppast diff --git a/src/cpp_entity_kind.cpp b/src/cpp_entity_kind.cpp index cfd6ea9..8a4fdea 100644 --- a/src/cpp_entity_kind.cpp +++ b/src/cpp_entity_kind.cpp @@ -270,3 +270,47 @@ bool cppast::is_template(cpp_entity_kind kind) noexcept return false; } + +bool cppast::is_template_specialization(cpp_entity_kind kind) noexcept +{ + switch (kind) + { + case cpp_entity_kind::function_template_specialization_t: + case cpp_entity_kind::class_template_specialization_t: + return true; + + case cpp_entity_kind::file_t: + case cpp_entity_kind::macro_definition_t: + case cpp_entity_kind::include_directive_t: + case cpp_entity_kind::language_linkage_t: + case cpp_entity_kind::namespace_t: + case cpp_entity_kind::namespace_alias_t: + case cpp_entity_kind::using_directive_t: + case cpp_entity_kind::using_declaration_t: + case cpp_entity_kind::type_alias_t: + case cpp_entity_kind::enum_t: + case cpp_entity_kind::enum_value_t: + case cpp_entity_kind::class_t: + case cpp_entity_kind::access_specifier_t: + case cpp_entity_kind::base_class_t: + case cpp_entity_kind::variable_t: + case cpp_entity_kind::member_variable_t: + case cpp_entity_kind::bitfield_t: + case cpp_entity_kind::function_parameter_t: + case cpp_entity_kind::function_t: + case cpp_entity_kind::member_function_t: + case cpp_entity_kind::conversion_op_t: + case cpp_entity_kind::constructor_t: + case cpp_entity_kind::destructor_t: + case cpp_entity_kind::template_type_parameter_t: + case cpp_entity_kind::non_type_template_parameter_t: + case cpp_entity_kind::template_template_parameter_t: + case cpp_entity_kind::alias_template_t: + case cpp_entity_kind::variable_template_t: + case cpp_entity_kind::function_template_t: + case cpp_entity_kind::class_template_t: + case cpp_entity_kind::count: + break; + } + return false; +} diff --git a/src/cpp_function_template.cpp b/src/cpp_function_template.cpp index a4e6101..de121b0 100644 --- a/src/cpp_function_template.cpp +++ b/src/cpp_function_template.cpp @@ -18,7 +18,12 @@ cpp_entity_kind cpp_function_template::do_get_entity_kind() const noexcept return kind(); } -cpp_entity_kind cpp_function_template_specialization::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_function_template_specialization::kind() noexcept { return cpp_entity_kind::function_template_specialization_t; } + +cpp_entity_kind cpp_function_template_specialization::do_get_entity_kind() const noexcept +{ + return kind(); +} diff --git a/src/libclang/expression_parser.cpp b/src/libclang/expression_parser.cpp index 94c3f76..025f0f2 100644 --- a/src/libclang/expression_parser.cpp +++ b/src/libclang/expression_parser.cpp @@ -8,18 +8,6 @@ using namespace cppast; -namespace -{ - std::string get_expression_str(detail::token_stream& stream, detail::token_iterator end) - { - // just concat everything - std::string expr; - while (stream.cur() != end) - expr += stream.get().c_str(); - return expr; - } -} - std::unique_ptr detail::parse_expression(const detail::parse_context& context, const CXCursor& cur) { @@ -30,7 +18,7 @@ std::unique_ptr detail::parse_expression(const detail::parse_con detail::token_stream stream(tokenizer, cur); auto type = parse_type(context, cur, clang_getCursorType(cur)); - auto expr = get_expression_str(stream, stream.end()); + auto expr = to_string(stream, stream.end()); if (kind == CXCursor_CallExpr && (expr.empty() || expr.back() != ')')) { // we have a call expression that doesn't end in a closing parentheses @@ -54,6 +42,6 @@ std::unique_ptr detail::parse_raw_expression(const parse_context { if (stream.done()) return nullptr; - auto expr = get_expression_str(stream, end); + auto expr = to_string(stream, end); return cpp_unexposed_expression::build(std::move(type), std::move(expr)); } diff --git a/src/libclang/function_parser.cpp b/src/libclang/function_parser.cpp index 0a92aed..97a8463 100644 --- a/src/libclang/function_parser.cpp +++ b/src/libclang/function_parser.cpp @@ -55,33 +55,97 @@ namespace }); } + bool is_templated(const CXCursor& cur) + { + return clang_getTemplateCursorKind(cur) != CXCursor_NoDeclFound + || !clang_Cursor_isNull(clang_getSpecializedCursorTemplate(cur)); + } + + // precondition: after the name void skip_parameters(detail::token_stream& stream) { + if (stream.peek() == "<") + // specialization arguments + detail::skip_brackets(stream); detail::skip_brackets(stream); } // just the tokens occurring in the prefix struct prefix_info { - bool is_constexpr = false; - bool is_virtual = false; + std::string scope_name; + bool is_constexpr = false; + bool is_virtual = false; + bool is_explicit = false; }; - prefix_info parse_prefix_info(detail::token_stream& stream, const detail::cxstring& name) + bool prefix_end(detail::token_stream& stream, const char* name) + { + auto cur = stream.cur(); + // name can have multiple tokens if it is an operator + if (!detail::skip_if(stream, name, true)) + return false; + else if (stream.peek() == "::") + { + // was a class name of constructor + stream.set_cur(cur); + return false; + } + else + return true; + } + + prefix_info parse_prefix_info(detail::token_stream& stream, const char* name) { prefix_info result; - // just check for keywords until we've reached the function name - // notes: name can have multiple tokens if it is an operator - while (!detail::skip_if(stream, name.c_str(), true)) + std::string scope; + while (!prefix_end(stream, name)) { if (detail::skip_if(stream, "constexpr")) + { result.is_constexpr = true; + scope.clear(); + } else if (detail::skip_if(stream, "virtual")) + { result.is_virtual = true; + scope.clear(); + } + else if (detail::skip_if(stream, "explicit")) + { + result.is_explicit = true; + scope.clear(); + } + // add identifiers and "::" to current scope name, + // clear if there is any other token in between, or mismatched combination + else if (stream.peek().kind() == CXToken_Identifier) + { + if (!scope.empty() && scope.back() != ':') + scope.clear(); + scope += stream.get().c_str(); + } + else if (stream.peek() == "::") + { + if (!scope.empty() && scope.back() == ':') + scope.clear(); + scope += stream.get().c_str(); + } + else if (stream.peek() == "<") + { + auto iter = detail::find_closing_bracket(stream); + scope += detail::to_string(stream, iter); + detail::skip(stream, ">"); + scope += ">"; + } else + { stream.bump(); + scope.clear(); + } } + if (!scope.empty() && scope.back() == ':') + result.scope_name = std::move(scope); return result; } @@ -275,11 +339,18 @@ namespace } std::unique_ptr parse_cpp_function_impl(const detail::parse_context& context, - const CXCursor& cur) + const CXCursor& cur, bool is_static) { auto name = detail::get_cursor_name(cur); - cpp_function::builder builder(name.c_str(), + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + auto prefix = parse_prefix_info(stream, name.c_str()); + DEBUG_ASSERT(!prefix.is_virtual && !prefix.is_explicit, detail::parse_error_handler{}, cur, + "free function cannot be virtual or explicit"); + + cpp_function::builder builder(prefix.scope_name + name.c_str(), detail::parse_type(context, cur, clang_getCursorResultType(cur))); context.comments.match(builder.get(), cur); @@ -287,14 +358,9 @@ namespace add_parameters(context, builder, cur); if (clang_Cursor_isVariadic(cur)) builder.is_variadic(); - builder.storage_class(detail::get_storage_class(cur)); - - detail::tokenizer tokenizer(context.tu, context.file, cur); - detail::token_stream stream(tokenizer, cur); - - auto prefix = parse_prefix_info(stream, name); - DEBUG_ASSERT(!prefix.is_virtual, detail::parse_error_handler{}, cur, - "free function cannot be virtual"); + builder.storage_class(cpp_storage_class_specifiers( + detail::get_storage_class(cur) + | (is_static ? cpp_storage_class_static : cpp_storage_class_none))); if (prefix.is_constexpr) builder.is_constexpr(); @@ -304,10 +370,10 @@ namespace if (suffix.noexcept_condition) builder.noexcept_condition(std::move(suffix.noexcept_condition)); - if (clang_getTemplateCursorKind(cur) == CXCursor_NoDeclFound) - return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); - else + if (is_templated(cur)) return builder.finish(suffix.body_kind); + else + return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); } } @@ -317,7 +383,7 @@ std::unique_ptr detail::parse_cpp_function(const detail::parse_conte DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_FunctionDecl || clang_getTemplateCursorKind(cur) == CXCursor_FunctionDecl, detail::assert_handler{}); - return parse_cpp_function_impl(context, cur); + return parse_cpp_function_impl(context, cur, false); } std::unique_ptr detail::try_parse_static_cpp_function( @@ -327,7 +393,7 @@ std::unique_ptr detail::try_parse_static_cpp_function( || clang_getTemplateCursorKind(cur) == CXCursor_CXXMethod, detail::assert_handler{}); if (clang_CXXMethod_isStatic(cur)) - return parse_cpp_function_impl(context, cur); + return parse_cpp_function_impl(context, cur, true); return nullptr; } @@ -413,7 +479,10 @@ namespace if (auto virt = calculate_virtual(cur, is_virtual, suffix.virtual_keywords)) builder.virtual_info(virt.value()); - return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); + if (is_templated(cur)) + return builder.finish(suffix.body_kind); + else + return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); } } @@ -425,7 +494,14 @@ std::unique_ptr detail::parse_cpp_member_function(const detail::pars detail::assert_handler{}); auto name = detail::get_cursor_name(cur); - cpp_member_function::builder builder(name.c_str(), + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + auto prefix = parse_prefix_info(stream, name.c_str()); + DEBUG_ASSERT(!prefix.is_explicit, detail::parse_error_handler{}, cur, + "member function cannot be explicit"); + + cpp_member_function::builder builder(prefix.scope_name + name.c_str(), detail::parse_type(context, cur, clang_getCursorResultType(cur))); context.comments.match(builder.get(), cur); @@ -433,10 +509,6 @@ std::unique_ptr detail::parse_cpp_member_function(const detail::pars if (clang_Cursor_isVariadic(cur)) builder.is_variadic(); - detail::tokenizer tokenizer(context.tu, context.file, cur); - detail::token_stream stream(tokenizer, cur); - - auto prefix = parse_prefix_info(stream, name); if (prefix.is_constexpr) builder.is_constexpr(); @@ -451,32 +523,13 @@ std::unique_ptr detail::parse_cpp_conversion_op(const detail::parse_ || clang_getTemplateCursorKind(cur) == CXCursor_ConversionFunction, detail::assert_handler{}); - auto type = clang_getCursorResultType(cur); - cpp_conversion_op::builder builder(std::string("operator ") - + cxstring(clang_getTypeSpelling(type)).c_str(), - detail::parse_type(context, cur, type)); - context.comments.match(builder.get(), cur); - detail::tokenizer tokenizer(context.tu, context.file, cur); detail::token_stream stream(tokenizer, cur); - // look for constexpr, explicit, virtual - // must come before the operator token - auto is_virtual = false; - while (!detail::skip_if(stream, "operator")) - { - if (detail::skip_if(stream, "virtual")) - is_virtual = true; - else if (detail::skip_if(stream, "constexpr")) - builder.is_constexpr(); - else if (detail::skip_if(stream, "explicit")) - builder.is_explicit(); - else - stream.bump(); - } - + auto prefix = parse_prefix_info(stream, "operator"); // heuristic to find arguments tokens // skip forward, skipping inside brackets + auto type_start = stream.cur(); while (true) { if (detail::skip_if(stream, "(")) @@ -495,8 +548,29 @@ std::unique_ptr detail::parse_cpp_conversion_op(const detail::parse_ else stream.bump(); } + // bump arguments back + stream.bump_back(); + stream.bump_back(); + auto type_end = stream.cur(); - return handle_suffix(context, cur, builder, stream, is_virtual); + // read the type + stream.set_cur(type_start); + auto type_spelling = detail::to_string(stream, type_end); + + // parse arguments again + detail::skip(stream, "("); + detail::skip(stream, ")"); + + auto type = clang_getCursorResultType(cur); + cpp_conversion_op::builder builder(prefix.scope_name + "operator " + type_spelling, + detail::parse_type(context, cur, type)); + context.comments.match(builder.get(), cur); + if (prefix.is_explicit) + builder.is_explicit(); + else if (prefix.is_constexpr) + builder.is_constexpr(); + + return handle_suffix(context, cur, builder, stream, prefix.is_virtual); } std::unique_ptr detail::parse_cpp_constructor(const detail::parse_context& context, @@ -507,25 +581,22 @@ std::unique_ptr detail::parse_cpp_constructor(const detail::parse_co detail::assert_handler{}); auto name = detail::get_cursor_name(cur); - cpp_constructor::builder builder(name.c_str()); + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + auto prefix = parse_prefix_info(stream, name.c_str()); + DEBUG_ASSERT(!prefix.is_virtual, detail::parse_error_handler{}, cur, + "constructor cannot be virtual"); + + cpp_constructor::builder builder(prefix.scope_name + name.c_str()); context.comments.match(builder.get(), cur); add_parameters(context, builder, cur); if (clang_Cursor_isVariadic(cur)) builder.is_variadic(); - - detail::tokenizer tokenizer(context.tu, context.file, cur); - detail::token_stream stream(tokenizer, cur); - - // parse prefix - while (!detail::skip_if(stream, name.c_str())) - { - if (detail::skip_if(stream, "constexpr")) - builder.is_constexpr(); - else if (detail::skip_if(stream, "explicit")) - builder.is_explicit(); - else - stream.bump(); - } + if (prefix.is_constexpr) + builder.is_constexpr(); + else if (prefix.is_explicit) + builder.is_explicit(); skip_parameters(stream); @@ -533,7 +604,10 @@ std::unique_ptr detail::parse_cpp_constructor(const detail::parse_co if (suffix.noexcept_condition) builder.noexcept_condition(std::move(suffix.noexcept_condition)); - return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); + if (is_templated(cur)) + return builder.finish(suffix.body_kind); + else + return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); } std::unique_ptr detail::parse_cpp_destructor(const detail::parse_context& context, diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 6fba89b..744c335 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -108,15 +108,22 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co return parse_cpp_member_variable(context, cur); case CXCursor_FunctionDecl: + if (auto tfunc = try_parse_cpp_function_template_specialization(context, cur)) + return tfunc; return parse_cpp_function(context, cur); case CXCursor_CXXMethod: - // check for static function - if (auto func = try_parse_static_cpp_function(context, cur)) + if (auto tfunc = try_parse_cpp_function_template_specialization(context, cur)) + return tfunc; + else if (auto func = try_parse_static_cpp_function(context, cur)) return func; return parse_cpp_member_function(context, cur); case CXCursor_ConversionFunction: + if (auto tfunc = try_parse_cpp_function_template_specialization(context, cur)) + return tfunc; return parse_cpp_conversion_op(context, cur); case CXCursor_Constructor: + if (auto tfunc = try_parse_cpp_function_template_specialization(context, cur)) + return tfunc; return parse_cpp_constructor(context, cur); case CXCursor_Destructor: return parse_cpp_destructor(context, cur); diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index 7785cde..5301ef7 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -91,6 +91,10 @@ namespace cppast std::unique_ptr try_parse_static_cpp_function(const parse_context& context, const CXCursor& cur); + // on all function cursors except on destructor + std::unique_ptr try_parse_cpp_function_template_specialization( + const parse_context& context, const CXCursor& cur); + std::unique_ptr parse_cpp_namespace(const parse_context& context, const CXCursor& cur); std::unique_ptr parse_cpp_namespace_alias(const parse_context& context, diff --git a/src/libclang/template_parser.cpp b/src/libclang/template_parser.cpp index 3e2e05b..fcb0572 100644 --- a/src/libclang/template_parser.cpp +++ b/src/libclang/template_parser.cpp @@ -261,3 +261,69 @@ std::unique_ptr detail::parse_cpp_function_template( parse_parameters(builder, context, cur); return builder.finish(*context.idx, detail::get_entity_id(cur)); } + +namespace +{ + template + void add_arguments(Builder& b, const detail::parse_context& context, const CXCursor& cur) + { + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + while (!stream.done() + && !detail::skip_if(stream, detail::get_cursor_name(cur).c_str(), true)) + stream.bump(); + + if (stream.peek() == "<") + { + auto iter = detail::find_closing_bracket(stream); + stream.bump(); + + auto args = detail::to_string(stream, iter); + b.add_unexposed_arguments(std::move(args)); + } + else + b.add_unexposed_arguments(""); + } +} + +std::unique_ptr detail::try_parse_cpp_function_template_specialization( + const detail::parse_context& context, const CXCursor& cur) +{ + auto templ = clang_getSpecializedCursorTemplate(cur); + if (clang_Cursor_isNull(templ)) + return nullptr; + + std::unique_ptr func; + switch (clang_getCursorKind(cur)) + { + case CXCursor_FunctionDecl: + func = detail::parse_cpp_function(context, cur); + break; + case CXCursor_CXXMethod: + if (auto sfunc = detail::try_parse_static_cpp_function(context, cur)) + func = std::move(sfunc); + else + func = detail::parse_cpp_member_function(context, cur); + break; + case CXCursor_ConversionFunction: + func = detail::parse_cpp_conversion_op(context, cur); + break; + case CXCursor_Constructor: + func = detail::parse_cpp_constructor(context, cur); + break; + + default: + DEBUG_UNREACHABLE(detail::parse_error_handler{}, cur, + "unexpected function kind that is being specialized"); + } + if (!func) + return nullptr; + + cpp_function_template_specialization::builder + builder(std::unique_ptr(static_cast(func.release())), + cpp_template_ref(detail::get_entity_id(templ), "")); + + add_arguments(builder, context, cur); + return builder.finish(*context.idx, detail::get_entity_id(cur)); +} diff --git a/src/libclang/tokenizer.cpp b/src/libclang/tokenizer.cpp index 38e56bf..b6068f0 100644 --- a/src/libclang/tokenizer.cpp +++ b/src/libclang/tokenizer.cpp @@ -332,3 +332,30 @@ bool detail::skip_attribute(detail::token_stream& stream) any = true; return any; } + +namespace +{ + bool is_identifier(char c) + { + return std::isalnum(c) || c == '_'; + } +} + +std::string detail::to_string(token_stream& stream, token_iterator end) +{ + std::string result; + while (stream.cur() != end) + { + auto& token = stream.get(); + if (!result.empty() && is_identifier(result.back()) && is_identifier(token.value()[0u])) + result += ' '; + result += token.c_str(); + } + if (stream.unmunch()) + { + DEBUG_ASSERT(!result.empty() && result.back() == '>', detail::assert_handler{}); + result.pop_back(); + DEBUG_ASSERT(!result.empty() && result.back() == '>', detail::assert_handler{}); + } + return result; +} diff --git a/src/libclang/tokenizer.hpp b/src/libclang/tokenizer.hpp index 71d9841..e841a3d 100644 --- a/src/libclang/tokenizer.hpp +++ b/src/libclang/tokenizer.hpp @@ -188,6 +188,9 @@ namespace cppast // skips an attribute bool skip_attribute(token_stream& stream); + + // converts a token range to a string, adding whitespace where necessary + std::string to_string(token_stream& stream, token_iterator end); } } // namespace cppast::detail diff --git a/src/libclang/type_parser.cpp b/src/libclang/type_parser.cpp index ffdc8af..da4949a 100644 --- a/src/libclang/type_parser.cpp +++ b/src/libclang/type_parser.cpp @@ -385,73 +385,6 @@ namespace type); } - const char* find_closing_bracket(const char* ptr) - { - for (auto paren_count = 0; *ptr; ++ptr) - { - if (*ptr == '(' || *ptr == '[' || *ptr == '{') - ++paren_count; - else if (*ptr == ')' || *ptr == ']' || *ptr == '}') - --paren_count; - else if (paren_count == 0 && *ptr == '>' && !std::isalnum(*ptr) && *ptr != '_') - // heuristic: this could be in fact a closing bracket - return ptr; - } - - return nullptr; - } - - std::string parse_argument(const CXCursor& cur, const char*& ptr, bool is_expression) - { - std::string arg; - - auto paren_count = 0; // non angle brackets - auto bracket_count = 0; // angle brackets - while (*ptr && ((paren_count + bracket_count) != 0 || *ptr != ',')) - { - if (*ptr == '(' || *ptr == '[' || *ptr == '{') - ++paren_count; - else if (*ptr == ')' || *ptr == ']' || *ptr == '}') - --paren_count; - // angle brackets are tricky - // as they can be both brackets and operators - // we only need to take care of those that aren't nested inside other brackets, luckily - else if (paren_count == 0) - { - if (is_expression && *ptr == '<') - { - // treat as brackets and see if it got a closing one - auto closing = find_closing_bracket(ptr); - if (closing) - { - // assume this is a closing bracket - while (ptr != closing) - arg += *ptr++; - } - } - // not an expression - // all top-level angle brackets are actually brackets - else if (*ptr == '<') - ++bracket_count; - else if (*ptr == '>') - --bracket_count; - } - - arg += *ptr++; - } - - DEBUG_ASSERT(*ptr == ',', detail::parse_error_handler{}, cur, - "unable to parse template argument"); - ++ptr; - - while (*ptr == ' ') - ++ptr; - while (!arg.empty() && arg.back() == ' ') - arg.pop_back(); - - return arg; - } - CXCursor get_instantiation_template(const CXCursor& cur, const CXType& type, const std::string& templ_name) { @@ -474,11 +407,10 @@ namespace return param; } - std::unique_ptr try_parse_instantiation_type(const detail::parse_context& context, + std::unique_ptr try_parse_instantiation_type(const detail::parse_context&, const CXCursor& cur, const CXType& type) { return make_leave_type(type, [&](std::string&& spelling) -> std::unique_ptr { - spelling.back() = ','; // to easily terminate the last argument auto ptr = spelling.c_str(); std::string templ_name; for (; *ptr && *ptr != '<'; ++ptr) @@ -493,32 +425,10 @@ namespace cpp_template_ref(detail::get_entity_id(templ), std::move(templ_name))); // parse arguments - // visit children of declaration to get the kind of argument expected - // then parse the string - detail::visit_children(templ, [&](const CXCursor& child) { - if (!*ptr) - return; - - auto kind = clang_getCursorKind(child); - if (kind == CXCursor_TemplateTypeParameter) - { - auto arg = parse_argument(cur, ptr, false); - builder.add_argument( - std::unique_ptr(cpp_unexposed_type::build(std::move(arg)))); - } - else if (kind == CXCursor_NonTypeTemplateParameter) - { - auto arg = parse_argument(cur, ptr, true); - auto arg_type = detail::parse_type(context, child, clang_getCursorType(child)); - builder.add_argument(std::unique_ptr( - cpp_unexposed_expression::build(std::move(arg_type), std::move(arg)))); - } - else if (kind == CXCursor_TemplateTemplateParameter) - { - auto arg = parse_argument(cur, ptr, false); - builder.add_argument(cpp_template_ref(cpp_entity_id(""), std::move(arg))); - } - }); + // i.e. not parse really, just add the string + DEBUG_ASSERT(!spelling.empty() && spelling.back() == '>', detail::assert_handler{}); + spelling.pop_back(); + builder.add_unexposed_arguments(ptr); return builder.finish(); }); @@ -655,32 +565,11 @@ std::unique_ptr detail::parse_type(const detail::parse_context& contex return std::move(result); } -namespace -{ - bool is_identifier(char c) - { - return std::isalnum(c) || c == '_'; - } -} - std::unique_ptr detail::parse_raw_type(const detail::parse_context&, detail::token_stream& stream, detail::token_iterator end) { - std::string result; - while (stream.cur() != end) - { - auto& token = stream.get(); - if (!result.empty() && is_identifier(result.back()) && is_identifier(token.value()[0u])) - result += ' '; - result += token.c_str(); - } - if (stream.unmunch()) - { - DEBUG_ASSERT(!result.empty() && result.back() == '>', detail::assert_handler{}); - result.pop_back(); - DEBUG_ASSERT(!result.empty() && result.back() == '>', detail::assert_handler{}); - } + auto result = detail::to_string(stream, end); return cpp_unexposed_type::build(std::move(result)); } diff --git a/test/cpp_alias_template.cpp b/test/cpp_alias_template.cpp index a128730..be26370 100644 --- a/test/cpp_alias_template.cpp +++ b/test/cpp_alias_template.cpp @@ -69,7 +69,7 @@ using h = g; cpp_template_instantiation_type::builder builder( cpp_template_ref(cpp_entity_id(""), "a")); - builder.add_argument(cpp_unexposed_type::build("void")); + builder.add_unexposed_arguments("void"); REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish())); } else if (alias.name() == "e") @@ -79,8 +79,7 @@ using h = g; cpp_template_instantiation_type::builder builder( cpp_template_ref(cpp_entity_id(""), "b")); - builder.add_argument( - cpp_unexposed_expression::build(cpp_builtin_type::build("int"), "I")); + builder.add_unexposed_arguments("I"); REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *cpp_cv_qualified_type::build(builder.finish(), cpp_cv_const))); } @@ -91,9 +90,7 @@ using h = g; cpp_template_instantiation_type::builder builder( cpp_template_ref(cpp_entity_id(""), "b")); - builder.add_argument(cpp_unexposed_expression::build(cpp_builtin_type::build("int"), - "I < a{(0 , 1)}")); - builder.add_argument(cpp_unexposed_type::build("int")); + builder.add_unexposed_arguments("I < a{(0 , 1)}, int"); REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish())); } else if (alias.name() == "g") @@ -104,7 +101,7 @@ using h = g; cpp_template_instantiation_type::builder builder( cpp_template_ref(cpp_entity_id(""), "Templ")); - builder.add_argument(cpp_unexposed_type::build("T")); + builder.add_unexposed_arguments("T"); REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish())); } else if (alias.name() == "h") @@ -113,8 +110,7 @@ using h = g; cpp_template_instantiation_type::builder builder( cpp_template_ref(cpp_entity_id(""), "g")); - builder.add_argument(cpp_unexposed_type::build("T")); - builder.add_argument(cpp_template_ref(cpp_entity_id("magic-allow-empty"), "a")); + builder.add_unexposed_arguments("T, a"); REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish())); } else diff --git a/test/cpp_function.cpp b/test/cpp_function.cpp index 5df484d..d772013 100644 --- a/test/cpp_function.cpp +++ b/test/cpp_function.cpp @@ -29,8 +29,14 @@ constexpr void i(); static constexpr void j(); // body -void k() = delete; -void l() +namespace ns +{ + void k() = delete; + + void l(); +} + +void ns::l() { // might confuse parser auto b = noexcept(g()); @@ -174,7 +180,7 @@ void l() REQUIRE(func.storage_class() == cpp_storage_class_static); } } - else if (func.name() == "k" || func.name() == "l") + else if (func.name() == "k" || func.name() == "l" || func.name() == "ns::l") { REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); REQUIRE(count_children(func) == 0u); @@ -186,12 +192,14 @@ void l() if (func.name() == "k") check_body(func, cpp_function_deleted); else if (func.name() == "l") + check_body(func, cpp_function_declaration); + else if (func.name() == "ns::l") check_body(func, cpp_function_definition); } else REQUIRE(false); }); - REQUIRE(count == 12u); + REQUIRE(count == 13u); } TEST_CASE("static cpp_function") @@ -206,6 +214,8 @@ struct foo static constexpr char c() = delete; }; + +void foo::a() {} )"; cpp_entity_index idx; @@ -215,12 +225,16 @@ struct foo REQUIRE(count_children(func) == 0u); REQUIRE(func.storage_class() == cpp_storage_class_static); - if (func.name() == "a") + if (func.name() == "a" || func.name() == "foo::a") { REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); REQUIRE(!func.noexcept_condition()); REQUIRE(!func.is_constexpr()); - REQUIRE(func.body_kind() == cpp_function_declaration); + + if (func.name() == "a") + REQUIRE(func.body_kind() == cpp_function_declaration); + else + REQUIRE(func.body_kind() == cpp_function_definition); } else if (func.name() == "b") { @@ -243,7 +257,7 @@ struct foo else REQUIRE(false); }); - REQUIRE(count == 3u); + REQUIRE(count == 4u); } // TODO: friend functions (clang 4.0 required) diff --git a/test/cpp_function_template.cpp b/test/cpp_function_template.cpp index 303f756..7130c3e 100644 --- a/test/cpp_function_template.cpp +++ b/test/cpp_function_template.cpp @@ -14,12 +14,12 @@ TEST_CASE("cpp_function_template") { // only check templated related stuff auto code = R"( -template -T a(const T& t); - template using type = int; +template +T a(const T& t); + struct d { template @@ -34,6 +34,21 @@ struct d template d(const T&); }; + +template <> +int a(const int& t); + +template <> +type<0> d::b<0, int>(int); + +template <> +auto d::c() -> int; + +template <> +d::operator int() const; + +template <> +d::d(const int&); )"; cpp_entity_index idx; @@ -75,8 +90,7 @@ struct d cpp_template_instantiation_type::builder builder( cpp_template_ref(cpp_entity_id(""), "type")); - builder.add_argument( - cpp_unexposed_expression::build(cpp_builtin_type::build("int"), "I")); + builder.add_unexposed_arguments("I"); REQUIRE(equal_types(idx, func.return_type(), *builder.finish())); auto type_parameter = cpp_template_type_parameter_ref(cpp_entity_id(""), "T"); @@ -91,6 +105,7 @@ struct d } else if (tfunc.name() == "c") { + check_parent(tfunc, "d", "d::c"); check_template_parameters(tfunc, {{cpp_entity_kind::template_type_parameter_t, "T"}}); REQUIRE(tfunc.function().kind() == cpp_entity_kind::member_function_t); @@ -103,6 +118,7 @@ struct d } else if (tfunc.name() == "operator T") { + check_parent(tfunc, "d", "d::operator T"); check_template_parameters(tfunc, {{cpp_entity_kind::template_type_parameter_t, "T"}}); REQUIRE(tfunc.function().kind() == cpp_entity_kind::conversion_op_t); @@ -115,6 +131,7 @@ struct d } else if (tfunc.name() == "d") { + check_parent(tfunc, "d", "d::d"); check_template_parameters(tfunc, {{cpp_entity_kind::template_type_parameter_t, "T"}}); REQUIRE(tfunc.function().kind() == cpp_entity_kind::constructor_t); @@ -139,4 +156,105 @@ struct d REQUIRE(false); }); REQUIRE(count == 5u); + + count = test_visit( + *file, [&](const cpp_function_template_specialization& tfunc) { + REQUIRE(tfunc.is_full_specialization()); + REQUIRE(!tfunc.arguments_exposed()); + + auto templ = tfunc.primary_template(); + if (tfunc.name() == "d::operator int") + REQUIRE(equal_ref(idx, templ, cpp_template_ref(cpp_entity_id(""), tfunc.name()), + "d::operator T")); + else + REQUIRE(equal_ref(idx, templ, cpp_template_ref(cpp_entity_id(""), tfunc.name()))); + + if (tfunc.name() == "a") + { + REQUIRE(tfunc.unexposed_arguments() == ""); + + REQUIRE(tfunc.function().kind() == cpp_entity_kind::function_t); + auto& func = static_cast(tfunc.function()); + + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("int"))); + + auto count = 0u; + for (auto& param : func) + { + ++count; + REQUIRE( + equal_types(idx, param.type(), + *cpp_reference_type:: + build(cpp_cv_qualified_type::build(cpp_builtin_type::build( + "int"), + cpp_cv_const), + cpp_ref_lvalue))); + } + REQUIRE(count == 1u); + } + else if (tfunc.name() == "d::b") + { + REQUIRE(tfunc.unexposed_arguments() == "0,int"); + + REQUIRE(tfunc.function().kind() == cpp_entity_kind::function_t); + auto& func = static_cast(tfunc.function()); + + cpp_template_instantiation_type::builder builder( + cpp_template_ref(cpp_entity_id(""), "type")); + builder.add_unexposed_arguments("0"); + REQUIRE(equal_types(idx, func.return_type(), *builder.finish())); + + auto count = 0u; + for (auto& param : func) + { + ++count; + REQUIRE(equal_types(idx, param.type(), *cpp_builtin_type::build("int"))); + } + REQUIRE(count == 1u); + } + else if (tfunc.name() == "d::c") + { + REQUIRE(tfunc.unexposed_arguments() == ""); + + REQUIRE(tfunc.function().kind() == cpp_entity_kind::member_function_t); + auto& func = static_cast(tfunc.function()); + REQUIRE(func.cv_qualifier() == cpp_cv_none); + + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("int"))); + } + else if (tfunc.name() == "d::operator int") + { + REQUIRE(tfunc.unexposed_arguments() == ""); + + REQUIRE(tfunc.function().kind() == cpp_entity_kind::conversion_op_t); + auto& func = static_cast(tfunc.function()); + REQUIRE(func.cv_qualifier() == cpp_cv_const); + + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("int"))); + } + else if (tfunc.name() == "d::d") + { + REQUIRE(tfunc.unexposed_arguments() == ""); + + REQUIRE(tfunc.function().kind() == cpp_entity_kind::constructor_t); + auto& func = static_cast(tfunc.function()); + + auto count = 0u; + for (auto& param : func) + { + ++count; + REQUIRE( + equal_types(idx, param.type(), + *cpp_reference_type:: + build(cpp_cv_qualified_type::build(cpp_builtin_type::build( + "int"), + cpp_cv_const), + cpp_ref_lvalue))); + } + REQUIRE(count == 1u); + } + else + REQUIRE(false); + }); + REQUIRE(count == 5u); } diff --git a/test/cpp_member_function.cpp b/test/cpp_member_function.cpp index dc42d12..ed83d13 100644 --- a/test/cpp_member_function.cpp +++ b/test/cpp_member_function.cpp @@ -160,7 +160,7 @@ struct foo if (!op.is_explicit() && !op.is_constexpr()) { - REQUIRE(op.name() == "operator int &"); + REQUIRE(op.name() == "operator int&"); REQUIRE(equal_types(idx, op.return_type(), *cpp_reference_type::build(cpp_builtin_type::build("int"), cpp_ref_lvalue))); diff --git a/test/cpp_type_alias.cpp b/test/cpp_type_alias.cpp index 71ec930..af70eaa 100644 --- a/test/cpp_type_alias.cpp +++ b/test/cpp_type_alias.cpp @@ -13,23 +13,6 @@ using namespace cppast; -template -bool equal_ref(const cpp_entity_index& idx, const basic_cpp_entity_ref& parsed, - const basic_cpp_entity_ref& synthesized) -{ - if (parsed.name() != synthesized.name()) - return false; - else if (parsed.is_overloaded() != synthesized.is_overloaded()) - return false; - else if (parsed.is_overloaded()) - return false; - - auto entities = parsed.get(idx); - if (entities.size() != 1u) - return synthesized.id()[0u] == cpp_entity_id("magic-allow-empty"); - return entities[0u]->name().empty() || full_name(*entities[0u]) == parsed.name(); -} - bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_type& synthesized) { if (parsed.kind() != synthesized.kind()) @@ -153,6 +136,10 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_ if (!equal_ref(idx, inst_parsed.primary_template(), inst_synthesized.primary_template())) return false; + else if (inst_parsed.arguments_exposed() != inst_synthesized.arguments_exposed()) + return false; + else if (!inst_parsed.arguments_exposed()) + return inst_parsed.unexposed_arguments() == inst_synthesized.unexposed_arguments(); auto iter_a = inst_parsed.arguments().begin(); auto iter_b = inst_synthesized.arguments().begin(); diff --git a/test/test_parser.hpp b/test/test_parser.hpp index 87ef687..967c5be 100644 --- a/test/test_parser.hpp +++ b/test/test_parser.hpp @@ -100,6 +100,26 @@ inline bool equal_expressions(const cppast::cpp_expression& parsed, return false; } +template +bool equal_ref(const cppast::cpp_entity_index& idx, + const cppast::basic_cpp_entity_ref& parsed, + const cppast::basic_cpp_entity_ref& synthesized, + const char* full_name_override = nullptr) +{ + if (parsed.name() != synthesized.name()) + return false; + else if (parsed.is_overloaded() != synthesized.is_overloaded()) + return false; + else if (parsed.is_overloaded()) + return false; + + auto entities = parsed.get(idx); + if (entities.size() != 1u) + return false; + return entities[0u]->name().empty() + || full_name(*entities[0u]) == (full_name_override ? full_name_override : parsed.name()); +} + template void check_template_parameters( const T& templ, std::initializer_list> params)