From 1a5a7aefcb97fb1aaeebfd6e4a7076173dbb8342 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 22 Feb 2022 13:02:04 +0100 Subject: [PATCH] Fixed rendering of member variables with alias to template or alias template (t00014) --- .../plantuml/class_diagram_generator.cc | 8 +- src/class_diagram/model/class.cc | 4 +- src/class_diagram/model/class.h | 4 +- src/class_diagram/model/diagram.cc | 38 ++- src/class_diagram/model/diagram.h | 10 +- .../visitor/translation_unit_visitor.cc | 229 +++++++++++++----- .../visitor/translation_unit_visitor.h | 2 + src/common/model/element.cc | 24 ++ src/common/model/element.h | 18 ++ src/common/model/nested_trait.h | 29 ++- src/config/config.cc | 14 ++ src/config/config.h | 12 +- src/cx/util.cc | 10 + src/cx/util.h | 3 + .../visitor/translation_unit_visitor.cc | 3 +- .../visitor/translation_unit_visitor.cc | 6 +- src/util/util.cc | 29 ++- src/util/util.h | 4 + tests/t00002/test_case.h | 4 +- tests/t00014/test_case.h | 11 + tests/test_util.cc | 2 + 21 files changed, 355 insertions(+), 109 deletions(-) diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 83d07643..afb5f98f 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -89,7 +89,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const return mp.to_string(m_config.using_namespace()); }); auto args_string = fmt::format("{}", fmt::join(params, ", ")); - if (m_config.generate_method_arguments() != + if (m_config.generate_method_arguments() == config::method_arguments::abbreviated) { args_string = clanguml::util::abbreviate(args_string, 10); } @@ -277,21 +277,21 @@ void generator::generate(std::ostream &ostr) const if (m_config.should_include_entities("classes")) { for (const auto &c : m_model.classes()) { - if (!m_config.should_include(c.get().name())) + if (!m_config.should_include(c->get_namespace(), c->name())) continue; generate_alias(*c, ostr); ostr << '\n'; } for (const auto &e : m_model.enums()) { - if (!m_config.should_include(e.get().name())) + if (!m_config.should_include(e->get_namespace(), e->name())) continue; generate_alias(*e, ostr); ostr << '\n'; } for (const auto &c : m_model.classes()) { - if (!m_config.should_include(c.get().name())) + if (!m_config.should_include(c->get_namespace(), c->name())) continue; generate(*c, ostr); ostr << '\n'; diff --git a/src/class_diagram/model/class.cc b/src/class_diagram/model/class.cc index fc886186..3192fd80 100644 --- a/src/class_diagram/model/class.cc +++ b/src/class_diagram/model/class.cc @@ -101,10 +101,10 @@ std::string class_::full_name(bool relative) const using namespace clanguml::util; std::ostringstream ostr; - if (relative) + if (relative && starts_with(get_namespace(), using_namespaces())) ostr << ns_relative(using_namespaces(), name()); else - ostr << name(); + ostr << name_and_ns(); if (!templates_.empty()) { std::vector tnames; diff --git a/src/class_diagram/model/class.h b/src/class_diagram/model/class.h index 0f5dbb11..891a41ec 100644 --- a/src/class_diagram/model/class.h +++ b/src/class_diagram/model/class.h @@ -37,9 +37,9 @@ public: class_(const std::vector &using_namespaces); class_(const class_ &) = delete; - class_(class_ &&) = default; + class_(class_ &&) noexcept = delete; class_ &operator=(const class_ &) = delete; - class_ &operator=(class_ &&) = default; + class_ &operator=(class_ &&) = delete; bool is_struct() const; void is_struct(bool is_struct); diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 52a4e834..afd26659 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -21,14 +21,17 @@ #include "util/error.h" #include "util/util.h" +#include +#include + namespace clanguml::class_diagram::model { -const std::vector> diagram::classes() const +const std::vector> diagram::classes() const { return classes_; } -const std::vector> diagram::enums() const +const std::vector> diagram::enums() const { return enums_; } @@ -57,16 +60,32 @@ void diagram::add_package(std::unique_ptr &&p) { LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true)); - add_element(p->get_namespace(), std::move(p)); + auto ns = p->get_namespace(); + add_element(ns, std::move(p)); } void diagram::add_class(std::unique_ptr &&c) { LOG_DBG("Adding class: {}, {}", c->name(), c->full_name()); + if (util::contains(c->name(), "::")) + throw std::runtime_error("Name cannot contain namespace: " + c->name()); + + if (util::contains(c->name(), "<")) + throw std::runtime_error("Name cannot contain <: " + c->name()); + + if (util::contains(c->name(), "*")) + throw std::runtime_error("Name cannot contain *: " + c->name()); + if (!has_class(*c)) { + LOG_DBG("### ADDED CLASS WITH ADDRESS: {}", (void *)c.get()); classes_.emplace_back(*c); - add_element(c->get_namespace(), std::move(c)); + auto ns = c->get_relative_namespace(); + auto name = c->name(); + add_element(ns, std::move(c)); + ns.push_back(name); + const auto ccc = get_element(ns); + assert(ccc.value().name() == name); } else LOG_DBG( @@ -77,9 +96,12 @@ void diagram::add_enum(std::unique_ptr &&e) { LOG_DBG("Adding enum: {}", e->name()); + assert(!util::contains(e->name(), "::")); + if (!has_enum(*e)) { enums_.emplace_back(*e); - add_element(e->get_namespace(), std::move(e)); + auto ns = e->get_relative_namespace(); + add_element(ns, std::move(e)); } else LOG_DBG("Enum {} already in the model", e->name()); @@ -90,13 +112,14 @@ std::string diagram::to_alias(const std::string &full_name) const LOG_DBG("Looking for alias for {}", full_name); for (const auto &c : classes_) { - if (c->full_name() == full_name) { + const auto &cc = c.get(); + if (cc.full_name() == full_name) { return c->alias(); } } for (const auto &e : enums_) { - if (e->full_name() == full_name) { + if (e.get().full_name() == full_name) { return e->alias(); } } @@ -104,4 +127,5 @@ std::string diagram::to_alias(const std::string &full_name) const throw error::uml_alias_missing( fmt::format("Missing alias for {}", full_name)); } + } diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 42aa479b..86f8acd7 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -40,9 +40,9 @@ public: diagram &operator=(const diagram &) = delete; diagram &operator=(diagram &&) = default; - const std::vector> classes() const; + const std::vector> classes() const; - const std::vector> enums() const; + const std::vector> enums() const; bool has_class(const class_ &c) const; @@ -58,9 +58,11 @@ public: std::string to_alias(const std::string &full_name) const; + friend void print_diagram_tree(const diagram &d, const int level); + private: - std::vector> classes_; - std::vector> enums_; + std::vector> classes_; + std::vector> enums_; std::map> type_aliases_; }; } diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index ac4abf65..30bd9850 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -106,9 +106,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) if (!util::starts_with(usn, package_path)) { auto p = std::make_unique( - ctx.config().using_namespace()); + util::split( + ctx.config().using_namespace()[0], "::")); util::remove_prefix(package_path, usn); - util::remove_prefix(package_parent, usn); p->set_name(e.name()); p->set_namespace(package_parent); @@ -129,8 +129,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) } if (!p->skip()) { - ctx.diagram().add_element( - package_parent, std::move(p)); + ctx.diagram().add_package(std::move(p)); ctx.set_current_package( ctx.diagram() .get_element( @@ -190,7 +189,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) } if (ctx.config().should_include( - cx::util::fully_prefixed(ctx.get_namespace(), cls))) + ctx.get_namespace(), cls.name())) + // cx::util::fully_prefixed(ctx.get_namespace(), + // cls))) process_class_declaration(cls); } else if (e.kind() == cppast::cpp_entity_kind::enum_t) { @@ -201,7 +202,9 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) auto &enm = static_cast(e); if (ctx.config().should_include( - cx::util::fully_prefixed(ctx.get_namespace(), enm))) + ctx.get_namespace(), enm.name())) + // cx::util::fully_prefixed(ctx.get_namespace(), + // enm))) process_enum_declaration(enm); } else if (e.kind() == cppast::cpp_entity_kind::type_alias_t) { @@ -210,7 +213,7 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) cppast::to_string(e.kind())); auto &ta = static_cast(e); - std::unique_ptr t; + auto t = std::make_unique(); t->set_alias(cx::util::full_name(ctx.get_namespace(), ta)); t->set_underlying_type(cx::util::full_name(ta.underlying_type(), ctx.entity_index(), cx::util::is_inside_class(e))); @@ -237,9 +240,15 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file) else { auto tinst = build_template_instantiation(static_cast< const cppast::cpp_template_instantiation_type &>( - at.type_alias().underlying_type())); + resolve_alias(at.type_alias().underlying_type()))); - ctx.diagram().add_class(std::move(tinst)); + ctx.add_type_alias_template( + cx::util::full_name(ctx.get_namespace(), at), + type_safe::ref(at.type_alias().underlying_type())); + + if (ctx.config().should_include( + tinst->get_namespace(), tinst->name())) + ctx.diagram().add_class(std::move(tinst)); } } }); @@ -254,9 +263,10 @@ void translation_unit_visitor::process_enum_declaration( return; } - auto e_ptr = std::make_unique(ctx.config().using_namespace()); + auto e_ptr = std::make_unique( + util::split(ctx.config().using_namespace()[0], "::")); auto &e = *e_ptr; - e.set_name(cx::util::full_name(ctx.get_namespace(), enm)); + e.set_name(enm.name()); e.set_namespace(ctx.get_namespace()); if (enm.comment().has_value()) @@ -284,7 +294,9 @@ void translation_unit_visitor::process_enum_declaration( e.add_relationship({relationship_t::kContainment, cx::util::full_name(ctx.get_namespace(), cur.value())}); - LOG_DBG("Added containment relationship {} +-- {}", e.name()); + LOG_DBG("Added containment relationship {} +-- {}", + cx::util::full_name(ctx.get_namespace(), cur.value()), + e.name()); break; } } @@ -296,10 +308,12 @@ void translation_unit_visitor::process_class_declaration( const cppast::cpp_class &cls, type_safe::optional_ref tspec) { - auto c_ptr = std::make_unique(ctx.config().using_namespace()); + auto c_ptr = std::make_unique( + util::split(ctx.config().using_namespace()[0], "::")); auto &c = *c_ptr; c.is_struct(cls.class_kind() == cppast::cpp_class_kind::struct_t); - c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); + // c.set_name(cx::util::full_name(ctx.get_namespace(), cls)); + c.set_name(cls.name()); c.set_namespace(ctx.get_namespace()); if (cls.comment().has_value()) @@ -576,6 +590,7 @@ void translation_unit_visitor::process_class_declaration( static_cast(cls.user_data()), fmt::ptr(reinterpret_cast(&cls))); + assert(c_ptr); ctx.diagram().add_class(std::move(c_ptr)); } @@ -588,6 +603,8 @@ bool translation_unit_visitor::process_field_with_template_instantiation( bool res = false; + auto tr_declaration = cppast::to_string(tr); + const auto &template_instantiation_type = static_cast(tr); @@ -595,7 +612,14 @@ bool translation_unit_visitor::process_field_with_template_instantiation( static_cast( resolve_alias(template_instantiation_type)); - auto tinst_ptr = build_template_instantiation(unaliased); + auto tr_unaliased_declaration = cppast::to_string(unaliased); + + std::unique_ptr tinst_ptr; + if (util::contains(tr_declaration, "<")) + tinst_ptr = build_template_instantiation(template_instantiation_type); + else + tinst_ptr = build_template_instantiation(unaliased); + auto &tinst = *tinst_ptr; // Infer the relationship of this field to the template @@ -604,7 +628,6 @@ bool translation_unit_visitor::process_field_with_template_instantiation( if (mv.type().kind() == cppast::cpp_type_kind::pointer_t || mv.type().kind() == cppast::cpp_type_kind::reference_t) relationship_type = relationship_t::kAssociation; - else relationship_type = relationship_t::kAggregation; @@ -623,13 +646,19 @@ bool translation_unit_visitor::process_field_with_template_instantiation( } } - if (ctx.config().should_include(tinst.name())) { + if (ctx.config().should_include(tinst.get_namespace(), tinst.name())) { LOG_DBG("Adding field instantiation relationship {} {} {} : {}", rr.destination(), clanguml::common::model::to_string(rr.type()), c.full_name(), rr.label()); c.add_relationship(std::move(rr)); + if (tr_declaration != tr_unaliased_declaration) { + // Add template instantiation/specialization relationship; + tinst.add_relationship( + {relationship_t::kInstantiation, tr_unaliased_declaration}); + } + res = true; LOG_DBG("Created template instantiation: {}", tinst.full_name()); @@ -668,11 +697,14 @@ void translation_unit_visitor::process_field( else if (tr.kind() == cppast::cpp_type_kind::user_defined_t) { LOG_DBG("Processing user defined type field {} {}", cppast::to_string(tr), mv.name()); + if (resolve_alias(tr).kind() == + cppast::cpp_type_kind::template_instantiation_t) + template_instantiation_added_as_aggregation = + process_field_with_template_instantiation(mv, tr, c, m, as); } else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) { template_instantiation_added_as_aggregation = - process_field_with_template_instantiation( - mv, resolve_alias(tr), c, m, as); + process_field_with_template_instantiation(mv, tr, c, m, as); } else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) { LOG_DBG( @@ -680,6 +712,8 @@ void translation_unit_visitor::process_field( // TODO } + auto tr_declaration = cppast::to_string(tr); + if (!m.skip_relationship() && !template_instantiation_added_as_aggregation && (tr.kind() != cppast::cpp_type_kind::builtin_t) && @@ -929,8 +963,10 @@ void translation_unit_visitor::process_function_parameter( find_relationships(cppast::remove_cv(param.type()), relationships, relationship_t::kDependency); for (const auto &[type, relationship_type] : relationships) { - if ((relationship_type != relationship_t::kNone) && - (type != c.name())) { + + if (ctx.config().should_include(cx::util::split_ns(type)) && + (relationship_type != relationship_t::kNone) && + (type != c.name_and_ns())) { relationship r{relationship_t::kDependency, type}; LOG_DBG("Adding field relationship {} {} {} : {}", @@ -954,11 +990,11 @@ void translation_unit_visitor::process_function_parameter( // Here we need the name of the primary template with full // namespace prefix to apply config inclusion filters - auto primary_template_name = - cx::util::full_name(ctx.get_namespace(), - template_instantiation_type.primary_template() - .get(ctx.entity_index())[0] - .get()); + // auto primary_template_name = + // cx::util::full_name(ctx.get_namespace(), + // template_instantiation_type.primary_template() + // .get(ctx.entity_index())[0] + // .get()); // Now check if the template arguments of this function param // are a subset of the method template params - if yes this is // not an instantiation but just a reference to an existing @@ -987,10 +1023,15 @@ void translation_unit_visitor::process_function_parameter( // arguments string } - LOG_DBG("Maybe building instantiation for: {}", - primary_template_name); + // LOG_DBG("Maybe building instantiation for: + // {}", + // primary_template_name); - if (ctx.config().should_include(primary_template_name)) { + if (ctx.config().should_include(ctx.get_namespace(), + template_instantiation_type.primary_template() + .get(ctx.entity_index())[0] + .get() + .name())) { if (template_is_not_instantiation) { LOG_DBG("Template is not an instantiation - " @@ -1073,9 +1114,11 @@ void translation_unit_visitor::process_friend( return; if (f.type()) { - auto name = cppast::to_string(f.type().value()); - - if (!ctx.config().should_include(name)) + auto name_with_ns = + util::split(cppast::to_string(f.type().value()), "::"); + auto name = name_with_ns.back(); + name_with_ns.pop_back(); + if (!ctx.config().should_include(name_with_ns, name)) return; LOG_DBG("Type friend declaration {}", name); @@ -1113,7 +1156,7 @@ void translation_unit_visitor::process_friend( name = cx::util::full_name(ctx.get_namespace(), f.entity().value()); } - if (!ctx.config().should_include(name)) + if (!ctx.config().should_include(ctx.get_namespace(), name)) return; r.set_destination(name); @@ -1192,7 +1235,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, else if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { // class_relationship r; - auto &tinst = + const auto &tinst = static_cast(t); if (!tinst.arguments_exposed()) { @@ -1201,28 +1244,47 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, return found; } - const auto &args = tinst.arguments().value(); + assert(tinst.arguments().has_value()); + assert(tinst.arguments().value().size() > 0u); + + [[maybe_unused]] const auto args_count = + tinst.arguments().value().size(); + + const auto args = tinst.arguments().value(); + + const auto [ns, base_name] = cx::util::split_ns(fn); + + auto ns_and_name = ns; + ns_and_name.push_back(base_name); + + auto full_name = fmt::format("{}", fmt::join(ns_and_name, "::")); // Try to match common containers // TODO: Refactor to a separate class with configurable // container list - if (name.find("std::unique_ptr") == 0) { + if (full_name.find("std::unique_ptr") == 0) { found = find_relationships(args[0u].type().value(), relationships, relationship_t::kAggregation); } - else if (name.find("std::shared_ptr") == 0) { + else if (full_name.find("std::shared_ptr") == 0) { found = find_relationships(args[0u].type().value(), relationships, relationship_t::kAssociation); } - else if (name.find("std::weak_ptr") == 0) { + else if (full_name.find("std::weak_ptr") == 0) { found = find_relationships(args[0u].type().value(), relationships, relationship_t::kAssociation); } - else if (name.find("std::vector") == 0) { - found = find_relationships(args[0u].type().value(), relationships, - relationship_t::kAggregation); + else if (full_name.find("std::vector") == 0) { + assert(args.size() == 1u); + if (args[0u].type().has_value()) + found = find_relationships(args[0u].type().value(), + relationships, relationship_t::kAggregation); + else + LOG_WARN( + "Failed to process template argument of std::vector at: {}", + fn); } - else if (ctx.config().should_include(fn)) { + else if (ctx.config().should_include(ns, name)) { LOG_DBG("User defined template instantiation: {} | {}", cppast::to_string(t_), cppast::to_string(t_.canonical())); @@ -1245,9 +1307,12 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, return found; } + // ??? else { for (const auto &arg : args) { - if (arg.type()) { + if (arg.type().has_value()) { + LOG_DBG("########## PROCESSING PARAMETER TYPE: {}", + cppast::to_string(arg.type().value())); found = find_relationships( arg.type().value(), relationships, relationship_type); } @@ -1262,7 +1327,8 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( const cppast::cpp_template_instantiation_type &t, std::optional parent) { - auto tinst_ptr = std::make_unique(ctx.config().using_namespace()); + auto tinst_ptr = std::make_unique( + util::split(ctx.config().using_namespace()[0], "::")); auto &tinst = *tinst_ptr; std::string full_template_name; @@ -1270,6 +1336,8 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( tinst.set_namespace(ctx.get_namespace()); + auto tinst_full_name = cppast::to_string(t); + if (t.primary_template().get(ctx.entity_index()).size()) { const auto &primary_template_ref = static_cast( @@ -1361,28 +1429,37 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( LOG_DBG("Building template instantiation for {}", full_template_name); // Extract namespace from base template name - std::vector ns_toks; - ns_toks = clanguml::util::split( - full_template_name.substr(0, full_template_name.find('<')), "::"); + // std::vector ns_toks; + // ns_toks = clanguml::util::split( + // full_template_name.substr(0, full_template_name.find('<')), "::"); + // + // std::string ns; + // if (ns_toks.size() > 1) { + // ns = fmt::format( + // "{}::", fmt::join(ns_toks.begin(), ns_toks.end() - 1, "::")); + // } - std::string ns; - if (ns_toks.size() > 1) { - ns = fmt::format( - "{}::", fmt::join(ns_toks.begin(), ns_toks.end() - 1, "::")); + // LOG_DBG("Template namespace is {}", ns); + + const auto [ns, name] = cx::util::split_ns(tinst_full_name); + tinst.set_name(name); + if (ns.empty()) + tinst.set_namespace(ctx.get_namespace()); + else + tinst.set_namespace(ns); + + if (tinst_full_name.find('<') != std::string::npos) { + tinst.set_name(tinst_full_name.substr(0, tinst_full_name.find('<'))); } - LOG_DBG("Template namespace is {}", ns); - - tinst.set_name(ns + util::split(cppast::to_string(t), "<")[0]); - tinst.is_template_instantiation(true); if (tinst.full_name().substr(0, tinst.full_name().find('<')).find("::") == std::string::npos) { - tinst.set_name(ns + tinst.full_name()); + tinst.set_name(name); } - // Process template argumetns + // Process template arguments int arg_index{0}; bool variadic_params{false}; if (t.arguments_exposed()) { @@ -1398,6 +1475,8 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( cx::util::unreferenced(targ.type().value())), ctx.entity_index(), false); + auto [fn_ns, fn_name] = cx::util::split_ns(fn); + if (targ.type().value().kind() == cppast::cpp_type_kind::template_instantiation_t) { @@ -1409,9 +1488,11 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( if (parent) nnn = (*parent)->name(); + auto [tinst_ns, tinst_name] = + cx::util::split_ns(tinst.full_name(false)); auto nested_tinst = build_template_instantiation(nested_template_parameter, - ctx.config().should_include(tinst.full_name(false)) + ctx.config().should_include(tinst_ns, tinst_name) ? std::make_optional(&tinst) : parent); @@ -1420,11 +1501,13 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( auto nested_tinst_full_name = nested_tinst->full_name(); - if (ctx.config().should_include(fn)) { + if (ctx.config().should_include(fn_ns, fn_name)) { ctx.diagram().add_class(std::move(nested_tinst)); } - if (ctx.config().should_include(tinst.full_name(false))) { + if (ctx.config().should_include(tinst_ns, tinst_name) && + ctx.config().should_include(cx::util::split_ns( + tinst_dependency.destination()))) { LOG_DBG( "Creating nested template dependency to template " "instantiation {}, {} -> {}", @@ -1463,7 +1546,7 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( "type {} -> {}", tinst.full_name(), tinst_dependency.destination()); - if (ctx.config().should_include(fn)) { + if (ctx.config().should_include(fn_ns, fn_name)) { tinst.add_relationship(std::move(tinst_dependency)); } else if (parent) { @@ -1547,12 +1630,32 @@ const cppast::cpp_type &translation_unit_visitor::resolve_alias( const cppast::cpp_type &type) { const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type)); - const auto type_full_name = + auto type_full_name = cx::util::full_name(raw_type, ctx.entity_index(), false); - if (ctx.has_type_alias(type_full_name)) { + + if (util::contains(type_full_name, "<")) + type_full_name = util::split(type_full_name, "<")[0]; + + if (ctx.has_type_alias_template(type_full_name)) { + return ctx.get_type_alias(type_full_name).get(); + } + else if (ctx.has_type_alias(type_full_name)) { return ctx.get_type_alias_final(raw_type).get(); } return type; } + +const cppast::cpp_type &translation_unit_visitor::resolve_alias_template( + const cppast::cpp_type &type) +{ + const auto &raw_type = cppast::remove_cv(cx::util::unreferenced(type)); + const auto type_full_name = + cx::util::full_name(raw_type, ctx.entity_index(), false); + if (ctx.has_type_alias_template(type_full_name)) { + return ctx.get_type_alias_template(type_full_name).get(); + } + + return type; +} } diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 5f158325..fa3ddaa9 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -130,6 +130,8 @@ private: * If t does not represent an alias, returns t. */ const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t); + const cppast::cpp_type &resolve_alias_template( + const cppast::cpp_type &type); // ctx allows to track current visitor context, e.g. current namespace translation_unit_context ctx; diff --git a/src/common/model/element.cc b/src/common/model/element.cc index 00fbc612..b79645d3 100644 --- a/src/common/model/element.cc +++ b/src/common/model/element.cc @@ -20,6 +20,8 @@ #include "util/util.h" +#include + namespace clanguml::common::model { std::atomic_uint64_t element::m_nextId = 1; @@ -28,6 +30,8 @@ element::element(const std::vector &using_namespaces) : using_namespaces_{using_namespaces} , m_id{m_nextId++} { + for (const auto &n : using_namespaces_) + assert(!util::contains(n, "::")); } std::string element::alias() const { return fmt::format("C_{:010}", m_id); } @@ -41,6 +45,13 @@ void element::add_relationship(relationship &&cr) return; } + if ((cr.type() == relationship_t::kInstantiation) && + (cr.destination() == full_name(true))) { + LOG_WARN("Skipping self instantiation relationship for {}", + cr.destination()); + return; + } + LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(), to_string(cr.type()), full_name(true)); @@ -50,6 +61,9 @@ void element::add_relationship(relationship &&cr) void element::set_using_namespaces(const std::vector &un) { + for (const auto &n : un) + assert(!util::contains(n, "::")); + using_namespaces_ = un; } @@ -71,4 +85,14 @@ bool operator==(const element &l, const element &r) { return l.full_name(false) == r.full_name(false); } + +std::ostream &operator<<(std::ostream &out, const element &rhs) +{ + out << "(" << rhs.name() << ", ns=[" + << util::join(rhs.get_namespace(), "::") << "], full_name=[" + << rhs.full_name(true) << "])"; + + return out; +} + } diff --git a/src/common/model/element.h b/src/common/model/element.h index 6206893c..4c8ea662 100644 --- a/src/common/model/element.h +++ b/src/common/model/element.h @@ -19,8 +19,10 @@ #include "decorated_element.h" #include "relationship.h" +#include "util/util.h" #include +#include #include #include @@ -38,10 +40,24 @@ public: std::string name() const { return name_; } + std::string name_and_ns() const + { + auto ns = namespace_; + ns.push_back(name()); + return util::join(ns, "::"); + } + void set_namespace(const std::vector &ns) { namespace_ = ns; } std::vector get_namespace() const { return namespace_; } + std::vector get_relative_namespace() const + { + auto relative_ns = namespace_; + util::remove_prefix(relative_ns, using_namespaces_); + return relative_ns; + } + virtual std::string full_name(bool relative) const { return name(); } void set_using_namespaces(const std::vector &un); @@ -58,6 +74,8 @@ public: friend bool operator==(const element &l, const element &r); + friend std::ostream &operator<<(std::ostream &out, const element &rhs); + protected: const uint64_t m_id{0}; diff --git a/src/common/model/nested_trait.h b/src/common/model/nested_trait.h index 55f0e2aa..1cc7779a 100644 --- a/src/common/model/nested_trait.h +++ b/src/common/model/nested_trait.h @@ -21,6 +21,7 @@ #include +#include #include #include @@ -55,7 +56,7 @@ public: { assert(p); - LOG_DBG("Adding nested element {} at path '{}'", p->name(), + LOG_DBG("Adding nested element {} at path {}", p->name(), fmt::join(path, "::")); if (path.empty()) { @@ -68,9 +69,11 @@ public: if (parent && dynamic_cast *>(&parent.value())) dynamic_cast &>(parent.value()) .template add_element(std::move(p)); - else + else { spdlog::error( "No parent element found at: {}", fmt::join(path, "::")); + throw std::runtime_error("No parent element found"); + } } template @@ -105,7 +108,7 @@ public: [&](const auto &p) { return name == p->name(); }); if (it == elements_.end()) - return type_safe::optional_ref{}; + return type_safe::optional_ref{type_safe::nullopt}; assert(it->get() != nullptr); @@ -113,7 +116,7 @@ public: return type_safe::optional_ref{ type_safe::ref(dynamic_cast(*it->get()))}; - return type_safe::optional_ref{}; + return type_safe::optional_ref{type_safe::nullopt}; } bool has_element(const std::string &name) const @@ -132,6 +135,24 @@ public: auto begin() const { return elements_.begin(); } auto end() const { return elements_.end(); } + void print_tree(const int level) + { + const auto &d = *this; + + if (level == 0) { + std::cout << "--- Printing tree:\n"; + } + for (const auto &e : d) { + if (dynamic_cast *>(e.get())) { + std::cout << std::string(level, ' ') << "[" << *e << "]\n"; + dynamic_cast *>(e.get())->print_tree(level + 1); + } + else { + std::cout << std::string(level, ' ') << "- " << *e << "]\n"; + } + } + } + private: std::vector> elements_; }; diff --git a/src/config/config.cc b/src/config/config.cc index f282a7ee..8e7e2cf2 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -129,6 +129,20 @@ bool diagram::should_include_relationship(const std::string &rel) return false; } +bool diagram::should_include( + const std::pair, std::string> &name) const +{ + return should_include(std::get<0>(name), std::get<1>(name)); +} + +bool diagram::should_include( + const std::vector &ns, const std::string &name) const +{ + auto ns_and_name = ns; + ns_and_name.push_back(name); + return should_include(util::join(ns_and_name, "::")); +} + bool diagram::should_include(const std::string &name_) const { auto name = clanguml::util::unqualify(name_); diff --git a/src/config/config.h b/src/config/config.h index c3b51a40..d220f7c7 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -87,8 +87,7 @@ struct inheritable_diagram_options { option puml{"plantuml", option_inherit_mode::append}; option generate_method_arguments{ "generate_method_arguments", method_arguments::full}; - option generate_packages{ - "generate_packages", false}; + option generate_packages{"generate_packages", false}; void inherit(const inheritable_diagram_options &parent); }; @@ -104,9 +103,16 @@ struct diagram : public inheritable_diagram_options { bool should_include_relationship(const std::string &rel); - bool should_include(const std::string &name_) const; + bool should_include( + const std::pair, std::string> &name) const; + + bool should_include( + const std::vector &ns, const std::string &name) const; bool should_include(const common::model::scope_t scope) const; + bool should_include(const std::string &name_) const; + +private: }; struct source_location { diff --git a/src/cx/util.cc b/src/cx/util.cc index 721acc09..fb4f0fdf 100644 --- a/src/cx/util.cc +++ b/src/cx/util.cc @@ -126,6 +126,16 @@ bool is_inside_class(const cppast::cpp_entity &e) return false; } +std::pair, std::string> split_ns( + const std::string &full_name) +{ + auto name_before_template = ::clanguml::util::split(full_name, "<")[0]; + auto ns = ::clanguml::util::split(name_before_template, "::"); + auto name = ns.back(); + ns.pop_back(); + return {ns, name}; +} + std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx) { if (t.kind() == cppast::cpp_type_kind::user_defined_t && diff --git a/src/cx/util.h b/src/cx/util.h index 9a0392e6..94fe8265 100644 --- a/src/cx/util.h +++ b/src/cx/util.h @@ -58,6 +58,9 @@ type_safe::optional_ref entity_ns( std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); +std::pair, std::string> split_ns( + const std::string &full_name); + bool is_inside_class(const cppast::cpp_entity &e); } // namespace util } // namespace cx diff --git a/src/package_diagram/visitor/translation_unit_visitor.cc b/src/package_diagram/visitor/translation_unit_visitor.cc index f76d97ba..bb7e10b6 100644 --- a/src/package_diagram/visitor/translation_unit_visitor.cc +++ b/src/package_diagram/visitor/translation_unit_visitor.cc @@ -353,6 +353,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, const auto fn = cx::util::full_name( resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false); auto t_ns = util::split(fn, "::"); + auto t_name = t_ns.back(); t_ns.pop_back(); const auto &t_raw = resolve_alias(cppast::remove_cv(t_)); @@ -465,7 +466,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_, found = find_relationships(args[0u].type().value(), relationships, relationship_t::kDependency); } - else if (ctx.config().should_include(fn)) { + else if (ctx.config().should_include(t_ns, t_name)) { LOG_DBG("User defined template instantiation: {} | {}", cppast::to_string(t_), cppast::to_string(t_.canonical())); diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 65244d07..29614816 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -80,7 +80,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e) .value(); m.from = cx::util::ns(caller) + "::" + caller.name(); - if (!ctx.config().should_include(m.from)) + if (!ctx.config().should_include( + util::split(cx::util::ns(caller), "::"), caller.name())) continue; if (caller.kind() == cpp_entity_kind::function_t) @@ -96,7 +97,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e) if (callee.kind() == cpp_entity_kind::function_t) m.to += "()"; - if (!ctx.config().should_include(m.to)) + if (!ctx.config().should_include( + util::split(cx::util::ns(callee), "::"), callee.name())) continue; m.to_usr = type_safe::get(function_call.get_callee_method_id()); diff --git a/src/util/util.cc b/src/util/util.cc index 0a43824b..92e79db3 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -44,22 +44,21 @@ std::vector split(std::string str, std::string delimiter) { std::vector result; - while (str.size()) { - int index = str.find(delimiter); - if (index != std::string::npos) { - result.push_back(str.substr(0, index)); - str = str.substr(index + delimiter.size()); - if (str.size() == 0) - result.push_back(str); - } - else { - result.push_back(str); - str = ""; - } - } - - if (result.empty()) + if (!contains(str, delimiter)) result.push_back(str); + else + while (str.size()) { + int index = str.find(delimiter); + if (index != std::string::npos) { + result.push_back(str.substr(0, index)); + str = str.substr(index + delimiter.size()); + } + else { + if (!str.empty()) + result.push_back(str); + str = ""; + } + } return result; } diff --git a/src/util/util.h b/src/util/util.h index 88332b68..d929f41a 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace clanguml { @@ -190,6 +191,9 @@ bool contains(const T &container, const E &element) [&element](const auto &e) { return *e == *element; }) != container.end(); } + else if constexpr (std::is_same_v, std::string>) { + return container.find(element) != std::string::npos; + } else { return std::find(container.begin(), container.end(), element) != container.end(); diff --git a/tests/t00002/test_case.h b/tests/t00002/test_case.h index c4f4c879..4c46f175 100644 --- a/tests/t00002/test_case.h +++ b/tests/t00002/test_case.h @@ -30,8 +30,8 @@ TEST_CASE("t00002", "[test-case][class]") REQUIRE(diagram->exclude().namespaces.size() == 0); - REQUIRE(diagram->should_include("clanguml::t00002::A")); - REQUIRE(!diagram->should_include("std::vector")); + REQUIRE(diagram->should_include({"clanguml", "t00002"}, "A")); + REQUIRE(!diagram->should_include({"std"}, "vector")); auto model = generate_class_diagram(db, diagram); diff --git a/tests/t00014/test_case.h b/tests/t00014/test_case.h index 0b81e88d..ec4e5cd7 100644 --- a/tests/t00014/test_case.h +++ b/tests/t00014/test_case.h @@ -39,12 +39,23 @@ TEST_CASE("t00014", "[test-case][class]") REQUIRE_THAT(puml, IsClassTemplate("A", "T,std::string")); REQUIRE_THAT(puml, IsClassTemplate("A", "bool,std::string")); REQUIRE_THAT(puml, IsClassTemplate("AString", "float")); + REQUIRE_THAT(puml, IsClassTemplate("AString", "int")); + REQUIRE_THAT(puml, IsClassTemplate("AString", "std::string")); REQUIRE_THAT( puml, !IsClassTemplate("std::std::function", "void(T...,int),int)")); REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); REQUIRE_THAT( puml, IsInstantiation(_A("A"), _A("AString"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("A"), _A("AString"))); + REQUIRE_THAT( + puml, !IsInstantiation(_A("AString"), _A("AString"))); + REQUIRE_THAT(puml, + IsInstantiation(_A("A"), _A("AString"))); + REQUIRE_THAT(puml, + !IsInstantiation( + _A("AString"), _A("AString"))); REQUIRE_THAT( puml, IsAggregation(_A("R"), _A("A"), "-boolstring")); REQUIRE_THAT( diff --git a/tests/test_util.cc b/tests/test_util.cc index 9fa461da..8e7b93fa 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -32,6 +32,8 @@ TEST_CASE("Test split", "[unit-test]") CHECK(split("ABCD", " ") == C{"ABCD"}); CHECK(split("std::vector::detail", "::") == C{"std", "vector", "detail"}); + + CHECK(split("std::vector::detail::", "::") == C{"std", "vector", "detail"}); } TEST_CASE("Test ns_relative", "[unit-test]")