From 210fcf2c36cfff6844b1be513217ff163b6d259d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Sat, 11 Mar 2017 15:36:51 +0100 Subject: [PATCH] Add support for forward declarations --- include/cppast/cpp_class.hpp | 18 +++- include/cppast/cpp_enum.hpp | 17 +++- include/cppast/cpp_forward_declarable.hpp | 108 ++++++++++++++++++++++ include/cppast/cpp_function.hpp | 38 +++++--- include/cppast/cpp_variable.hpp | 13 ++- src/cpp_variable.cpp | 12 +++ src/libclang/class_parser.cpp | 10 +- src/libclang/enum_parser.cpp | 7 +- src/libclang/variable_parser.cpp | 12 ++- test/cpp_class.cpp | 45 ++++++++- test/cpp_enum.cpp | 77 ++++++++++----- test/cpp_variable.cpp | 39 +++++--- 12 files changed, 332 insertions(+), 64 deletions(-) create mode 100644 include/cppast/cpp_forward_declarable.hpp diff --git a/include/cppast/cpp_class.hpp b/include/cppast/cpp_class.hpp index 68964ef..248b9e7 100644 --- a/include/cppast/cpp_class.hpp +++ b/include/cppast/cpp_class.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace cppast @@ -112,7 +113,13 @@ namespace cppast }; /// A [cppast::cpp_entity]() modelling a C++ class. - class cpp_class final : public cpp_entity, public cpp_entity_container + /// + /// This can either be a definition or just a forward declaration. + /// If it is just a forward declaration, + /// everything except the class type will not be available. + class cpp_class final : public cpp_entity, + public cpp_entity_container, + public cpp_forward_declarable { public: static cpp_entity_kind kind() noexcept; @@ -164,6 +171,15 @@ namespace cppast std::unique_ptr finish(const cpp_entity_index& idx, cpp_entity_id id) noexcept; + /// \effects Marks the class as forward declaration. + /// \returns The finished class. + /// \notes It will not be registered, as it is not the main definition. + std::unique_ptr finish_declaration(cpp_entity_id definition_id) noexcept + { + class_->set_definition(definition_id); + return std::move(class_); + } + private: std::unique_ptr class_; }; diff --git a/include/cppast/cpp_enum.hpp b/include/cppast/cpp_enum.hpp index 25302af..a50a796 100644 --- a/include/cppast/cpp_enum.hpp +++ b/include/cppast/cpp_enum.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace cppast @@ -48,7 +49,12 @@ namespace cppast }; /// A [cppast::cpp_entity]() modelling a C++ enumeration. - class cpp_enum final : public cpp_entity, public cpp_entity_container + /// + /// This can either be a definition or just a forward declaration. + /// If it is just forward declared, it will not have any children. + class cpp_enum final : public cpp_entity, + public cpp_entity_container, + public cpp_forward_declarable { public: static cpp_entity_kind kind() noexcept; @@ -79,6 +85,15 @@ namespace cppast return std::move(enum_); } + /// \effects Marks the enum as forward declaration. + /// \returns The finished enum. + /// \notes It will not be registered, as it is not the main definition. + std::unique_ptr finish_declaration(cpp_entity_id definition_id) noexcept + { + enum_->set_definition(definition_id); + return std::move(enum_); + } + private: std::unique_ptr enum_; }; diff --git a/include/cppast/cpp_forward_declarable.hpp b/include/cppast/cpp_forward_declarable.hpp new file mode 100644 index 0000000..7539172 --- /dev/null +++ b/include/cppast/cpp_forward_declarable.hpp @@ -0,0 +1,108 @@ +// 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. + +#ifndef CPPAST_CPP_FORWARD_DECLARABLE_HPP_INCLUDED +#define CPPAST_CPP_FORWARD_DECLARABLE_HPP_INCLUDED + +#include + +#include + +#include +#include + +namespace cppast +{ + /// Mixin base class for all entities that can have a forward declaration. + /// + /// Examples are [cppast::cpp_enum]() or [cppast::cpp_class](), + /// but also [cppast::cpp_function_base](). + /// Those entities can have multiple declarations and one definition. + class cpp_forward_declarable + { + public: + /// \returns Whether or not the entity is the definition. + bool is_definition() const noexcept + { + return !definition_.has_value(); + } + + /// \returns Whether or not the entity is "just" a declaration. + bool is_declaration() const noexcept + { + return definition_.has_value(); + } + + /// \returns The [cppast::cpp_entity_id]() of the definition, + /// if the current entity is not the definition. + const type_safe::optional& definition() const noexcept + { + return definition_; + } + + protected: + /// \effects Marks the entity as definition. + /// \notes If it is not a definition, + /// [*set_definition]() must be called. + cpp_forward_declarable() noexcept = default; + + ~cpp_forward_declarable() noexcept = default; + + /// \effects Sets the definition of the entity, + /// marking it as a forward declaration. + void set_definition(cpp_entity_id def) noexcept + { + definition_ = std::move(def); + } + + private: + type_safe::optional definition_; + }; + + /// \exclude + namespace detail + { + template + auto get_definition_impl(const cpp_entity_index& idx, const T& entity) -> + typename std::enable_if::value, + type_safe::optional_ref>::type + { + if (!entity.definition()) + return type_safe::opt_cref(&entity); + else + return idx + .lookup(entity.definition().value()) + // downcast + .map([](const cpp_entity& e) -> const T& { + DEBUG_ASSERT(e.kind() == T::kind(), detail::assert_handler{}); + return static_cast(e); + }); + } + + template + auto get_definition_impl(const cpp_entity_index&, const T& entity) -> + typename std::enable_if::value, + type_safe::optional_ref>::type + { + return type_safe::opt_cref(&entity); + } + } // namespace detail + + /// Gets the definition of an entity. + /// \returns A [ts::optional_ref]() to the entity that is the definition. + /// If the entity is a definition or not derived from [cppast::cpp_forward_declarable](), + /// returns a reference to the entity itself. + /// Otherwise lookups the definition id and returns it. + /// \requires `entity` must be derived from [cppast::cpp_entity](). + /// \param 1 + /// \exclude + template ::value>::type> + type_safe::optional_ref get_definition(const cpp_entity_index& idx, const T& entity) + { + return detail::get_definition_impl(idx, entity); + } +} // namespace cppast + +#endif // CPPAST_CPP_FORWARD_DECLARABLE_HPP_INCLUDED diff --git a/include/cppast/cpp_function.hpp b/include/cppast/cpp_function.hpp index 66a36c0..30446f8 100644 --- a/include/cppast/cpp_function.hpp +++ b/include/cppast/cpp_function.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -40,14 +41,30 @@ namespace cppast cpp_function_deleted, //< Deleted definition. }; + /// \returns Whether or not the function body is a declaration, + /// without a definition. + inline bool is_declaration(cpp_function_body_kind body) noexcept + { + return body == cpp_function_declaration; + } + + /// \returns Whether or not the function body is a definition. + inline bool is_definition(cpp_function_body_kind body) noexcept + { + return !is_declaration(body); + } + /// Base class for all entities that are functions. /// /// It contains arguments and common flags. - class cpp_function_base : public cpp_entity, - public cpp_entity_container + class cpp_function_base + : public cpp_entity, + public cpp_entity_container, + public cpp_forward_declarable { public: /// \returns The [cppast::cpp_function_body_kind](). + /// \notes This matches the [cppast::cpp_forward_declarable]() queries. cpp_function_body_kind body_kind() const noexcept { return body_; @@ -98,17 +115,16 @@ namespace cppast static_cast(*function).noexcept_expr_ = std::move(cond); } - /// \effects Sets the [cppast::cpp_function_body_kind](). - void body_kind(cpp_function_body_kind kind) - { - static_cast(*function).body_ = kind; - } - - /// \effects Registers the function. + /// \effects If the body is a definition, registers it. + /// Else marks it as a declaration. /// \returns The finished function. - std::unique_ptr finish(const cpp_entity_index& idx, cpp_entity_id id) + std::unique_ptr finish(const cpp_entity_index& idx, cpp_entity_id id, + cpp_function_body_kind body_kind) { - idx.register_entity(std::move(id), type_safe::cref(*function)); + if (cppast::is_definition(body_kind)) + idx.register_entity(std::move(id), type_safe::cref(*function)); + else + function->set_definition(id); return std::move(function); } diff --git a/include/cppast/cpp_variable.hpp b/include/cppast/cpp_variable.hpp index 42afe26..da3c0d1 100644 --- a/include/cppast/cpp_variable.hpp +++ b/include/cppast/cpp_variable.hpp @@ -6,6 +6,7 @@ #define CPPAST_CPP_VARIABLE_HPP_INCLUDED #include +#include #include #include @@ -15,7 +16,9 @@ namespace cppast /// \notes This is not a member variable, /// use [cppast::cpp_member_variable]() for that. /// But it can be `static` member variable. - class cpp_variable final : public cpp_entity, public cpp_variable_base + class cpp_variable final : public cpp_entity, + public cpp_variable_base, + public cpp_forward_declarable { public: static cpp_entity_kind kind() noexcept; @@ -28,6 +31,14 @@ namespace cppast cpp_storage_class_specifiers spec, bool is_constexpr); + /// \returns A newly created variable that is a declaration. + /// A declaration will not be registered and it does not have the default value. + static std::unique_ptr build_declaration(cpp_entity_id definition_id, + std::string name, + std::unique_ptr type, + cpp_storage_class_specifiers spec, + bool is_constexpr); + /// \returns The [cppast::cpp_storage_specifiers]() on that variable. cpp_storage_class_specifiers storage_class() const noexcept { diff --git a/src/cpp_variable.cpp b/src/cpp_variable.cpp index 58b918e..368ede8 100644 --- a/src/cpp_variable.cpp +++ b/src/cpp_variable.cpp @@ -25,6 +25,18 @@ std::unique_ptr cpp_variable::build(const cpp_entity_index& idx, c return result; } +std::unique_ptr cpp_variable::build_declaration(cpp_entity_id definition_id, + std::string name, + std::unique_ptr type, + cpp_storage_class_specifiers spec, + bool is_constexpr) +{ + auto result = std::unique_ptr( + new cpp_variable(std::move(name), std::move(type), nullptr, spec, is_constexpr)); + result->set_definition(definition_id); + return result; +} + cpp_entity_kind cpp_variable::do_get_entity_kind() const noexcept { return kind(); diff --git a/src/libclang/class_parser.cpp b/src/libclang/class_parser.cpp index 148e676..62d9501 100644 --- a/src/libclang/class_parser.cpp +++ b/src/libclang/class_parser.cpp @@ -88,11 +88,12 @@ namespace } } +#include +#include "debug_helper.hpp" + std::unique_ptr detail::parse_cpp_class(const detail::parse_context& context, const CXCursor& cur) { - if (!clang_isCursorDefinition(cur)) - return nullptr; auto builder = make_class_builder(cur); detail::visit_children(cur, [&](const CXCursor& child) { auto kind = clang_getCursorKind(child); @@ -105,5 +106,8 @@ std::unique_ptr detail::parse_cpp_class(const detail::parse_context& else if (auto entity = parse_entity(context, child)) builder.add_child(std::move(entity)); }); - return builder.finish(*context.idx, get_entity_id(cur)); + if (clang_isCursorDefinition(cur)) + return builder.finish(*context.idx, get_entity_id(cur)); + else + return builder.finish_declaration(get_entity_id(cur)); } diff --git a/src/libclang/enum_parser.cpp b/src/libclang/enum_parser.cpp index 37670b3..19f9d21 100644 --- a/src/libclang/enum_parser.cpp +++ b/src/libclang/enum_parser.cpp @@ -66,8 +66,6 @@ std::unique_ptr detail::parse_cpp_enum(const detail::parse_context& const CXCursor& cur) { DEBUG_ASSERT(cur.kind == CXCursor_EnumDecl, detail::assert_handler{}); - if (!clang_isCursorDefinition(cur)) - return nullptr; auto builder = make_enum_builder(context, cur); detail::visit_children(cur, [&](const CXCursor& child) { @@ -81,5 +79,8 @@ std::unique_ptr detail::parse_cpp_enum(const detail::parse_context& context.logger->log("libclang parser", ex.get_diagnostic()); } }); - return builder.finish(*context.idx, get_entity_id(cur)); + if (clang_isCursorDefinition(cur)) + return builder.finish(*context.idx, get_entity_id(cur)); + else + return builder.finish_declaration(get_entity_id(cur)); } diff --git a/src/libclang/variable_parser.cpp b/src/libclang/variable_parser.cpp index 6fe5bd5..81e2738 100644 --- a/src/libclang/variable_parser.cpp +++ b/src/libclang/variable_parser.cpp @@ -66,7 +66,6 @@ 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 default_value = parse_default_value(context, cur); auto storage_class = parse_storage_class(cur); auto is_constexpr = false; @@ -80,8 +79,15 @@ std::unique_ptr detail::parse_cpp_variable(const detail::parse_conte else if (token.value() == "constexpr") is_constexpr = true; - return cpp_variable::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type), - std::move(default_value), storage_class, is_constexpr); + if (clang_isCursorDefinition(cur)) + { + auto default_value = parse_default_value(context, cur); + return cpp_variable::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type), + std::move(default_value), storage_class, is_constexpr); + } + else + return cpp_variable::build_declaration(get_entity_id(cur), name.c_str(), std::move(type), + storage_class, is_constexpr); } std::unique_ptr detail::parse_cpp_member_variable(const detail::parse_context& context, diff --git a/test/cpp_class.cpp b/test/cpp_class.cpp index 53f8f3f..7e1f1e3 100644 --- a/test/cpp_class.cpp +++ b/test/cpp_class.cpp @@ -11,13 +11,16 @@ using namespace cppast; TEST_CASE("cpp_class") { auto code = R"( +// forward declarations +struct a; +class b; +struct unresolved; + // basic struct a {}; class b final {}; union c {}; -struct ignore_me; - // members struct d { @@ -62,16 +65,42 @@ struct g REQUIRE(!c.is_final()); REQUIRE(c.bases().begin() == c.bases().end()); REQUIRE(c.begin() == c.end()); + + auto definition = get_definition(idx, c); + REQUIRE(definition); + REQUIRE(definition.value().name() == c.name()); } else if (c.name() == "b") { REQUIRE(c.class_kind() == cpp_class_kind::class_t); - REQUIRE(c.is_final()); REQUIRE(c.bases().begin() == c.bases().end()); REQUIRE(c.begin() == c.end()); + + if (c.is_definition()) + REQUIRE(c.is_final()); + else + REQUIRE(c.is_declaration()); + + auto definition = get_definition(idx, c); + REQUIRE(definition); + REQUIRE(definition.value().name() == "b"); + } + else if (c.name() == "unresolved") + { + REQUIRE(c.is_declaration()); + REQUIRE(!c.is_definition()); + REQUIRE(c.class_kind() == cpp_class_kind::struct_t); + REQUIRE(!c.is_final()); + REQUIRE(c.bases().begin() == c.bases().end()); + REQUIRE(c.begin() == c.end()); + + auto definition = get_definition(idx, c); + REQUIRE(!definition); } else if (c.name() == "c") { + REQUIRE(c.is_definition()); + REQUIRE(!c.is_declaration()); REQUIRE(c.class_kind() == cpp_class_kind::union_t); REQUIRE(!c.is_final()); REQUIRE(c.bases().begin() == c.bases().end()); @@ -79,6 +108,8 @@ struct g } else if (c.name() == "d") { + REQUIRE(c.is_definition()); + REQUIRE(!c.is_declaration()); REQUIRE(c.class_kind() == cpp_class_kind::struct_t); REQUIRE(!c.is_final()); REQUIRE(c.bases().begin() == c.bases().end()); @@ -133,6 +164,8 @@ struct g } else if (c.name() == "e") { + REQUIRE(c.is_definition()); + REQUIRE(!c.is_declaration()); REQUIRE(c.class_kind() == cpp_class_kind::class_t); REQUIRE(!c.is_final()); REQUIRE(c.begin() == c.end()); @@ -166,6 +199,8 @@ struct g } else if (c.name() == "f") { + REQUIRE(c.is_definition()); + REQUIRE(!c.is_declaration()); REQUIRE(c.class_kind() == cpp_class_kind::struct_t); REQUIRE(!c.is_final()); REQUIRE(c.begin() == c.end()); @@ -200,6 +235,8 @@ struct g } else if (c.name() == "g") { + REQUIRE(c.is_definition()); + REQUIRE(!c.is_declaration()); REQUIRE(c.class_kind() == cpp_class_kind::struct_t); REQUIRE(!c.is_final()); REQUIRE(c.begin() == c.end()); @@ -226,5 +263,5 @@ struct g else REQUIRE(false); }); - REQUIRE(count == 8u); + REQUIRE(count == 11u); } diff --git a/test/cpp_enum.cpp b/test/cpp_enum.cpp index b0063c3..f219ff0 100644 --- a/test/cpp_enum.cpp +++ b/test/cpp_enum.cpp @@ -11,8 +11,6 @@ using namespace cppast; TEST_CASE("cpp_enum") { auto code = R"( -enum ignore_me : int; - enum a { a_a, @@ -21,7 +19,7 @@ enum a a_d = a_a + 2, }; -enum class ignore_me2; +enum class b; // forward declaration enum class b : int { @@ -30,6 +28,7 @@ enum class b : int b_c }; +enum c : int; )"; cpp_entity_index idx; @@ -37,6 +36,8 @@ enum class b : int auto count = test_visit(*file, [&](const cpp_enum& e) { if (e.name() == "a") { + REQUIRE(e.is_definition()); + REQUIRE(!e.is_declaration()); REQUIRE(!e.is_scoped()); REQUIRE(!e.underlying_type()); @@ -79,34 +80,62 @@ enum class b : int else if (e.name() == "b") { REQUIRE(e.is_scoped()); - REQUIRE(e.underlying_type()); - REQUIRE(equal_types(idx, e.underlying_type().value(), *cpp_builtin_type::build("int"))); - auto no_vals = 0u; - for (auto& val : e) + if (e.is_definition()) { - REQUIRE(full_name(val) == "b::" + val.name()); - if (val.name() == "b_a" || val.name() == "b_c") + REQUIRE(e.underlying_type()); + REQUIRE( + equal_types(idx, e.underlying_type().value(), *cpp_builtin_type::build("int"))); + + auto no_vals = 0u; + for (auto& val : e) { - ++no_vals; - REQUIRE(!val.value()); + REQUIRE(full_name(val) == "b::" + val.name()); + if (val.name() == "b_a" || val.name() == "b_c") + { + ++no_vals; + REQUIRE(!val.value()); + } + else if (val.name() == "b_b") + { + ++no_vals; + REQUIRE(val.value()); + auto& expr = val.value().value(); + REQUIRE(expr.kind() == cpp_expression_kind::literal); + REQUIRE(static_cast(expr).value() == "42"); + REQUIRE(equal_types(idx, expr.type(), *cpp_builtin_type::build("int"))); + } + else + REQUIRE(false); } - else if (val.name() == "b_b") - { - ++no_vals; - REQUIRE(val.value()); - auto& expr = val.value().value(); - REQUIRE(expr.kind() == cpp_expression_kind::literal); - REQUIRE(static_cast(expr).value() == "42"); - REQUIRE(equal_types(idx, expr.type(), *cpp_builtin_type::build("int"))); - } - else - REQUIRE(false); + REQUIRE(no_vals == 3u); } - REQUIRE(no_vals == 3u); + else if (e.is_declaration()) + { + REQUIRE(!e.underlying_type()); // no underlying type, implicit int + REQUIRE(count_children(e) == 0u); + + auto definition = get_definition(idx, e); + REQUIRE(definition); + REQUIRE(definition.value().name() == "b"); + REQUIRE(count_children(definition.value()) == 3u); + } + else + REQUIRE(false); + } + else if (e.name() == "c") + { + REQUIRE(e.is_declaration()); + REQUIRE(!e.is_definition()); + REQUIRE(!e.is_scoped()); + REQUIRE(equal_types(idx, e.underlying_type().value(), *cpp_builtin_type::build("int"))); + REQUIRE(count_children(e) == 0u); + + auto definition = get_definition(idx, e); + REQUIRE(!definition); } else REQUIRE(false); }); - REQUIRE(count == 2u); + REQUIRE(count == 4u); } diff --git a/test/cpp_variable.cpp b/test/cpp_variable.cpp index 51c75e7..f114439 100644 --- a/test/cpp_variable.cpp +++ b/test/cpp_variable.cpp @@ -35,7 +35,18 @@ static struct {} l; cpp_entity_index idx; auto check_variable = [&](const cpp_variable& var, const cpp_type& type, type_safe::optional_ref default_value, - int storage_class, bool is_constexpr) { + int storage_class, bool is_constexpr, bool is_declaration) { + if (is_declaration) + { + REQUIRE(var.is_declaration()); + REQUIRE(!var.is_definition()); + } + else + { + REQUIRE(var.is_definition()); + REQUIRE(!var.is_declaration()); + } + REQUIRE(equal_types(idx, var.type(), type)); if (var.default_value()) { @@ -54,35 +65,35 @@ static struct {} l; auto int_type = cpp_builtin_type::build("int"); auto count = test_visit(*file, [&](const cpp_variable& var) { if (var.name() == "a") - check_variable(var, *int_type, nullptr, cpp_storage_class_none, false); + check_variable(var, *int_type, nullptr, cpp_storage_class_none, false, false); else if (var.name() == "b") check_variable(var, *cpp_builtin_type::build("unsigned long long"), // unexposed due to implicit cast, I think *cpp_unexposed_expression::build(cpp_builtin_type::build("int"), "42"), - cpp_storage_class_none, false); + cpp_storage_class_none, false, false); else if (var.name() == "c") check_variable(var, *cpp_builtin_type::build("float"), *cpp_unexposed_expression::build(cpp_builtin_type::build("float"), "3.f+0.14f"), - cpp_storage_class_none, false); + cpp_storage_class_none, false, false); else if (var.name() == "d") - check_variable(var, *int_type, nullptr, cpp_storage_class_extern, false); + check_variable(var, *int_type, nullptr, cpp_storage_class_extern, false, true); else if (var.name() == "e") - check_variable(var, *int_type, nullptr, cpp_storage_class_static, false); + check_variable(var, *int_type, nullptr, cpp_storage_class_static, false, false); else if (var.name() == "f") - check_variable(var, *int_type, nullptr, cpp_storage_class_thread_local, false); + check_variable(var, *int_type, nullptr, cpp_storage_class_thread_local, false, false); else if (var.name() == "g") check_variable(var, *int_type, nullptr, - cpp_storage_class_static | cpp_storage_class_thread_local, false); + cpp_storage_class_static | cpp_storage_class_thread_local, false, false); else if (var.name() == "h") check_variable(var, *cpp_cv_qualified_type::build(cpp_builtin_type::build("int"), cpp_cv_const), *cpp_literal_expression::build(cpp_builtin_type::build("int"), "12"), - cpp_storage_class_none, true); + cpp_storage_class_none, true, false); else if (var.name() == "i") check_variable(var, *cpp_user_defined_type::build(cpp_type_ref(cpp_entity_id(""), "foo")), - nullptr, cpp_storage_class_none, false); + nullptr, cpp_storage_class_none, false, false); else if (var.name() == "j") check_variable(var, *cpp_cv_qualified_type::build(cpp_user_defined_type::build( cpp_type_ref(cpp_entity_id(""), @@ -92,7 +103,7 @@ static struct {} l; cpp_type_ref(cpp_entity_id(""), "bar")), "bar()"), - cpp_storage_class_none, false); + cpp_storage_class_none, false, false); else if (var.name() == "k") check_variable(var, *cpp_user_defined_type::build(cpp_type_ref(cpp_entity_id(""), "baz")), @@ -100,10 +111,10 @@ static struct {} l; cpp_type_ref(cpp_entity_id(""), "baz")), "{}"), - cpp_storage_class_none, false); + cpp_storage_class_none, false, false); else if (var.name() == "l") check_variable(var, *cpp_user_defined_type::build(cpp_type_ref(cpp_entity_id(""), "")), - nullptr, cpp_storage_class_static, false); + nullptr, cpp_storage_class_static, false, false); else REQUIRE(false); }); @@ -123,6 +134,8 @@ struct test auto file = parse({}, "static_cpp_variable.cpp", code); auto count = test_visit(*file, [&](const cpp_variable& var) { + REQUIRE(var.is_declaration()); + REQUIRE(!var.is_definition()); REQUIRE(!var.default_value()); REQUIRE(!var.is_constexpr()); REQUIRE(is_static(var.storage_class()));