Added template insantiation relation

This commit is contained in:
Bartek Kryza
2021-03-10 00:03:03 +01:00
parent e07392dae6
commit 9cb21ab7a2
10 changed files with 267 additions and 16 deletions

View File

@@ -19,6 +19,9 @@
#include "cx/type.h"
#include <list>
#include <string>
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; }

42
src/cx/type.cc Normal file
View File

@@ -0,0 +1,42 @@
/**
* src/cx/type.cc
*
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
*
* 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();
}
}
}

View File

@@ -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.
*

View File

@@ -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;

View File

@@ -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<class_member> members;
std::vector<class_method> methods;
std::vector<class_parent> bases;
std::vector<std::string> inner_classes;
std::vector<class_relationship> relationships;
std::vector<class_template> templates;
std::string base_template_usr;
std::string full_name(
const std::vector<std::string> &using_namespaces) const
@@ -186,6 +190,17 @@ struct diagram {
return full_name;
}
std::string usr_to_name(const std::vector<std::string> &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);
}
};
}
}

View File

@@ -56,13 +56,15 @@ struct tu_context {
};
template <typename T> 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<class_>(c);
auto class_ctx =
element_visitor_context<class_>(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<enum_>(e);
auto enum_ctx = element_visitor_context<enum_>(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<class_>(c);
auto class_ctx = element_visitor_context<class_>(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<enum_>(e);
auto enum_ctx = element_visitor_context<enum_>(ctx->d, e);
enum_ctx.ctx = ctx;
clang_visitChildren(cursor.get(), enum_visitor, &enum_ctx);

12
tests/t00009/.clanguml Normal file
View File

@@ -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

43
tests/t00009/t00009.cc Normal file
View File

@@ -0,0 +1,43 @@
#include <string>
#include <vector>
/*
@startuml
class "A<T>" as C_0000000046
class C_0000000046 {
+T value
}
class "A<int>" as ABC
class "A<std::string>" as ABCD
class "B" as C_0000000047
class C_0000000047 {
+A<int> aint
+A<std::string> astring
}
C_0000000046 <|.. ABC
C_0000000046 <|.. ABCD
ABC <-- C_0000000047 : aint
ABCD <-- C_0000000047 : astring
@enduml
*/
namespace clanguml {
namespace t00009 {
template <typename T> class A {
public:
T value;
};
class B {
public:
A<int> aint;
A<std::string> astring;
};
}
}

64
tests/t00009/test_case.h Normal file
View File

@@ -0,0 +1,64 @@
/**
* tests/t00009/test_case.cc
*
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
*
* 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<P> values")));
REQUIRE_THAT(puml, IsField(Public("std::array<int, N> ints")));
REQUIRE_THAT(puml, IsField(Public("bool (*)(int, int) comparator")));
REQUIRE_THAT(puml, IsClassTemplate("B", "T, C<>"));
REQUIRE_THAT(puml, IsField(Public("C<T> template_template")));
*/
save_puml(
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
}

View File

@@ -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"