Initial support for concept dependency relationships in class diagrams
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
72
src/class_diagram/model/concept.cc
Normal file
72
src/class_diagram/model/concept.cc
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
59
src/class_diagram/model/concept.h
Normal file
59
src/class_diagram/model/concept.h
Normal 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_;
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user