Initial support for concept dependency relationships in class diagrams

This commit is contained in:
Bartek Kryza
2023-02-25 01:50:07 +01:00
parent 20a0f2d338
commit 274a698713
15 changed files with 750 additions and 34 deletions

View File

@@ -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() << " <<concept>>";
if (m_config.generate_links) {
common_generator<diagram_config, diagram_model>::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<concept_ *>(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<std::string> rendered_relations;
std::stringstream all_relations_str;
std::set<std::string> 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<concept_ *>(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<concept_ *>(e); cpt) {
generate_alias(*cpt, ostr);
generate(*cpt, ostr);
}
}
ostr << "}\n";
@@ -589,6 +725,12 @@ void generator::generate_relationships(
dynamic_cast<enum_ &>(*subpackage), ostr);
}
}
else if (dynamic_cast<concept_ *>(subpackage.get()) != nullptr) {
if (m_model.should_include(*subpackage)) {
generate_relationships(
dynamic_cast<concept_ &>(*subpackage), ostr);
}
}
}
}
@@ -648,6 +790,20 @@ void generator::generate_top_level_elements(std::ostream &ostr) const
}
}
}
else if (auto *cpt = dynamic_cast<concept_ *>(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<concept_ *>(e); cpt) {
generate_alias(*cpt, ostr);
generate(*cpt, ostr);
}
}
ostr << "}\n";

View File

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

View File

@@ -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<std::pair<std::string, common::model::relationship_t>>
&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<class_member> members_;
std::vector<class_method> methods_;
std::vector<class_parent> bases_;

View File

