diff --git a/include/cppast/cpp_concept.hpp b/include/cppast/cpp_concept.hpp new file mode 100644 index 0000000..ff00607 --- /dev/null +++ b/include/cppast/cpp_concept.hpp @@ -0,0 +1,74 @@ +// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors +// SPDX-License-Identifier: MIT + +#ifndef CPPAST_CPP_CONCEPT_HPP_INCLUDED +#define CPPAST_CPP_CONCEPT_HPP_INCLUDED + +#include +#include +#include + +namespace cppast +{ +/// A [[cppast::cpp_entity]() modelling a c++ concept declaration +/// \notes while concepts are technically templates, +/// this is not a [cppast::cpp_template](), +/// as concepts act very differently from other templates +class cpp_concept final : public cpp_entity +{ +public: + static cpp_entity_kind kind() noexcept; + + /// \returns the template parameters as a string + const cpp_token_string& parameters() const noexcept + { + return parameters_; + } + + /// \returns the [cppast::cpp_expression]() defining the concept constraint + const cpp_expression& constraint_expression() const noexcept + { + return *expression_; + } + + class builder + { + public: + builder(std::string name) + : concept_(new cpp_concept(std::move(name))) + {} + + cpp_token_string& set_parameters(cpp_token_string string) noexcept + { + concept_->parameters_ = std::move(string); + return concept_->parameters_; + } + + cpp_expression& set_expression(std::unique_ptr expression) noexcept + { + concept_->expression_ = std::move(expression); + return *concept_->expression_; + } + + std::unique_ptr finish(const cpp_entity_index& idx, cpp_entity_id id); + + private: + std::unique_ptr concept_; + }; + +private: + cpp_concept(std::string name) + : cpp_entity(std::move(name)), parameters_({}) + {} + + cpp_entity_kind do_get_entity_kind() const noexcept override; + + cpp_token_string parameters_; + + std::unique_ptr expression_; +}; + + +} // namespace cppast + +#endif \ No newline at end of file diff --git a/include/cppast/cpp_entity_kind.hpp b/include/cppast/cpp_entity_kind.hpp index c5d3449..73dc6e9 100644 --- a/include/cppast/cpp_entity_kind.hpp +++ b/include/cppast/cpp_entity_kind.hpp @@ -57,6 +57,7 @@ enum class cpp_entity_kind function_template_specialization_t, class_template_t, class_template_specialization_t, + concept_t, static_assert_t, diff --git a/include/cppast/cpp_template.hpp b/include/cppast/cpp_template.hpp index 4b0b60d..aabb440 100644 --- a/include/cppast/cpp_template.hpp +++ b/include/cppast/cpp_template.hpp @@ -15,7 +15,7 @@ namespace cppast { -/// Base class for all entities modelling a C++ template of some kind. +/// Base class for all entities modelling a C++ template of some kind, aside from concepts /// /// It is a container of a single [cppast::cpp_entity]() that is the entity being templated. class cpp_template : public cpp_entity, public cpp_entity_container diff --git a/include/cppast/cpp_template_parameter.hpp b/include/cppast/cpp_template_parameter.hpp index c3ef62a..1e59874 100644 --- a/include/cppast/cpp_template_parameter.hpp +++ b/include/cppast/cpp_template_parameter.hpp @@ -36,7 +36,8 @@ private: enum class cpp_template_keyword { keyword_class, - keyword_typename + keyword_typename, + concept_contraint }; /// \returns The string associated of the keyword. @@ -52,7 +53,8 @@ public: /// \notes The `default_type` may be `nullptr` in which case the parameter has no default. static std::unique_ptr build( const cpp_entity_index& idx, cpp_entity_id id, std::string name, cpp_template_keyword kw, - bool variadic, std::unique_ptr default_type = nullptr); + bool variadic, std::unique_ptr default_type = nullptr, + type_safe::optional concept_constraint = type_safe::nullopt); /// \returns A [ts::optional_ref]() to the default type. type_safe::optional_ref default_type() const noexcept @@ -66,17 +68,24 @@ public: return keyword_; } + const type_safe::optional& concept_constraint() const noexcept + { + return concept_constraint_; + } + private: cpp_template_type_parameter(std::string name, cpp_template_keyword kw, bool variadic, - std::unique_ptr default_type) + std::unique_ptr default_type, + type_safe::optional concept_constraint) : cpp_template_parameter(std::move(name), variadic), default_type_(std::move(default_type)), - keyword_(kw) + keyword_(kw), concept_constraint_(concept_constraint) {} cpp_entity_kind do_get_entity_kind() const noexcept override; std::unique_ptr default_type_; cpp_template_keyword keyword_; + type_safe::optional concept_constraint_; }; /// \exclude diff --git a/include/cppast/cppast_fwd.hpp b/include/cppast/cppast_fwd.hpp index 2f77776..4cdae6e 100644 --- a/include/cppast/cppast_fwd.hpp +++ b/include/cppast/cppast_fwd.hpp @@ -20,6 +20,7 @@ class cpp_builtin_type; class cpp_class; class cpp_class_template; class cpp_class_template_specialization; +class cpp_concept; class cpp_constructor; class cpp_conversion_op; class cpp_cv_qualified_type; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2fa30b0..6228f8f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ set(header ../include/cppast/cpp_attribute.hpp ../include/cppast/cpp_class.hpp ../include/cppast/cpp_class_template.hpp + ../include/cppast/cpp_concept.hpp ../include/cppast/cpp_decltype_type.hpp ../include/cppast/cpp_entity.hpp ../include/cppast/cpp_entity_container.hpp @@ -54,6 +55,7 @@ set(source cpp_attribute.cpp cpp_class.cpp cpp_class_template.cpp + cpp_concept.cpp cpp_entity.cpp cpp_entity_index.cpp cpp_entity_kind.cpp @@ -80,6 +82,7 @@ set(source visitor.cpp) set(libclang_source libclang/class_parser.cpp + libclang/concept_parser.cpp libclang/cxtokenizer.cpp libclang/cxtokenizer.hpp libclang/debug_helper.cpp diff --git a/src/code_generator.cpp b/src/code_generator.cpp index 20f264c..fae3ee0 100644 --- a/src/code_generator.cpp +++ b/src/code_generator.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -879,7 +880,10 @@ bool generate_template_type_parameter(code_generator& generat code_generator::output output(type_safe::ref(generator), type_safe::ref(param), cur_access); if (output) { - output << keyword(to_string(param.keyword())); + if(param.keyword() == cpp_template_keyword::concept_contraint) + detail::write_token_string(output, param.concept_constraint().value()); + else + output << keyword(to_string(param.keyword())); if (param.is_variadic()) output << operator_ws << punctuation("..."); if (!param.name().empty()) @@ -1036,6 +1040,23 @@ bool generate_class_template_specialization(code_generator& return static_cast(output); } +bool generate_concept(code_generator& generator, + const cpp_concept& con, + cpp_access_specifier_kind cur_access) +{ + code_generator::output output(type_safe::ref(generator), type_safe::ref(con), cur_access); + if(output) + { + output << keyword("template") << operator_ws << punctuation("<") << bracket_ws; + detail::write_token_string(output, con.parameters()); + output << bracket_ws << punctuation(">") << newl; + output << keyword("concept") << operator_ws << identifier(con.name()) << operator_ws << punctuation("=") << operator_ws; + detail::write_expression(output, con.constraint_expression()); + output << operator_ws << punctuation(";") << newl; + } + return static_cast(output); +} + bool generate_static_assert(code_generator& generator, const cpp_static_assert& assert, cpp_access_specifier_kind cur_access) { @@ -1115,6 +1136,7 @@ bool generate_code_impl(code_generator& generator, const cpp_entity& e, CPPAST_DETAIL_HANDLE(function_template_specialization) CPPAST_DETAIL_HANDLE(class_template) CPPAST_DETAIL_HANDLE(class_template_specialization) + CPPAST_DETAIL_HANDLE(concept) CPPAST_DETAIL_HANDLE(static_assert) diff --git a/src/cpp_concept.cpp b/src/cpp_concept.cpp new file mode 100644 index 0000000..0198d81 --- /dev/null +++ b/src/cpp_concept.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors +// SPDX-License-Identifier: MIT + +#include + +#include + +using namespace cppast; + +cpp_entity_kind cppast::cpp_concept::kind() noexcept +{ + return cpp_entity_kind::concept_t; +} + +cpp_entity_kind cpp_concept::do_get_entity_kind() const noexcept +{ + return cpp_entity_kind::concept_t; +} + +std::unique_ptr cpp_concept::builder::finish(const cpp_entity_index& idx, cpp_entity_id id) +{ + idx.register_definition(id, type_safe::ref(*concept_)); + return std::move(concept_); +} diff --git a/src/cpp_entity_kind.cpp b/src/cpp_entity_kind.cpp index 7da8500..a82cb61 100644 --- a/src/cpp_entity_kind.cpp +++ b/src/cpp_entity_kind.cpp @@ -88,6 +88,8 @@ const char* cppast::to_string(cpp_entity_kind kind) noexcept return "class template"; case cpp_entity_kind::class_template_specialization_t: return "class template specialization"; + case cpp_entity_kind::concept_t: + return "concept"; case cpp_entity_kind::static_assert_t: return "static_assert"; @@ -142,6 +144,7 @@ bool cppast::is_function(cpp_entity_kind kind) noexcept case cpp_entity_kind::function_template_specialization_t: case cpp_entity_kind::class_template_t: case cpp_entity_kind::class_template_specialization_t: + case cpp_entity_kind::concept_t: case cpp_entity_kind::static_assert_t: case cpp_entity_kind::unexposed_t: case cpp_entity_kind::count: @@ -191,6 +194,7 @@ bool cppast::is_parameter(cpp_entity_kind kind) noexcept case cpp_entity_kind::function_template_specialization_t: case cpp_entity_kind::class_template_t: case cpp_entity_kind::class_template_specialization_t: + case cpp_entity_kind::concept_t: case cpp_entity_kind::static_assert_t: case cpp_entity_kind::unexposed_t: case cpp_entity_kind::count: @@ -209,6 +213,7 @@ bool cppast::is_template(cpp_entity_kind kind) noexcept case cpp_entity_kind::function_template_specialization_t: case cpp_entity_kind::class_template_t: case cpp_entity_kind::class_template_specialization_t: + case cpp_entity_kind::concept_t: return true; case cpp_entity_kind::file_t: @@ -288,6 +293,7 @@ bool cppast::is_template_specialization(cpp_entity_kind kind) noexcept case cpp_entity_kind::variable_template_t: case cpp_entity_kind::function_template_t: case cpp_entity_kind::class_template_t: + case cpp_entity_kind::concept_t: case cpp_entity_kind::static_assert_t: case cpp_entity_kind::unexposed_t: case cpp_entity_kind::count: diff --git a/src/cpp_forward_declarable.cpp b/src/cpp_forward_declarable.cpp index 40c6255..47bc0f4 100644 --- a/src/cpp_forward_declarable.cpp +++ b/src/cpp_forward_declarable.cpp @@ -56,6 +56,7 @@ type_safe::optional_ref get_declarable(const cpp_e case cpp_entity_kind::template_type_parameter_t: case cpp_entity_kind::non_type_template_parameter_t: case cpp_entity_kind::template_template_parameter_t: + case cpp_entity_kind::concept_t: case cpp_entity_kind::alias_template_t: case cpp_entity_kind::variable_template_t: case cpp_entity_kind::static_assert_t: diff --git a/src/cpp_template_parameter.cpp b/src/cpp_template_parameter.cpp index aae4a2f..b133f2e 100644 --- a/src/cpp_template_parameter.cpp +++ b/src/cpp_template_parameter.cpp @@ -15,6 +15,8 @@ const char* cppast::to_string(cpp_template_keyword kw) noexcept return "class"; case cpp_template_keyword::keyword_typename: return "typename"; + case cpp_template_keyword::concept_contraint: + return "concept constraint data lost"; } return "should not get here"; @@ -22,10 +24,11 @@ const char* cppast::to_string(cpp_template_keyword kw) noexcept std::unique_ptr cpp_template_type_parameter::build( const cpp_entity_index& idx, cpp_entity_id id, std::string name, cpp_template_keyword kw, - bool variadic, std::unique_ptr default_type) + bool variadic, std::unique_ptr default_type, + type_safe::optional concept_constraint) { std::unique_ptr result( - new cpp_template_type_parameter(std::move(name), kw, variadic, std::move(default_type))); + new cpp_template_type_parameter(std::move(name), kw, variadic, std::move(default_type), std::move(concept_constraint))); idx.register_definition(std::move(id), type_safe::cref(*result)); return result; } diff --git a/src/cpp_type.cpp b/src/cpp_type.cpp index f11e72e..377a3c8 100644 --- a/src/cpp_type.cpp +++ b/src/cpp_type.cpp @@ -148,6 +148,7 @@ bool detail::cpp_type_ref_predicate::operator()(const cpp_entity& e) case cpp_entity_kind::function_template_specialization_t: case cpp_entity_kind::class_template_t: case cpp_entity_kind::class_template_specialization_t: + case cpp_entity_kind::concept_t: case cpp_entity_kind::static_assert_t: case cpp_entity_kind::unexposed_t: case cpp_entity_kind::count: diff --git a/src/libclang/concept_parser.cpp b/src/libclang/concept_parser.cpp new file mode 100644 index 0000000..6350fd3 --- /dev/null +++ b/src/libclang/concept_parser.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors +// SPDX-License-Identifier: MIT + +#include + +#include + +#include "libclang_visitor.hpp" +#include "parse_functions.hpp" + +using namespace cppast; + +std::unique_ptr detail::try_parse_cpp_concept(const detail::parse_context& context, + const CXCursor& cur) +{ + DEBUG_ASSERT(cur.kind == CXCursor_UnexposedDecl, detail::assert_handler{}); + + detail::cxtokenizer tokenizer(context.tu, context.file, cur); + detail::cxtoken_stream stream(tokenizer, cur); + + if (!detail::skip_if(stream, "template")) + return nullptr; + + if (stream.peek() != "<") + return nullptr; + + + auto closing_bracket_iter = detail::find_closing_bracket(stream); + auto params = to_string(stream, closing_bracket_iter); + + if (!detail::skip_if(stream, ">")) + return nullptr; + + if (!detail::skip_if(stream, "concept")) + return nullptr; + + const auto& identifier_token = stream.get(); + if (identifier_token.kind() != CXTokenKind::CXToken_Identifier) + { + return nullptr; + } + + cpp_concept::builder builder(identifier_token.value().std_str()); + + if (!detail::skip_if(stream, "=")) + { + return nullptr; + } + + if (*(stream.end() - 1) != ";") + { + return nullptr; + } + + builder.set_expression( + parse_raw_expression(context, stream, stream.end() - 1, + cpp_builtin_type::build(cpp_builtin_type_kind::cpp_bool))); + + builder.set_parameters(std::move(params)); + + return builder.finish(*context.idx, detail::get_entity_id(cur)); +} diff --git a/src/libclang/cxtokenizer.cpp b/src/libclang/cxtokenizer.cpp index 022c839..4795a90 100644 --- a/src/libclang/cxtokenizer.cpp +++ b/src/libclang/cxtokenizer.cpp @@ -532,6 +532,38 @@ void detail::skip_brackets(detail::cxtoken_stream& stream) stream.set_cur(std::next(closing)); } +detail::cxtoken_iterator detail::find_sequence(detail::cxtoken_stream stream, detail::cxtoken_iterator start, + detail::cxtoken_iterator end) +{ + detail::cxtoken_iterator search_start = stream.cur(); + while(search_start != stream.end()) + { + detail::cxtoken_iterator search_iter = search_start; + detail::cxtoken_iterator seq_iter = start; + bool failed = false; + + while(!failed && search_iter != stream.end() && seq_iter != end) + { + if(search_iter->value() != seq_iter->value() + || search_iter->kind() != seq_iter->kind()) + { + failed = true; + } + else + { + ++search_iter; + ++seq_iter; + } + } + if(!failed) + return search_start; + + ++search_start; + } + + return stream.end(); +} + namespace { type_safe::optional parse_attribute_using(detail::cxtoken_stream& stream) diff --git a/src/libclang/cxtokenizer.hpp b/src/libclang/cxtokenizer.hpp index 03e79e2..e7ae2e5 100644 --- a/src/libclang/cxtokenizer.hpp +++ b/src/libclang/cxtokenizer.hpp @@ -184,6 +184,10 @@ namespace detail // note: < might not work in the arguments of a template specialization void skip_brackets(cxtoken_stream& stream); + // finds the location of the given sequence in a stream + // returns an iterator to the first token of the found sequence, or stream.end() if not found + cxtoken_iterator find_sequence(cxtoken_stream stream, cxtoken_iterator start, cxtoken_iterator end); + // parses attributes // if skip_anyway is true it will bump even if no attributes have been parsed cpp_attribute_list parse_attributes(cxtoken_stream& stream, bool skip_anyway = false); diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 39e9aa9..fc4a635 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -125,6 +125,8 @@ try // go through all the try_parse_XXX functions if (auto entity = try_parse_cpp_language_linkage(context, cur)) return entity; + if (auto entity = try_parse_cpp_concept(context, cur)) + return entity; break; case CXCursor_MacroDefinition: diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index 822b203..70f755d 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -91,6 +91,11 @@ namespace detail // unexposed std::unique_ptr try_parse_cpp_language_linkage(const parse_context& context, const CXCursor& cur); + + //unexposed + std::unique_ptr try_parse_cpp_concept(const parse_context& context, + const CXCursor& cur); + // CXXMethod std::unique_ptr try_parse_static_cpp_function(const parse_context& context, const CXCursor& cur); diff --git a/src/libclang/template_parser.cpp b/src/libclang/template_parser.cpp index 4f89952..04f02af 100644 --- a/src/libclang/template_parser.cpp +++ b/src/libclang/template_parser.cpp @@ -42,22 +42,64 @@ type_safe::optional get_builder(const detail::parse std::unique_ptr(static_cast(entity.release()))); } +cpp_token_string extract_parameter_constraint(const detail::parse_context& context, + const CXCursor& parent, + detail::cxtoken_iterator target_range_start, + detail::cxtoken_iterator target_range_end) +{ + //search the parent context for the *exact* sequence in it's entirety + detail::cxtokenizer tokenizer(context.tu, context.file, parent); + detail::cxtoken_stream stream(tokenizer, parent); + + detail::cxtoken_iterator found_start = detail::find_sequence(stream, target_range_start, target_range_end); + if(found_start == stream.end()) + return detail::to_string(stream, stream.cur() + 1); + + stream.set_cur(found_start); + stream.bump(); + if (stream.peek() == "<") + detail::skip_brackets(stream); + + detail::cxtoken_iterator constraint_end = stream.cur(); + stream.set_cur(found_start); + //seek backwards until we are at the start of the qualified name + while(stream.peek().value() != "<" && stream.peek().value() != ",") + { + std::string str = stream.peek().value().std_str(); + stream.bump_back(); + } + stream.bump(); + return detail::to_string(stream, constraint_end); +} + std::unique_ptr parse_type_parameter(const detail::parse_context& context, - const CXCursor& cur) + const CXCursor& cur, + const CXCursor& parent) { DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_TemplateTypeParameter, detail::assert_handler{}); + detail::cxtokenizer tokenizer(context.tu, context.file, cur); detail::cxtoken_stream stream(tokenizer, cur); auto name = detail::get_cursor_name(cur); - // syntax: typename/class [...] name [= ...] - auto keyword = cpp_template_keyword::keyword_class; + // syntax: typename/class/constraint [...] name [= ...] + auto keyword = cpp_template_keyword::keyword_class; + type_safe::optional constraint = type_safe::nullopt; + if (detail::skip_if(stream, "typename")) keyword = cpp_template_keyword::keyword_typename; - else - detail::skip(stream, "class"); + else if (!detail::skip_if(stream, "class")) + { + keyword = cpp_template_keyword::concept_contraint; + + //try to extract the constraint token string + constraint = extract_parameter_constraint(context, parent, stream.cur(), stream.end()); + stream.bump(); + if (stream.peek() == "<") + detail::skip_brackets(stream); + } auto variadic = false; if (detail::skip_if(stream, "...")) @@ -72,7 +114,7 @@ std::unique_ptr parse_type_parameter(const detail::parse def = detail::parse_raw_type(context, stream, stream.end()); return cpp_template_type_parameter::build(*context.idx, detail::get_entity_id(cur), - name.c_str(), keyword, variadic, std::move(def)); + name.c_str(), keyword, variadic, std::move(def), constraint); } std::unique_ptr parse_non_type_parameter( @@ -147,7 +189,7 @@ std::unique_ptr parse_template_parameter( detail::visit_children(cur, [&](const CXCursor& child) { auto kind = clang_getCursorKind(child); if (kind == CXCursor_TemplateTypeParameter) - builder.add_parameter(parse_type_parameter(context, child)); + builder.add_parameter(parse_type_parameter(context, child, cur)); else if (kind == CXCursor_NonTypeTemplateParameter) builder.add_parameter(parse_non_type_parameter(context, child)); else if (kind == CXCursor_TemplateTemplateParameter) @@ -188,7 +230,7 @@ void parse_parameters(Builder& builder, const detail::parse_context& context, co detail::visit_children(cur, [&](const CXCursor& child) { auto kind = clang_getCursorKind(child); if (kind == CXCursor_TemplateTypeParameter) - builder.add_parameter(parse_type_parameter(context, child)); + builder.add_parameter(parse_type_parameter(context, child, cur)); else if (kind == CXCursor_NonTypeTemplateParameter) builder.add_parameter(parse_non_type_parameter(context, child)); else if (kind == CXCursor_TemplateTemplateParameter) @@ -389,3 +431,4 @@ std::unique_ptr detail::parse_cpp_class_template_specialization( return builder.finish(*context.idx, detail::get_entity_id(cur), builder.get().class_().is_definition()); } + diff --git a/src/visitor.cpp b/src/visitor.cpp index 8cc66dd..54a230c 100644 --- a/src/visitor.cpp +++ b/src/visitor.cpp @@ -112,6 +112,7 @@ bool detail::visit(const cpp_entity& e, detail::visitor_callback_t cb, void* fun case cpp_entity_kind::template_type_parameter_t: case cpp_entity_kind::non_type_template_parameter_t: case cpp_entity_kind::template_template_parameter_t: + case cpp_entity_kind::concept_t: case cpp_entity_kind::static_assert_t: case cpp_entity_kind::unexposed_t: return cb(functor, e, {visitor_info::leaf_entity, cur_access, last_child}); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 45b5b9e..a883c35 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ set(tests cpp_attribute.cpp cpp_class.cpp cpp_class_template.cpp + cpp_concept.cpp cpp_enum.cpp cpp_friend.cpp cpp_function.cpp diff --git a/test/cpp_concept.cpp b/test/cpp_concept.cpp new file mode 100644 index 0000000..f7b3f7c --- /dev/null +++ b/test/cpp_concept.cpp @@ -0,0 +1,85 @@ +// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors +// SPDX-License-Identifier: MIT + +#include + +#include + +#include "test_parser.hpp" + +using namespace cppast; + +TEST_CASE("cpp_concept") +{ + if (libclang_parser::libclang_minor_version() < 60) + return; + + auto code = R"( +#include + +/// template +/// concept a = requires(T t, int i) +/// { +/// {t.a()}; +/// {t.b()} -> std::copy_constructible; +/// {t.c(i)} -> std::same_as; +/// typename T::inner; +/// }; +template +concept a = requires(T t, int i) +{ + {t.a()}; + {t.b()} -> std::copy_constructible; + {t.c(i)} -> std::same_as; + typename T::inner; +}; + +/// template +/// concept b = a && std::constructible_from; +template +concept b = a && std::constructible_from; + +/// template +/// void f1(T param); +template + requires a +void f1(T param); + +/// template +/// void f2(T param); +template +void f2(T param); + +/// template T> +/// void f3(T param); +template T> +void f3(T param); + +)"; + cpp_entity_index idx; + auto file = parse(idx, "cpp_concept.cpp", code, false, cppast::cpp_standard::cpp_20); + + auto count = test_visit(*file, [&](const cpp_concept& con) {}, false); + REQUIRE(count == 2u); + + count = test_visit(*file, [&](const cpp_function_template& tfunc) { + REQUIRE(is_templated(tfunc.function())); + REQUIRE(!tfunc.scope_name()); + check_template_parameters(tfunc, {{cpp_entity_kind::template_type_parameter_t, "T"}}); + + if(tfunc.name() == "f1") + { + REQUIRE( + static_cast(*tfunc.parameters().begin()).keyword() + == cpp_template_keyword::keyword_typename); + } + else if (tfunc.name() == "f2" || tfunc.name() == "f3") + { + REQUIRE(static_cast(*tfunc.parameters().begin()) + .keyword() + == cpp_template_keyword::concept_contraint); + } + }); + + REQUIRE(count == 3u); +}