diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index e964244c..a240f8eb 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -99,15 +99,16 @@ void diagram::add_type_alias(std::unique_ptr &&ta) type_aliases_[ta->alias()] = std::move(ta); } -void diagram::add_package(std::unique_ptr &&p) +bool diagram::add_package(std::unique_ptr &&p) { LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true)); auto ns = p->get_relative_namespace(); - add_element(ns, std::move(p)); + + return add_element(ns, std::move(p)); } -void diagram::add_class(std::unique_ptr &&c) +bool diagram::add_class(std::unique_ptr &&c) { const auto base_name = c->name(); const auto full_name = c->full_name(false); @@ -128,33 +129,48 @@ void diagram::add_class(std::unique_ptr &&c) auto name = base_name; auto name_with_ns = c->name_and_ns(); auto name_and_ns = ns | name; + const auto &cc = *c; - if (!has_class(*c)) { - classes_.push_back(type_safe::ref(*c)); + auto cc_ref = type_safe::ref(cc); - add_element(ns, std::move(c)); + if (!has_class(cc)) { + if (add_element(ns, std::move(c))) + classes_.push_back(std::move(cc_ref)); const auto &el = get_element(name_and_ns).value(); + assert(el.name() == name); assert(el.get_relative_namespace() == ns); + + return true; } - else - LOG_DBG("Class {} ({}) already in the model", base_name, full_name); + + LOG_DBG("Class {} ({}) already in the model", base_name, full_name); + + return false; } -void diagram::add_enum(std::unique_ptr &&e) +bool diagram::add_enum(std::unique_ptr &&e) { - LOG_DBG("Adding enum: {}", e->name()); + const auto full_name = e->name(); + + LOG_DBG("Adding enum: {}", full_name); assert(!util::contains(e->name(), "::")); + auto e_ref = type_safe::ref(*e); + auto ns = e->get_relative_namespace(); + if (!has_enum(*e)) { - enums_.emplace_back(*e); - auto ns = e->get_relative_namespace(); - add_element(ns, std::move(e)); + if (add_element(ns, std::move(e))) { + enums_.emplace_back(std::move(e_ref)); + return true; + } } - else - LOG_DBG("Enum {} already in the model", e->name()); + + LOG_DBG("Enum {} already in the model", full_name); + + return false; } void diagram::get_parents( diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index ccfc546d..48d3887b 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -63,11 +63,11 @@ public: void add_type_alias(std::unique_ptr &&ta); - void add_class(std::unique_ptr &&c); + bool add_class(std::unique_ptr &&c); - void add_enum(std::unique_ptr &&e); + bool add_enum(std::unique_ptr &&e); - void add_package(std::unique_ptr &&p); + bool add_package(std::unique_ptr &&p); std::string to_alias(const std::string &full_name) const; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 83ca6db7..acbe6349 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -765,9 +765,12 @@ bool translation_unit_visitor::process_field_with_template_instantiation( } } + const auto tinst_namespace = tinst.get_namespace(); + const auto tinst_name = tinst.name(); + // Add instantiation relationship from the generated template instantiation // of the field type to its primary template - if (ctx.diagram().should_include(tinst.get_namespace(), tinst.name())) { + if (ctx.diagram().should_include(tinst_namespace, tinst_name)) { LOG_DBG("Adding field instantiation relationship {} {} {} : {}", rr.destination(), clanguml::common::model::to_string(rr.type()), c.full_name(), rr.label()); @@ -787,7 +790,7 @@ bool translation_unit_visitor::process_field_with_template_instantiation( // Only add nested template relationships to this class if the top level // template is not in the diagram (e.g. it is a std::shared_ptr<>) // - if (!ctx.diagram().should_include(tinst.get_namespace(), tinst.name())) { + if (!ctx.diagram().should_include(tinst_namespace, tinst_name)) { res = add_nested_template_relationships(mv, c, member, as, tinst, relationship_type, decorator_rtype, decorator_rmult); } @@ -1660,16 +1663,21 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( tinst.set_namespace(ctx.get_namespace()); - auto tinst_full_name = cppast::to_string(t); + std::string tinst_full_name; // // Typically, every template instantiation should have a - // primary_template() which should also be generated here if it doesn't + // primary_template(), which should also be generated here if it doesn't // exist yet in the model // - if (t.primary_template().get(ctx.entity_index()).size()) { - auto size = t.primary_template().get(ctx.entity_index()).size(); - (void)size; + if (t_is_alias && + unaliased.primary_template().get(ctx.entity_index()).size()) { + tinst_full_name = cppast::to_string(unaliased); + build_template_instantiation_primary_template( + unaliased, tinst, template_base_params, parent, full_template_name); + } + else if (t.primary_template().get(ctx.entity_index()).size()) { + tinst_full_name = cppast::to_string(t); build_template_instantiation_primary_template( t, tinst, template_base_params, parent, full_template_name); } @@ -1677,6 +1685,7 @@ std::unique_ptr translation_unit_visitor::build_template_instantiation( LOG_DBG("Template instantiation {} has no primary template?", cppast::to_string(t)); + tinst_full_name = cppast::to_string(t); full_template_name = cppast::to_string(t); } diff --git a/src/common/model/nested_trait.h b/src/common/model/nested_trait.h index b4e79cfe..bd134a7f 100644 --- a/src/common/model/nested_trait.h +++ b/src/common/model/nested_trait.h @@ -38,21 +38,24 @@ public: virtual ~nested_trait() = default; - template void add_element(std::unique_ptr p) + template + [[nodiscard]] bool add_element(std::unique_ptr p) { auto it = std::find_if(elements_.begin(), elements_.end(), [&p](const auto &e) { return *e == *p; }); if (it != elements_.end()) { - (*it)->append(*p); - } - else { - elements_.emplace_back(std::move(p)); + // Element already in element tree + return false; } + + elements_.emplace_back(std::move(p)); + + return true; } template - void add_element(const Path &path, std::unique_ptr p) + bool add_element(const Path &path, std::unique_ptr p) { assert(p); @@ -60,14 +63,13 @@ public: path.to_string()); if (path.is_empty()) { - add_element(std::move(p)); - return; + return add_element(std::move(p)); } auto parent = get_element(path); if (parent && dynamic_cast *>(&parent.value())) - dynamic_cast &>(parent.value()) + return dynamic_cast &>(parent.value()) .template add_element(std::move(p)); else { spdlog::error("No parent element found at: {}", path.to_string()); diff --git a/src/include_diagram/visitor/translation_unit_visitor.cc b/src/include_diagram/visitor/translation_unit_visitor.cc index 29daf43e..da1189b2 100644 --- a/src/include_diagram/visitor/translation_unit_visitor.cc +++ b/src/include_diagram/visitor/translation_unit_visitor.cc @@ -132,7 +132,8 @@ void translation_unit_visitor::process_external_system_header( f->set_name(include_directive.name()); f->set_type(common::model::source_file_t::kHeader); - ctx.diagram().add_element(std::move(f)); + if (!ctx.diagram().add_element(std::move(f))) + LOG_DBG("Include {} already in the model", include_directive.name()); auto dependency_relationship = common::model::relationship{ common::model::relationship_t::kDependency, diff --git a/tests/t00014/t00014.cc b/tests/t00014/t00014.cc index 880f7d0b..c065b3f7 100644 --- a/tests/t00014/t00014.cc +++ b/tests/t00014/t00014.cc @@ -48,11 +48,13 @@ using BVector2 = BVector; using AIntString = AString; using ACharString = AString; -using AWCharString = AString; + using AStringString = AString; using BStringString = AStringString; class R { + using AWCharString = AString; + PairPairBA bapair; APtr abool; diff --git a/tests/t00014/test_case.h b/tests/t00014/test_case.h index 7b5c0299..cac37dce 100644 --- a/tests/t00014/test_case.h +++ b/tests/t00014/test_case.h @@ -61,7 +61,8 @@ TEST_CASE("t00014", "[test-case][class]") REQUIRE_THAT(puml, IsField("bs", "BVector")); REQUIRE_THAT(puml, IsField("cb", "SimpleCallback")); - REQUIRE_THAT(puml, IsField("gcb", "GenericCallback")); + REQUIRE_THAT( + puml, IsField("gcb", "GenericCallback")); REQUIRE_THAT(puml, IsField("vcb", "VoidCallback")); REQUIRE_THAT( diff --git a/tests/t00044/.clang-uml b/tests/t00044/.clang-uml new file mode 100644 index 00000000..69dab8ac --- /dev/null +++ b/tests/t00044/.clang-uml @@ -0,0 +1,13 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00044_class: + type: class + generate_packages: true + glob: + - ../../tests/t00044/t00044.cc + using_namespace: + - clanguml::t00044 + include: + namespaces: + - clanguml::t00044 \ No newline at end of file diff --git a/tests/t00044/t00044.cc b/tests/t00044/t00044.cc new file mode 100644 index 00000000..67ff645b --- /dev/null +++ b/tests/t00044/t00044.cc @@ -0,0 +1,35 @@ +// Inspired by skypjack/entt signal handlers +// This test case checks that at least clang-uml does not crash on this code +namespace clanguml::t00044 { + +template class sink; + +template class signal_handler; + +template +class sink> { + using signal_t = signal_handler; + +public: + sink(signal_t &sh) + : signal{&sh} + { + } + +private: + signal_t *signal; +}; + +template +class signal_handler { +}; + +template +sink(signal_handler &) + -> sink>; + +signal_handler int_handler; + +sink sink1{int_handler}; + +} // namespace clanguml::t00044 diff --git a/tests/t00044/test_case.h b/tests/t00044/test_case.h new file mode 100644 index 00000000..26b4fa16 --- /dev/null +++ b/tests/t00044/test_case.h @@ -0,0 +1,43 @@ +/** + * tests/t00044/test_case.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t00044", "[test-case][class]") +{ + auto [config, db] = load_config("t00044"); + + auto diagram = config.diagrams["t00044_class"]; + + REQUIRE(diagram->name == "t00044_class"); + REQUIRE(diagram->generate_packages() == true); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model->name() == "t00044_class"); + + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check dependants filter + REQUIRE_THAT(puml, IsClassTemplate("signal_handler", "Ret,Args...,A")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 7e78b34f..02683907 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -225,6 +225,7 @@ using namespace clanguml::test::matchers; #include "t00041/test_case.h" #include "t00042/test_case.h" #include "t00043/test_case.h" +#include "t00044/test_case.h" // // Sequence diagram tests