Added basic test case for sequence diagrams with template specializations

This commit is contained in:
Bartek Kryza
2022-11-23 00:05:43 +01:00
parent 4513e17275
commit b264ef5402
6 changed files with 144 additions and 43 deletions

View File

@@ -68,12 +68,12 @@ int template_trait::calculate_template_specialization_match(
{ {
int res{}; int res{};
// std::string left = name_and_ns(); // std::string left = name_and_ns();
// // TODO: handle variadic templates // // TODO: handle variadic templates
// if ((name_and_ns() != full_name) || // if ((name_and_ns() != full_name) ||
// (templates().size() != other.templates().size())) { // (templates().size() != other.templates().size())) {
// return res; // return res;
// } // }
// Iterate over all template arguments // Iterate over all template arguments
for (auto i = 0U; i < other.templates().size(); i++) { for (auto i = 0U; i < other.templates().size(); i++) {
@@ -156,7 +156,6 @@ std::string class_::full_name(bool relative) const
bool operator==(const class_ &l, const class_ &r) { return l.id() == r.id(); } bool operator==(const class_ &l, const class_ &r) { return l.id() == r.id(); }
function::function(const common::model::namespace_ &using_namespace) function::function(const common::model::namespace_ &using_namespace)
: participant{using_namespace} : participant{using_namespace}
{ {

View File

@@ -42,9 +42,14 @@ struct template_trait {
int calculate_template_specialization_match( int calculate_template_specialization_match(
const template_trait &other, const std::string &full_name) const; const template_trait &other, const std::string &full_name) const;
bool is_implicit() const { return is_implicit_; }
void set_implicit(bool implicit) { is_implicit_ = implicit; }
private: private:
std::vector<class_diagram::model::template_parameter> templates_; std::vector<class_diagram::model::template_parameter> templates_;
std::string base_template_full_name_; std::string base_template_full_name_;
bool is_implicit_{false};
}; };
struct participant : public common::model::element, struct participant : public common::model::element,
@@ -155,6 +160,18 @@ struct method : public participant {
void set_class_id(diagram_element::id_t id) { class_id_ = id; } void set_class_id(diagram_element::id_t id) { class_id_ = id; }
void set_class_full_name(const std::string &name)
{
class_full_name_ = name;
}
const auto &class_full_name() const { return class_full_name_; }
std::string full_name(bool /*relative*/) const override
{
return class_full_name() + "::" + method_name();
}
diagram_element::id_t class_id() const { return class_id_; } diagram_element::id_t class_id() const { return class_id_; }
std::string to_string() const override std::string to_string() const override
@@ -166,6 +183,7 @@ struct method : public participant {
private: private:
diagram_element::id_t class_id_; diagram_element::id_t class_id_;
std::string method_name_; std::string method_name_;
std::string class_full_name_;
}; };
struct function_template : public participant, public template_trait { struct function_template : public participant, public template_trait {

View File

@@ -201,9 +201,6 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
if (!diagram().should_include(cls->getQualifiedNameAsString())) if (!diagram().should_include(cls->getQualifiedNameAsString()))
return true; return true;
if(cls->isImplicit())
LOG_DBG("!!!!!!!!!!!!!!!!!!!!!");
LOG_DBG("= Visiting template specialization declaration {} at {}", LOG_DBG("= Visiting template specialization declaration {} at {}",
cls->getQualifiedNameAsString(), cls->getQualifiedNameAsString(),
cls->getLocation().printToString(source_manager())); cls->getLocation().printToString(source_manager()));
@@ -249,7 +246,9 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m) bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m)
{ {
if (call_expression_context_.current_class_decl_ == nullptr && if (call_expression_context_.current_class_decl_ == nullptr &&
call_expression_context_.current_class_template_decl_ == nullptr) call_expression_context_.current_class_template_decl_ == nullptr &&
call_expression_context_.current_class_template_specialization_decl_ ==
nullptr)
return true; return true;
call_expression_context_.update(m); call_expression_context_.update(m);
@@ -258,20 +257,49 @@ bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m)
config().using_namespace()); config().using_namespace());
common::model::namespace_ ns{m->getQualifiedNameAsString()}; common::model::namespace_ ns{m->getQualifiedNameAsString()};
m_ptr->set_method_name(ns.name()); auto method_name = ns.name();
m_ptr->set_method_name(method_name);
ns.pop_back(); ns.pop_back();
m_ptr->set_name(ns.name()); m_ptr->set_name(ns.name());
ns.pop_back(); ns.pop_back();
m_ptr->set_namespace(ns); // m_ptr->set_namespace(ns);
if (call_expression_context_.current_class_decl_) if (call_expression_context_.current_class_decl_) {
m_ptr->set_class_id(get_ast_local_id( const auto &current_class =
call_expression_context_.current_class_decl_->getID()) diagram()
.value()); .get_participant<model::class_>(get_ast_local_id(
else call_expression_context_.current_class_decl_->getID())
m_ptr->set_class_id(get_ast_local_id( .value())
call_expression_context_.current_class_template_decl_->getID()) .value();
.value());
m_ptr->set_class_id(current_class.id());
m_ptr->set_class_full_name(current_class.full_name(false));
}
else if (call_expression_context_.current_class_template_decl_) {
const auto &current_template_class =
diagram()
.get_participant<model::class_>(
get_ast_local_id(call_expression_context_
.current_class_template_decl_->getID())
.value())
.value();
m_ptr->set_class_id(current_template_class.id());
m_ptr->set_class_full_name(current_template_class.full_name(false));
}
else {
const auto &current_template_specialization_class =
diagram()
.get_participant<model::class_>(get_ast_local_id(
call_expression_context_
.current_class_template_specialization_decl_->getID())
.value())
.value();
m_ptr->set_class_id(current_template_specialization_class.id());
m_ptr->set_class_full_name(
current_template_specialization_class.full_name(false));
}
m_ptr->set_name( m_ptr->set_name(
diagram().participants.at(m_ptr->class_id())->full_name_no_ns() + diagram().participants.at(m_ptr->class_id())->full_name_no_ns() +
@@ -396,8 +424,6 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::activity;
using clanguml::sequence_diagram::model::message; using clanguml::sequence_diagram::model::message;
// expr->dump();
// Skip casts, moves and such // Skip casts, moves and such
if (expr->isCallToStdMove()) if (expr->isCallToStdMove())
return true; return true;
@@ -455,30 +481,73 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
callee_template_specialization->getQualifiedNameAsString()); callee_template_specialization->getQualifiedNameAsString());
if (!get_ast_local_id(callee_template_specialization->getID())) { if (!get_ast_local_id(callee_template_specialization->getID())) {
callee_template_specialization->dump(); call_expression_context context_backup =
call_expression_context_;
// Since this visitor will overwrite the
call_expression_context context_backup = call_expression_context_; // call_expression_context_ we need to back it up and restore it
// later
// Since this visitor will overwrite the call_expression_context_
// we need to back it up and restore it later
VisitClassTemplateSpecializationDecl( VisitClassTemplateSpecializationDecl(
const_cast<clang::ClassTemplateSpecializationDecl *>( const_cast<clang::ClassTemplateSpecializationDecl *>(
callee_template_specialization)); callee_template_specialization));
call_expression_context_ = context_backup; call_expression_context_ = context_backup;
diagram()
.get_participant<model::class_>(get_ast_local_id(
callee_template_specialization->getID())
.value())
.value()
.set_implicit(true);
} }
m.to = get_ast_local_id(callee_template_specialization->getID()) const auto &participant =
.value(); diagram()
.get_participant<model::class_>(get_ast_local_id(
callee_template_specialization->getID())
.value())
.value();
if (participant.is_implicit()) {
const auto *parent_template =
callee_template_specialization->getSpecializedTemplate();
const auto &parent_template_participant =
diagram()
.get_participant<model::class_>(
get_ast_local_id(parent_template->getID()).value())
.value();
const auto parent_method_name =
parent_template_participant.full_name(false) +
"::" + method_decl->getNameAsString();
m.to = common::to_id(parent_method_name);
m.message_name = participant.full_name_no_ns() +
"::" + method_decl->getNameAsString();
}
else {
const auto &specialization_participant =
diagram()
.get_participant<model::class_>(get_ast_local_id(
callee_template_specialization->getID())
.value())
.value();
const auto specialization_method_name =
specialization_participant.full_name(false) +
"::" + method_decl->getNameAsString();
m.to = common::to_id(specialization_method_name);
m.message_name = method_decl->getNameAsString();
}
} }
else { else {
// TODO: The method can be called before it's declaration has been // TODO: The method can be called before it's declaration has been
// encountered by the visitor - for now it's not a problem // encountered by the visitor - for now it's not a problem
// as overloaded methods are not supported // as overloaded methods are not supported
m.to = common::to_id(method_decl->getQualifiedNameAsString()); m.to = common::to_id(method_decl->getQualifiedNameAsString());
m.message_name = method_decl->getNameAsString();
} }
m.message_name = method_decl->getNameAsString();
m.return_type = method_call_expr->getCallReturnType(current_ast_context) m.return_type = method_call_expr->getCallReturnType(current_ast_context)
.getAsString(); .getAsString();
@@ -555,12 +624,10 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
for (const auto *decl : unresolved_expr->decls()) { for (const auto *decl : unresolved_expr->decls()) {
if (clang::dyn_cast_or_null< if (clang::dyn_cast_or_null<
clang::FunctionTemplateDecl>(decl)) { clang::FunctionTemplateDecl>(decl)) {
// Yes, it's a template
auto *ftd = clang::dyn_cast_or_null< auto *ftd = clang::dyn_cast_or_null<
clang::FunctionTemplateDecl>(decl); clang::FunctionTemplateDecl>(decl);
// m.to_name =
// to_string(ftd);
m.to = get_ast_local_id(ftd->getID()).value(); m.to = get_ast_local_id(ftd->getID()).value();
auto message_name = auto message_name =
diagram() diagram()

View File

@@ -33,9 +33,11 @@ struct call_expression_context {
call_expression_context() call_expression_context()
: current_class_decl_{nullptr} : current_class_decl_{nullptr}
, current_class_template_decl_{nullptr} , current_class_template_decl_{nullptr}
, current_class_template_specialization_decl_{nullptr}
, current_method_decl_{nullptr} , current_method_decl_{nullptr}
, current_function_decl_{nullptr} , current_function_decl_{nullptr}
, current_function_template_decl_{nullptr} , current_function_template_decl_{nullptr}
, current_caller_id_{0}
{ {
} }
@@ -44,6 +46,7 @@ struct call_expression_context {
current_caller_id_ = 0; current_caller_id_ = 0;
current_class_decl_ = nullptr; current_class_decl_ = nullptr;
current_class_template_decl_ = nullptr; current_class_template_decl_ = nullptr;
current_class_template_specialization_decl_ = nullptr;
current_method_decl_ = nullptr; current_method_decl_ = nullptr;
current_function_decl_ = nullptr; current_function_decl_ = nullptr;
current_function_template_decl_ = nullptr; current_function_template_decl_ = nullptr;
@@ -55,6 +58,8 @@ struct call_expression_context {
LOG_DBG("current_class_decl_ = {}", (void *)current_class_decl_); LOG_DBG("current_class_decl_ = {}", (void *)current_class_decl_);
LOG_DBG("current_class_template_decl_ = {}", LOG_DBG("current_class_template_decl_ = {}",
(void *)current_class_template_decl_); (void *)current_class_template_decl_);
LOG_DBG("current_class_template_specialization_decl_ = {}",
(void *)current_class_template_specialization_decl_);
LOG_DBG("current_method_decl_ = {}", (void *)current_method_decl_); LOG_DBG("current_method_decl_ = {}", (void *)current_method_decl_);
LOG_DBG("current_function_decl_ = {}", (void *)current_function_decl_); LOG_DBG("current_function_decl_ = {}", (void *)current_function_decl_);
LOG_DBG("current_function_template_decl_ = {}", LOG_DBG("current_function_template_decl_ = {}",
@@ -65,6 +70,7 @@ struct call_expression_context {
{ {
return (current_class_decl_ != nullptr) || return (current_class_decl_ != nullptr) ||
(current_class_template_decl_ != nullptr) || (current_class_template_decl_ != nullptr) ||
(current_class_template_specialization_decl_ != nullptr) ||
(current_method_decl_ != nullptr) || (current_method_decl_ != nullptr) ||
(current_function_decl_ != nullptr) || (current_function_decl_ != nullptr) ||
(current_function_template_decl_ != nullptr); (current_function_template_decl_ != nullptr);
@@ -72,6 +78,10 @@ struct call_expression_context {
clang::ASTContext *get_ast_context() clang::ASTContext *get_ast_context()
{ {
if (current_class_template_specialization_decl_)
return &current_class_template_specialization_decl_
->getASTContext();
if (current_class_template_decl_) if (current_class_template_decl_)
return &current_class_template_decl_->getASTContext(); return &current_class_template_decl_->getASTContext();
@@ -86,6 +96,11 @@ struct call_expression_context {
void update(clang::CXXRecordDecl *cls) { current_class_decl_ = cls; } void update(clang::CXXRecordDecl *cls) { current_class_decl_ = cls; }
void update(clang::ClassTemplateSpecializationDecl *clst)
{
current_class_template_specialization_decl_ = clst;
}
void update(clang::ClassTemplateDecl *clst) void update(clang::ClassTemplateDecl *clst)
{ {
current_class_template_decl_ = clst; current_class_template_decl_ = clst;
@@ -147,6 +162,8 @@ struct call_expression_context {
clang::CXXRecordDecl *current_class_decl_; clang::CXXRecordDecl *current_class_decl_;
clang::ClassTemplateDecl *current_class_template_decl_; clang::ClassTemplateDecl *current_class_template_decl_;
clang::ClassTemplateSpecializationDecl
*current_class_template_specialization_decl_;
clang::CXXMethodDecl *current_method_decl_; clang::CXXMethodDecl *current_method_decl_;
clang::FunctionDecl *current_function_decl_; clang::FunctionDecl *current_function_decl_;
clang::FunctionTemplateDecl *current_function_template_decl_; clang::FunctionTemplateDecl *current_function_template_decl_;

View File

@@ -4,17 +4,17 @@ namespace clanguml {
namespace t20006 { namespace t20006 {
template <typename T> struct A { template <typename T> struct A {
T a(T arg) { return arg; } T a_int(T arg) { return arg + 1; }
T a1(T arg) { return arg; } T a_string(T arg) { return arg + "_string"; }
}; };
template <typename T> struct B { template <typename T> struct B {
T b(T arg) { return a_.a(arg); } T b(T arg) { return a_.a_int(arg); }
A<T> a_; A<T> a_;
}; };
template <> struct B<std::string> { template <> struct B<std::string> {
std::string b(std::string arg) { return arg; } std::string b(std::string arg) { return a_.a_string(arg); }
A<std::string> a_; A<std::string> a_;
}; };

View File

@@ -35,12 +35,12 @@ TEST_CASE("t20006", "[test-case][sequence]")
REQUIRE_THAT(puml, EndsWith("@enduml\n")); REQUIRE_THAT(puml, EndsWith("@enduml\n"));
// Check if all calls exist // Check if all calls exist
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B<int>"), "b")); REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B<T>"), "B<int>::b"));
REQUIRE_THAT(puml, HasCall(_A("B<int>"), _A("A<int>"), "a")); REQUIRE_THAT(puml, HasCall(_A("B<T>"), _A("A<T>"), "a_int"));
REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B<std::string>"), "b")); REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B<std::string>"), "b"));
REQUIRE_THAT( REQUIRE_THAT(puml,
puml, !HasCall(_A("B<std::string>"), _A("A<std::string>"), "a")); HasCall(_A("B<std::string>"), _A("A<T>"), "A<std::string>::a_string"));
save_puml( save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml); "./" + config.output_directory() + "/" + diagram->name + ".puml", puml);