Add basic concept support (#144)

This commit is contained in:
waitingtocompile 2022-09-03 20:22:59 +01:00 committed by GitHub
commit a937efbcbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 396 additions and 16 deletions

View file

@ -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 <cppast/cpp_entity.hpp>
#include <cppast/cpp_template_parameter.hpp>
#include <cppast/cpp_expression.hpp>
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<cpp_expression> expression) noexcept
{
concept_->expression_ = std::move(expression);
return *concept_->expression_;
}
std::unique_ptr<cpp_concept> finish(const cpp_entity_index& idx, cpp_entity_id id);
private:
std::unique_ptr<cpp_concept> 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<cpp_expression> expression_;
};
} // namespace cppast
#endif

View file

@ -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,

View file

@ -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<cpp_template, cpp_entity>

View file

@ -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<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<cpp_type> default_type = nullptr);
bool variadic, std::unique_ptr<cpp_type> default_type = nullptr,
type_safe::optional<cpp_token_string> concept_constraint = type_safe::nullopt);
/// \returns A [ts::optional_ref]() to the default type.
type_safe::optional_ref<const cpp_type> default_type() const noexcept
@ -66,17 +68,24 @@ public:
return keyword_;
}
const type_safe::optional<cpp_token_string>& concept_constraint() const noexcept
{
return concept_constraint_;
}
private:
cpp_template_type_parameter(std::string name, cpp_template_keyword kw, bool variadic,
std::unique_ptr<cpp_type> default_type)
std::unique_ptr<cpp_type> default_type,
type_safe::optional<cpp_token_string> 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<cpp_type> default_type_;
cpp_template_keyword keyword_;
type_safe::optional<cpp_token_string> concept_constraint_;
};
/// \exclude

View file

@ -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;

View file

@ -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

View file

@ -6,6 +6,7 @@
#include <cppast/cpp_alias_template.hpp>
#include <cppast/cpp_class.hpp>
#include <cppast/cpp_class_template.hpp>
#include <cppast/cpp_concept.hpp>
#include <cppast/cpp_entity_kind.hpp>
#include <cppast/cpp_enum.hpp>
#include <cppast/cpp_file.hpp>
@ -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<bool>(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<bool>(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)

24
src/cpp_concept.cpp Normal file
View file

@ -0,0 +1,24 @@
// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors
// SPDX-License-Identifier: MIT
#include <cppast/cpp_concept.hpp>
#include <cppast/cpp_entity_kind.hpp>
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> 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_);
}

View file

@ -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:

View file

@ -56,6 +56,7 @@ type_safe::optional_ref<const cpp_forward_declarable> 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:

View file

@ -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> 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<cpp_type> default_type)
bool variadic, std::unique_ptr<cpp_type> default_type,
type_safe::optional<cpp_token_string> concept_constraint)
{
std::unique_ptr<cpp_template_type_parameter> 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;
}

View file

@ -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:

View file

@ -0,0 +1,62 @@
// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors
// SPDX-License-Identifier: MIT
#include <cppast/cpp_concept.hpp>
#include <cppast/cpp_entity_kind.hpp>
#include "libclang_visitor.hpp"
#include "parse_functions.hpp"
using namespace cppast;
std::unique_ptr<cpp_entity> 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));
}

View file

@ -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<std::string> parse_attribute_using(detail::cxtoken_stream& stream)

View file

@ -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);

View file

@ -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:

View file

@ -91,6 +91,11 @@ namespace detail
// unexposed
std::unique_ptr<cpp_entity> try_parse_cpp_language_linkage(const parse_context& context,
const CXCursor& cur);
//unexposed
std::unique_ptr<cpp_entity> try_parse_cpp_concept(const parse_context& context,
const CXCursor& cur);
// CXXMethod
std::unique_ptr<cpp_entity> try_parse_static_cpp_function(const parse_context& context,
const CXCursor& cur);

View file

@ -42,22 +42,64 @@ type_safe::optional<typename TemplateT::builder> get_builder(const detail::parse
std::unique_ptr<EntityT>(static_cast<EntityT*>(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<cpp_template_parameter> 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<cpp_token_string> 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<cpp_template_parameter> 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<cpp_template_parameter> parse_non_type_parameter(
@ -147,7 +189,7 @@ std::unique_ptr<cpp_template_template_parameter> 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<cpp_entity> detail::parse_cpp_class_template_specialization(
return builder.finish(*context.idx, detail::get_entity_id(cur),
builder.get().class_().is_definition());
}

View file

@ -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});

View file

@ -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

85
test/cpp_concept.cpp Normal file
View file

@ -0,0 +1,85 @@
// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors
// SPDX-License-Identifier: MIT
#include <cppast/cpp_concept.hpp>
#include <cppast/cpp_function_template.hpp>
#include "test_parser.hpp"
using namespace cppast;
TEST_CASE("cpp_concept")
{
if (libclang_parser::libclang_minor_version() < 60)
return;
auto code = R"(
#include <concepts>
/// template<typename T>
/// concept a = requires(T t, int i)
/// {
/// {t.a()};
/// {t.b()} -> std::copy_constructible;
/// {t.c(i)} -> std::same_as<int>;
/// typename T::inner;
/// };
template<typename T>
concept a = requires(T t, int i)
{
{t.a()};
{t.b()} -> std::copy_constructible;
{t.c(i)} -> std::same_as<int>;
typename T::inner;
};
/// template<typename T>
/// concept b = a<T> && std::constructible_from<T, int>;
template<typename T>
concept b = a<T> && std::constructible_from<T, int>;
/// template<typename T>
/// void f1(T param);
template<typename T>
requires a<T>
void f1(T param);
/// template<b T>
/// void f2(T param);
template<b T>
void f2(T param);
/// template<std::convertible_to<int> T>
/// void f3(T param);
template<std::convertible_to<int> 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<cpp_concept>(*file, [&](const cpp_concept& con) {}, false);
REQUIRE(count == 2u);
count = test_visit<cpp_function_template>(*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<const cpp_template_type_parameter&>(*tfunc.parameters().begin()).keyword()
== cpp_template_keyword::keyword_typename);
}
else if (tfunc.name() == "f2" || tfunc.name() == "f3")
{
REQUIRE(static_cast<const cpp_template_type_parameter&>(*tfunc.parameters().begin())
.keyword()
== cpp_template_keyword::concept_contraint);
}
});
REQUIRE(count == 3u);
}