From dbb3e68c3f95a66386c4f6fa460f01869703d21b Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 26 Feb 2023 23:29:55 +0100 Subject: [PATCH] Added rendering of concept requirements in concept body --- .../plantuml/class_diagram_generator.cc | 16 ++ src/class_diagram/model/concept.cc | 21 +++ src/class_diagram/model/concept.h | 14 +- src/class_diagram/model/method_parameter.cc | 18 +- src/class_diagram/model/method_parameter.h | 4 + .../visitor/translation_unit_visitor.cc | 174 +++++++++++------- .../visitor/translation_unit_visitor.h | 3 + src/common/clang_utils.cc | 15 ++ src/common/clang_utils.h | 2 + tests/t00056/t00056.cc | 9 +- tests/t00056/test_case.h | 18 ++ tests/test_cases.h | 7 + 12 files changed, 232 insertions(+), 69 deletions(-) diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 6961a439..74c792fc 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -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 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'; } diff --git a/src/class_diagram/model/concept.cc b/src/class_diagram/model/concept.cc index fa4a4a18..0abc55bd 100644 --- a/src/class_diagram/model/concept.cc +++ b/src/class_diagram/model/concept.cc @@ -17,6 +17,7 @@ */ #include "concept.h" +#include "method_parameter.h" #include @@ -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 &concept_::requires_parameters() const +{ + return requires_parameters_; +} + +void concept_::add_statement(std::string stmt) +{ + requires_statements_.emplace_back(std::move(stmt)); +} + +const std::vector &concept_::requires_statements() const +{ + return requires_statements_; +} + } diff --git a/src/class_diagram/model/concept.h b/src/class_diagram/model/concept.h index 1ae9349e..f18c29ee 100644 --- a/src/class_diagram/model/concept.h +++ b/src/class_diagram/model/concept.h @@ -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 &requires_parameters() const; + + void add_statement(std::string stmt); + + const std::vector &requires_statements() const; + +private: std::vector requires_expression_; - std::string full_name_; + std::vector requires_parameters_; + + std::vector requires_statements_; }; } \ No newline at end of file diff --git a/src/class_diagram/model/method_parameter.cc b/src/class_diagram/model/method_parameter.cc index f072344c..e6a66a60 100644 --- a/src/class_diagram/model/method_parameter.cc +++ b/src/class_diagram/model/method_parameter.cc @@ -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 diff --git a/src/class_diagram/model/method_parameter.h b/src/class_diagram/model/method_parameter.h index acd7ca73..2709dcc6 100644 --- a/src/class_diagram/model/method_parameter.h +++ b/src/class_diagram/model/method_parameter.h @@ -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; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 0db5fc74..916397d5 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -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(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(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(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(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(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(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(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(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(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(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(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(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(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(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) { diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 04192cd3..351ac8a1 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -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); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 88d033e3..4910cf1d 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -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) { diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 584e7af3..4cc0d547 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -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); diff --git a/tests/t00056/t00056.cc b/tests/t00056/t00056.cc index 57af9f4e..6fec5863 100644 --- a/tests/t00056/t00056.cc +++ b/tests/t00056/t00056.cc @@ -32,9 +32,16 @@ concept has_value_type = requires }; template -concept convertible_to_string = requires(T s) +concept convertible_to_string = max_four_bytes && requires(T s) { std::string{s}; + { + std::to_string(s) + } + noexcept; + { + std::to_string(s) + } -> std::same_as; }; // Compound requirement diff --git a/tests/t00056/test_case.h b/tests/t00056/test_case.h index caa3ec9e..a708ebb3 100644 --- a/tests/t00056/test_case.h +++ b/tests/t00056/test_case.h @@ -44,6 +44,24 @@ TEST_CASE("t00056", "[test-case][class]") REQUIRE_THAT(puml, IsConcept(_A("iterable_with_value_type"))); REQUIRE_THAT(puml, IsConcept(_A("iterable_or_small_value_type"))); + REQUIRE_THAT(puml, + IsConceptRequirement( + _A("greater_than_with_requires"), "sizeof (l) > sizeof (r)")); + + REQUIRE_THAT( + puml, IsConceptRequirement(_A("iterable"), "container.begin()")); + REQUIRE_THAT( + puml, IsConceptRequirement(_A("iterable"), "container.end()")); + + REQUIRE_THAT(puml, + IsConceptRequirement(_A("convertible_to_string"), "std::string{s}")); + REQUIRE_THAT(puml, + IsConceptRequirement( + _A("convertible_to_string"), "{std::to_string(s)} noexcept")); + REQUIRE_THAT(puml, + IsConceptRequirement(_A("convertible_to_string"), + "{std::to_string(s)} -> std::same_as")); + // Check if class templates exist REQUIRE_THAT(puml, IsClassTemplate("A", "max_four_bytes T")); REQUIRE_THAT(puml, IsClassTemplate("B", "T")); diff --git a/tests/test_cases.h b/tests/test_cases.h index 25134416..1ff9fa73 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -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)