diff --git a/include/cppast/cpp_attribute.hpp b/include/cppast/cpp_attribute.hpp index d301107..e1c5f3e 100644 --- a/include/cppast/cpp_attribute.hpp +++ b/include/cppast/cpp_attribute.hpp @@ -17,6 +17,8 @@ namespace cppast /// The known C++ attributes. enum class cpp_attribute_kind { + // update get_attribute_kind() in tokenizer, when updating this + alignas_, carries_dependency, deprecated, @@ -52,6 +54,8 @@ namespace cppast case cpp_attribute_kind::unknown: return "unknown"; } + + return ""; } } // namespace detail diff --git a/include/cppast/cpp_class.hpp b/include/cppast/cpp_class.hpp index e050844..a7a1194 100644 --- a/include/cppast/cpp_class.hpp +++ b/include/cppast/cpp_class.hpp @@ -144,17 +144,19 @@ namespace cppast } /// \effects Builds a [cppast::cpp_base_class]() and adds it. - void base_class(std::string name, std::unique_ptr type, - cpp_access_specifier_kind access, bool is_virtual) + cpp_base_class& base_class(std::string name, std::unique_ptr type, + cpp_access_specifier_kind access, bool is_virtual) { - add_base_class( + return add_base_class( cpp_base_class::build(std::move(name), std::move(type), access, is_virtual)); } /// \effects Adds a new base class. - void add_base_class(std::unique_ptr base) noexcept + cpp_base_class& add_base_class(std::unique_ptr base) noexcept { + auto bptr = base.get(); class_->bases_.push_back(*class_, std::move(base)); + return *bptr; } /// \effects Builds a [cppast::cpp_access_specifier]() and adds it. diff --git a/include/cppast/cpp_entity.hpp b/include/cppast/cpp_entity.hpp index 177dc7e..73a1454 100644 --- a/include/cppast/cpp_entity.hpp +++ b/include/cppast/cpp_entity.hpp @@ -136,6 +136,12 @@ namespace cppast attributes_.push_back(std::move(attr)); } + /// \effects Adds multiple arguments for that entity. + void add_attribute(const cpp_attribute_list& list) noexcept + { + attributes_.insert(attributes_.end(), list.begin(), list.end()); + } + /// \returns The specified user data. void* user_data() const noexcept { diff --git a/src/libclang/class_parser.cpp b/src/libclang/class_parser.cpp index 169a958..9c5ec73 100644 --- a/src/libclang/class_parser.cpp +++ b/src/libclang/class_parser.cpp @@ -12,18 +12,28 @@ using namespace cppast; namespace { - cpp_class_kind parse_class_kind(const CXCursor& cur) + cpp_class_kind parse_class_kind(detail::cxtoken_stream& stream) { - auto kind = clang_getTemplateCursorKind(cur); + auto kind = clang_getTemplateCursorKind(stream.cursor()); if (kind == CXCursor_NoDeclFound) - kind = clang_getCursorKind(cur); + kind = clang_getCursorKind(stream.cursor()); + + if (detail::skip_if(stream, "template")) + // skip template parameters + detail::skip_brackets(stream); + + detail::skip_if(stream, "friend"); + switch (kind) { case CXCursor_ClassDecl: + detail::skip(stream, "class"); return cpp_class_kind::class_t; case CXCursor_StructDecl: + detail::skip(stream, "struct"); return cpp_class_kind::struct_t; case CXCursor_UnionDecl: + detail::skip(stream, "union"); return cpp_class_kind::union_t; default: break; @@ -32,11 +42,18 @@ namespace return cpp_class_kind::class_t; } - cpp_class::builder make_class_builder(const CXCursor& cur) + cpp_class::builder make_class_builder(const detail::parse_context& context, const CXCursor& cur) { - auto kind = parse_class_kind(cur); - auto name = detail::get_cursor_name(cur); - return cpp_class::builder(name.c_str(), kind); + detail::cxtokenizer tokenizer(context.tu, context.file, cur); + detail::cxtoken_stream stream(tokenizer, cur); + + auto kind = parse_class_kind(stream); + auto attributes = detail::parse_attributes(stream); + auto name = detail::get_cursor_name(cur); + + auto result = cpp_class::builder(name.c_str(), kind); + result.get().add_attribute(attributes); + return result; } cpp_access_specifier_kind convert_access(const CXCursor& cur) @@ -76,15 +93,16 @@ namespace // [] [virtual] [] // can't use spelling to get the name - detail::skip_attribute(stream); + auto attributes = detail::parse_attributes(stream); if (is_virtual) detail::skip(stream, "virtual"); detail::skip_if(stream, to_string(access)); auto name = detail::to_string(stream, stream.end()).as_string(); - auto type = detail::parse_type(context, class_cur, clang_getCursorType(cur)); - builder.base_class(std::move(name), std::move(type), access, is_virtual); + auto type = detail::parse_type(context, class_cur, clang_getCursorType(cur)); + auto& base = builder.base_class(std::move(name), std::move(type), access, is_virtual); + base.add_attribute(attributes); } } @@ -100,7 +118,7 @@ std::unique_ptr detail::parse_cpp_class(const detail::parse_context& auto is_friend = false; #endif - auto builder = make_class_builder(cur); + auto builder = make_class_builder(context, cur); type_safe::optional semantic_parent; if (!is_friend) { diff --git a/src/libclang/cxtokenizer.cpp b/src/libclang/cxtokenizer.cpp index cc0c6de..69ab211 100644 --- a/src/libclang/cxtokenizer.cpp +++ b/src/libclang/cxtokenizer.cpp @@ -73,11 +73,16 @@ namespace }; bool token_after_is(const CXTranslationUnit& tu, const CXFile& file, const CXCursor& cur, - const CXSourceLocation& loc, const char* token_str) + const CXSourceLocation& loc, const char* token_str, int inc = 1) { - auto loc_after = get_next_location(tu, file, loc); + auto loc_after = get_next_location(tu, file, loc, inc); + if (!clang_Location_isFromMainFile(loc_after)) + return false; - simple_tokenizer tokenizer(tu, clang_getRange(loc, loc_after), cur); + simple_tokenizer tokenizer(tu, + inc > 0 ? clang_getRange(loc, loc_after) : + clang_getRange(loc_after, loc), + cur); detail::cxstring spelling(clang_getTokenSpelling(tu, tokenizer[0u])); return spelling == token_str; } @@ -96,10 +101,27 @@ namespace auto end = clang_getRangeEnd(extent); auto kind = clang_getCursorKind(cur); + if ((cursor_is_function(kind) || cursor_is_function(clang_getTemplateCursorKind(cur)) + || kind == CXCursor_VarDecl || kind == CXCursor_FieldDecl || kind == CXCursor_ParmDecl + || kind == CXCursor_NonTypeTemplateParameter) + && token_after_is(tu, file, cur, begin, "]", -2) + && token_after_is(tu, file, cur, begin, "]", -3)) + { + while (!token_after_is(tu, file, cur, begin, "[", -1) + && !token_after_is(tu, file, cur, begin, "[", -2)) + begin = get_next_location(tu, file, begin, -1); + + begin = get_next_location(tu, file, begin, -3); + DEBUG_ASSERT(token_after_is(tu, file, cur, begin, "[") + && token_after_is(tu, file, cur, get_next_location(tu, file, begin), + "["), + detail::parse_error_handler{}, cur, + "error in pre-function attribute parsing"); + } + if (cursor_is_function(kind) || cursor_is_function(clang_getTemplateCursorKind(cur))) { auto is_definition = false; - // if a function we need to remove the body // it does not need to be parsed detail::visit_children(cur, [&](const CXCursor& child) { @@ -216,6 +238,13 @@ namespace while (!token_after_is(tu, file, cur, end, ";")) end = get_next_location(tu, file, end); } + else if (kind == CXCursor_EnumConstantDecl && !token_after_is(tu, file, cur, end, ",") + && !token_after_is(tu, file, cur, end, ";")) + { + while (!token_after_is(tu, file, cur, end, ",") + && !token_after_is(tu, file, cur, end, ";")) + end = get_next_location(tu, file, end); + } else if (kind == CXCursor_FieldDecl || kind == CXCursor_ParmDecl || kind == CXCursor_NonTypeTemplateParameter || kind == CXCursor_TemplateTemplateParameter @@ -368,14 +397,103 @@ void detail::skip_brackets(detail::cxtoken_stream& stream) namespace { - bool skip_attribute_impl(detail::cxtoken_stream& stream) + type_safe::optional parse_attribute_using(detail::cxtoken_stream& stream) + { + // using identifier : + if (skip_if(stream, "using")) + { + DEBUG_ASSERT(stream.peek().kind() == CXToken_Identifier, detail::parse_error_handler{}, + stream.cursor(), "expected identifier"); + auto scope = stream.get().value().std_str(); + skip(stream, ":"); + + return scope; + } + else + return type_safe::nullopt; + } + + cpp_attribute_kind get_attribute_kind(const std::string& name) + { + if (name == "carries_dependency") + return cpp_attribute_kind::carries_dependency; + else if (name == "deprecated") + return cpp_attribute_kind::deprecated; + else if (name == "fallthrough") + return cpp_attribute_kind::fallthrough; + else if (name == "maybe_unused") + return cpp_attribute_kind::maybe_unused; + else if (name == "nodiscard") + return cpp_attribute_kind::nodiscard; + else if (name == "noreturn") + return cpp_attribute_kind::noreturn; + else + return cpp_attribute_kind::unknown; + } + + cpp_attribute parse_attribute_token(detail::cxtoken_stream& stream, + type_safe::optional scope) + { + // (identifier ::)_opt identifier ( '(' some tokens ')' )_opt ..._opt + + // parse name + DEBUG_ASSERT(stream.peek().kind() == CXToken_Identifier, detail::parse_error_handler{}, + stream.cursor(), "expected identifier"); + auto name = stream.get().value().std_str(); + if (skip_if(stream, "::")) + { + // name was actually a scope, so parse name again + DEBUG_ASSERT(!scope, detail::parse_error_handler{}, stream.cursor(), + "attribute using + scope not allowed"); + scope = std::move(name); + + DEBUG_ASSERT(stream.peek().kind() == CXToken_Identifier, detail::parse_error_handler{}, + stream.cursor(), "expected identifier"); + name = stream.get().value().std_str(); + } + + // parse arguments + type_safe::optional arguments; + if (stream.peek() == "(") + { + auto end = find_closing_bracket(stream); + skip(stream, "("); + + arguments = detail::to_string(stream, end); + + stream.set_cur(end); + skip(stream, ")"); + } + + // parse variadic token + auto is_variadic = skip_if(stream, "..."); + + // get kind + auto kind = get_attribute_kind(name); + if (!scope && kind != cpp_attribute_kind::unknown) + return cpp_attribute(kind, std::move(arguments)); + else + return cpp_attribute(std::move(scope), std::move(name), std::move(arguments), + is_variadic); + } + + bool parse_attribute_impl(cpp_attribute_list& result, detail::cxtoken_stream& stream) { if (skip_if(stream, "[") && stream.peek() == "[") { // C++11 attribute // [[]] // ^ - skip_brackets(stream); + skip(stream, "["); + + auto scope = parse_attribute_using(stream); + while (!skip_if(stream, "]")) + { + auto attribute = parse_attribute_token(stream, scope); + result.push_back(std::move(attribute)); + detail::skip_if(stream, ","); + } + // [[]] // ^ skip(stream, "]"); @@ -402,12 +520,13 @@ namespace } } -bool detail::skip_attribute(detail::cxtoken_stream& stream) +cpp_attribute_list detail::parse_attributes(detail::cxtoken_stream& stream) { - auto any = false; - while (skip_attribute_impl(stream)) - any = true; - return any; + cpp_attribute_list result; + while (parse_attribute_impl(result, stream)) + { + } + return result; } namespace diff --git a/src/libclang/cxtokenizer.hpp b/src/libclang/cxtokenizer.hpp index bc311a2..5e11d15 100644 --- a/src/libclang/cxtokenizer.hpp +++ b/src/libclang/cxtokenizer.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include "raii_wrapper.hpp" @@ -189,8 +190,8 @@ namespace cppast // note: < might not work in the arguments of a template specialization void skip_brackets(cxtoken_stream& stream); - // skips an attribute - bool skip_attribute(cxtoken_stream& stream); + // parses attributes + cpp_attribute_list parse_attributes(cxtoken_stream& stream); // converts a token range to a string cpp_token_string to_string(cxtoken_stream& stream, cxtoken_iterator end); diff --git a/src/libclang/enum_parser.cpp b/src/libclang/enum_parser.cpp index 9e7431f..658d7c5 100644 --- a/src/libclang/enum_parser.cpp +++ b/src/libclang/enum_parser.cpp @@ -25,8 +25,8 @@ namespace // [], // or: [] = , - auto& name = stream.get().value(); - detail::skip_attribute(stream); + auto& name = stream.get().value(); + auto attributes = detail::parse_attributes(stream); std::unique_ptr value; if (detail::skip_if(stream, "=")) @@ -40,8 +40,10 @@ namespace }); } - return cpp_enum_value::build(*context.idx, detail::get_entity_id(cur), name.c_str(), - std::move(value)); + auto result = cpp_enum_value::build(*context.idx, detail::get_entity_id(cur), name.c_str(), + std::move(value)); + result->add_attribute(attributes); + return result; } cpp_enum::builder make_enum_builder(const detail::parse_context& context, const CXCursor& cur, @@ -51,11 +53,11 @@ namespace detail::cxtokenizer tokenizer(context.tu, context.file, cur); detail::cxtoken_stream stream(tokenizer, cur); - // [] enum [class] [] name [: type] { - detail::skip_attribute(stream); + // enum [class/struct] [] name [: type] { detail::skip(stream, "enum"); - auto scoped = detail::skip_if(stream, "class"); - detail::skip_attribute(stream); + auto scoped = detail::skip_if(stream, "class") || detail::skip_if(stream, "struct"); + auto attributes = detail::parse_attributes(stream); + std::string scope; while (!detail::skip_if(stream, name.c_str())) if (!detail::append_scope(stream, scope)) @@ -70,7 +72,9 @@ namespace auto type = detail::parse_type(context, cur, clang_getEnumDeclIntegerType(cur)); auto type_given = detail::skip_if(stream, ":"); - return cpp_enum::builder(name.c_str(), scoped, std::move(type), type_given); + auto result = cpp_enum::builder(name.c_str(), scoped, std::move(type), type_given); + result.get().add_attribute(attributes); + return result; } } diff --git a/src/libclang/function_parser.cpp b/src/libclang/function_parser.cpp index 5d20e9b..1f84563 100644 --- a/src/libclang/function_parser.cpp +++ b/src/libclang/function_parser.cpp @@ -18,14 +18,18 @@ namespace auto name = detail::get_cursor_name(cur); auto type = detail::parse_type(context, cur, clang_getCursorType(cur)); - auto default_value = detail::parse_default_value(context, cur, name.c_str()); + cpp_attribute_list attributes; + auto default_value = detail::parse_default_value(attributes, context, cur, name.c_str()); + std::unique_ptr result; if (name.empty()) - return cpp_function_parameter::build(std::move(type), std::move(default_value)); + result = cpp_function_parameter::build(std::move(type), std::move(default_value)); else - return cpp_function_parameter::build(*context.idx, detail::get_entity_id(cur), - name.c_str(), std::move(type), - std::move(default_value)); + result = cpp_function_parameter::build(*context.idx, detail::get_entity_id(cur), + name.c_str(), std::move(type), + std::move(default_value)); + result->add_attribute(attributes); + return result; } template @@ -212,10 +216,11 @@ namespace // just the tokens occurring in the prefix struct prefix_info { - bool is_constexpr = false; - bool is_virtual = false; - bool is_explicit = false; - bool is_friend = false; + cpp_attribute_list attributes; + bool is_constexpr = false; + bool is_virtual = false; + bool is_explicit = false; + bool is_friend = false; }; bool prefix_end(detail::cxtoken_stream& stream, const char* name, bool is_ctor) @@ -275,7 +280,14 @@ namespace else if (detail::skip_if(stream, "explicit")) result.is_explicit = true; else - stream.bump(); + { + auto attributes = detail::parse_attributes(stream); + if (attributes.empty()) + stream.bump(); + else + result.attributes.insert(result.attributes.end(), attributes.begin(), + attributes.end()); + } } DEBUG_ASSERT(!stream.done(), detail::parse_error_handler{}, stream.cursor(), "unable to find end of function prefix"); @@ -283,12 +295,17 @@ namespace { // function name can be enclosed in parentheses } + auto attributes = detail::parse_attributes(stream); + if (!attributes.empty()) + result.attributes.insert(result.attributes.end(), attributes.begin(), attributes.end()); + return result; } // just the tokens occurring in the suffix struct suffix_info { + cpp_attribute_list attributes; std::unique_ptr noexcept_condition; cpp_function_body_kind body_kind; cpp_cv cv_qualifier = cpp_cv_none; @@ -392,12 +409,13 @@ namespace suffix_info result(stream.cursor()); // syntax: - detail::skip_attribute(stream); + result.attributes = detail::parse_attributes(stream); if (allow_qualifier) { result.cv_qualifier = parse_cv(stream); result.ref_qualifier = parse_ref(stream); } + if (detail::skip_if(stream, "throw")) // just because I can detail::skip_brackets(stream); @@ -414,14 +432,17 @@ namespace // check for trailing return type if (detail::skip_if(stream, "->")) { - //detail::print_tokens(context.tu, context.file, stream.cursor()); // this is rather tricky to skip // so loop over all tokens and see if matching keytokens occur // note that this isn't quite correct // use a heuristic to skip brackets, which should be good enough while (!stream.done()) { - if (stream.peek() == "(" || stream.peek() == "[" || stream.peek() == "<") + auto attributes = detail::parse_attributes(stream); + if (!attributes.empty()) + result.attributes.insert(result.attributes.end(), attributes.begin(), + attributes.end()); + else if (stream.peek() == "(" || stream.peek() == "[" || stream.peek() == "<") detail::skip_brackets(stream); else if (stream.peek() == "{") // begin of definition @@ -472,6 +493,11 @@ namespace result.virtual_keywords.value() |= cpp_virtual_flags::override; } + auto attributes = detail::parse_attributes(stream); + if (!attributes.empty()) + result.attributes.insert(result.attributes.end(), attributes.begin(), + attributes.end()); + if (detail::skip_if(stream, "=")) parse_body(stream, result, allow_virtual); else if (detail::skip_if(stream, "{") || detail::skip_if(stream, ":") @@ -499,6 +525,7 @@ namespace detail::parse_type(context, cur, clang_getCursorResultType(cur))); context.comments.match(builder.get(), cur); + builder.get().add_attribute(prefix.attributes); add_parameters(context, builder, cur); if (clang_Cursor_isVariadic(cur)) @@ -512,6 +539,7 @@ namespace skip_parameters(stream); auto suffix = parse_suffix_info(stream, context, false, false); + builder.get().add_attribute(suffix.attributes); if (suffix.noexcept_condition) builder.noexcept_condition(std::move(suffix.noexcept_condition)); @@ -617,6 +645,7 @@ namespace auto allow_qualifiers = set_qualifier(0, builder, cpp_cv_none, cpp_ref_none); auto suffix = parse_suffix_info(stream, context, allow_qualifiers, true); + builder.get().add_attribute(suffix.attributes); set_qualifier(0, builder, suffix.cv_qualifier, suffix.ref_qualifier); if (suffix.noexcept_condition) builder.noexcept_condition(move(suffix.noexcept_condition)); @@ -651,6 +680,7 @@ std::unique_ptr detail::parse_cpp_member_function(const detail::pars detail::parse_type(context, cur, clang_getCursorResultType(cur))); context.comments.match(builder.get(), cur); + builder.get().add_attribute(prefix.attributes); add_parameters(context, builder, cur); if (clang_Cursor_isVariadic(cur)) builder.is_variadic(); @@ -674,6 +704,7 @@ std::unique_ptr detail::parse_cpp_conversion_op(const detail::parse_ detail::cxtoken_stream stream(tokenizer, cur); auto prefix = parse_prefix_info(stream, "operator", false); + // heuristic to find arguments tokens // skip forward, skipping inside brackets auto type_start = stream.cur(); @@ -715,6 +746,7 @@ std::unique_ptr detail::parse_cpp_conversion_op(const detail::parse_ cpp_conversion_op::builder builder("operator " + type_spelling, detail::parse_type(context, cur, type)); context.comments.match(builder.get(), cur); + builder.get().add_attribute(prefix.attributes); if (prefix.is_explicit) builder.is_explicit(); else if (prefix.is_constexpr) @@ -745,6 +777,8 @@ std::unique_ptr detail::parse_cpp_constructor(const detail::parse_co cpp_constructor::builder builder(name.c_str()); context.comments.match(builder.get(), cur); add_parameters(context, builder, cur); + builder.get().add_attribute(prefix.attributes); + if (clang_Cursor_isVariadic(cur)) builder.is_variadic(); if (prefix.is_constexpr) @@ -755,6 +789,7 @@ std::unique_ptr detail::parse_cpp_constructor(const detail::parse_co skip_parameters(stream); auto suffix = parse_suffix_info(stream, context, false, false); + builder.get().add_attribute(suffix.attributes); if (suffix.noexcept_condition) builder.noexcept_condition(std::move(suffix.noexcept_condition)); @@ -780,6 +815,7 @@ std::unique_ptr detail::parse_cpp_destructor(const detail::parse_con auto name = std::string("~") + stream.get().c_str(); cpp_destructor::builder builder(std::move(name)); context.comments.match(builder.get(), cur); + builder.get().add_attribute(prefix_info.attributes); detail::skip(stream, "("); detail::skip(stream, ")"); diff --git a/src/libclang/namespace_parser.cpp b/src/libclang/namespace_parser.cpp index 1391565..c5d1cd9 100644 --- a/src/libclang/namespace_parser.cpp +++ b/src/libclang/namespace_parser.cpp @@ -25,7 +25,7 @@ namespace is_inline = true; skip(stream, "namespace"); - skip_attribute(stream); + auto attributes = parse_attributes(stream); // { // or when anonymous: { @@ -33,9 +33,15 @@ namespace return cpp_namespace::builder("", is_inline); auto& name = stream.get().value(); - skip_attribute(stream); + + auto other_attributes = parse_attributes(stream); + attributes.insert(attributes.end(), other_attributes.begin(), other_attributes.end()); + skip(stream, "{"); - return cpp_namespace::builder(name.c_str(), is_inline); + + auto result = cpp_namespace::builder(name.c_str(), is_inline); + result.get().add_attribute(attributes); + return result; } } diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index b98d640..2c33b20 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -66,7 +66,8 @@ namespace cppast }; // parse default value of variable, function parameter... - std::unique_ptr parse_default_value(const parse_context& context, + std::unique_ptr parse_default_value(cpp_attribute_list& attributes, + const parse_context& context, const CXCursor& cur, const char* name); std::unique_ptr parse_type(const parse_context& context, const CXCursor& cur, diff --git a/src/libclang/template_parser.cpp b/src/libclang/template_parser.cpp index de4ecef..96117f8 100644 --- a/src/libclang/template_parser.cpp +++ b/src/libclang/template_parser.cpp @@ -85,7 +85,9 @@ namespace auto name = detail::get_cursor_name(cur); auto type = clang_getCursorType(cur); - auto def = detail::parse_default_value(context, cur, name.c_str()); + + cpp_attribute_list attributes; + auto def = detail::parse_default_value(attributes, context, cur, name.c_str()); detail::cxtokenizer tokenizer(context.tu, context.file, cur); detail::cxtoken_stream stream(tokenizer, cur); @@ -108,10 +110,13 @@ namespace break; } - return cpp_non_type_template_parameter::build(*context.idx, detail::get_entity_id(cur), - name.c_str(), - detail::parse_type(context, cur, type), - is_variadic, std::move(def)); + auto result = + cpp_non_type_template_parameter::build(*context.idx, detail::get_entity_id(cur), + name.c_str(), + detail::parse_type(context, cur, type), + is_variadic, std::move(def)); + result->add_attribute(attributes); + return result; } std::unique_ptr parse_template_parameter( @@ -196,6 +201,17 @@ namespace builder.add_parameter(parse_template_parameter(context, child)); }); } + + void handle_comment_attributes(cpp_entity& templ_entity, cpp_entity& non_template) + { + // steal comment + auto comment = type_safe::copy(non_template.comment()); + non_template.set_comment(type_safe::nullopt); + templ_entity.set_comment(std::move(comment)); + + // copy attributes over + templ_entity.add_attribute(non_template.attributes()); + } } std::unique_ptr detail::parse_cpp_alias_template(const detail::parse_context& context, @@ -210,6 +226,7 @@ std::unique_ptr detail::parse_cpp_alias_template(const detail::parse if (!builder) return nullptr; context.comments.match(builder.value().get(), cur); + builder.value().get().add_attribute(builder.value().get().begin()->attributes()); parse_parameters(builder.value(), context, cur); return builder.value().finish(*context.idx, detail::get_entity_id(cur), false); // not a definition @@ -245,15 +262,12 @@ std::unique_ptr detail::parse_cpp_function_template( if (!func) return nullptr; - - // steal comment - auto comment = type_safe::copy(func->comment()); - func->set_comment(type_safe::nullopt); + auto func_ptr = func.get(); cpp_function_template::builder builder( std::unique_ptr(static_cast(func.release()))); - builder.get().set_comment(std::move(comment)); parse_parameters(builder, context, cur); + handle_comment_attributes(builder.get(), *func_ptr); return builder.finish(*context.idx, detail::get_entity_id(cur), builder.get().function().is_definition()); } @@ -315,16 +329,13 @@ std::unique_ptr detail::try_parse_cpp_function_template_specializati } if (!func) return nullptr; - - // steal comment - auto comment = type_safe::copy(func->comment()); - func->set_comment(type_safe::nullopt); + auto func_ptr = func.get(); cpp_function_template_specialization::builder builder(std::unique_ptr(static_cast(func.release())), cpp_template_ref(detail::get_entity_id(templ), "")); - builder.get().set_comment(std::move(comment)); parse_arguments(builder, context, cur); + handle_comment_attributes(builder.get(), *func_ptr); return builder.finish(*context.idx, detail::get_entity_id(cur), builder.get().function().is_definition()); } @@ -337,14 +348,11 @@ std::unique_ptr detail::parse_cpp_class_template(const detail::parse auto c = detail::parse_cpp_class(context, cur, clang_getNullCursor()); if (!c) return nullptr; - - // steal comment - auto comment = type_safe::copy(c->comment()); - c->set_comment(type_safe::nullopt); + auto c_ptr = c.get(); cpp_class_template::builder builder( std::unique_ptr(static_cast(c.release()))); - builder.get().set_comment(std::move(comment)); + handle_comment_attributes(builder.get(), *c_ptr); parse_parameters(builder, context, cur); return builder.finish(*context.idx, detail::get_entity_id(cur), builder.get().class_().is_definition()); @@ -377,15 +385,12 @@ std::unique_ptr detail::parse_cpp_class_template_specialization( auto c = detail::parse_cpp_class(context, cur, clang_getNullCursor()); if (!c) return nullptr; - - // steal comment - auto comment = type_safe::copy(c->comment()); - c->set_comment(type_safe::nullopt); + auto c_ptr = c.get(); cpp_class_template_specialization::builder builder(std::unique_ptr(static_cast(c.release())), cpp_template_ref(detail::get_entity_id(primary), "")); - builder.get().set_comment(std::move(comment)); + handle_comment_attributes(builder.get(), *c_ptr); parse_parameters(builder, context, cur); parse_arguments(builder, context, cur); return builder.finish(*context.idx, detail::get_entity_id(cur), diff --git a/src/libclang/type_parser.cpp b/src/libclang/type_parser.cpp index 5e0dd62..b17e429 100644 --- a/src/libclang/type_parser.cpp +++ b/src/libclang/type_parser.cpp @@ -749,13 +749,25 @@ std::unique_ptr detail::parse_cpp_type_alias(const detail::parse_con auto type = parse_type(context, clang_Cursor_isNull(template_cur) ? cur : template_cur, clang_getTypedefDeclUnderlyingType(cur)); + std::unique_ptr result; if (!clang_Cursor_isNull(template_cur)) - return cpp_type_alias::build(name.c_str(), std::move(type)); + result = cpp_type_alias::build(name.c_str(), std::move(type)); else { - auto result = + result = cpp_type_alias::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type)); context.comments.match(*result, cur); - return std::move(result); } + + // look for attributes + detail::cxtokenizer tokenizer(context.tu, context.file, cur); + detail::cxtoken_stream stream(tokenizer, cur); + if (detail::skip_if(stream, "using")) + { + // syntax: using attributes + detail::skip(stream, name.c_str()); + result->add_attribute(detail::parse_attributes(stream)); + } + + return result; } diff --git a/src/libclang/variable_parser.cpp b/src/libclang/variable_parser.cpp index f49e240..c45fb37 100644 --- a/src/libclang/variable_parser.cpp +++ b/src/libclang/variable_parser.cpp @@ -11,7 +11,8 @@ using namespace cppast; -std::unique_ptr detail::parse_default_value(const detail::parse_context& context, +std::unique_ptr detail::parse_default_value(cpp_attribute_list& attributes, + const detail::parse_context& context, const CXCursor& cur, const char* name) { detail::cxtokenizer tokenizer(context.tu, context.file, cur); @@ -36,7 +37,13 @@ std::unique_ptr detail::parse_default_value(const detail::parse_ break; } else - stream.bump(); + { + auto cur_attributes = detail::parse_attributes(stream); + if (cur_attributes.empty()) + stream.bump(); + else + attributes.insert(attributes.end(), cur_attributes.begin(), cur_attributes.end()); + } } if (has_default) return parse_raw_expression(context, stream, stream.end(), @@ -65,10 +72,12 @@ std::unique_ptr detail::parse_cpp_variable(const detail::parse_conte else if (token.value() == "constexpr") is_constexpr = true; + cpp_attribute_list attributes; + auto default_value = parse_default_value(attributes, context, cur, name.c_str()); + std::unique_ptr result; if (clang_isCursorDefinition(cur)) { - auto default_value = parse_default_value(context, cur, name.c_str()); result = cpp_variable::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type), std::move(default_value), storage_class, is_constexpr); @@ -77,6 +86,7 @@ std::unique_ptr detail::parse_cpp_variable(const detail::parse_conte result = cpp_variable::build_declaration(get_entity_id(cur), name.c_str(), std::move(type), storage_class, is_constexpr); context.comments.match(*result, cur); + result->add_attribute(attributes); return std::move(result); } @@ -89,6 +99,9 @@ std::unique_ptr detail::parse_cpp_member_variable(const detail::pars auto type = parse_type(context, cur, clang_getCursorType(cur)); auto is_mutable = clang_CXXField_isMutable(cur) != 0u; + cpp_attribute_list attributes; + auto default_value = parse_default_value(attributes, context, cur, name.c_str()); + std::unique_ptr result; if (clang_Cursor_isBitField(cur)) { @@ -102,10 +115,10 @@ std::unique_ptr detail::parse_cpp_member_variable(const detail::pars } else { - auto default_value = parse_default_value(context, cur, name.c_str()); result = cpp_member_variable::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type), std::move(default_value), is_mutable); } + result->add_attribute(attributes); context.comments.match(*result, cur); return std::move(result); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 85282a6..281a4b1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ endif() set(tests code_generator.cpp cpp_alias_template.cpp + cpp_attribute.cpp cpp_class.cpp cpp_class_template.cpp cpp_enum.cpp diff --git a/test/cpp_attribute.cpp b/test/cpp_attribute.cpp new file mode 100644 index 0000000..b5a16b2 --- /dev/null +++ b/test/cpp_attribute.cpp @@ -0,0 +1,198 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include "test_parser.hpp" + +using namespace cppast; + +TEST_CASE("cpp_attribute") +{ + auto code = R"( +// multiple attributes +[[attribute1]] void [[attribute2]] a(); +[[attribute1, attribute2]] void b(); + +// variadic attributes - not actually supported by clang +//[[variadic...]] void c(); + +// scoped attributes +[[ns::attribute]] void d(); + +// argument attributes +[[attribute(arg1, arg2, +(){}, 42, "Hello!")]] void e(); + +// all of the above +[[ns::attribute(+, -, 0 4), other_attribute]] void f(); + +// known attributes +[[deprecated]] void g(); +[[maybe_unused]] void h(); +[[nodiscard]] int i(); +[[noreturn]] void j(); +)"; + + auto file = parse({}, "cpp_attribute.cpp", code); + + auto check_attribute = [](const cpp_attribute& attr, const char* name, + type_safe::optional scope, bool variadic, + const char* args = "", + cpp_attribute_kind kind = cpp_attribute_kind::unknown) { + REQUIRE(attr.kind() == kind); + REQUIRE(attr.name() == name); + REQUIRE(attr.scope() == scope); + REQUIRE(attr.is_variadic() == variadic); + + if (attr.arguments()) + REQUIRE(attr.arguments().value().as_string() == args); + else + REQUIRE(*args == '\0'); + }; + + auto count = + test_visit(*file, + [&](const cpp_entity& e) { + auto& attributes = e.attributes(); + REQUIRE(attributes.size() >= 1u); + auto& attr = attributes.front(); + + if (e.name() == "a" || e.name() == "b") + { + REQUIRE(attributes.size() == 2u); + check_attribute(attr, "attribute1", type_safe::nullopt, + false); + check_attribute(attributes[1u], "attribute2", + type_safe::nullopt, false); + } + else if (e.name() == "c") + check_attribute(attr, "variadic", type_safe::nullopt, + true); + else if (e.name() == "d") + check_attribute(attr, "attribute", "ns", false); + else if (e.name() == "e") + check_attribute(attr, "attribute", type_safe::nullopt, + false, R"(arg1,arg2,+(){},42,"Hello!")"); + else if (e.name() == "f") + { + REQUIRE(attributes.size() == 2u); + check_attribute(attr, "attribute", "ns", false, "+,-,0 4"); + check_attribute(attributes[1u], "other_attribute", + type_safe::nullopt, false); + } + else if (e.name() == "g") + check_attribute(attr, "deprecated", type_safe::nullopt, + false, "", cpp_attribute_kind::deprecated); + else if (e.name() == "h") + check_attribute(attr, "maybe_unused", type_safe::nullopt, + false, "", + cpp_attribute_kind::maybe_unused); + else if (e.name() == "i") + check_attribute(attr, "nodiscard", type_safe::nullopt, + false, "", cpp_attribute_kind::nodiscard); + else if (e.name() == "j") + check_attribute(attr, "noreturn", type_safe::nullopt, + false, "", cpp_attribute_kind::noreturn); + }, + false); + REQUIRE(count == 9); +} + +TEST_CASE("cpp_attribute matching") +{ + auto code = R"( +// classes +struct [[a]] a {}; +class [[b]] b {}; + +template +class [[c]] c {}; +template +class [[c]] c {}; +template <> +class [[c]] c {}; + +// enums +enum [[e]] e {}; +enum class [[f]] f +{ + a [[a]], + b [[b]] = 42, +}; + +// functions +[[g]] void g(); +void [[h]] h(); +void i [[i]] (); +void j() [[j]]; +auto k() -> int [[k]]; + +struct [[member_functions]] member_functions +{ + void a() [[a]]; + void b() const && [[b]]; + virtual void c() [[c]] final; + virtual void d() [[d]] = 0; + + [[member_functions]] member_functions(); + member_functions(const member_functions&) [[member_functions]]; +}; + +// variables +[[l]] const int l = 42; +static void* [[m]] m; + +void [[function_params]] function_params +([[a]] int a, int [[b]] b, int c [[c]] = 42); + +struct [[members]] members +{ + int [[a]] a; + int [[b]] b : 2; +}; + +struct [[bases]] bases +: [[a]] public a, + [[members]] members +{}; + +// namespace +namespace [[n]] n {} + +// type aliases +using o [[o]] = int; + +template +using p [[p]] = T; +)"; + + auto file = parse({}, "cpp_attribute__matching.cpp", code); + + auto count = 0u; + auto check = [&](const cppast::cpp_entity& e) { + INFO(e.name()); + REQUIRE(e.attributes().size() == 1u); + REQUIRE(e.attributes().begin()->name() == e.name()); + ++count; + }; + + visit(*file, [&](const cppast::cpp_entity& e, const cppast::visitor_info& info) { + if (info.event != cppast::visitor_info::container_entity_exit + && e.kind() != cppast::cpp_file::kind() && !is_friended(e) && !is_templated(e)) + { + check(e); + if (e.kind() == cppast::cpp_function::kind()) + for (auto& param : static_cast(e).parameters()) + check(param); + else if (e.kind() == cppast::cpp_class::kind()) + for (auto& base : static_cast(e).bases()) + check(base); + } + + return true; + }); + REQUIRE(count == 36u); +}