From 85ca22611740155b3c561fc9959e9970ebcefe82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Sun, 12 Mar 2017 16:04:28 +0100 Subject: [PATCH] Parse cpp_member_function --- include/cppast/cpp_member_function.hpp | 90 ++++++-- src/cpp_member_function.cpp | 7 +- src/libclang/function_parser.cpp | 299 ++++++++++++++++++++++--- src/libclang/parse_functions.cpp | 2 +- src/libclang/parse_functions.hpp | 2 + test/cpp_member_function.cpp | 132 +++++++++++ 6 files changed, 487 insertions(+), 45 deletions(-) create mode 100644 test/cpp_member_function.cpp diff --git a/include/cppast/cpp_member_function.hpp b/include/cppast/cpp_member_function.hpp index c2a330e..4b221a2 100644 --- a/include/cppast/cpp_member_function.hpp +++ b/include/cppast/cpp_member_function.hpp @@ -5,30 +5,72 @@ #ifndef CPPAST_CPP_MEMBER_FUNCTION_HPP_INCLUDED #define CPPAST_CPP_MEMBER_FUNCTION_HPP_INCLUDED +#include + #include namespace cppast { /// The `virtual`-ness of a member function. - enum cpp_virtual + /// + /// This is a [ts::flag_set]() `enum`. + /// \notes It does not specify whether a member function is `virtual` or not, + /// only the kind of `virtual`. + /// \notes As surprising as it may be, any of these can be used in combination, + /// i.e. you can have a `final` non-overriding function or an overriding pure `virtual` function. + enum class cpp_virtual_flags { - cpp_virtual_none, //< Not `virtual`. - cpp_virtual_pure, //< Pure `virtual` function. - cpp_virtual_new, //< New `virtual` function. - cpp_virtual_override, //< Overriden `virtual` function (attribute doesn't matter). - cpp_virtual_final, //< `final` `virtual` function. + pure, //< Set if the function is pure. + override, //< Set if the function overrides a base class function. + final, //< Set if the function is marked `final`. }; +} // namespace cppast - /// \returns Whether or not the given flag means the function is `virtual`. - inline bool is_virtual(cpp_virtual virt) noexcept +/// \exclude +namespace type_safe +{ + template <> + struct flag_set_traits : std::true_type { - return virt != cpp_virtual_none; + static constexpr std::size_t size() noexcept + { + return 3u; + } + }; +} // namespace type_safe + +namespace cppast +{ + /// The `virtual` information of a member function. + /// + /// This is an optional of the combination of the [cppast::cpp_virtual_flags](). + /// If the optional has a value, the member function is `virtual`, + /// and the [ts::flag_set]() describes additional information. + using cpp_virtual = type_safe::optional>; + + /// \returns Whether or not a member function is `virtual`. + inline bool is_virtual(const cpp_virtual& virt) noexcept + { + return virt.has_value(); } - /// \returns Whether or not the given flag means the function overrides a `virtual` function. - inline bool is_overriden(cpp_virtual virt) noexcept + /// \returns Whether or not a member function is pure. + inline bool is_pure(const cpp_virtual& virt) noexcept { - return virt == cpp_virtual_override || virt == cpp_virtual_final; + return static_cast(virt.value_or(cpp_virtual_flags::final) & cpp_virtual_flags::pure); + } + + /// \returns Whether or not a member function overrides another one. + inline bool is_overriding(const cpp_virtual& virt) noexcept + { + return static_cast(virt.value_or(cpp_virtual_flags::pure) + & cpp_virtual_flags::override); + } + + /// \returns Whether or not a member function is `final`. + inline bool is_final(const cpp_virtual& virt) noexcept + { + return static_cast(virt.value_or(cpp_virtual_flags::pure) & cpp_virtual_flags::final); } /// Base classes for all regular member function. @@ -43,8 +85,14 @@ namespace cppast return *return_type_; } + /// \returns Whether or not it is `virtual`. + bool is_virtual() const noexcept + { + return virtual_info().has_value(); + } + /// \returns The `virtual`-ness of the member function. - cpp_virtual virtual_info() const noexcept + const cpp_virtual& virtual_info() const noexcept { return virtual_; } @@ -88,7 +136,7 @@ namespace cppast } /// \effects Sets the `virtual`-ness of the function. - void virtual_info(cpp_virtual virt) noexcept + void virtual_info(type_safe::flag_set virt) noexcept { static_cast(*this->function).virtual_ = virt; } @@ -104,7 +152,6 @@ namespace cppast cpp_member_function_base(std::string name, std::unique_ptr return_type) : cpp_function_base(std::move(name)), return_type_(std::move(return_type)), - virtual_(cpp_virtual_none), cv_(cpp_cv_none), ref_(cpp_ref_none), constexpr_(false) @@ -123,6 +170,8 @@ namespace cppast class cpp_member_function final : public cpp_member_function_base { public: + static cpp_entity_kind kind() noexcept; + /// Builder for [cppast::cpp_member_function](). class builder : public basic_member_builder { @@ -134,6 +183,8 @@ namespace cppast using cpp_member_function_base::cpp_member_function_base; cpp_entity_kind do_get_entity_kind() const noexcept override; + + friend basic_member_builder; }; /// A [cppast::cpp_entity]() modelling a C++ conversion operator. @@ -238,6 +289,12 @@ namespace cppast using basic_builder::is_variadic; }; + /// \returns Whether or not it is `virtual`. + bool is_virtual() const noexcept + { + return virtual_info().has_value(); + } + /// \returns The `virtual`-ness of the constructor. cpp_virtual virtual_info() const noexcept { @@ -245,8 +302,7 @@ namespace cppast } private: - cpp_destructor(std::string name) - : cpp_function_base(std::move(name)), virtual_(cpp_virtual_none) + cpp_destructor(std::string name) : cpp_function_base(std::move(name)) { } diff --git a/src/cpp_member_function.cpp b/src/cpp_member_function.cpp index ed8d108..a867440 100644 --- a/src/cpp_member_function.cpp +++ b/src/cpp_member_function.cpp @@ -8,11 +8,16 @@ using namespace cppast; -cpp_entity_kind cpp_member_function::do_get_entity_kind() const noexcept +cpp_entity_kind cpp_member_function::kind() noexcept { return cpp_entity_kind::member_function_t; } +cpp_entity_kind cpp_member_function::do_get_entity_kind() const noexcept +{ + return kind(); +} + cpp_entity_kind cpp_conversion_op::do_get_entity_kind() const noexcept { return cpp_entity_kind::conversion_op_t; diff --git a/src/libclang/function_parser.cpp b/src/libclang/function_parser.cpp index ad1fb45..d67c6af 100644 --- a/src/libclang/function_parser.cpp +++ b/src/libclang/function_parser.cpp @@ -3,6 +3,7 @@ // found in the top-level directory of this distribution. #include +#include #include "libclang_visitor.hpp" #include "parse_functions.hpp" @@ -53,8 +54,79 @@ namespace detail::skip_brackets(stream); } - std::unique_ptr try_parse_noexcept(detail::token_stream& stream, - const detail::parse_context& context) + // just the tokens occurring in the prefix + struct prefix_info + { + bool is_constexpr = false; + bool is_virtual = false; + }; + + prefix_info parse_prefix_info(detail::token_stream& stream, const detail::cxstring& name) + { + prefix_info result; + + // just check for keywords until we've reached the function name + while (!detail::skip_if(stream, name.c_str())) + { + if (detail::skip_if(stream, "constexpr")) + result.is_constexpr = true; + else if (detail::skip_if(stream, "virtual")) + result.is_virtual = true; + else + stream.bump(); + } + + return result; + } + + // just the tokens occurring in the suffix + struct suffix_info + { + std::unique_ptr noexcept_condition; + cpp_function_body_kind body_kind; + cpp_cv cv_qualifier = cpp_cv_none; + cpp_reference ref_qualifier = cpp_ref_none; + cpp_virtual virtual_keywords; + + suffix_info(const CXCursor& cur) + : body_kind(clang_isCursorDefinition(cur) ? cpp_function_definition : + cpp_function_declaration) + { + } + }; + + cpp_cv parse_cv(detail::token_stream& stream) + { + if (detail::skip_if(stream, "const")) + { + if (detail::skip_if(stream, "volatile")) + return cpp_cv_const_volatile; + else + return cpp_cv_const; + } + else if (detail::skip_if(stream, "volatile")) + { + if (detail::skip_if(stream, "const")) + return cpp_cv_const_volatile; + else + return cpp_cv_volatile; + } + else + return cpp_cv_none; + } + + cpp_reference parse_ref(detail::token_stream& stream) + { + if (detail::skip_if(stream, "&")) + return cpp_ref_lvalue; + else if (detail::skip_if(stream, "&&")) + return cpp_ref_rvalue; + else + return cpp_ref_none; + } + + std::unique_ptr parse_noexcept(detail::token_stream& stream, + const detail::parse_context& context) { if (!detail::skip_if(stream, "noexcept")) return nullptr; @@ -72,17 +144,115 @@ namespace return expr; } - cpp_function_body_kind parse_body_kind(detail::token_stream& stream) + cpp_function_body_kind parse_body_kind(detail::token_stream& stream, bool& pure_virtual) { + pure_virtual = false; if (detail::skip_if(stream, "default")) return cpp_function_defaulted; else if (detail::skip_if(stream, "delete")) return cpp_function_deleted; + else if (detail::skip_if(stream, "0")) + { + pure_virtual = true; + return cpp_function_declaration; + } + DEBUG_UNREACHABLE(detail::parse_error_handler{}, stream.cursor(), "unexpected token for function body kind"); return cpp_function_declaration; } + void parse_body(detail::token_stream& stream, suffix_info& result) + { + auto pure_virtual = false; + result.body_kind = parse_body_kind(stream, pure_virtual); + if (pure_virtual) + { + if (result.virtual_keywords) + result.virtual_keywords.value() &= cpp_virtual_flags::pure; + else + result.virtual_keywords = cpp_virtual_flags::pure; + } + } + + // precondition: we've skipped the function parameters + suffix_info parse_suffix_info(detail::token_stream& stream, + const detail::parse_context& context) + { + suffix_info result(stream.cursor()); + + // syntax: + detail::skip_attribute(stream); + 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); + result.noexcept_condition = parse_noexcept(stream, context); + + // check if we have leftovers of the return type + // i.e.: `void (*foo(int a, int b) const)(int)`; + // ^^^^^^- attributes + // ^^^^^^- leftovers + // if we have a closing parenthesis, skip brackets + if (detail::skip_if(stream, ")")) + detail::skip_brackets(stream); + + // check for trailing return type + if (detail::skip_if(stream, "->")) + { + // 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() == "<" + || stream.peek() == "{") + detail::skip_brackets(stream); + else if (detail::skip_if(stream, "override")) + { + if (result.virtual_keywords) + result.virtual_keywords.value() |= cpp_virtual_flags::override; + else + result.virtual_keywords = cpp_virtual_flags::override; + } + else if (detail::skip_if(stream, "final")) + { + if (result.virtual_keywords) + result.virtual_keywords.value() |= cpp_virtual_flags::final; + else + result.virtual_keywords = cpp_virtual_flags::final; + } + else if (detail::skip_if(stream, "=")) + parse_body(stream, result); + else + stream.bump(); + } + } + else + { + // syntax: + if (detail::skip_if(stream, "override")) + { + result.virtual_keywords = cpp_virtual_flags::override; + if (detail::skip_if(stream, "final")) + result.virtual_keywords.value() |= cpp_virtual_flags::final; + } + else if (detail::skip_if(stream, "final")) + { + result.virtual_keywords = cpp_virtual_flags::final; + if (detail::skip_if(stream, "override")) + result.virtual_keywords.value() |= cpp_virtual_flags::override; + } + + if (detail::skip_if(stream, "=")) + parse_body(stream, result); + } + + return result; + } + std::unique_ptr parse_cpp_function_impl(const detail::parse_context& context, const CXCursor& cur) { @@ -98,32 +268,22 @@ namespace 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 - stream.bump(); - } - // skip parameters + auto prefix = parse_prefix_info(stream, name); + DEBUG_ASSERT(!prefix.is_virtual, detail::parse_error_handler{}, cur, + "unexpected tokens in function prefix"); + if (prefix.is_constexpr) + builder.is_constexpr(); + skip_parameters(stream); - auto body = - clang_isCursorDefinition(cur) ? cpp_function_definition : cpp_function_declaration; - // parse suffix - // tokenizer only tokenizes signature, so !stream.done() is sufficient - while (!stream.done()) - { - if (auto expr = try_parse_noexcept(stream, context)) - builder.noexcept_condition(std::move(expr)); - else if (skip_if(stream, "=")) - body = parse_body_kind(stream); - else - stream.bump(); - } + auto suffix = parse_suffix_info(stream, context); + DEBUG_ASSERT(suffix.cv_qualifier == cpp_cv_none && suffix.ref_qualifier == cpp_ref_none + && !suffix.virtual_keywords, + detail::parse_error_handler{}, cur, "unexpected tokens in function suffix"); + if (suffix.noexcept_condition) + builder.noexcept_condition(std::move(suffix.noexcept_condition)); - return builder.finish(*context.idx, detail::get_entity_id(cur), body); + return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); } } @@ -142,3 +302,90 @@ std::unique_ptr detail::try_parse_static_cpp_function( return parse_cpp_function_impl(context, cur); return nullptr; } + +namespace +{ + bool overrides_function(const CXCursor& cur) + { + CXCursor* overrides = nullptr; + auto num = 0u; + clang_getOverriddenCursors(cur, &overrides, &num); + clang_disposeOverriddenCursors(overrides); + return num != 0u; + } + + cpp_virtual calculate_virtual(const CXCursor& cur, bool virtual_keyword, + const cpp_virtual& virtual_suffix) + { + if (!clang_CXXMethod_isVirtual(cur)) + { + // not a virtual function, ensure it wasn't parsed that way + DEBUG_ASSERT(!virtual_keyword && !virtual_suffix.has_value(), + detail::parse_error_handler{}, cur, "virtualness not parsed properly"); + return {}; + } + else if (clang_CXXMethod_isPureVirtual(cur)) + { + // pure virtual function - all information in the suffix + DEBUG_ASSERT(virtual_suffix.has_value() + && virtual_suffix.value() & cpp_virtual_flags::pure, + detail::parse_error_handler{}, cur, "pure virtual not detected"); + return virtual_suffix; + } + else + { + // non-pure virtual function + DEBUG_ASSERT(!virtual_suffix.has_value() + || !(virtual_suffix.value() & cpp_virtual_flags::pure), + detail::parse_error_handler{}, cur, + "pure virtual function detected, even though it isn't"); + // calculate whether it overrides + auto overrides = !virtual_keyword + || (virtual_suffix.has_value() + && virtual_suffix.value() & cpp_virtual_flags::override) + || overrides_function(cur); + + // result are all the flags in the suffix + auto result = virtual_suffix; + if (!result) + // make sure it isn't empty + result.emplace(); + if (overrides) + // make sure it contains the override flag + result.value() |= cpp_virtual_flags::override; + return result; + } + } +} + +std::unique_ptr detail::parse_cpp_member_function(const detail::parse_context& context, + const CXCursor& cur) +{ + DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_CXXMethod, detail::assert_handler{}); + auto name = detail::get_cursor_name(cur); + + cpp_member_function::builder builder(name.c_str(), + detail::parse_type(context, + clang_getCursorResultType(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); + + auto prefix = parse_prefix_info(stream, name); + if (prefix.is_constexpr) + builder.is_constexpr(); + + skip_parameters(stream); + + auto suffix = parse_suffix_info(stream, context); + builder.cv_ref_qualifier(suffix.cv_qualifier, suffix.ref_qualifier); + if (suffix.noexcept_condition) + builder.noexcept_condition(std::move(suffix.noexcept_condition)); + if (auto virt = calculate_virtual(cur, prefix.is_virtual, suffix.virtual_keywords)) + builder.virtual_info(virt.value()); + + return builder.finish(*context.idx, detail::get_entity_id(cur), suffix.body_kind); +} diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 812bf22..a5f5309 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -91,7 +91,7 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co // check for static function if (auto func = try_parse_static_cpp_function(context, cur)) return func; - break; + return parse_cpp_member_function(context, cur); default: break; diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index f911ea4..00307e7 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -87,6 +87,8 @@ namespace cppast std::unique_ptr parse_cpp_function(const parse_context& context, const CXCursor& cur); + std::unique_ptr parse_cpp_member_function(const parse_context& context, + const CXCursor& cur); std::unique_ptr parse_entity(const parse_context& context, const CXCursor& cur); } diff --git a/test/cpp_member_function.cpp b/test/cpp_member_function.cpp new file mode 100644 index 0000000..afe2e1e --- /dev/null +++ b/test/cpp_member_function.cpp @@ -0,0 +1,132 @@ +// 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 "test_parser.hpp" + +using namespace cppast; + +TEST_CASE("cpp_member_function") +{ + auto code = R"( +// no need to test parameters/return types +struct foo +{ + void a(); + void b() noexcept; + + void c() const; + auto d() const volatile -> void; + void e() &; + void f() const volatile &&; + + virtual void g(); + virtual void h() = 0; + + void i() {} + void j() = delete; +}; + +struct bar : foo +{ + void g(); + virtual auto h() -> void override final; +}; +)"; + + cpp_entity_index idx; + auto file = parse(idx, "cpp_member_function.cpp", code); + auto count = test_visit(*file, [&](const cpp_member_function& func) { + REQUIRE(count_children(func) == 0u); + REQUIRE(!func.is_variadic()); + REQUIRE(!func.is_constexpr()); + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); + if (func.name() != "b") + REQUIRE(!func.noexcept_condition()); + if (func.name() != "g" && func.name() != "h") + REQUIRE(!func.virtual_info()); + if (func.name() != "i" && func.name() != "j") + REQUIRE(func.body_kind() == cpp_function_declaration); + + if (func.name() == "a") + { + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + } + else if (func.name() == "b") + { + REQUIRE(func.noexcept_condition()); + REQUIRE( + equal_expressions(func.noexcept_condition().value(), + *cpp_literal_expression::build(cpp_builtin_type::build("bool"), + "true"))); + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + } + else if (func.name() == "c") + { + REQUIRE(func.cv_qualifier() == cpp_cv_const); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + } + else if (func.name() == "d") + { + REQUIRE(func.cv_qualifier() == cpp_cv_const_volatile); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + } + else if (func.name() == "e") + { + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_lvalue); + } + else if (func.name() == "f") + { + REQUIRE(func.cv_qualifier() == cpp_cv_const_volatile); + REQUIRE(func.ref_qualifier() == cpp_ref_rvalue); + } + else if (func.name() == "g") + { + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + REQUIRE(func.virtual_info()); + if (func.parent().value().name() == "foo") + REQUIRE(func.virtual_info().value() == type_safe::flag_set{}); + else if (func.parent().value().name() == "bar") + { + INFO(bool(func.virtual_info().value() & cpp_virtual_flags::override)); + REQUIRE(func.virtual_info().value() == cpp_virtual_flags::override); + } + else + REQUIRE(false); + } + else if (func.name() == "h") + { + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + REQUIRE(func.virtual_info()); + if (func.parent().value().name() == "foo") + REQUIRE(func.virtual_info().value() == cpp_virtual_flags::pure); + else if (func.parent().value().name() == "bar") + REQUIRE(func.virtual_info().value() + == (cpp_virtual_flags::override | cpp_virtual_flags::final)); + else + REQUIRE(false); + } + else if (func.name() == "i") + { + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + REQUIRE(func.body_kind() == cpp_function_definition); + } + else if (func.name() == "j") + { + REQUIRE(func.cv_qualifier() == cpp_cv_none); + REQUIRE(func.ref_qualifier() == cpp_ref_none); + REQUIRE(func.body_kind() == cpp_function_deleted); + } + else + REQUIRE(false); + }); + REQUIRE(count == 12u); +}