@@ -0,0 +1,72 @@
/**
* src/class_diagram/model/concept.cc
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* 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 <sstream>
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 "<<anonymous>>";
return res;
}
}

View File

@@ -0,0 +1,59 @@
/**
* src/class_diagram/model/concept.h
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* 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 <string>
#include <vector>
namespace clanguml::class_diagram::model {
struct requires_expression {
common::model::template_parameter parameter;
std::vector<std::string> 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<std::string> requires_expression_;
std::string full_name_;
};
}

View File

@@ -32,6 +32,11 @@ const common::reference_vector<class_> &diagram::classes() const
const common::reference_vector<enum_> &diagram::enums() const { return enums_; }
const common::reference_vector<concept_> &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<clanguml::common::model::diagram_element> 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<clanguml::common::model::diagram_element> 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<class_> diagram::get_class(const std::string &name) const
{
for (const auto &c : classes_) {
@@ -126,6 +147,32 @@ common::optional_ref<enum_> diagram::get_enum(
return {};
}
common::optional_ref<concept_> 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<concept_> 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<common::model::package> &&p)
{
LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true));
@@ -173,8 +220,8 @@ bool diagram::add_class(std::unique_ptr<class_> &&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<enum_> &&e)
return false;
}
bool diagram::add_concept(std::unique_ptr<concept_> &&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<concept_>(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<class_> &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));
}

View File

@@ -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 <string>
@@ -54,10 +55,14 @@ public:
const common::reference_vector<enum_> &enums() const;
const common::reference_vector<concept_> &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<class_> get_class(const std::string &name) const;
common::optional_ref<class_> get_class(
@@ -68,10 +73,17 @@ public:
common::optional_ref<enum_> get_enum(
clanguml::common::model::diagram_element::id_t id) const;
common::optional_ref<concept_> get_concept(const std::string &name) const;
common::optional_ref<concept_> get_concept(
clanguml::common::model::diagram_element::id_t id) const;
bool add_class(std::unique_ptr<class_> &&c);
bool add_enum(std::unique_ptr<enum_> &&e);
bool add_concept(std::unique_ptr<concept_> &&e);
bool add_package(std::unique_ptr<common::model::package> &&p);
std::string to_alias(
@@ -90,6 +102,8 @@ private:
common::reference_vector<class_> classes_;
common::reference_vector<enum_> enums_;
common::reference_vector<concept_> concepts_;
};
} // namespace clanguml::class_diagram::model

View File

@@ -19,6 +19,7 @@
#include "translation_unit_visitor.h"
#include "common/clang_utils.h"
#include <clang/AST/ExprConcepts.h>
#include <clang/Basic/FileManager.h>
#include <clang/Lex/Preprocessor.h>
#include <spdlog/spdlog.h>
@@ -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<const clang::Expr *, 24> 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<clang::RequiresExpr>(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<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),
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<clang::ConceptSpecializationExpr>(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<clang::RequiresExpr>(expr);
constraint) {
// TODO
}
else if (const auto *binop = clang::dyn_cast<clang::BinaryOperator>(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<clang::UnaryOperator>(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<clanguml::class_diagram::model::concept_>
translation_unit_visitor::create_concept_declaration(clang::ConceptDecl *cpt)
{
assert(cpt != nullptr);
auto concept_ptr{
std::make_unique<model::concept_>(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<class_> translation_unit_visitor::create_record_declaration(
clang::RecordDecl *rec)
{
@@ -490,7 +680,7 @@ std::unique_ptr<class_> 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<common::model::element> 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<clang::NonTypeTemplateParmDecl>(
@@ -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()

View File

@@ -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<clanguml::class_diagram::model::class_>
create_record_declaration(clang::RecordDecl *rec);
std::unique_ptr<clanguml::class_diagram::model::concept_>
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<common::model::element> templated_element = {});
void process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
@@ -249,6 +249,9 @@ private:
const std::set<std::string> &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,

View File

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

View File

@@ -72,6 +72,9 @@ template <typename T> 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<clanguml::common::model::namespace_> get_enclosing_namespace(
const clang::DeclContext *decl);

View File

@@ -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<std::string> &template_parameter::concept_constraint() const
{
return concept_constraint_;
}
} // namespace clanguml::common::model

View File

@@ -100,6 +100,9 @@ public:
const std::function<bool(const std::string &full_name)> &should_include)
const;
void set_concept_constraint(std::string constraint);
const std::optional<std::string> &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<std::string> concept_constraint_;
// Nested template parameters
std::vector<template_parameter> template_params_;

View File

@@ -242,6 +242,39 @@ void for_each_if(const T &collection, C &&cond, F &&func)
});
}
template <typename T, typename F, typename FElse>
void apply_if_not_null(const T *pointer, F &&func, FElse &&func_else)
{
if (pointer != nullptr) {
std::forward<F>(func)(pointer);
}
else if (func_else) {
std::forward<FElse>(func_else)();
}
}
template <typename T, typename F>
void apply_if_not_null(const T *pointer, F &&func)
{
apply_if_not_null(pointer, std::forward<F>(func), []() {});
}
template <typename F, typename FElse>
void apply_if(const bool condition, F &&func, FElse &&func_else)
{
if (condition) {
std::forward<F>(func)();
}
else if (func_else) {
std::forward<FElse>(func_else)();
}
}
template <typename F> void apply_if(const bool condition, F &&func)
{
apply_if(condition, std::forward<F>(func), []() {});
}
std::size_t hash_seed(std::size_t seed);
/**

View File

@@ -2,53 +2,71 @@
namespace clanguml {
namespace t00056 {
template <typename T, typename L>
concept greater_than = sizeof(T) > sizeof(L);
template <typename T, typename P>
concept greater_than_requires = requires(T l, P r)
{
sizeof(l) > sizeof(r);
};
// Constraint expression
template <typename T>
concept MaxFourBytes = sizeof(T) <= 4;
concept max_four_bytes = sizeof(T) <= 4;
// Simple requirement
template <typename T>
concept Iterable = requires(T container) {
container.begin();
container.end();
};
concept iterable = requires(T container)
{
container.begin();
container.end();
};
// Type requirement
template <typename T>
concept HasValueType = requires { typename T::value_type; };
concept has_value_type = requires
{
typename T::value_type;
};
template <typename T>
concept ConvertibleToString = requires(T s) { std::string{s}; };
concept convertible_to_string = requires(T s)
{
std::string{s};
};
// Compound requirement
// ...
// Combined concept
template <typename T>
concept IterableWithValueType = Iterable<T> && HasValueType<T>;
concept iterable_with_value_type = iterable<T> && has_value_type<T>;
template <typename T>
concept IterableWithSmallValueType =
IterableWithValueType<T> && MaxFourBytes<T>;
concept iterable_or_small_value_type =
iterable_with_value_type<T> || max_four_bytes<T>;
// Simple type constraint
template <MaxFourBytes T> struct A {
template <max_four_bytes T> struct A {
T a;
};
// Requires constant expression
template <typename T>
requires MaxFourBytes<T>
requires iterable_or_small_value_type<T>
struct B {
T b;
};
// Anonymous concept requirement
template <typename T>
requires requires(T t) {
--t;
t--;
}
requires requires(T t)
{
--t;
t--;
}
struct C {
T c;
};