diff --git a/src/uml/class_diagram_visitor.cc b/src/uml/class_diagram_visitor.cc index d7178597..7b7be600 100644 --- a/src/uml/class_diagram_visitor.cc +++ b/src/uml/class_diagram_visitor.cc @@ -106,7 +106,8 @@ void tu_visitor::operator()(const cppast::cpp_entity &file) auto &tspec = static_cast< const cppast::cpp_class_template_specialization &>(e); - process_class_declaration(tspec.class_(), tspec); + process_class_declaration( + tspec.class_(), type_safe::ref(tspec)); } else if (e.kind() == cppast::cpp_entity_kind::class_t) { LOG_DBG("========== Visiting '{}' - {}", @@ -358,8 +359,97 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls, } } else { - LOG_WARN("Class {} is templated but it's scope {} is not :-|", + LOG_WARN("Class {} is templated but it's scope {} is not - " + "probably this is a specialization", cls.name(), scope.name()); + + // Add specialization arguments + if (tspec) { + if (!tspec.value().arguments_exposed()) { + // Create template specialization with unexposed arguments + auto ua = tspec.value().unexposed_arguments().as_string(); + + // Naive parse of template arguments: + auto toks = util::split(ua, ","); + for (const auto &t : toks) { + class_template ct; + ct.type = t; + ct.default_value = ""; + ct.is_variadic = false; + ct.name = ""; + c.templates.emplace_back(std::move(ct)); + + const auto &primary_template_ref = + static_cast( + tspec.value() + .primary_template() + .get(ctx.entity_index)[0] + .get()) + .class_(); + + if (primary_template_ref.user_data()) { + auto base_template_usr = static_cast( + primary_template_ref.user_data()); + LOG_DBG("Primary template ref set to: {}", + base_template_usr); + // Add template specialization/instantiation + // relationship + class_relationship r; + r.type = relationship_t::kInstantiation; + r.label = ""; + r.destination = base_template_usr; + c.add_relationship(std::move(r)); + } + else { + LOG_WARN("No user data for base template {}", + primary_template_ref.name()); + } + } + } + else { + for (auto &tp : tspec.value().parameters()) { + switch (tp.kind()) { + case cppast::cpp_entity_kind:: + template_type_parameter_t: { + LOG_DBG("Processing template type parameter {}", + tp.name()); + process_template_type_parameter( + static_cast(tp), + c); + } break; + case cppast::cpp_entity_kind:: + non_type_template_parameter_t: { + LOG_DBG("Processing template nontype parameter {}", + tp.name()); + process_template_nontype_parameter( + static_cast(tp), + c); + } break; + case cppast::cpp_entity_kind:: + template_template_parameter_t: { + LOG_DBG("Processing template template parameter {}", + tp.name()); + process_template_template_parameter( + static_cast(tp), + c); + } break; + default: + LOG_DBG("Unhandled template parameter " + "type {}", + cppast::to_string(tp.kind())); + break; + } + } + } + } + else { + LOG_DBG("Skipping template class declaration which has only " + "unexposed arguments but no tspec provided"); + return; + } } } @@ -810,7 +900,7 @@ void tu_visitor::process_friend(const cppast::cpp_friend &f, class_ &parent) r.destination = name; } else if (f.entity()) { - std::string name {}; + std::string name{}; if (f.entity().value().kind() == cppast::cpp_entity_kind::class_template_t) { diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index a21bc971..c826d0c4 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -45,9 +45,9 @@ struct tu_context { tu_context(cppast::cpp_entity_index &idx, clanguml::model::class_diagram::diagram &d_, const clanguml::config::class_diagram &config_) - : entity_index {idx} - , d {d_} - , config {config_} + : entity_index{idx} + , d{d_} + , config{config_} { } @@ -128,13 +128,13 @@ struct tu_context { template struct element_visitor_context { element_visitor_context(clanguml::model::class_diagram::diagram &d_, T &e) : element(e) - , d {d_} + , d{d_} { } tu_context *ctx; T &element; - clanguml::model::class_diagram::class_ *parent_class {}; + clanguml::model::class_diagram::class_ *parent_class{}; clanguml::model::class_diagram::diagram &d; }; @@ -143,7 +143,7 @@ public: tu_visitor(cppast::cpp_entity_index &idx_, clanguml::model::class_diagram::diagram &d_, const clanguml::config::class_diagram &config_) - : ctx {idx_, d_, config_} + : ctx{idx_, d_, config_} { } diff --git a/tests/t00016/t00016.cc b/tests/t00016/t00016.cc index 6962d8f3..519df160 100644 --- a/tests/t00016/t00016.cc +++ b/tests/t00016/t00016.cc @@ -1,7 +1,7 @@ namespace clanguml { namespace t00016 { -template struct is_numeric { +template struct is_numeric { enum { value = false }; }; diff --git a/tests/t00016/test_case.h b/tests/t00016/test_case.h index 5f50d76b..96ac4d2e 100644 --- a/tests/t00016/test_case.h +++ b/tests/t00016/test_case.h @@ -41,7 +41,20 @@ TEST_CASE("t00016", "[test-case][class]") REQUIRE_THAT(puml, StartsWith("@startuml")); REQUIRE_THAT(puml, EndsWith("@enduml\n")); - REQUIRE_THAT(puml, IsClass(_A("is_numeric"))); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "int")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "bool")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "char")); + REQUIRE_THAT(puml, IsClassTemplate("is_numeric", "unsigned char")); + + REQUIRE_THAT( + puml, IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); + REQUIRE_THAT(puml, + IsInstantiation(_A("is_numeric<>"), _A("is_numeric"))); save_puml( "./" + config.output_directory + "/" + diagram->name + ".puml", puml);