From 274a69871322d072158d03ea4e9cf9e5a150ff91 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 25 Feb 2023 01:50:07 +0100 Subject: [PATCH] Initial support for concept dependency relationships in class diagrams --- .../plantuml/class_diagram_generator.cc | 160 +++++++++++++ .../plantuml/class_diagram_generator.h | 8 + src/class_diagram/model/class.h | 4 - src/class_diagram/model/concept.cc | 72 ++++++ src/class_diagram/model/concept.h | 59 +++++ src/class_diagram/model/diagram.cc | 105 ++++++++- src/class_diagram/model/diagram.h | 14 ++ .../visitor/translation_unit_visitor.cc | 221 +++++++++++++++++- .../visitor/translation_unit_visitor.h | 17 +- src/common/clang_utils.cc | 8 + src/common/clang_utils.h | 3 + src/common/model/template_parameter.cc | 23 +- src/common/model/template_parameter.h | 7 + src/util/util.h | 33 +++ tests/t00056/t00056.cc | 50 ++-- 15 files changed, 750 insertions(+), 34 deletions(-) create mode 100644 src/class_diagram/model/concept.cc create mode 100644 src/class_diagram/model/concept.h diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 8b18db8b..c41385cc 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -102,6 +102,23 @@ void generator::generate_alias(const enum_ &e, std::ostream &ostr) const m_generated_aliases.emplace(e.alias()); } +void generator::generate_alias(const concept_ &c, std::ostream &ostr) const +{ + print_debug(c, ostr); + + if (m_config.generate_packages()) + ostr << "annotation" + << " \"" << c.name(); + else + ostr << "annotation" + << " \"" << render_name(c.full_name()); + + ostr << "\" as " << c.alias() << '\n'; + + // Register the added alias + m_generated_aliases.emplace(c.alias()); +} + void generator::generate(const class_ &c, std::ostream &ostr) const { namespace plantuml_common = clanguml::common::generators::plantuml; @@ -279,6 +296,26 @@ void generator::generate(const class_ &c, std::ostream &ostr) const generate_member_notes(ostr, method, c.alias()); } +void generator::generate(const concept_ &c, std::ostream &ostr) const +{ + namespace plantuml_common = clanguml::common::generators::plantuml; + + std::string class_type{"annotation"}; + + ostr << class_type << " " << c.alias() << " <>"; + + if (m_config.generate_links) { + common_generator::generate_link(ostr, c); + } + + if (!c.style().empty()) + ostr << " " << c.style(); + + ostr << " {" << '\n'; + + ostr << "}" << '\n'; +} + void generator::generate_member_notes(std::ostream &ostr, const class_element &member, const std::string &alias) const { @@ -309,6 +346,11 @@ void generator::generate_relationships(std::ostream &ostr) const generate_relationships(*enm, ostr); } } + else if (auto *cpt = dynamic_cast(p.get()); cpt) { + if (m_model.should_include(*cpt)) { + generate_relationships(*cpt, ostr); + } + } } } @@ -409,6 +451,82 @@ void generator::generate_relationships( ostr << all_relations_str.str(); } +void generator::generate_relationships( + const concept_ &c, std::ostream &ostr) const +{ + namespace plantuml_common = clanguml::common::generators::plantuml; + + // + // Process relationships + // + std::set rendered_relations; + + std::stringstream all_relations_str; + std::set unique_relations; + + for (const auto &r : c.relationships()) { + if (!m_model.should_include(r.type())) + continue; + + LOG_DBG("== Processing relationship {}", + plantuml_common::to_plantuml(r.type(), r.style())); + + std::stringstream relstr; + clanguml::common::id_t destination{0}; + try { + destination = r.destination(); + + std::string puml_relation; + if (!r.multiplicity_source().empty()) + puml_relation += "\"" + r.multiplicity_source() + "\" "; + + puml_relation += plantuml_common::to_plantuml(r.type(), r.style()); + + if (!r.multiplicity_destination().empty()) + puml_relation += " \"" + r.multiplicity_destination() + "\""; + + std::string target_alias; + try { + target_alias = m_model.to_alias(destination); + } + catch (...) { + LOG_DBG("Failed to find alias to {}", destination); + continue; + } + + if (m_generated_aliases.find(target_alias) == + m_generated_aliases.end()) + continue; + + relstr << c.alias() << " " << puml_relation << " " << target_alias; + + if (!r.label().empty()) { + relstr << " : " << plantuml_common::to_plantuml(r.access()) + << r.label(); + rendered_relations.emplace(r.label()); + } + + if (unique_relations.count(relstr.str()) == 0) { + unique_relations.emplace(relstr.str()); + + relstr << '\n'; + + LOG_DBG("=== Adding relation {}", relstr.str()); + + all_relations_str << relstr.str(); + } + } + catch (error::uml_alias_missing &e) { + LOG_DBG("=== Skipping {} relation from {} to {} due " + "to: {}", + plantuml_common::to_plantuml(r.type(), r.style()), + c.full_name(), destination, e.what()); + } + } + + ostr << all_relations_str.str(); +} + void generator::generate(const enum_ &e, std::ostream &ostr) const { ostr << "enum " << e.alias(); @@ -534,6 +652,20 @@ void generator::generate(const package &p, std::ostream &ostr) const } } } + else if (auto *cpt = dynamic_cast(subpackage.get()); cpt) { + if (m_model.should_include(*subpackage)) { + auto together_group = + m_config.get_together_group(cpt->full_name(false)); + if (together_group) { + together_group_stack_.group_together( + together_group.value(), cpt); + } + else { + generate_alias(*cpt, ostr); + generate(*cpt, ostr); + } + } + } } if (m_config.generate_packages()) { @@ -552,6 +684,10 @@ void generator::generate(const package &p, std::ostream &ostr) const generate_alias(*enm, ostr); generate(*enm, ostr); } + if (auto *cpt = dynamic_cast(e); cpt) { + generate_alias(*cpt, ostr); + generate(*cpt, ostr); + } } ostr << "}\n"; @@ -589,6 +725,12 @@ void generator::generate_relationships( dynamic_cast(*subpackage), ostr); } } + else if (dynamic_cast(subpackage.get()) != nullptr) { + if (m_model.should_include(*subpackage)) { + generate_relationships( + dynamic_cast(*subpackage), ostr); + } + } } } @@ -648,6 +790,20 @@ void generator::generate_top_level_elements(std::ostream &ostr) const } } } + else if (auto *cpt = dynamic_cast(p.get()); cpt) { + if (m_model.should_include(*cpt)) { + auto together_group = + m_config.get_together_group(cpt->full_name(false)); + if (together_group) { + together_group_stack_.group_together( + together_group.value(), cpt); + } + else { + generate_alias(*cpt, ostr); + generate(*cpt, ostr); + } + } + } } } @@ -666,6 +822,10 @@ void generator::generate_groups(std::ostream &ostr) const generate_alias(*enm, ostr); generate(*enm, ostr); } + if (auto *cpt = dynamic_cast(e); cpt) { + generate_alias(*cpt, ostr); + generate(*cpt, ostr); + } } ostr << "}\n"; diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.h b/src/class_diagram/generators/plantuml/class_diagram_generator.h index 063a286b..4929c7c1 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.h +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.h @@ -18,6 +18,7 @@ #pragma once #include "class_diagram/model/class.h" +#include "class_diagram/model/concept.h" #include "class_diagram/model/diagram.h" #include "class_diagram/model/enum.h" #include "class_diagram/visitor/translation_unit_visitor.h" @@ -47,6 +48,7 @@ using common_generator = using clanguml::class_diagram::model::class_; using clanguml::class_diagram::model::class_element; +using clanguml::class_diagram::model::concept_; using clanguml::class_diagram::model::enum_; using clanguml::common::model::access_t; using clanguml::common::model::package; @@ -65,6 +67,8 @@ public: void generate_alias(const enum_ &e, std::ostream &ostr) const; + void generate_alias(const concept_ &c, std::ostream &ostr) const; + void generate(const class_ &c, std::ostream &ostr) const; void generate_top_level_elements(std::ostream &ostr) const; @@ -77,6 +81,10 @@ public: void generate_relationships(const enum_ &c, std::ostream &ostr) const; + void generate(const concept_ &c, std::ostream &ostr) const; + + void generate_relationships(const concept_ &c, std::ostream &ostr) const; + void generate(const package &p, std::ostream &ostr) const; void generate_relationships(const package &p, std::ostream &ostr) const; diff --git a/src/class_diagram/model/class.h b/src/class_diagram/model/class.h index eb346177..97b55e19 100644 --- a/src/class_diagram/model/class.h +++ b/src/class_diagram/model/class.h @@ -79,9 +79,6 @@ public: bool is_abstract() const; - bool is_concept() const { return is_concept_; } - void is_concept(bool concept_) { is_concept_ = concept_; } - void find_relationships( std::vector> &nested_relationships); @@ -95,7 +92,6 @@ private: bool is_template_instantiation_{false}; bool is_alias_{false}; bool is_union_{false}; - bool is_concept_{false}; std::vector members_; std::vector methods_; std::vector bases_; diff --git a/src/class_diagram/model/concept.cc b/src/class_diagram/model/concept.cc new file mode 100644 index 00000000..fa4a4a18 --- /dev/null +++ b/src/class_diagram/model/concept.cc @@ -0,0 +1,72 @@ +/** + * src/class_diagram/model/concept.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "concept.h" + +#include + +namespace clanguml::class_diagram::model { + +concept_::concept_(const common::model::namespace_ &using_namespace) + : element{using_namespace} +{ +} + +bool operator==(const concept_ &l, const concept_ &r) +{ + return l.id() == r.id(); +} + +std::string concept_::full_name_no_ns() const +{ + using namespace clanguml::util; + + std::ostringstream ostr; + + ostr << name(); + + render_template_params(ostr, using_namespace(), false); + + return ostr.str(); +} + +std::string concept_::full_name(bool relative) const +{ + using namespace clanguml::util; + using clanguml::common::model::namespace_; + + std::ostringstream ostr; + + ostr << name_and_ns(); + + render_template_params(ostr, using_namespace(), relative); + + std::string res; + + if (relative) + res = using_namespace().relative(ostr.str()); + else + res = ostr.str(); + + if (res.empty()) + return "<>"; + + return res; +} + +} diff --git a/src/class_diagram/model/concept.h b/src/class_diagram/model/concept.h new file mode 100644 index 00000000..1ae9349e --- /dev/null +++ b/src/class_diagram/model/concept.h @@ -0,0 +1,59 @@ +/** + * src/class_diagram/model/concept.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "common/model/element.h" +#include "common/model/stylable_element.h" +#include "common/model/template_parameter.h" +#include "common/model/template_trait.h" +#include "common/types.h" + +#include +#include + +namespace clanguml::class_diagram::model { + +struct requires_expression { + common::model::template_parameter parameter; + std::vector requirements; +}; + +class concept_ : public common::model::element, + public common::model::stylable_element, + public common::model::template_trait { +public: + concept_(const common::model::namespace_ &using_namespace); + + concept_(const concept_ &) = delete; + concept_(concept_ &&) noexcept = default; + concept_ &operator=(const concept_ &) = delete; + concept_ &operator=(concept_ &&) = delete; + + std::string type_name() const override { return "concept"; } + + friend bool operator==(const concept_ &l, const concept_ &r); + + std::string full_name(bool relative = true) const override; + + std::string full_name_no_ns() const override; + + std::vector requires_expression_; + + std::string full_name_; +}; +} \ No newline at end of file diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 2a365684..56b37016 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -32,6 +32,11 @@ const common::reference_vector &diagram::classes() const const common::reference_vector &diagram::enums() const { return enums_; } +const common::reference_vector &diagram::concepts() const +{ + return concepts_; +} + common::model::diagram_t diagram::type() const { return common::model::diagram_t::kClass; @@ -48,6 +53,11 @@ common::optional_ref diagram::get( res = get_enum(full_name); + if (res.has_value()) + return res; + + res = get_concept(full_name); + return res; } @@ -63,6 +73,11 @@ common::optional_ref diagram::get( res = get_enum(id); + if (res.has_value()) + return res; + + res = get_concept(id); + return res; } @@ -78,6 +93,12 @@ bool diagram::has_enum(const enum_ &e) const [&e](const auto &ee) { return ee.get().full_name() == e.full_name(); }); } +bool diagram::has_concept(const concept_ &c) const +{ + return std::any_of(concepts_.cbegin(), concepts_.cend(), + [&c](const auto &cc) { return cc.get() == c; }); +} + common::optional_ref diagram::get_class(const std::string &name) const { for (const auto &c : classes_) { @@ -126,6 +147,32 @@ common::optional_ref diagram::get_enum( return {}; } +common::optional_ref diagram::get_concept( + const std::string &name) const +{ + for (const auto &c : concepts_) { + const auto full_name = c.get().full_name(false); + + if (full_name == name) { + return {c}; + } + } + + return {}; +} + +common::optional_ref diagram::get_concept( + clanguml::common::model::diagram_element::id_t id) const +{ + for (const auto &c : concepts_) { + if (c.get().id() == id) { + return {c}; + } + } + + return {}; +} + bool diagram::add_package(std::unique_ptr &&p) { LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true)); @@ -173,8 +220,8 @@ bool diagram::add_class(std::unique_ptr &&c) } } catch (const std::runtime_error &e) { - LOG_WARN("Cannot add template specialization {} with id {} due to: {}", - name, id, e.what()); + LOG_WARN( + "Cannot add concept {} with id {} due to: {}", name, id, e.what()); return false; } @@ -207,6 +254,55 @@ bool diagram::add_enum(std::unique_ptr &&e) return false; } +bool diagram::add_concept(std::unique_ptr &&c) +{ + const auto base_name = c->name(); + const auto full_name = c->full_name(false); + + LOG_DBG("Adding concept: {}::{}, {}", c->get_namespace().to_string(), + base_name, full_name); + + if (util::contains(base_name, "::")) + throw std::runtime_error("Name cannot contain namespace: " + base_name); + + if (util::contains(base_name, "*")) + throw std::runtime_error("Name cannot contain *: " + base_name); + + const auto ns = c->get_relative_namespace(); + auto name = base_name; + auto name_with_ns = c->name_and_ns(); + auto name_and_ns = ns | name; + auto &cc = *c; + auto id = cc.id(); + + try { + if (!has_concept(cc)) { + if (add_element(ns, std::move(c))) + concepts_.push_back(std::ref(cc)); + + const auto &el = get_element(name_and_ns).value(); + + if ((el.name() != name) || !(el.get_relative_namespace() == ns)) + throw std::runtime_error( + "Invalid element stored in the diagram tree"); + + LOG_DBG("Added concept {} ({} - [{}])", base_name, full_name, id); + + return true; + } + } + catch (const std::runtime_error &e) { + LOG_WARN( + "Cannot add concept {} with id {} due to: {}", name, id, e.what()); + return false; + } + + LOG_DBG("Concept {} ({} - [{}]) already in the model", base_name, full_name, + id); + + return false; +} + void diagram::get_parents( clanguml::common::reference_set &parents) const { @@ -257,6 +353,11 @@ std::string diagram::to_alias( return e.get().alias(); } + for (const auto &c : concepts_) { + if (c.get().id() == id) + return c.get().alias(); + } + throw error::uml_alias_missing(fmt::format("Missing alias for {}", id)); } diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 61ffee1a..cb0e39b5 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -22,6 +22,7 @@ #include "common/model/nested_trait.h" #include "common/model/package.h" #include "common/types.h" +#include "concept.h" #include "enum.h" #include @@ -54,10 +55,14 @@ public: const common::reference_vector &enums() const; + const common::reference_vector &concepts() const; + bool has_class(const class_ &c) const; bool has_enum(const enum_ &e) const; + bool has_concept(const concept_ &e) const; + common::optional_ref get_class(const std::string &name) const; common::optional_ref get_class( @@ -68,10 +73,17 @@ public: common::optional_ref get_enum( clanguml::common::model::diagram_element::id_t id) const; + common::optional_ref get_concept(const std::string &name) const; + + common::optional_ref get_concept( + clanguml::common::model::diagram_element::id_t id) const; + bool add_class(std::unique_ptr &&c); bool add_enum(std::unique_ptr &&e); + bool add_concept(std::unique_ptr &&e); + bool add_package(std::unique_ptr &&p); std::string to_alias( @@ -90,6 +102,8 @@ private: common::reference_vector classes_; common::reference_vector enums_; + + common::reference_vector concepts_; }; } // namespace clanguml::class_diagram::model diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 62ddcafa..5bfc8bc0 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -19,6 +19,7 @@ #include "translation_unit_visitor.h" #include "common/clang_utils.h" +#include #include #include #include @@ -284,7 +285,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl( // Override the id with the template id, for now we don't care about the // underlying templated class id - process_template_parameters(*cls, *c_ptr); + process_template_parameters(*cls, *c_ptr, *c_ptr); const auto cls_full_name = c_ptr->full_name(false); const auto id = common::to_id(cls_full_name); @@ -293,6 +294,15 @@ bool translation_unit_visitor::VisitClassTemplateDecl( set_ast_local_id(cls->getID(), id); + llvm::SmallVector constraints{}; + if (cls->hasAssociatedConstraints()) { + cls->getAssociatedConstraints(constraints); + } + + for (const auto *expr : constraints) { + find_relationships_in_constraint_expression(*c_ptr, expr); + } + if (!cls->getTemplatedDecl()->isCompleteDefinition()) { forward_declarations_.emplace(id, std::move(c_ptr)); return true; @@ -367,6 +377,155 @@ bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *rec) return true; } +bool translation_unit_visitor::TraverseConceptDecl(clang::ConceptDecl *cpt) +{ + // Skip system headers + if (source_manager().isInSystemHeader(cpt->getSourceRange().getBegin())) + return true; + + if (!diagram().should_include(cpt->getQualifiedNameAsString())) + return true; + + LOG_DBG("= Visiting concept (isType: {}) declaration {} at {}", + cpt->isTypeConcept(), cpt->getQualifiedNameAsString(), + cpt->getLocation().printToString(source_manager())); + + auto concept_model = create_concept_declaration(cpt); + + if (!concept_model) + return true; + + const auto concept_id = concept_model->id(); + + set_ast_local_id(cpt->getID(), concept_id); + + process_template_parameters(*cpt, *concept_model); + + if (const auto *constraint = + clang::dyn_cast(cpt->getConstraintExpr()); + 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()) { + // decl->dump(); + + 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), + concept_model->id()); + + diagram_.add_concept(std::move(concept_model)); + } + else { + LOG_DBG("Skipping concept {} with id {}", concept_model->full_name(), + concept_model->id()); + } + + return true; +} + +void translation_unit_visitor::find_relationships_in_constraint_expression( + clanguml::common::model::element &c, const clang::Expr *expr) +{ + if (expr == nullptr) + return; + + if (const auto *concept_specialization = + clang::dyn_cast(expr); + concept_specialization) { + if (concept_specialization->getNamedConcept() && + diagram().should_include(concept_specialization->getNamedConcept() + ->getQualifiedNameAsString())) { + auto target_id = get_ast_local_id( + concept_specialization->getNamedConcept()->getID()) + .value(); + + for (const auto ta : + concept_specialization->getTemplateArguments()) { + if (ta.getKind() == clang::TemplateArgument::Template) + ta.getAsTemplateOrTemplatePattern().dump(); + } + + c.add_relationship({relationship_t::kDependency, target_id}); + } + } + else if (const auto *constraint = + clang::dyn_cast(expr); + constraint) { + // TODO + } + else if (const auto *binop = clang::dyn_cast(expr); + binop) { + find_relationships_in_constraint_expression(c, binop->getLHS()); + find_relationships_in_constraint_expression(c, binop->getRHS()); + } + else if (const auto *unop = clang::dyn_cast(expr); + unop) { + find_relationships_in_constraint_expression(c, unop->getSubExpr()); + } +} + bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) { // Skip system headers @@ -440,6 +599,37 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) return true; } +std::unique_ptr +translation_unit_visitor::create_concept_declaration(clang::ConceptDecl *cpt) +{ + assert(cpt != nullptr); + + auto concept_ptr{ + std::make_unique(config_.using_namespace())}; + auto &concept_model = *concept_ptr; + + auto qualified_name = cpt->getQualifiedNameAsString(); + + if (!diagram().should_include(qualified_name)) + return {}; + + auto ns = common::get_template_namespace(*cpt); + + concept_model.set_name(cpt->getNameAsString()); + concept_model.set_namespace(ns); + concept_model.set_id(common::to_id(concept_model.full_name(false))); + + process_comment(*cpt, concept_model); + set_source_location(*cpt, concept_model); + + if (concept_model.skip()) + return {}; + + concept_model.set_style(concept_model.style_spec()); + + return concept_ptr; +} + std::unique_ptr translation_unit_visitor::create_record_declaration( clang::RecordDecl *rec) { @@ -490,7 +680,7 @@ std::unique_ptr translation_unit_visitor::create_class_declaration( if (!diagram().should_include(qualified_name)) return {}; - auto ns = common::get_tag_namespace(*cls); + auto ns{common::get_tag_namespace(*cls)}; process_record_parent(cls, c, ns); @@ -531,7 +721,8 @@ void translation_unit_visitor::process_record_parent( // regular class id_opt = get_ast_local_id(local_id); - // If not, check if the parent template declaration is in the model + // If not, check if the parent template declaration is in the + // model if (!id_opt) { if (parent_record_decl->getDescribedTemplate() != nullptr) { local_id = @@ -600,7 +791,8 @@ void translation_unit_visitor::process_class_declaration( bool translation_unit_visitor::process_template_parameters( const clang::TemplateDecl &template_declaration, - common::model::template_trait &c) + common::model::template_trait &c, + common::optional_ref templated_element) { LOG_DBG("Processing class {} template parameters...", common::get_qualified_name(template_declaration)); @@ -621,6 +813,16 @@ bool translation_unit_visitor::process_template_parameters( ct.set_default_value(""); ct.is_variadic(template_type_parameter->isParameterPack()); + if (template_type_parameter->getTypeConstraint()) { + util::apply_if_not_null( + template_type_parameter->getTypeConstraint() + ->getNamedConcept(), + [&ct](const clang::ConceptDecl *named_concept) mutable { + ct.set_concept_constraint( + named_concept->getQualifiedNameAsString()); + }); + } + c.add_template(std::move(ct)); } else if (clang::dyn_cast_or_null( @@ -2405,6 +2607,17 @@ void translation_unit_visitor::resolve_local_to_global_ids() } } } + for (const auto &cpt : diagram().concepts()) { + for (auto &rel : cpt.get().relationships()) { + const auto maybe_local_id = rel.destination(); + if (get_ast_local_id(maybe_local_id)) { + LOG_DBG("= Resolved instantiation destination from local " + "id {} to global id {}", + maybe_local_id, *get_ast_local_id(maybe_local_id)); + rel.set_destination(*get_ast_local_id(maybe_local_id)); + } + } + } } void translation_unit_visitor::finalize() diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 63758ecb..2c8d65d7 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -18,6 +18,7 @@ #pragma once #include "class_diagram/model/class.h" +#include "class_diagram/model/concept.h" #include "class_diagram/model/diagram.h" #include "common/model/enums.h" #include "common/model/template_trait.h" @@ -83,12 +84,7 @@ public: virtual bool VisitTypeAliasTemplateDecl(clang::TypeAliasTemplateDecl *cls); -// bool TraverseTypeConstraint(const clang::TypeConstraint *C); -// bool TraverseConceptRequirement(clang::concepts::Requirement *R); -// bool TraverseConceptTypeRequirement(clang::concepts::TypeRequirement *R); -// bool TraverseConceptExprRequirement(clang::concepts::ExprRequirement *R); -// bool TraverseConceptNestedRequirement( -// clang::concepts::NestedRequirement *R); + virtual bool TraverseConceptDecl(clang::ConceptDecl *cpt); /** * @brief Get diagram model reference @@ -121,6 +117,9 @@ private: std::unique_ptr create_record_declaration(clang::RecordDecl *rec); + std::unique_ptr + create_concept_declaration(clang::ConceptDecl *cpt); + void process_class_declaration(const clang::CXXRecordDecl &cls, clanguml::class_diagram::model::class_ &c); @@ -141,7 +140,8 @@ private: bool process_template_parameters( const clang::TemplateDecl &template_declaration, - clanguml::common::model::template_trait &t); + clanguml::common::model::template_trait &t, + common::optional_ref templated_element = {}); void process_template_specialization_argument( const clang::ClassTemplateSpecializationDecl *cls, @@ -249,6 +249,9 @@ private: const std::set &template_parameter_names, const clang::TemplateSpecializationType &template_instantiation_type); + void find_relationships_in_constraint_expression( + clanguml::common::model::element &c, const clang::Expr *expr); + void process_unexposed_template_specialization_parameters( const std::string &tspec, clanguml::common::model::template_parameter &tp, diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index ce94c1ff..88d033e3 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -90,6 +90,14 @@ model::namespace_ get_tag_namespace(const clang::TagDecl &declaration) return ns; } +model::namespace_ get_template_namespace(const clang::TemplateDecl &declaration) +{ + model::namespace_ ns{declaration.getQualifiedNameAsString()}; + ns.pop_back(); + + return ns; +} + std::string get_tag_name(const clang::TagDecl &declaration) { auto base_name = declaration.getNameAsString(); diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 6e40ee6d..6b64ef0a 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -72,6 +72,9 @@ template std::string get_qualified_name(const T &declaration) model::namespace_ get_tag_namespace(const clang::TagDecl &declaration); +model::namespace_ get_template_namespace( + const clang::TemplateDecl &declaration); + std::optional get_enclosing_namespace( const clang::DeclContext *decl); diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index ed276b6c..87ec2361 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -138,6 +138,8 @@ std::string template_parameter::to_string( { using clanguml::common::model::namespace_; + assert(!(!type().empty() && concept_constraint().has_value())); + std::string res; if (!type().empty()) { if (!relative) @@ -146,8 +148,17 @@ std::string template_parameter::to_string( res += namespace_{type()}.relative_to(using_namespace).to_string(); } + if (concept_constraint()) { + if (!relative) + res += namespace_{concept_constraint().value()}.to_string(); + else + res += namespace_{concept_constraint().value()} + .relative_to(using_namespace) + .to_string(); + } + if (!name().empty()) { - if (!type().empty()) + if (!type().empty() || concept_constraint()) res += " "; if (!relative) res += namespace_{name()}.to_string(); @@ -216,4 +227,14 @@ bool template_parameter::find_nested_relationships( return added_aggregation_relationship; } +void template_parameter::set_concept_constraint(std::string constraint) +{ + concept_constraint_ = std::move(constraint); +} + +const std::optional &template_parameter::concept_constraint() const +{ + return concept_constraint_; +} + } // namespace clanguml::common::model diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index 3ca0f173..b9ce70eb 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -100,6 +100,9 @@ public: const std::function &should_include) const; + void set_concept_constraint(std::string constraint); + const std::optional &concept_constraint() const; + private: /// Represents the type of non-type template parameters /// e.g. 'int' @@ -125,6 +128,10 @@ private: /// Whether the argument specializes argument pack from parent template bool is_pack_{false}; + /// Stores optional fully qualified name of constraint for this template + /// parameter + std::optional concept_constraint_; + // Nested template parameters std::vector template_params_; diff --git a/src/util/util.h b/src/util/util.h index 1cddf3a4..8b1c7433 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -242,6 +242,39 @@ void for_each_if(const T &collection, C &&cond, F &&func) }); } +template +void apply_if_not_null(const T *pointer, F &&func, FElse &&func_else) +{ + if (pointer != nullptr) { + std::forward(func)(pointer); + } + else if (func_else) { + std::forward(func_else)(); + } +} + +template +void apply_if_not_null(const T *pointer, F &&func) +{ + apply_if_not_null(pointer, std::forward(func), []() {}); +} + +template +void apply_if(const bool condition, F &&func, FElse &&func_else) +{ + if (condition) { + std::forward(func)(); + } + else if (func_else) { + std::forward(func_else)(); + } +} + +template void apply_if(const bool condition, F &&func) +{ + apply_if(condition, std::forward(func), []() {}); +} + std::size_t hash_seed(std::size_t seed); /** diff --git a/tests/t00056/t00056.cc b/tests/t00056/t00056.cc index 4d40a2e4..088b114b 100644 --- a/tests/t00056/t00056.cc +++ b/tests/t00056/t00056.cc @@ -2,53 +2,71 @@ namespace clanguml { namespace t00056 { + +template +concept greater_than = sizeof(T) > sizeof(L); + +template +concept greater_than_requires = requires(T l, P r) +{ + sizeof(l) > sizeof(r); +}; + // Constraint expression template -concept MaxFourBytes = sizeof(T) <= 4; +concept max_four_bytes = sizeof(T) <= 4; // Simple requirement template -concept Iterable = requires(T container) { - container.begin(); - container.end(); - }; +concept iterable = requires(T container) +{ + container.begin(); + container.end(); +}; // Type requirement template -concept HasValueType = requires { typename T::value_type; }; +concept has_value_type = requires +{ + typename T::value_type; +}; template -concept ConvertibleToString = requires(T s) { std::string{s}; }; +concept convertible_to_string = requires(T s) +{ + std::string{s}; +}; // Compound requirement // ... // Combined concept template -concept IterableWithValueType = Iterable && HasValueType; +concept iterable_with_value_type = iterable && has_value_type; template -concept IterableWithSmallValueType = - IterableWithValueType && MaxFourBytes; +concept iterable_or_small_value_type = + iterable_with_value_type || max_four_bytes; // Simple type constraint -template struct A { +template struct A { T a; }; // Requires constant expression template - requires MaxFourBytes +requires iterable_or_small_value_type struct B { T b; }; // Anonymous concept requirement template - requires requires(T t) { - --t; - t--; - } +requires requires(T t) +{ + --t; + t--; +} struct C { T c; };