From 1b03d106ab3521156bd39894eb725acd09d8ab2d Mon Sep 17 00:00:00 2001 From: Minh Lu Date: Tue, 16 Feb 2021 15:45:30 -0800 Subject: [PATCH] Add support for consteval --- include/cppast/cpp_function.hpp | 15 ++- include/cppast/cpp_member_function.hpp | 30 +++++- src/code_generator.cpp | 14 ++- src/libclang/function_parser.cpp | 16 ++- test/cpp_function.cpp | 45 ++++++++ test/cpp_member_function.cpp | 139 ++++++++++++++++++++++++- 6 files changed, 250 insertions(+), 9 deletions(-) diff --git a/include/cppast/cpp_function.hpp b/include/cppast/cpp_function.hpp index ed55706..98bc8e2 100644 --- a/include/cppast/cpp_function.hpp +++ b/include/cppast/cpp_function.hpp @@ -226,6 +226,12 @@ public: { function->constexpr_ = true; } + + /// \effects Marks the function as `consteval`. + void is_consteval() + { + function->consteval_ = true; + } }; /// \returns A reference to the return [cppast::cpp_type](). @@ -248,17 +254,24 @@ public: return constexpr_; } + /// \returns Whether the function is marked `consteval`. + bool is_consteval() const noexcept + { + return consteval_; + } + private: cpp_entity_kind do_get_entity_kind() const noexcept override; cpp_function(std::string name, std::unique_ptr ret) : cpp_function_base(std::move(name)), return_type_(std::move(ret)), - storage_(cpp_storage_class_auto), constexpr_(false) + storage_(cpp_storage_class_auto), constexpr_(false), consteval_(false) {} std::unique_ptr return_type_; cpp_storage_class_specifiers storage_; bool constexpr_; + bool consteval_; }; } // namespace cppast diff --git a/include/cppast/cpp_member_function.hpp b/include/cppast/cpp_member_function.hpp index 667e120..7d96007 100644 --- a/include/cppast/cpp_member_function.hpp +++ b/include/cppast/cpp_member_function.hpp @@ -100,6 +100,12 @@ public: return constexpr_; } + /// \returns Whether or not the member function is `consteval`. + bool is_consteval() const noexcept + { + return consteval_; + } + protected: /// Builder class for member functions. template @@ -132,6 +138,12 @@ protected: static_cast(*this->function).constexpr_ = true; } + /// \effects Marks the function as `consteval`. + void is_consteval() noexcept + { + static_cast(*this->function).consteval_ = true; + } + protected: basic_member_builder() noexcept = default; }; @@ -139,7 +151,7 @@ protected: /// \effects Sets name and return type, as well as the rest to defaults. cpp_member_function_base(std::string name, std::unique_ptr return_type) : cpp_function_base(std::move(name)), return_type_(std::move(return_type)), cv_(cpp_cv_none), - ref_(cpp_ref_none), constexpr_(false) + ref_(cpp_ref_none), constexpr_(false), consteval_(false) {} protected: @@ -151,6 +163,7 @@ private: cpp_cv cv_; cpp_reference ref_; bool constexpr_; + bool consteval_; }; /// A [cppast::cpp_entity]() modelling a member function. @@ -239,6 +252,12 @@ public: { function->constexpr_ = true; } + + /// \effects Marks the constructor `consteval`. + void is_consteval() noexcept + { + function->consteval_ = true; + } }; /// \returns Whether or not the constructor is `explicit`. @@ -253,15 +272,22 @@ public: return constexpr_; } + /// \returns Whether or not the constructor is `consteval`. + bool is_consteval() const noexcept + { + return consteval_; + } + private: cpp_constructor(std::string name) - : cpp_function_base(std::move(name)), explicit_(false), constexpr_(false) + : cpp_function_base(std::move(name)), explicit_(false), constexpr_(false), consteval_(false) {} cpp_entity_kind do_get_entity_kind() const noexcept override; bool explicit_; bool constexpr_; + bool consteval_; friend basic_builder; }; diff --git a/src/code_generator.cpp b/src/code_generator.cpp index 0be8667..1c7d717 100644 --- a/src/code_generator.cpp +++ b/src/code_generator.cpp @@ -465,7 +465,7 @@ bool write_variable_base(code_generator::output& output, const cpp_variable_base } void write_storage_class(code_generator::output& output, cpp_storage_class_specifiers storage, - bool is_constexpr) + bool is_constexpr, bool is_consteval) { if (is_static(storage)) output << keyword("static") << whitespace; @@ -475,6 +475,8 @@ void write_storage_class(code_generator::output& output, cpp_storage_class_speci output << keyword("thread_local") << whitespace; if (is_constexpr) output << keyword("constexpr") << whitespace; + else if (is_consteval) + output << keyword("consteval") << whitespace; } bool generate_variable(code_generator& generator, const cpp_variable& var, @@ -483,7 +485,7 @@ bool generate_variable(code_generator& generator, const cpp_variable& var, code_generator::output output(type_safe::ref(generator), type_safe::ref(var), cur_access); if (output) { - write_storage_class(output, var.storage_class(), var.is_constexpr()); + write_storage_class(output, var.storage_class(), var.is_constexpr(), false); write_variable_base(output, var, var.name()); output << punctuation(";") << newl; @@ -602,7 +604,7 @@ bool generate_function(code_generator& generator, const cpp_function& func, { if (is_friended(func)) output << keyword("friend") << whitespace; - write_storage_class(output, func.storage_class(), func.is_constexpr()); + write_storage_class(output, func.storage_class(), func.is_constexpr(), func.is_consteval()); if (output.options() & code_generator::exclude_return) output.excluded(func) << whitespace; @@ -705,6 +707,8 @@ bool generate_member_function(code_generator& generator, const cpp_member_functi output << keyword("friend") << whitespace; if (func.is_constexpr()) output << keyword("constexpr") << whitespace; + else if (func.is_consteval()) + output << keyword("consteval") << whitespace; else write_prefix_virtual(output, func.virtual_info()); @@ -756,6 +760,8 @@ bool generate_conversion_op(code_generator& generator, const cpp_conversion_op& output << keyword("explicit") << whitespace; if (op.is_constexpr()) output << keyword("constexpr") << whitespace; + else if (op.is_consteval()) + output << keyword("consteval") << whitespace; else write_prefix_virtual(output, op.virtual_info()); @@ -791,6 +797,8 @@ bool generate_constructor(code_generator& generator, const cpp_constructor& ctor output << keyword("explicit") << whitespace; if (ctor.is_constexpr()) output << keyword("constexpr") << whitespace; + if (ctor.is_consteval()) + output << keyword("consteval") << whitespace; output << identifier(ctor.semantic_scope()) << identifier(ctor.name()); write_function_parameters(output, ctor); diff --git a/src/libclang/function_parser.cpp b/src/libclang/function_parser.cpp index 94175df..20975d2 100644 --- a/src/libclang/function_parser.cpp +++ b/src/libclang/function_parser.cpp @@ -220,6 +220,7 @@ struct prefix_info { cpp_attribute_list attributes; bool is_constexpr = false; + bool is_consteval = false; bool is_virtual = false; bool is_explicit = false; bool is_friend = false; @@ -284,7 +285,9 @@ prefix_info parse_prefix_info(detail::cxtoken_stream& stream, const char* name, while (!stream.done() && !prefix_end(stream, name, is_ctor_dtor)) { - if (detail::skip_if(stream, "constexpr")) + if (detail::skip_if(stream, "consteval")) + result.is_consteval = true; + else if (detail::skip_if(stream, "constexpr")) result.is_constexpr = true; else if (detail::skip_if(stream, "virtual")) result.is_virtual = true; @@ -534,8 +537,13 @@ std::unique_ptr parse_cpp_function_impl(const detail::parse_context& builder.storage_class(cpp_storage_class_specifiers( detail::get_storage_class(cur) | (is_static ? cpp_storage_class_static : cpp_storage_class_none))); + + DEBUG_ASSERT(!(prefix.is_constexpr && prefix.is_consteval), detail::parse_error_handler{}, cur, + "function cannot be both constexpr and consteval"); if (prefix.is_constexpr) builder.is_constexpr(); + else if (prefix.is_consteval) + builder.is_consteval(); skip_parameters(stream); @@ -687,6 +695,8 @@ std::unique_ptr detail::parse_cpp_member_function(const detail::pars if (prefix.is_constexpr) builder.is_constexpr(); + else if (prefix.is_consteval) + builder.is_consteval(); skip_parameters(stream); return handle_suffix(context, cur, builder, stream, prefix.is_virtual, @@ -751,6 +761,8 @@ std::unique_ptr detail::parse_cpp_conversion_op(const detail::parse_ builder.is_explicit(); else if (prefix.is_constexpr) builder.is_constexpr(); + else if (prefix.is_consteval) + builder.is_consteval(); return handle_suffix(context, cur, builder, stream, prefix.is_virtual, parse_scope(cur, is_friend)); @@ -785,6 +797,8 @@ std::unique_ptr detail::parse_cpp_constructor(const detail::parse_co builder.is_constexpr(); else if (prefix.is_explicit) builder.is_explicit(); + else if (prefix.is_consteval) + builder.is_consteval(); skip_parameters(stream); diff --git a/test/cpp_function.cpp b/test/cpp_function.cpp index f10e597..a906e17 100644 --- a/test/cpp_function.cpp +++ b/test/cpp_function.cpp @@ -274,6 +274,51 @@ void n(int i = int()); REQUIRE(count == 15u); } +TEST_CASE("consteval cpp_function") +{ + if (libclang_parser::libclang_minor_version() < 60) + return; + + auto code = R"( +/// consteval void p(); +consteval void p(); +/// static consteval void q(); +static consteval void q(); +)"; + 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, "consteval_function.cpp", code, false, cppast::cpp_standard::cpp_2a); + auto count = test_visit(*file, [&](const cpp_function& func) { + if (func.name() == "p" || func.name() == "q") + { + REQUIRE(equal_types(idx, func.return_type(), *cpp_builtin_type::build(cpp_void))); + REQUIRE(func.signature() == "()"); + REQUIRE(!func.is_variadic()); + REQUIRE(!func.noexcept_condition()); + check_body(func, cpp_function_declaration); + + if (func.name() == "p") + { + REQUIRE(func.is_consteval()); + REQUIRE(func.storage_class() == cpp_storage_class_none); + } + else if (func.name() == "q") + { + REQUIRE(func.is_consteval()); + REQUIRE(func.storage_class() == cpp_storage_class_static); + } + } + else + REQUIRE(false); + }); + REQUIRE(count == 2u); +} + TEST_CASE("static cpp_function") { auto code = R"( diff --git a/test/cpp_member_function.cpp b/test/cpp_member_function.cpp index 3072dec..9d296fa 100644 --- a/test/cpp_member_function.cpp +++ b/test/cpp_member_function.cpp @@ -2,11 +2,10 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. +#include "test_parser.hpp" #include #include -#include "test_parser.hpp" - using namespace cppast; TEST_CASE("cpp_member_function") @@ -454,3 +453,139 @@ e::~e() = default; }); REQUIRE(count == 7u); } + +TEST_CASE("consteval cpp_constructor") +{ + if (libclang_parser::libclang_minor_version() < 60) + return; + + // only test constructor specific stuff + const char* code; + auto is_template = false; + SECTION("non-template") + { + code = R"( +struct foo +{ + /// consteval foo(int)=delete; + consteval foo(int) = delete; +}; + +)"; + } + SECTION("template") + { + is_template = true; + code = R"( +template +struct foo +{ + /// consteval foo(int)=delete; + consteval foo(int) = delete; +}; + +)"; + } + INFO(is_template); + + cpp_entity_index idx; + auto file = parse(idx, "consteval_constructor.cpp", code, false, cppast::cpp_standard::cpp_2a); + auto count = test_visit(*file, [&](const cpp_constructor& cont) { + REQUIRE(!cont.is_variadic()); + REQUIRE(cont.name() == "foo"); + + if (cont.semantic_parent()) + { + if (is_template) + REQUIRE(cont.semantic_parent().value().name() == "foo::"); + else + REQUIRE(cont.semantic_parent().value().name() == "foo::"); + REQUIRE(!cont.noexcept_condition()); + REQUIRE(!cont.is_constexpr()); + } + else + { + REQUIRE(cont.name() == "foo"); + + if (count_children(cont.parameters()) == 1u) + { + REQUIRE(!cont.noexcept_condition()); + REQUIRE(!cont.is_explicit()); + REQUIRE(cont.is_consteval()); + REQUIRE(cont.body_kind() == cpp_function_deleted); + REQUIRE(cont.signature() == "(int)"); + } + else + REQUIRE(false); + } + }); + REQUIRE(count == 1u); +} + +TEST_CASE("consteval cpp_conversion_op") +{ + if (libclang_parser::libclang_minor_version() < 60) + return; + + auto code = R"( +namespace ns +{ + template + struct type2 {}; +} + +// most of it only need to be check in member function +struct foo +{ + /// consteval operator ns::type2(); + consteval operator ns::type2() + { + return {}; + } + + /// consteval operator ns::type2(); + consteval operator ns::type2() + { + return {}; + } +}; +)"; + cpp_entity_index idx; + auto file = parse(idx, "consteval_op.cpp", code, false, cppast::cpp_standard::cpp_2a); + auto count = test_visit(*file, [&](const cpp_conversion_op& op) { + REQUIRE(count_children(op.parameters()) == 0u); + REQUIRE(!op.is_variadic()); + REQUIRE(op.body_kind() == cpp_function_definition); + REQUIRE(op.ref_qualifier() == cpp_ref_none); + REQUIRE(!op.virtual_info()); + REQUIRE(!op.noexcept_condition()); + + if (!op.is_explicit() && op.is_consteval()) + { + REQUIRE(op.cv_qualifier() == cpp_cv_none); + REQUIRE(op.signature() == "()"); + if (op.name() == "operator ns::type2") + { + REQUIRE(op.return_type().kind() == cpp_type_kind::template_instantiation_t); + auto& inst = static_cast(op.return_type()); + + REQUIRE(inst.primary_template().name() == "ns::type2"); + REQUIRE(!inst.arguments_exposed()); + REQUIRE(inst.unexposed_arguments() == "int"); + } + else if (op.name() == "operator ns::type2") + { + REQUIRE(op.return_type().kind() == cpp_type_kind::template_instantiation_t); + auto& inst = static_cast(op.return_type()); + + REQUIRE(inst.primary_template().name() == "ns::type2"); + REQUIRE(!inst.arguments_exposed()); + REQUIRE(inst.unexposed_arguments() == "char"); + } + else + REQUIRE(false); + } + }); + REQUIRE(count == 2u); +} +