diff --git a/docs/test_cases.md b/docs/test_cases.md index 19d605fa..c1a89cdf 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -31,6 +31,7 @@ * [t00030](./test_cases/t00030.md) - PlantUML relationship decorators test case * [t00031](./test_cases/t00031.md) - PlantUML style decorator test case * [t00032](./test_cases/t00032.md) - Class template with template base classes test case + * [t00033](./test_cases/t00033.md) - Nested template instantiation dependency test case ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram ## Configuration diagrams diff --git a/docs/test_cases/t00006_class.png b/docs/test_cases/t00006_class.png index fb5f38a9..6b052977 100644 Binary files a/docs/test_cases/t00006_class.png and b/docs/test_cases/t00006_class.png differ diff --git a/docs/test_cases/t00025_class.png b/docs/test_cases/t00025_class.png index 15f6e781..9b76a458 100644 Binary files a/docs/test_cases/t00025_class.png and b/docs/test_cases/t00025_class.png differ diff --git a/docs/test_cases/t00032_class.png b/docs/test_cases/t00032_class.png index a8dd3edb..cbd72e5c 100644 Binary files a/docs/test_cases/t00032_class.png and b/docs/test_cases/t00032_class.png differ diff --git a/docs/test_cases/t00033.md b/docs/test_cases/t00033.md new file mode 100644 index 00000000..25ef901d --- /dev/null +++ b/docs/test_cases/t00033.md @@ -0,0 +1,52 @@ +# t00033 - Nested template instantiation dependency test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00033_class: + type: class + glob: + - ../../tests/t00033/t00033.cc + using_namespace: + - clanguml::t00033 + include: + namespaces: + - clanguml::t00033 + +``` +## Source code +File t00033.cc +```cpp +#include +#include + +namespace clanguml { +namespace t00033 { + +template struct A { + T aaa; +}; + +template struct B { + T bbb; +}; + +template struct C { + T ccc; +}; + +struct D { + int ddd; +}; + +struct R { + A>> abc; +}; + +} // namespace t00033 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00033_class](./t00033_class.png "Nested template instantiation dependency test case") diff --git a/docs/test_cases/t00033_class.png b/docs/test_cases/t00033_class.png new file mode 100644 index 00000000..987938ca Binary files /dev/null and b/docs/test_cases/t00033_class.png differ diff --git a/src/uml/class_diagram_visitor.cc b/src/uml/class_diagram_visitor.cc index 3282cbea..0eef9f45 100644 --- a/src/uml/class_diagram_visitor.cc +++ b/src/uml/class_diagram_visitor.cc @@ -169,16 +169,6 @@ void tu_visitor::operator()(const cppast::cpp_entity &file) const cppast::cpp_template_instantiation_type &>( at.type_alias().underlying_type())); - class_relationship r; - r.destination = tinst.base_template_usr; - r.type = relationship_t::kInstantiation; - r.label = ""; - r.scope = scope_t::kNone; - tinst.add_relationship(std::move(r)); - - LOG_DBG("Created template instantiation: {}, {}, {}", - tinst.name, tinst.usr, tinst.base_template_usr); - ctx.d.add_class(std::move(tinst)); } }); @@ -549,26 +539,8 @@ bool tu_visitor::process_field_with_template_instantiation( resolve_alias(template_instantiation_type)); class_ tinst = build_template_instantiation(unaliased); - class_relationship r; - const auto &tt = cppast::remove_cv( - cx::util::unreferenced(template_instantiation_type)); - auto fn = cx::util::full_name(tt, ctx.entity_index, false); - fn = util::split(fn, "<")[0]; - - if (ctx.has_type_alias(fn)) { - // If this is a template alias - set the instantiation - // relationship to the first alias target - r.destination = cppast::to_string(ctx.get_type_alias(fn).get()); - } - else { - // Otherwise point to the base template - r.destination = tinst.base_template_usr; - } - r.type = relationship_t::kInstantiation; - r.label = ""; - r.scope = scope_t::kNone; - tinst.add_relationship(std::move(r)); - + // Infer the relationship of this field to the template + // instantiation class_relationship rr; rr.destination = tinst.usr; if (mv.type().kind() == cppast::cpp_type_kind::pointer_t || @@ -580,6 +552,7 @@ bool tu_visitor::process_field_with_template_instantiation( rr.scope = detail::cpp_access_specifier_to_scope(as); rr.style = m.style_spec(); + // Process field decorators auto [decorator_rtype, decorator_rmult] = m.relationship(); if (decorator_rtype != relationship_t::kNone) { rr.type = decorator_rtype; @@ -937,15 +910,6 @@ void tu_visitor::process_function_parameter( class_ tinst = build_template_instantiation( template_instantiation_type); - LOG_DBG("Created template instantiation: {}, {}", - tinst.name, tinst.usr); - - class_relationship r; - r.destination = tinst.base_template_usr; - r.type = relationship_t::kInstantiation; - r.label = ""; - tinst.add_relationship(std::move(r)); - class_relationship rr; rr.destination = tinst.usr; rr.type = relationship_t::kDependency; @@ -1292,6 +1256,60 @@ class_ tu_visitor::build_template_instantiation( class_template ct; if (targ.type()) { ct.type = cppast::to_string(targ.type().value()); + + LOG_DBG("Template argument is a type {}", ct.type); + + auto fn = cx::util::full_name( + cppast::remove_cv(cx::util::unreferenced(targ.type().value())), + ctx.entity_index, false); + + if (ctx.config.should_include(fn)) { + + if (targ.type().value().kind() == + cppast::cpp_type_kind::template_instantiation_t) { + class_ nested_tinst = + build_template_instantiation(static_cast< + const cppast::cpp_template_instantiation_type &>( + targ.type().value())); + + fn = util::split(fn, "<")[0]; + + class_relationship tinst_dependency; + tinst_dependency.type = relationship_t::kDependency; + tinst_dependency.label = ""; + + tinst_dependency.destination = + nested_tinst.full_name(ctx.config.using_namespace); + + LOG_DBG("Creating nested template dependency to template " + "instantiation {} -> {}", + tinst.full_name(ctx.config.using_namespace), + tinst_dependency.destination); + + tinst.add_relationship(std::move(tinst_dependency)); + + ctx.d.add_class(std::move(nested_tinst)); + } + else if (targ.type().value().kind() == + cppast::cpp_type_kind::user_defined_t) { + class_relationship tinst_dependency; + tinst_dependency.type = relationship_t::kDependency; + tinst_dependency.label = ""; + + tinst_dependency.destination = cx::util::full_name( + cppast::remove_cv( + cx::util::unreferenced(targ.type().value())), + ctx.entity_index, false); + + LOG_DBG( + "Creating nested template dependency to user defined " + "type {} -> {}", + tinst.full_name(ctx.config.using_namespace), + tinst_dependency.destination); + + tinst.add_relationship(std::move(tinst_dependency)); + } + } } else if (targ.expression()) { const auto &exp = targ.expression().value(); @@ -1304,6 +1322,8 @@ class_ tu_visitor::build_template_instantiation( static_cast(exp) .expression() .as_string(); + + LOG_DBG("Template argument is an expression {}", ct.type); } // In case any of the template arguments are base classes, add @@ -1342,6 +1362,27 @@ class_ tu_visitor::build_template_instantiation( tinst.usr = ns + tinst.usr; } + // Add instantiation relationship to primary template of this + // instantiation + class_relationship r; + const auto &tt = cppast::remove_cv(cx::util::unreferenced(t)); + auto fn = cx::util::full_name(tt, ctx.entity_index, false); + fn = util::split(fn, "<")[0]; + + if (ctx.has_type_alias(fn)) { + // If this is a template alias - set the instantiation + // relationship to the first alias target + r.destination = cppast::to_string(ctx.get_type_alias(fn).get()); + } + else { + // Otherwise point to the base template + r.destination = tinst.base_template_usr; + } + r.type = relationship_t::kInstantiation; + r.label = ""; + r.scope = scope_t::kNone; + tinst.add_relationship(std::move(r)); + return tinst; } diff --git a/tests/t00033/.clang-uml b/tests/t00033/.clang-uml new file mode 100644 index 00000000..5b474474 --- /dev/null +++ b/tests/t00033/.clang-uml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00033_class: + type: class + glob: + - ../../tests/t00033/t00033.cc + using_namespace: + - clanguml::t00033 + include: + namespaces: + - clanguml::t00033 diff --git a/tests/t00033/t00033.cc b/tests/t00033/t00033.cc new file mode 100644 index 00000000..80453c92 --- /dev/null +++ b/tests/t00033/t00033.cc @@ -0,0 +1,28 @@ +#include +#include + +namespace clanguml { +namespace t00033 { + +template struct A { + T aaa; +}; + +template struct B { + T bbb; +}; + +template struct C { + T ccc; +}; + +struct D { + int ddd; +}; + +struct R { + A>> abc; +}; + +} // namespace t00033 +} // namespace clanguml diff --git a/tests/t00033/test_case.h b/tests/t00033/test_case.h new file mode 100644 index 00000000..a1f3bbec --- /dev/null +++ b/tests/t00033/test_case.h @@ -0,0 +1,61 @@ +/** + * tests/t00033/test_case.cc + * + * Copyright (c) 2021 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("t00033", "[test-case][class]") +{ + auto [config, db] = load_config("t00033"); + + auto diagram = config.diagrams["t00033_class"]; + + REQUIRE(diagram->name == "t00033_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00033"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00033::A")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00033_class"); + + auto puml = generate_class_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, IsClassTemplate("A", "T")); + REQUIRE_THAT(puml, IsClassTemplate("B", "T")); + REQUIRE_THAT(puml, IsClassTemplate("C", "T")); + REQUIRE_THAT(puml, IsClass(_A("D"))); + REQUIRE_THAT(puml, IsClass(_A("R"))); + + REQUIRE_THAT(puml, IsDependency(_A("A>>"), _A("B>"))); + REQUIRE_THAT(puml, IsDependency(_A("B>"), _A("C"))); + REQUIRE_THAT(puml, IsDependency(_A("C"), _A("D"))); + + REQUIRE_THAT(puml, IsInstantiation(_A("C"), _A("C"))); + REQUIRE_THAT(puml, IsInstantiation(_A("B"), _A("B>"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A>>"))); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index d410f137..bde5c0c8 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -136,6 +136,7 @@ using namespace clanguml::test::matchers; #include "t00030/test_case.h" #include "t00031/test_case.h" #include "t00032/test_case.h" +#include "t00033/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 160c327b..823ba7e8 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -93,6 +93,9 @@ test_cases: - name: t00032 title: Class template with template base classes test case description: + - name: t00033 + title: Nested template instantiation dependency test case + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram