Added rendering of concept requirements in concept body
This commit is contained in:
@@ -313,6 +313,22 @@ void generator::generate(const concept_ &c, std::ostream &ostr) const
|
||||
|
||||
ostr << " {" << '\n';
|
||||
|
||||
if (true &&
|
||||
(c.requires_parameters().size() + c.requires_statements().size()) >
|
||||
0) { // TODO: add option to enable/disable this
|
||||
std::vector<std::string> parameters;
|
||||
parameters.reserve(c.requires_parameters().size());
|
||||
for (const auto &p : c.requires_parameters()) {
|
||||
parameters.emplace_back(p.to_string(m_config.using_namespace()));
|
||||
}
|
||||
|
||||
ostr << fmt::format("({})\n", fmt::join(parameters, ","));
|
||||
|
||||
ostr << "..\n";
|
||||
|
||||
ostr << fmt::format("{}\n", fmt::join(c.requires_statements(), "\n"));
|
||||
}
|
||||
|
||||
ostr << "}" << '\n';
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "concept.h"
|
||||
#include "method_parameter.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -69,4 +70,24 @@ std::string concept_::full_name(bool relative) const
|
||||
return res;
|
||||
}
|
||||
|
||||
void concept_::add_parameter(method_parameter mp)
|
||||
{
|
||||
requires_parameters_.emplace_back(std::move(mp));
|
||||
}
|
||||
|
||||
const std::vector<method_parameter> &concept_::requires_parameters() const
|
||||
{
|
||||
return requires_parameters_;
|
||||
}
|
||||
|
||||
void concept_::add_statement(std::string stmt)
|
||||
{
|
||||
requires_statements_.emplace_back(std::move(stmt));
|
||||
}
|
||||
|
||||
const std::vector<std::string> &concept_::requires_statements() const
|
||||
{
|
||||
return requires_statements_;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "class_diagram/model/method_parameter.h"
|
||||
#include "common/model/element.h"
|
||||
#include "common/model/stylable_element.h"
|
||||
#include "common/model/template_parameter.h"
|
||||
@@ -52,8 +53,19 @@ public:
|
||||
|
||||
std::string full_name_no_ns() const override;
|
||||
|
||||
void add_parameter(method_parameter mp);
|
||||
|
||||
const std::vector<method_parameter> &requires_parameters() const;
|
||||
|
||||
void add_statement(std::string stmt);
|
||||
|
||||
const std::vector<std::string> &requires_statements() const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> requires_expression_;
|
||||
|
||||
std::string full_name_;
|
||||
std::vector<method_parameter> requires_parameters_;
|
||||
|
||||
std::vector<std::string> requires_statements_;
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,14 @@
|
||||
|
||||
namespace clanguml::class_diagram::model {
|
||||
|
||||
method_parameter::method_parameter(
|
||||
std::string type, std::string name, std::string default_value)
|
||||
: type_{std::move(type)}
|
||||
, name_{std::move(name)}
|
||||
, default_value_{std::move(default_value)}
|
||||
{
|
||||
}
|
||||
|
||||
void method_parameter::set_type(const std::string &type) { type_ = type; }
|
||||
|
||||
std::string method_parameter::type() const { return type_; }
|
||||
@@ -43,10 +51,14 @@ std::string method_parameter::to_string(
|
||||
using namespace clanguml::util;
|
||||
auto type_ns =
|
||||
using_namespace.relative(common::model::namespace_{type()}.to_string());
|
||||
if (default_value().empty())
|
||||
return fmt::format("{} {}", type_ns, name());
|
||||
|
||||
return fmt::format("{} {} = {}", type_ns, name(), default_value());
|
||||
auto name_ns =
|
||||
using_namespace.relative(common::model::namespace_{name()}.to_string());
|
||||
|
||||
if (default_value().empty())
|
||||
return fmt::format("{} {}", type_ns, name_ns);
|
||||
|
||||
return fmt::format("{} {} = {}", type_ns, name_ns, default_value());
|
||||
}
|
||||
|
||||
} // namespace clanguml::class_diagram::model
|
||||
|
||||
@@ -27,6 +27,10 @@ namespace clanguml::class_diagram::model {
|
||||
|
||||
class method_parameter : public common::model::decorated_element {
|
||||
public:
|
||||
method_parameter() = default;
|
||||
method_parameter(
|
||||
std::string type, std::string name, std::string default_value = {});
|
||||
|
||||
void set_type(const std::string &type);
|
||||
std::string type() const;
|
||||
|
||||
|
||||
@@ -401,73 +401,13 @@ bool translation_unit_visitor::TraverseConceptDecl(clang::ConceptDecl *cpt)
|
||||
|
||||
process_template_parameters(*cpt, *concept_model);
|
||||
|
||||
if (const auto *constraint =
|
||||
clang::dyn_cast<clang::RequiresExpr>(cpt->getConstraintExpr());
|
||||
constraint) {
|
||||
if (cpt->getConstraintExpr()) {
|
||||
process_constraint_requirements(
|
||||
cpt, cpt->getConstraintExpr(), *concept_model);
|
||||
|
||||
auto constraint_source = common::to_string(constraint);
|
||||
|
||||
LOG_DBG("== Processing constraint: '{}'", constraint_source);
|
||||
|
||||
for (const auto *requirement : constraint->getRequirements()) {
|
||||
LOG_DBG("== Processing requirement: '{}'", requirement->getKind());
|
||||
}
|
||||
|
||||
// process 'requires (...)' declaration
|
||||
for (const auto *decl : constraint->getBody()->decls()) {
|
||||
if (const auto *parm_var_decl =
|
||||
clang::dyn_cast<clang::ParmVarDecl>(decl);
|
||||
parm_var_decl) {
|
||||
parm_var_decl->getQualifiedNameAsString();
|
||||
|
||||
LOG_DBG("=== Processing parameter variable declaration: {}, {}",
|
||||
parm_var_decl->getQualifiedNameAsString(),
|
||||
common::to_string(
|
||||
parm_var_decl->getType(), cpt->getASTContext()));
|
||||
}
|
||||
else {
|
||||
LOG_DBG(
|
||||
"=== Processing some other declaration: {}", decl->getID());
|
||||
}
|
||||
}
|
||||
|
||||
// process concept body requirements '{ }' if any
|
||||
for (const auto *req : constraint->getRequirements()) {
|
||||
if (req->getKind() == clang::concepts::Requirement::RK_Simple) {
|
||||
const auto *simple_req =
|
||||
clang::dyn_cast<clang::concepts::ExprRequirement>(req);
|
||||
LOG_DBG("=== Processing expression requirement: {}",
|
||||
common::to_string(simple_req->getExpr()));
|
||||
}
|
||||
else if (req->getKind() == clang::concepts::Requirement::RK_Type) {
|
||||
const auto *type_req =
|
||||
clang::dyn_cast<clang::concepts::TypeRequirement>(req);
|
||||
LOG_DBG(
|
||||
"=== Processing type requirement: {}", type_req->getKind());
|
||||
}
|
||||
else if (req->getKind() ==
|
||||
clang::concepts::Requirement::RK_Nested) {
|
||||
const auto *nested_req =
|
||||
clang::dyn_cast<clang::concepts::NestedRequirement>(req);
|
||||
LOG_DBG("=== Processing nested requirement: {}",
|
||||
common::to_string(nested_req->getConstraintExpr()));
|
||||
}
|
||||
else if (req->getKind() ==
|
||||
clang::concepts::Requirement::RK_Compound) {
|
||||
const auto *nested_req =
|
||||
clang::dyn_cast<clang::concepts::ExprRequirement>(req);
|
||||
LOG_DBG("=== Processing compound requirement: {}",
|
||||
common::to_string(nested_req->getExpr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO
|
||||
}
|
||||
|
||||
if (cpt->getConstraintExpr())
|
||||
find_relationships_in_constraint_expression(
|
||||
*concept_model, cpt->getConstraintExpr());
|
||||
}
|
||||
|
||||
if (diagram_.should_include(*concept_model)) {
|
||||
LOG_DBG("Adding concept {} with id {}", concept_model->full_name(false),
|
||||
@@ -483,6 +423,112 @@ bool translation_unit_visitor::TraverseConceptDecl(clang::ConceptDecl *cpt)
|
||||
return true;
|
||||
}
|
||||
|
||||
void translation_unit_visitor::process_constraint_requirements(
|
||||
const clang::ConceptDecl *cpt, const clang::Expr *expr,
|
||||
model::concept_ &concept_model) const
|
||||
{
|
||||
if (const auto *constraint = llvm::dyn_cast<clang::RequiresExpr>(expr);
|
||||
constraint) {
|
||||
|
||||
auto constraint_source = common::to_string(constraint);
|
||||
|
||||
LOG_DBG("== Processing constraint: '{}'", constraint_source);
|
||||
|
||||
for (const auto *requirement : constraint->getRequirements()) {
|
||||
LOG_DBG("== Processing requirement: '{}'", requirement->getKind());
|
||||
}
|
||||
|
||||
// process 'requires (...)' declaration
|
||||
for (const auto *decl : constraint->getBody()->decls()) {
|
||||
if (const auto *parm_var_decl =
|
||||
llvm::dyn_cast<clang::ParmVarDecl>(decl);
|
||||
parm_var_decl) {
|
||||
parm_var_decl->getQualifiedNameAsString();
|
||||
|
||||
auto param_name = parm_var_decl->getQualifiedNameAsString();
|
||||
auto param_type = common::to_string(
|
||||
parm_var_decl->getType(), cpt->getASTContext());
|
||||
|
||||
LOG_DBG("=== Processing parameter variable declaration: {}, {}",
|
||||
param_name, param_type);
|
||||
|
||||
concept_model.add_parameter(
|
||||
{std::move(param_type), std::move(param_name)});
|
||||
}
|
||||
else {
|
||||
LOG_DBG("=== Processing some other concept declaration: {}",
|
||||
decl->getID());
|
||||
}
|
||||
}
|
||||
|
||||
// process concept body requirements '{ }' if any
|
||||
for (const auto *req : constraint->getRequirements()) {
|
||||
if (req->getKind() == clang::concepts::Requirement::RK_Simple) {
|
||||
const auto *simple_req =
|
||||
llvm::dyn_cast<clang::concepts::ExprRequirement>(req);
|
||||
|
||||
auto simple_expr = common::to_string(simple_req->getExpr());
|
||||
|
||||
LOG_DBG(
|
||||
"=== Processing expression requirement: {}", simple_expr);
|
||||
|
||||
concept_model.add_statement(std::move(simple_expr));
|
||||
}
|
||||
else if (req->getKind() == clang::concepts::Requirement::RK_Type) {
|
||||
const auto *type_req =
|
||||
llvm::dyn_cast<clang::concepts::TypeRequirement>(req);
|
||||
|
||||
auto type_name = common::to_string(
|
||||
type_req->getType()->getType(), cpt->getASTContext());
|
||||
|
||||
LOG_DBG("=== Processing type requirement: {}", type_name);
|
||||
|
||||
concept_model.add_statement(std::move(type_name));
|
||||
}
|
||||
else if (req->getKind() ==
|
||||
clang::concepts::Requirement::RK_Nested) {
|
||||
const auto *nested_req =
|
||||
llvm::dyn_cast<clang::concepts::NestedRequirement>(req);
|
||||
|
||||
LOG_DBG("=== Processing nested requirement: {}",
|
||||
common::to_string(nested_req->getConstraintExpr()));
|
||||
}
|
||||
else if (req->getKind() ==
|
||||
clang::concepts::Requirement::RK_Compound) {
|
||||
const auto *compound_req =
|
||||
llvm::dyn_cast<clang::concepts::ExprRequirement>(req);
|
||||
|
||||
auto compound_expr = common::to_string(compound_req->getExpr());
|
||||
|
||||
auto req_return_type = compound_req->getReturnTypeRequirement();
|
||||
|
||||
if (!req_return_type.isEmpty()) {
|
||||
compound_expr = fmt::format("{{{}}} -> {}", compound_expr,
|
||||
common::to_string(req_return_type.getTypeConstraint()));
|
||||
}
|
||||
else if (compound_req->hasNoexceptRequirement()) {
|
||||
compound_expr =
|
||||
fmt::format("{{{}}} noexcept", compound_expr);
|
||||
}
|
||||
|
||||
LOG_DBG(
|
||||
"=== Processing compound requirement: {}", compound_expr);
|
||||
|
||||
concept_model.add_statement(std::move(compound_expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const auto *binop = llvm::dyn_cast<clang::BinaryOperator>(expr);
|
||||
binop) {
|
||||
process_constraint_requirements(cpt, binop->getLHS(), concept_model);
|
||||
process_constraint_requirements(cpt, binop->getRHS(), concept_model);
|
||||
}
|
||||
else if (const auto *unop = llvm::dyn_cast<clang::UnaryOperator>(expr);
|
||||
unop) {
|
||||
process_constraint_requirements(cpt, unop->getSubExpr(), concept_model);
|
||||
}
|
||||
}
|
||||
|
||||
void translation_unit_visitor::find_relationships_in_constraint_expression(
|
||||
clanguml::common::model::element &c, const clang::Expr *expr)
|
||||
{
|
||||
|
||||
@@ -270,6 +270,9 @@ private:
|
||||
bool simplify_system_template(common::model::template_parameter &ct,
|
||||
const std::string &full_name) const;
|
||||
|
||||
void process_constraint_requirements(const clang::ConceptDecl *cpt,
|
||||
const clang::Expr *expr, model::concept_ &concept_model) const;
|
||||
|
||||
void process_concept_specialization_relationships(common::model::element &c,
|
||||
const clang::ConceptSpecializationExpr *concept_specialization);
|
||||
|
||||
|
||||
@@ -228,6 +228,21 @@ std::string to_string(const clang::FunctionTemplateDecl *decl)
|
||||
fmt::join(template_parameters, ","), "");
|
||||
}
|
||||
|
||||
std::string to_string(const clang::TypeConstraint *tc)
|
||||
{
|
||||
if (tc == nullptr)
|
||||
return {};
|
||||
|
||||
const clang::PrintingPolicy print_policy(
|
||||
tc->getNamedConcept()->getASTContext().getLangOpts());
|
||||
|
||||
std::string ostream_buf;
|
||||
llvm::raw_string_ostream ostream{ostream_buf};
|
||||
tc->print(ostream, print_policy);
|
||||
|
||||
return ostream.str();
|
||||
}
|
||||
|
||||
std::string get_source_text_raw(
|
||||
clang::SourceRange range, const clang::SourceManager &sm)
|
||||
{
|
||||
|
||||
@@ -90,6 +90,8 @@ std::string to_string(const clang::Stmt *stmt);
|
||||
|
||||
std::string to_string(const clang::FunctionTemplateDecl *decl);
|
||||
|
||||
std::string to_string(const clang::TypeConstraint *tc);
|
||||
|
||||
std::string get_source_text_raw(
|
||||
clang::SourceRange range, const clang::SourceManager &sm);
|
||||
|
||||
|
||||
@@ -32,9 +32,16 @@ concept has_value_type = requires
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept convertible_to_string = requires(T s)
|
||||
concept convertible_to_string = max_four_bytes<T> && requires(T s)
|
||||
{
|
||||
std::string{s};
|
||||
{
|
||||
std::to_string(s)
|
||||
}
|
||||
noexcept;
|
||||
{
|
||||
std::to_string(s)
|
||||
} -> std::same_as<std::string>;
|
||||
};
|
||||
|
||||
// Compound requirement
|
||||
|
||||
@@ -44,6 +44,24 @@ TEST_CASE("t00056", "[test-case][class]")
|
||||
REQUIRE_THAT(puml, IsConcept(_A("iterable_with_value_type<T>")));
|
||||
REQUIRE_THAT(puml, IsConcept(_A("iterable_or_small_value_type<T>")));
|
||||
|
||||
REQUIRE_THAT(puml,
|
||||
IsConceptRequirement(
|
||||
_A("greater_than_with_requires<T,P>"), "sizeof (l) > sizeof (r)"));
|
||||
|
||||
REQUIRE_THAT(
|
||||
puml, IsConceptRequirement(_A("iterable<T>"), "container.begin()"));
|
||||
REQUIRE_THAT(
|
||||
puml, IsConceptRequirement(_A("iterable<T>"), "container.end()"));
|
||||
|
||||
REQUIRE_THAT(puml,
|
||||
IsConceptRequirement(_A("convertible_to_string<T>"), "std::string{s}"));
|
||||
REQUIRE_THAT(puml,
|
||||
IsConceptRequirement(
|
||||
_A("convertible_to_string<T>"), "{std::to_string(s)} noexcept"));
|
||||
REQUIRE_THAT(puml,
|
||||
IsConceptRequirement(_A("convertible_to_string<T>"),
|
||||
"{std::to_string(s)} -> std::same_as<std::string>"));
|
||||
|
||||
// Check if class templates exist
|
||||
REQUIRE_THAT(puml, IsClassTemplate("A", "max_four_bytes T"));
|
||||
REQUIRE_THAT(puml, IsClassTemplate("B", "T"));
|
||||
|
||||
@@ -445,6 +445,13 @@ ContainsMatcher IsConstraint(std::string const &from, std::string const &to,
|
||||
fmt::format("{} ..> {} : {}", from, to, label), caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsConceptRequirement(std::string const &cpt,
|
||||
std::string const &requirement,
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
return ContainsMatcher(CasedString(requirement, caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsLayoutHint(std::string const &from, std::string const &hint,
|
||||
std::string const &to,
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
|
||||
Reference in New Issue
Block a user