// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors // SPDX-License-Identifier: MIT #include #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(); // alignas struct alignas(8) type {}; alignas(type) int var; // keyword attributes [[const]] int k(); // multiple attributes but separately [[a]] [[b]] [[c]] int l(); )"; 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) { 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); REQUIRE(has_attribute(e, "attribute1")); REQUIRE(has_attribute(e, "attribute2")); check_attribute(attr, "attribute1", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); check_attribute(attributes[1u], "attribute2", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); } else if (e.name() == "c") check_attribute(attr, "variadic", type_safe::nullopt, true, "", cpp_attribute_kind::unknown); else if (e.name() == "d") { REQUIRE(has_attribute(e, "ns::attribute")); check_attribute(attr, "attribute", "ns", false, "", cpp_attribute_kind::unknown); } else if (e.name() == "e") check_attribute(attr, "attribute", type_safe::nullopt, false, R"(arg1,arg2,+(){},42,"Hello!")", cpp_attribute_kind::unknown); else if (e.name() == "f") { REQUIRE(attributes.size() == 2u); check_attribute(attr, "attribute", "ns", false, "+,-,0 4", cpp_attribute_kind::unknown); check_attribute(attributes[1u], "other_attribute", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); } else if (e.name() == "g") { REQUIRE(has_attribute(e, cpp_attribute_kind::deprecated)); 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); else if (e.name() == "k") check_attribute(attr, "const", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); else if (e.name() == "l") { REQUIRE_NOTHROW(attributes.size() == 3); check_attribute(attributes[0], "a", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); check_attribute(attributes[1], "b", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); check_attribute(attributes[2], "c", type_safe::nullopt, false, "", cpp_attribute_kind::unknown); } }, false); REQUIRE(count == 11); count = test_visit( *file, [&](const cpp_entity& e) { auto& attributes = e.attributes(); REQUIRE(attributes.size() == 1u); auto& attr = attributes.front(); check_attribute(attr, "alignas", type_safe::nullopt, false, "8", cpp_attribute_kind::alignas_); }, false); REQUIRE(count == 1u); count = test_visit( *file, [&](const cpp_entity& e) { auto& attributes = e.attributes(); INFO(e.name()); REQUIRE(attributes.size() == 1u); auto& attr = attributes.front(); check_attribute(attr, "alignas", type_safe::nullopt, false, "type", cpp_attribute_kind::alignas_); }, false); REQUIRE(count == 1u); } 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; // constructor struct [[q]] q { [[q]] q(); }; struct [[r]] r { [[r]] r(); }; // type defined inline struct [[inline_type]] inline_type { [[field]] int field; } [[s]] s; int t [[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 == 44u); }