diff --git a/include/cppast/cpp_function.hpp b/include/cppast/cpp_function.hpp index 30446f8..b035205 100644 --- a/include/cppast/cpp_function.hpp +++ b/include/cppast/cpp_function.hpp @@ -17,6 +17,8 @@ namespace cppast class cpp_function_parameter final : public cpp_entity, public cpp_variable_base { public: + static cpp_entity_kind kind() noexcept; + /// \returns A newly created and registered function parameter. static std::unique_ptr build( const cpp_entity_index& idx, cpp_entity_id id, std::string name, @@ -121,6 +123,7 @@ namespace cppast std::unique_ptr finish(const cpp_entity_index& idx, cpp_entity_id id, cpp_function_body_kind body_kind) { + function->body_ = body_kind; if (cppast::is_definition(body_kind)) idx.register_entity(std::move(id), type_safe::cref(*function)); else @@ -161,6 +164,8 @@ namespace cppast class cpp_function final : public cpp_function_base { public: + static cpp_entity_kind kind() noexcept; + /// Builds a [cppast::cpp_function](). class builder : public cpp_function_base::basic_builder { diff --git a/include/cppast/cpp_storage_class_specifiers.hpp b/include/cppast/cpp_storage_class_specifiers.hpp index 6535d5f..6b9039e 100644 --- a/include/cppast/cpp_storage_class_specifiers.hpp +++ b/include/cppast/cpp_storage_class_specifiers.hpp @@ -12,7 +12,7 @@ namespace cppast /// See http://en.cppreference.com/w/cpp/language/storage_duration, for example. /// \notes These are just all the possible *keywords* used in a variable declaration, /// not necessarily their *semantic* meaning. - enum cpp_storage_class_specifiers + enum cpp_storage_class_specifiers : int { cpp_storage_class_none = 0, //< no storage class specifier given. diff --git a/src/cpp_function.cpp b/src/cpp_function.cpp index 54e4f5c..ab65060 100644 --- a/src/cpp_function.cpp +++ b/src/cpp_function.cpp @@ -8,6 +8,11 @@ using namespace cppast; +cpp_entity_kind cpp_function_parameter::kind() noexcept +{ + return cpp_entity_kind::function_parameter_t; +} + std::unique_ptr cpp_function_parameter::build( const cpp_entity_index& idx, cpp_entity_id id, std::string name, std::unique_ptr type, std::unique_ptr def) @@ -20,10 +25,15 @@ std::unique_ptr cpp_function_parameter::build( cpp_entity_kind cpp_function_parameter::do_get_entity_kind() const noexcept { - return cpp_entity_kind::function_parameter_t; + return kind(); +} + +cpp_entity_kind cpp_function::kind() noexcept +{ + return cpp_entity_kind::function_t; } cpp_entity_kind cpp_function::do_get_entity_kind() const noexcept { - return cpp_entity_kind::function_t; + return kind(); } diff --git a/src/libclang/debug_helper.cpp b/src/libclang/debug_helper.cpp index bbfe688..eba7bdd 100644 --- a/src/libclang/debug_helper.cpp +++ b/src/libclang/debug_helper.cpp @@ -38,11 +38,11 @@ void detail::print_cursor_info(const CXCursor& cur) noexcept cxstring(clang_getCursorKindSpelling(cur.kind)).c_str()); } -void detail::print_tokens(const detail::cxtranslation_unit& tu, const CXFile& file, +void detail::print_tokens(const CXTranslationUnit& tu, const CXFile& file, const CXCursor& cur) noexcept { std::lock_guard lock(mtx); - detail::tokenizer tokenizer(tu.get(), file, cur); + detail::tokenizer tokenizer(tu, file, cur); for (auto& token : tokenizer) std::printf("%s ", token.c_str()); std::puts("\n"); diff --git a/src/libclang/debug_helper.hpp b/src/libclang/debug_helper.hpp index 4fcc116..0160513 100644 --- a/src/libclang/debug_helper.hpp +++ b/src/libclang/debug_helper.hpp @@ -19,7 +19,7 @@ namespace cppast void print_cursor_info(const CXCursor& cur) noexcept; - void print_tokens(const cxtranslation_unit& tu, const CXFile& file, + void print_tokens(const CXTranslationUnit& tu, const CXFile& file, const CXCursor& cur) noexcept; } } // namespace cppast::detail diff --git a/src/libclang/expression_parser.cpp b/src/libclang/expression_parser.cpp index 569a31e..975eb8f 100644 --- a/src/libclang/expression_parser.cpp +++ b/src/libclang/expression_parser.cpp @@ -10,11 +10,11 @@ using namespace cppast; namespace { - std::string get_expression_str(detail::token_stream& stream) + std::string get_expression_str(detail::token_stream& stream, detail::token_iterator end) { // just concat everything std::string expr; - while (!stream.done()) + while (stream.cur() != end) expr += stream.get().c_str(); return expr; } @@ -30,7 +30,7 @@ std::unique_ptr detail::parse_expression(const detail::parse_con detail::token_stream stream(tokenizer, cur); auto type = parse_type(context, clang_getCursorType(cur)); - auto expr = get_expression_str(stream); + auto expr = get_expression_str(stream, stream.end()); if (kind == CXCursor_CallExpr && (expr.empty() || expr.back() != ')')) { // we have a call expression that doesn't end in a closing parentheses @@ -47,12 +47,13 @@ std::unique_ptr detail::parse_expression(const detail::parse_con return cpp_unexposed_expression::build(std::move(type), std::move(expr)); } -std::unique_ptr detail::parse_raw_expression(const parse_context& context, - token_stream& stream, - const CXType& type) +std::unique_ptr detail::parse_raw_expression(const parse_context&, + token_stream& stream, + token_iterator end, + std::unique_ptr type) { if (stream.done()) return nullptr; - auto expr = get_expression_str(stream); - return cpp_unexposed_expression::build(parse_type(context, type), std::move(expr)); + auto expr = get_expression_str(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 new file mode 100644 index 0000000..203c6b4 --- /dev/null +++ b/src/libclang/function_parser.cpp @@ -0,0 +1,128 @@ +// 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 "libclang_visitor.hpp" +#include "parse_functions.hpp" + +using namespace cppast; + +namespace +{ + std::unique_ptr parse_parameter(const detail::parse_context& context, + const CXCursor& cur) + { + auto name = detail::get_cursor_name(cur); + auto type = detail::parse_type(context, clang_getCursorType(cur)); + + std::unique_ptr default_value; + detail::visit_children(cur, [&](const CXCursor& child) { + DEBUG_ASSERT(clang_isExpression(child.kind) && !default_value, + detail::parse_error_handler{}, child, + "unexpected child cursor of function parameter"); + default_value = detail::parse_expression(context, child); + }); + + return cpp_function_parameter::build(*context.idx, detail::get_entity_id(cur), name.c_str(), + std::move(type), std::move(default_value)); + } + + template + void add_parameters(const detail::parse_context& context, Builder& builder, const CXCursor& cur) + { + detail::visit_children(cur, [&](const CXCursor& child) { + if (clang_getCursorKind(child) != CXCursor_ParmDecl) + return; + + try + { + auto parameter = parse_parameter(context, child); + builder.add_parameter(std::move(parameter)); + } + catch (detail::parse_error& ex) + { + context.logger->log("libclang parser", ex.get_diagnostic()); + } + }); + } + + void skip_parameters(detail::token_stream& stream) + { + detail::skip_brackets(stream); + } + + std::unique_ptr try_parse_noexcept(detail::token_stream& stream, + const detail::parse_context& context) + { + if (!detail::skip_if(stream, "noexcept")) + return nullptr; + + auto type = cpp_builtin_type::build("bool"); + if (stream.peek().value() != "(") + return cpp_literal_expression::build(std::move(type), "true"); + + auto closing = detail::find_closing_bracket(stream); + + detail::skip(stream, "("); + auto expr = detail::parse_raw_expression(context, stream, closing, std::move(type)); + detail::skip(stream, ")"); + + return expr; + } + + cpp_function_body_kind parse_body_kind(detail::token_stream& stream) + { + if (detail::skip_if(stream, "default")) + return cpp_function_defaulted; + else if (detail::skip_if(stream, "delete")) + return cpp_function_deleted; + DEBUG_UNREACHABLE(detail::parse_error_handler{}, stream.cursor(), + "unexpected token for function body kind"); + return cpp_function_declaration; + } +} + +std::unique_ptr detail::parse_cpp_function(const detail::parse_context& context, + const CXCursor& cur) +{ + DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_FunctionDecl, detail::assert_handler{}); + auto name = detail::get_cursor_name(cur); + + cpp_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(); + builder.storage_class(detail::get_storage_class(cur)); + + 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 + 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(); + } + + return builder.finish(*context.idx, detail::get_entity_id(cur), body); +} diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index b9fe2b7..9d348dd 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -156,7 +156,7 @@ namespace } } - return tu; + return detail::cxtranslation_unit(tu); } unsigned get_line_no(const CXCursor& cursor) diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 15f5e40..fac03ff 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -4,6 +4,8 @@ #include "parse_functions.hpp" +#include + using namespace cppast; cpp_entity_id detail::get_entity_id(const CXCursor& cur) @@ -18,6 +20,35 @@ detail::cxstring detail::get_cursor_name(const CXCursor& cur) return cxstring(clang_getCursorSpelling(cur)); } +cpp_storage_class_specifiers detail::get_storage_class(const CXCursor& cur) +{ + switch (clang_Cursor_getStorageClass(cur)) + { + case CX_SC_Invalid: + break; + + case CX_SC_None: + return cpp_storage_class_none; + + case CX_SC_Auto: + case CX_SC_Register: + return cpp_storage_class_auto; + + case CX_SC_Extern: + return cpp_storage_class_extern; + case CX_SC_Static: + return cpp_storage_class_static; + + case CX_SC_PrivateExtern: + case CX_SC_OpenCLWorkGroupLocal: + // non-exposed storage classes + return cpp_storage_class_auto; + } + + DEBUG_UNREACHABLE(detail::parse_error_handler{}, cur, "unexpected storage class"); + return cpp_storage_class_auto; +} + std::unique_ptr detail::parse_entity(const detail::parse_context& context, const CXCursor& cur) try { @@ -54,6 +85,9 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co case CXCursor_FieldDecl: return parse_cpp_member_variable(context, cur); + case CXCursor_FunctionDecl: + return parse_cpp_function(context, cur); + default: break; } diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index b1f78fb..1c2fd8c 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -16,6 +16,7 @@ namespace cppast { class cpp_expression; class cpp_type; + enum cpp_storage_class_specifiers : int; namespace detail { @@ -26,6 +27,9 @@ namespace cppast // as then you won't get it "as-is" cxstring get_cursor_name(const CXCursor& cur); + // note: does not handle thread_local + cpp_storage_class_specifiers get_storage_class(const CXCursor& cur); + struct parse_context { CXTranslationUnit tu; @@ -39,12 +43,13 @@ namespace cppast std::unique_ptr parse_expression(const parse_context& context, const CXCursor& cur); // parse the expression starting at the current token in the stream - // and ends at the end of the stream + // and ends at the given iterator // this is required for situations where there is no expression cursor exposed, // like member initializers - std::unique_ptr parse_raw_expression(const parse_context& context, - token_stream& stream, - const CXType& type); + std::unique_ptr parse_raw_expression(const parse_context& context, + token_stream& stream, + token_iterator end, + std::unique_ptr type); // parse_entity() dispatches on the cursor type // it calls one of the other parse functions defined elsewhere @@ -76,6 +81,9 @@ namespace cppast std::unique_ptr parse_cpp_member_variable(const parse_context& context, const CXCursor& cur); + std::unique_ptr parse_cpp_function(const parse_context& context, + const CXCursor& cur); + std::unique_ptr parse_entity(const parse_context& context, const CXCursor& cur); } } // namespace cppast::detail diff --git a/src/libclang/raii_wrapper.hpp b/src/libclang/raii_wrapper.hpp index 03e2304..17f3278 100644 --- a/src/libclang/raii_wrapper.hpp +++ b/src/libclang/raii_wrapper.hpp @@ -28,7 +28,7 @@ namespace cppast { } - raii_wrapper(T obj) noexcept : obj_(obj) + explicit raii_wrapper(T obj) noexcept : obj_(obj) { DEBUG_ASSERT(obj_, detail::assert_handler{}); } diff --git a/src/libclang/tokenizer.hpp b/src/libclang/tokenizer.hpp index 68c21a7..ed4e9a2 100644 --- a/src/libclang/tokenizer.hpp +++ b/src/libclang/tokenizer.hpp @@ -124,11 +124,21 @@ namespace cppast return cursor_; } + token_iterator begin() const noexcept + { + return begin_; + } + token_iterator cur() const noexcept { return cur_; } + token_iterator end() const noexcept + { + return end_; + } + void set_cur(token_iterator iter) noexcept { cur_ = iter; diff --git a/src/libclang/type_parser.cpp b/src/libclang/type_parser.cpp index 386d59b..ca047ad 100644 --- a/src/libclang/type_parser.cpp +++ b/src/libclang/type_parser.cpp @@ -183,6 +183,9 @@ namespace return cpp_ref_none; } + std::unique_ptr parse_type_impl(const detail::parse_context& context, + const CXType& type); + std::unique_ptr parse_array_size(const CXType& type) { auto size = clang_getArraySize(type); @@ -214,8 +217,27 @@ namespace size_expr.rend())); } - std::unique_ptr parse_type_impl(const detail::parse_context& context, - const CXType& type); + std::unique_ptr try_parse_array_type(const detail::parse_context& context, + const CXType& type) + { + auto canonical = clang_getCanonicalType(type); + auto value_type = clang_getArrayElementType(type); + if (value_type.kind == CXType_Invalid) + { + // value_type is invalid, however type can still be an array + // as there seems to be a libclang bug + // only if the canonical type is not an array, + // is it truly not an array + value_type = clang_getArrayElementType(canonical); + if (value_type.kind == CXType_Invalid) + return nullptr; + // we have an array, even though type isn't one directly + // only downside of this workaround: we've stripped away typedefs + } + + auto size = parse_array_size(canonical); // type may not work, see above + return cpp_array_type::build(parse_type_impl(context, value_type), std::move(size)); + } template std::unique_ptr add_parameters(Builder& builder, const detail::parse_context& context, @@ -317,6 +339,9 @@ namespace // guess what: after you've called clang_getPointeeType() on a function pointer // you'll get an unexposed type return ftype; + else if (auto atype = try_parse_array_type(context, type)) + // same deal here + return atype; return cpp_unexposed_type::build(get_type_spelling(type).c_str()); case CXType_Void: @@ -379,11 +404,7 @@ namespace case CXType_VariableArray: case CXType_DependentSizedArray: case CXType_ConstantArray: - { - auto size = parse_array_size(type); - auto value_type = parse_type_impl(context, clang_getArrayElementType(type)); - return cpp_array_type::build(std::move(value_type), std::move(size)); - } + return try_parse_array_type(context, type); case CXType_FunctionNoProto: case CXType_FunctionProto: diff --git a/src/libclang/variable_parser.cpp b/src/libclang/variable_parser.cpp index 81e2738..12dcce9 100644 --- a/src/libclang/variable_parser.cpp +++ b/src/libclang/variable_parser.cpp @@ -28,35 +28,6 @@ namespace }); return expression; } - - cpp_storage_class_specifiers parse_storage_class(const CXCursor& cur) - { - switch (clang_Cursor_getStorageClass(cur)) - { - case CX_SC_Invalid: - break; - - case CX_SC_None: - return cpp_storage_class_none; - - case CX_SC_Auto: - case CX_SC_Register: - return cpp_storage_class_auto; - - case CX_SC_Extern: - return cpp_storage_class_extern; - case CX_SC_Static: - return cpp_storage_class_static; - - case CX_SC_PrivateExtern: - case CX_SC_OpenCLWorkGroupLocal: - // non-exposed storage classes - return cpp_storage_class_auto; - } - - DEBUG_UNREACHABLE(detail::parse_error_handler{}, cur, "unexpected storage class"); - return cpp_storage_class_auto; - } } std::unique_ptr detail::parse_cpp_variable(const detail::parse_context& context, @@ -66,7 +37,7 @@ std::unique_ptr detail::parse_cpp_variable(const detail::parse_conte auto name = get_cursor_name(cur); auto type = parse_type(context, clang_getCursorType(cur)); - auto storage_class = parse_storage_class(cur); + auto storage_class = get_storage_class(cur); auto is_constexpr = false; // just look for thread local or constexpr @@ -118,7 +89,8 @@ std::unique_ptr detail::parse_cpp_member_variable(const detail::pars // look for the equal sign, default value starts there while (!stream.done() && !skip_if(stream, "=")) stream.bump(); - auto default_value = parse_raw_expression(context, stream, clang_getCursorType(cur)); + auto default_value = parse_raw_expression(context, stream, stream.end(), + parse_type(context, clang_getCursorType(cur))); return cpp_member_variable::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type), std::move(default_value), is_mutable); diff --git a/test/cpp_function.cpp b/test/cpp_function.cpp new file mode 100644 index 0000000..70865e0 --- /dev/null +++ b/test/cpp_function.cpp @@ -0,0 +1,195 @@ +// 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_function") +{ + auto code = R"( +// parameters and return type are only tested here +void a(); +int b(int a, float* b = nullptr); +int (&c(int a, ...))[10]; + +// noexcept conditions +void d() noexcept; +void e() noexcept(false); +void f() noexcept(noexcept(d())); + +// storage class + constexpr +extern void g(); +static void h(); +constexpr void i(); +static constexpr void j(); + +// body +void k() = delete; +void l() +{ + // might confuse parser + auto b = noexcept(g()); +} +)"; + + auto check_body = [](const cpp_function& func, cpp_function_body_kind kind) { + REQUIRE(func.body_kind() == kind); + REQUIRE(func.is_declaration() == is_declaration(kind)); + REQUIRE(func.is_definition() == is_definition(kind)); + }; + + cpp_entity_index idx; + auto file = parse(idx, "cpp_function.cpp", code); + auto count = test_visit(*file, [&](const cpp_function& func) { + REQUIRE(!func.is_friend()); + + if (func.name() == "a" || func.name() == "b" || func.name() == "c") + { + REQUIRE(!func.noexcept_condition()); + REQUIRE(func.storage_class() == cpp_storage_class_none); + REQUIRE(!func.is_constexpr()); + check_body(func, cpp_function_declaration); + + if (func.name() == "a") + { + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); + REQUIRE(count_children(func) == 0u); + REQUIRE(!func.is_variadic()); + } + else if (func.name() == "b") + { + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("int"))); + + auto count = 0u; + for (auto& param : func) + { + if (param.name() == "a") + { + REQUIRE(equal_types(idx, param.type(), *cpp_builtin_type::build("int"))); + REQUIRE(!param.default_value()); + } + else if (param.name() == "b") + { + REQUIRE( + equal_types(idx, param.type(), *cpp_pointer_type::build( + cpp_builtin_type::build("float")))); + REQUIRE(param.default_value()); + REQUIRE(equal_expressions(param.default_value().value(), + *cpp_unexposed_expression:: + build(cpp_pointer_type::build( + cpp_builtin_type::build("float")), + "nullptr"))); + } + else + REQUIRE(false); + ++count; + } + REQUIRE(count == 2u); + REQUIRE(!func.is_variadic()); + } + else if (func.name() == "c") + { + REQUIRE( + equal_types(idx, func.return_type(), + *cpp_reference_type:: + build(cpp_array_type::build(cpp_builtin_type::build("int"), + cpp_literal_expression:: + build(cpp_builtin_type::build( + "unsigned long long"), + "10")), + cpp_ref_lvalue))); + + auto count = 0u; + for (auto& param : func) + { + if (param.name() == "a") + { + REQUIRE(equal_types(idx, param.type(), *cpp_builtin_type::build("int"))); + REQUIRE(!param.default_value()); + } + else + REQUIRE(false); + ++count; + } + REQUIRE(count == 1u); + REQUIRE(func.is_variadic()); + } + } + else if (func.name() == "d" || func.name() == "e" || func.name() == "f") + { + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); + REQUIRE(count_children(func) == 0u); + REQUIRE(!func.is_variadic()); + REQUIRE(func.storage_class() == cpp_storage_class_none); + REQUIRE(!func.is_constexpr()); + REQUIRE(func.noexcept_condition()); + check_body(func, cpp_function_declaration); + + auto bool_t = cpp_builtin_type::build("bool"); + if (func.name() == "d") + REQUIRE( + equal_expressions(func.noexcept_condition().value(), + *cpp_literal_expression::build(std::move(bool_t), "true"))); + else if (func.name() == "e") + REQUIRE(equal_expressions(func.noexcept_condition().value(), + *cpp_unexposed_expression::build(std::move(bool_t), + "false"))); + else if (func.name() == "f") + REQUIRE(equal_expressions(func.noexcept_condition().value(), + *cpp_unexposed_expression::build(std::move(bool_t), + "noexcept(d())"))); + } + else if (func.name() == "g" || func.name() == "h" || func.name() == "i" + || func.name() == "j") + { + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); + REQUIRE(count_children(func) == 0u); + REQUIRE(!func.is_variadic()); + REQUIRE(!func.noexcept_condition()); + check_body(func, cpp_function_declaration); + + if (func.name() == "g") + { + REQUIRE(!func.is_constexpr()); + REQUIRE(func.storage_class() == cpp_storage_class_extern); + } + else if (func.name() == "h") + { + REQUIRE(!func.is_constexpr()); + REQUIRE(func.storage_class() == cpp_storage_class_static); + } + else if (func.name() == "i") + { + REQUIRE(func.is_constexpr()); + REQUIRE(func.storage_class() == cpp_storage_class_none); + } + else if (func.name() == "j") + { + REQUIRE(func.is_constexpr()); + REQUIRE(func.storage_class() == cpp_storage_class_static); + } + } + else if (func.name() == "k" || func.name() == "l") + { + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build("void"))); + REQUIRE(count_children(func) == 0u); + REQUIRE(!func.is_variadic()); + REQUIRE(!func.noexcept_condition()); + REQUIRE(!func.is_constexpr()); + REQUIRE(func.storage_class() == cpp_storage_class_none); + + if (func.name() == "k") + check_body(func, cpp_function_deleted); + else if (func.name() == "l") + check_body(func, cpp_function_definition); + } + else + REQUIRE(false); + }); + REQUIRE(count == 12u); +}