diff --git a/src/cx/cursor.h b/src/cx/cursor.h index 57fb9408..0fec6790 100644 --- a/src/cx/cursor.h +++ b/src/cx/cursor.h @@ -19,6 +19,9 @@ #include "cx/type.h" +#include +#include + namespace clanguml { namespace cx { @@ -214,6 +217,11 @@ public: return clang_Cursor_getTemplateArgumentValue(m_cursor, i); } + cursor specialized_cursor_template() const + { + return clang_getSpecializedCursorTemplate(m_cursor); + } + std::string usr() const { return to_string(clang_getCursorUSR(m_cursor)); } const CXCursor &get() const { return m_cursor; } diff --git a/src/cx/type.cc b/src/cx/type.cc new file mode 100644 index 00000000..39499f01 --- /dev/null +++ b/src/cx/type.cc @@ -0,0 +1,42 @@ +/** + * src/cx/type.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. + */ +#include "type.h" +#include "cursor.h" + +namespace clanguml { +namespace cx { + +cursor type::type_declaration() const +{ + return {clang_getTypeDeclaration(m_type)}; +} + +std::string type::instantiation_template() const +{ + assert(is_template_instantiation()); + + auto s = spelling(); + auto it = s.find('<'); + auto template_base_name = s.substr(0, it); + + auto cur = type_declaration(); + + return cur.fully_qualified(); +} +} +} diff --git a/src/cx/type.h b/src/cx/type.h index c6bf1d8e..60c6a543 100644 --- a/src/cx/type.h +++ b/src/cx/type.h @@ -30,6 +30,8 @@ namespace cx { using util::to_string; +class cursor; + class type { public: type(CXType &&t) @@ -50,6 +52,8 @@ public: type canonical() const { return {clang_getCanonicalType(m_type)}; } + bool is_unexposed() const { return kind() == CXType_Unexposed; } + bool is_const_qualified() const { return clang_isConstQualifiedType(m_type); @@ -72,12 +76,7 @@ public: type pointee_type() const { return {clang_getPointeeType(m_type)}; } - /* - *cursor type_declaration() const - *{ - * return {clang_getTypeDeclaration(m_type)}; - *} - */ + cursor type_declaration() const; /* *std::string type_kind_spelling() const @@ -151,7 +150,7 @@ public: bool is_relationship() const { return is_pointer() || is_record() || is_reference() || !is_pod() || - is_array() || + is_array() || is_template() || (spelling().find("std::array") == 0 /* There must be a better way... */); } @@ -204,6 +203,15 @@ public: return clang_Type_getCXXRefQualifier(m_type); } + bool is_template_instantiation() const + { + auto s = spelling(); + auto it = s.find('<'); + return it != std::string::npos; + } + + std::string instantiation_template() const; + /** * @brief Remove all qualifiers from field declaration. * diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index ffd35fd3..11e30aa6 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -88,6 +88,8 @@ public: return "+--"; case relationship_t::kAssociation: return "-->"; + case relationship_t::kInstantiation: + return "..|>"; default: return ""; } @@ -159,11 +161,22 @@ public: } for (const auto &r : c.relationships) { + std::string destination; + if (r.type == relationship_t::kInstantiation) { + destination = m_model.usr_to_name( + m_config.using_namespace, r.destination); + } + else { + destination = r.destination; + } + ostr << m_model.to_alias(m_config.using_namespace, - ns_relative(m_config.using_namespace, c.name)) + ns_relative(m_config.using_namespace, + c.full_name(m_config.using_namespace))) << " " << to_string(r.type) << " " << m_model.to_alias(m_config.using_namespace, - ns_relative(m_config.using_namespace, r.destination)); + ns_relative(m_config.using_namespace, destination)); + if (!r.label.empty()) ostr << " : " << r.label; diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h index 131b9b53..09acd848 100644 --- a/src/uml/class_diagram_model.h +++ b/src/uml/class_diagram_model.h @@ -43,7 +43,8 @@ enum class relationship_t { kAggregation, kContainment, kOwnership, - kAssociation + kAssociation, + kInstantiation }; class element { @@ -112,14 +113,17 @@ struct class_template { class class_ : public element { public: + std::string usr; bool is_struct{false}; bool is_template{false}; + bool is_template_instantiation{false}; std::vector members; std::vector methods; std::vector bases; std::vector inner_classes; std::vector relationships; std::vector templates; + std::string base_template_usr; std::string full_name( const std::vector &using_namespaces) const @@ -186,6 +190,17 @@ struct diagram { return full_name; } + + std::string usr_to_name(const std::vector &using_namespaces, + const std::string &usr) const + { + for (const auto &c : classes) { + if (c.usr == usr) + return c.full_name(using_namespaces); + } + + throw std::runtime_error("Cannot resolve USR: " + usr); + } }; } } diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index e1468767..2d27b67d 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -56,13 +56,15 @@ struct tu_context { }; template struct element_visitor_context { - element_visitor_context(T &e) + element_visitor_context(diagram &d_, T &e) : element(e) + , d{d_} { } tu_context *ctx; T &element; + diagram &d; }; enum CXChildVisitResult visit_if_cursor_valid( @@ -238,7 +240,8 @@ static enum CXChildVisitResult class_visitor( spdlog::info("Class {} has {} template arguments.", c.name, cursor.template_argument_count()); - auto class_ctx = element_visitor_context(c); + auto class_ctx = + element_visitor_context(ctx->ctx->d, c); class_ctx.ctx = ctx->ctx; clang_visitChildren(cursor.get(), class_visitor, &class_ctx); @@ -266,7 +269,7 @@ static enum CXChildVisitResult class_visitor( e.name = cursor.fully_qualified(); e.namespace_ = ctx->ctx->namespace_; - auto enum_ctx = element_visitor_context(e); + auto enum_ctx = element_visitor_context(ctx->ctx->d, e); enum_ctx.ctx = ctx->ctx; clang_visitChildren(cursor.get(), enum_visitor, &enum_ctx); @@ -367,6 +370,43 @@ static enum CXChildVisitResult class_visitor( spdlog::info("Adding member {} {}::{} {}", m.type, ctx->element.name, cursor.spelling(), t); + if (t.is_unexposed()) { + if (t.is_template_instantiation() && + t.type_declaration() + .specialized_cursor_template() + .kind() != CXCursor_InvalidFile) { + spdlog::info( + "Found template instantiation: {} ..|> {}", + t.type_declaration(), + t.type_declaration() + .specialized_cursor_template()); + class_ tinst; + tinst.name = t.type_declaration().spelling(); + tinst.is_template_instantiation = true; + tinst.usr = t.type_declaration().usr(); + for (int i = 0; i < t.template_arguments_count(); + i++) { + class_template ct; + ct.type = + t.template_argument_type(i).spelling(); + tinst.templates.emplace_back(std::move(ct)); + } + tinst.base_template_usr = + t.type_declaration() + .specialized_cursor_template() + .usr(); + + class_relationship r; + r.destination = tinst.base_template_usr; + r.type = relationship_t::kInstantiation; + r.label = ""; + + tinst.relationships.emplace_back(std::move(r)); + + ctx->d.classes.emplace_back(std::move(tinst)); + } + } + relationship_t relationship_type = relationship_t::kNone; auto name = t.canonical().unqualified(); @@ -407,9 +447,13 @@ static enum CXChildVisitResult class_visitor( ctx->element.members.emplace_back(std::move(m)); }); - ret = CXChildVisit_Continue; + ret = CXChildVisit_Recurse; break; } + case CXCursor_ClassTemplatePartialSpecialization: { + spdlog::info("Found template specialization: {}", cursor); + ret = CXChildVisit_Continue; + } break; case CXCursor_CXXBaseSpecifier: { if (!config.should_include(cursor.referenced().fully_qualified())) { ret = CXChildVisit_Continue; @@ -493,11 +537,12 @@ static enum CXChildVisitResult translation_unit_visitor( visit_if_cursor_valid(cursor, [ctx, is_struct](cx::cursor cursor) { class_ c{}; + c.usr = cursor.usr(); c.is_struct = is_struct; c.name = cursor.fully_qualified(); c.namespace_ = ctx->namespace_; - auto class_ctx = element_visitor_context(c); + auto class_ctx = element_visitor_context(ctx->d, c); class_ctx.ctx = ctx; clang_visitChildren(cursor.get(), class_visitor, &class_ctx); @@ -520,7 +565,7 @@ static enum CXChildVisitResult translation_unit_visitor( e.name = cursor.fully_qualified(); e.namespace_ = ctx->namespace_; - auto enum_ctx = element_visitor_context(e); + auto enum_ctx = element_visitor_context(ctx->d, e); enum_ctx.ctx = ctx; clang_visitChildren(cursor.get(), enum_visitor, &enum_ctx); diff --git a/tests/t00009/.clanguml b/tests/t00009/.clanguml new file mode 100644 index 00000000..fd2b5b38 --- /dev/null +++ b/tests/t00009/.clanguml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00009_class: + type: class + glob: + - ../../tests/t00009/t00009.cc + using_namespace: + - clanguml::t00009 + include: + namespaces: + - clanguml::t00009 diff --git a/tests/t00009/t00009.cc b/tests/t00009/t00009.cc new file mode 100644 index 00000000..5fbd2f91 --- /dev/null +++ b/tests/t00009/t00009.cc @@ -0,0 +1,43 @@ +#include +#include + +/* +@startuml + +class "A" as C_0000000046 +class C_0000000046 { ++T value +} + +class "A" as ABC +class "A" as ABCD + +class "B" as C_0000000047 +class C_0000000047 { ++A aint ++A astring +} + +C_0000000046 <|.. ABC +C_0000000046 <|.. ABCD + +ABC <-- C_0000000047 : aint +ABCD <-- C_0000000047 : astring + +@enduml +*/ +namespace clanguml { +namespace t00009 { + +template class A { +public: + T value; +}; + +class B { +public: + A aint; + A astring; +}; +} +} diff --git a/tests/t00009/test_case.h b/tests/t00009/test_case.h new file mode 100644 index 00000000..fc659d3d --- /dev/null +++ b/tests/t00009/test_case.h @@ -0,0 +1,64 @@ +/** + * tests/t00009/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("Test t00009", "[unit-test]") +{ + spdlog::set_level(spdlog::level::debug); + + auto [config, db] = load_config("t00009"); + + auto diagram = config.diagrams["t00009_class"]; + + REQUIRE(diagram->name == "t00009_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00009"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00009::A")); + REQUIRE(diagram->should_include("clanguml::t00009::B")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00009_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, IsClass(_A("B"))); + + /* + REQUIRE_THAT(puml, IsField(Public("T value"))); + REQUIRE_THAT(puml, IsField(Public("T * pointer"))); + REQUIRE_THAT(puml, IsField(Public("T & reference"))); + REQUIRE_THAT(puml, IsField(Public("std::vector

values"))); + REQUIRE_THAT(puml, IsField(Public("std::array ints"))); + REQUIRE_THAT(puml, IsField(Public("bool (*)(int, int) comparator"))); + + REQUIRE_THAT(puml, IsClassTemplate("B", "T, C<>")); + + REQUIRE_THAT(puml, IsField(Public("C template_template"))); + */ + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index a79fc7f5..e9a9bae0 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -119,3 +119,4 @@ using clanguml::test::matchers::Static; #include "t00006/test_case.h" #include "t00007/test_case.h" #include "t00008/test_case.h" +#include "t00009/test_case.h"