Added test case for recursive variadic template specialization

This commit is contained in:
Bartek Kryza
2022-08-07 23:08:37 +02:00
parent 1844b992aa
commit ae7ef11e43
20 changed files with 416 additions and 130 deletions

View File

@@ -10,7 +10,7 @@ project(clang-uml)
# #
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE OFF)
# #
# clang-uml custom defines # clang-uml custom defines

View File

@@ -31,15 +31,37 @@ template_parameter::template_parameter(const std::string &type,
set_type(type); set_type(type);
} }
void template_parameter::set_type(const std::string &type) { type_ = type; } void template_parameter::set_type(const std::string &type)
{
if (util::ends_with(type, std::string{"..."})) {
type_ = type.substr(0, type.size() - 3);
is_variadic_ = true;
}
else
type_ = type;
}
std::string template_parameter::type() const { return type_; } std::string template_parameter::type() const
{
if (is_variadic_ && !type_.empty())
return type_ + "...";
void template_parameter::set_name(const std::string &name) { name_ = name; } return type_;
}
void template_parameter::set_name(const std::string &name)
{
if (util::ends_with(name, std::string{"..."})) {
name_ = name.substr(0, name.size() - 3);
is_variadic_ = true;
}
else
name_ = name;
}
std::string template_parameter::name() const std::string template_parameter::name() const
{ {
if (is_variadic_) if (is_variadic_ && type_.empty())
return name_ + "..."; return name_ + "...";
return name_; return name_;

View File

@@ -54,6 +54,9 @@ public:
void is_variadic(bool is_variadic) noexcept; void is_variadic(bool is_variadic) noexcept;
bool is_variadic() const noexcept; bool is_variadic() const noexcept;
void is_pack(bool is_pack) noexcept;
bool is_pack() const noexcept;
bool is_specialization_of(const template_parameter &ct) const; bool is_specialization_of(const template_parameter &ct) const;
friend bool operator==( friend bool operator==(
@@ -118,6 +121,9 @@ private:
/// Whether the template parameter is variadic /// Whether the template parameter is variadic
bool is_variadic_{false}; bool is_variadic_{false};
/// Whether the argument specializes argument pack from parent template
bool is_pack_{false};
// Nested template parameters // Nested template parameters
std::vector<template_parameter> template_params_; std::vector<template_parameter> template_params_;

View File

@@ -321,8 +321,8 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
return true; return true;
// Skip forward declarations // Skip forward declarations
if (!cls->getTemplatedDecl()->isCompleteDefinition()) // if (!cls->getTemplatedDecl()->isCompleteDefinition())
return true; // return true;
auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); auto c_ptr = create_class_declaration(cls->getTemplatedDecl());
@@ -337,14 +337,14 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
process_template_parameters(*cls, *c_ptr); process_template_parameters(*cls, *c_ptr);
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
if (!cls->getTemplatedDecl()->isCompleteDefinition()) { if (!cls->getTemplatedDecl()->isCompleteDefinition()) {
forward_declarations_.emplace(id, std::move(c_ptr)); forward_declarations_.emplace(id, std::move(c_ptr));
return true; return true;
} }
else else {
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
forward_declarations_.erase(id); forward_declarations_.erase(id);
}
if (diagram_.should_include(*c_ptr)) { if (diagram_.should_include(*c_ptr)) {
LOG_DBG("Adding class template {} with id {}", c_ptr->full_name(), LOG_DBG("Adding class template {} with id {}", c_ptr->full_name(),
@@ -362,6 +362,10 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin())) if (source_manager_.isInSystemHeader(cls->getSourceRange().getBegin()))
return true; return true;
// Skip forward declarations
if (!cls->isCompleteDefinition())
return true;
// Templated records are handled by VisitClassTemplateDecl() // Templated records are handled by VisitClassTemplateDecl()
if (cls->isTemplated() || cls->isTemplateDecl() || if (cls->isTemplated() || cls->isTemplateDecl() ||
(clang::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(cls) != (clang::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(cls) !=
@@ -414,6 +418,9 @@ std::unique_ptr<class_> translation_unit_visitor::create_class_declaration(
// TODO: refactor to method get_qualified_name() // TODO: refactor to method get_qualified_name()
auto qualified_name = common::get_qualified_name(*cls); auto qualified_name = common::get_qualified_name(*cls);
if (!diagram().should_include(qualified_name))
return {};
namespace_ ns{qualified_name}; namespace_ ns{qualified_name};
ns.pop_back(); ns.pop_back();
c.set_name(cls->getNameAsString()); c.set_name(cls->getNameAsString());
@@ -1070,8 +1077,21 @@ translation_unit_visitor::process_template_specialization(
const auto template_args_count = cls->getTemplateArgs().size(); const auto template_args_count = cls->getTemplateArgs().size();
for (auto arg_it = 0U; arg_it < template_args_count; arg_it++) { for (auto arg_it = 0U; arg_it < template_args_count; arg_it++) {
const auto arg = cls->getTemplateArgs().get(arg_it); const auto arg = cls->getTemplateArgs().get(arg_it);
process_template_specialization_argument(
cls, template_instantiation, arg, arg_it);
}
return c_ptr;
}
void translation_unit_visitor::process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
class_ &template_instantiation, const clang::TemplateArgument &arg,
size_t argument_index, bool in_parameter_pack)
{
const auto argument_kind = arg.getKind(); const auto argument_kind = arg.getKind();
if (argument_kind == clang::TemplateArgument::ArgKind::Type) {
if (argument_kind == clang::TemplateArgument::Type) {
template_parameter argument; template_parameter argument;
argument.is_template_parameter(false); argument.is_template_parameter(false);
@@ -1088,10 +1108,8 @@ translation_unit_visitor::process_template_specialization(
argument.set_name(nested_template_name); argument.set_name(nested_template_name);
auto nested_template_instantiation = auto nested_template_instantiation = build_template_instantiation(
build_template_instantiation( *arg.getAsType()->getAs<clang::TemplateSpecializationType>(),
*arg.getAsType()
->getAs<clang::TemplateSpecializationType>(),
{&template_instantiation}); {&template_instantiation});
argument.set_id(nested_template_instantiation->id()); argument.set_id(nested_template_instantiation->id());
@@ -1105,9 +1123,38 @@ translation_unit_visitor::process_template_specialization(
simplify_system_template(argument, simplify_system_template(argument,
argument.to_string(config().using_namespace(), false)); argument.to_string(config().using_namespace(), false));
} }
else if (arg.getAsType()->getAs<clang::TemplateTypeParmType>()) {
auto type_name = to_string(arg.getAsType(), cls->getASTContext());
// clang does not provide declared template parameter/argument names
// in template specializations - so we have to extract them from
// raw source code...
if (type_name.find("type-parameter-") == 0) {
auto declaration_text =
get_source_text_raw(cls->getSourceRange(), source_manager_);
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
cls->getNameAsString().size() + 1);
auto template_params =
cx::util::parse_unexposed_template_params(
declaration_text, [](const auto &t) { return t; });
if (template_params.size() > argument_index)
type_name = template_params[argument_index].to_string(
config().using_namespace(), false);
else { else {
auto type_name = LOG_DBG("Failed to find type specialization for argument "
to_string(arg.getAsType(), cls->getASTContext()); "{} at index {} in declaration \n===\n{}\n===\n",
type_name, argument_index, declaration_text);
}
}
argument.set_name(type_name);
}
else {
auto type_name = to_string(arg.getAsType(), cls->getASTContext());
if (type_name.find('<') != std::string::npos) { if (type_name.find('<') != std::string::npos) {
// Sometimes template instantiation is reported as // Sometimes template instantiation is reported as
// RecordType in the AST and getAs to // RecordType in the AST and getAs to
@@ -1122,38 +1169,74 @@ translation_unit_visitor::process_template_specialization(
argument.set_name(type_name.substr(0, type_name.find('<'))); argument.set_name(type_name.substr(0, type_name.find('<')));
} }
else else if (type_name.find("type-parameter-") == 0) {
auto declaration_text =
get_source_text_raw(cls->getSourceRange(), source_manager_);
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
cls->getNameAsString().size() + 1);
auto template_params =
cx::util::parse_unexposed_template_params(
declaration_text, [](const auto &t) { return t; });
if (template_params.size() > argument_index)
type_name = template_params[argument_index].to_string(
config().using_namespace(), false);
else {
LOG_DBG("Failed to find type specialization for argument "
"{} at index {} in declaration \n===\n{}\n===\n",
type_name, argument_index, declaration_text);
}
// Otherwise just set the name for the template argument to // Otherwise just set the name for the template argument to
// whatever clang says // whatever clang says
argument.set_name(type_name); argument.set_name(type_name);
} }
else
argument.set_name(type_name);
}
LOG_DBG("Adding template instantiation argument {}", LOG_DBG("Adding template instantiation argument {}",
argument.to_string(config().using_namespace(), false)); argument.to_string(config().using_namespace(), false));
simplify_system_template(
argument, argument.to_string(config().using_namespace(), false));
template_instantiation.add_template(std::move(argument)); template_instantiation.add_template(std::move(argument));
} }
else if (argument_kind == clang::TemplateArgument::ArgKind::Integral) { else if (argument_kind == clang::TemplateArgument::Integral) {
template_parameter argument; template_parameter argument;
argument.is_template_parameter(false); argument.is_template_parameter(false);
argument.set_type( argument.set_type(std::to_string(arg.getAsIntegral().getExtValue()));
std::to_string(arg.getAsIntegral().getExtValue()));
template_instantiation.add_template(std::move(argument)); template_instantiation.add_template(std::move(argument));
} }
else if (argument_kind == else if (argument_kind == clang::TemplateArgument::Expression) {
clang::TemplateArgument::ArgKind::Expression) {
template_parameter argument; template_parameter argument;
argument.is_template_parameter(false); argument.is_template_parameter(false);
argument.set_type(get_source_text( argument.set_type(get_source_text(
arg.getAsExpr()->getSourceRange(), source_manager_)); arg.getAsExpr()->getSourceRange(), source_manager_));
template_instantiation.add_template(std::move(argument)); template_instantiation.add_template(std::move(argument));
} }
else { else if (argument_kind == clang::TemplateArgument::TemplateExpansion) {
LOG_ERROR("UNSUPPORTED ARGUMENT KIND FOR ARG {}", arg.getKind()); template_parameter argument;
} argument.is_template_parameter(true);
}
return c_ptr; cls->getLocation().dump(source_manager_);
}
else if (argument_kind == clang::TemplateArgument::Pack) {
// This will only work for now if pack is at the end
size_t argument_pack_index{argument_index};
for (const auto &template_argument : arg.getPackAsArray()) {
process_template_specialization_argument(cls,
template_instantiation, template_argument,
argument_pack_index++, true);
}
}
else {
LOG_ERROR("Unsupported template argument kind {}", arg.getKind());
}
} }
void translation_unit_visitor:: void translation_unit_visitor::
@@ -1161,7 +1244,7 @@ void translation_unit_visitor::
const std::string &type_name, template_parameter &tp, class_ &c) const std::string &type_name, template_parameter &tp, class_ &c)
{ {
auto template_params = cx::util::parse_unexposed_template_params( auto template_params = cx::util::parse_unexposed_template_params(
type_name, [this](const std::string &t) { return t; }); type_name, [](const std::string &t) { return t; });
found_relationships_t relationships; found_relationships_t relationships;
for (auto &param : template_params) { for (auto &param : template_params) {
@@ -1220,7 +1303,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
template_base_params{}; template_base_params{};
auto *template_type_ptr = &template_type_decl; auto *template_type_ptr = &template_type_decl;
if (template_type_decl.isTypeAlias()) if (template_type_decl.isTypeAlias() &&
template_type_decl.getAliasedType()
->getAs<clang::TemplateSpecializationType>())
template_type_ptr = template_type_decl.getAliasedType() template_type_ptr = template_type_decl.getAliasedType()
->getAs<clang::TemplateSpecializationType>(); ->getAs<clang::TemplateSpecializationType>();
@@ -1402,8 +1487,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
argument.add_template_param(t); argument.add_template_param(t);
// Check if this template should be simplified (e.g. system // Check if this template should be simplified (e.g. system
// template aliases such as 'std:basic_string<char>' should be // template aliases such as 'std:basic_string<char>' should
// simply 'std::string') // be simply 'std::string')
simplify_system_template(argument, simplify_system_template(argument,
argument.to_string(config().using_namespace(), false)); argument.to_string(config().using_namespace(), false));
@@ -1455,7 +1540,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
if (diagram().should_include( if (diagram().should_include(
full_template_specialization_name)) { full_template_specialization_name)) {
// Add dependency relationship to the parent template // Add dependency relationship to the parent
// template
template_instantiation.add_relationship( template_instantiation.add_relationship(
{relationship_t::kDependency, {relationship_t::kDependency,
arg.getAsType() arg.getAsType()
@@ -1546,8 +1632,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
template_instantiation.add_relationship( template_instantiation.add_relationship(
{relationship_t::kInstantiation, best_match_id}); {relationship_t::kInstantiation, best_match_id});
} }
// If we can't find optimal match for parent template specialization, just // If we can't find optimal match for parent template specialization,
// use whatever clang suggests // just use whatever clang suggests
else if (diagram().has_element(template_type.getTemplateName() else if (diagram().has_element(template_type.getTemplateName()
.getAsTemplateDecl() .getAsTemplateDecl()
->getID())) { ->getID())) {
@@ -1609,8 +1695,8 @@ void translation_unit_visitor::process_field(
auto type_name = to_string(field_type, field_declaration.getASTContext()); auto type_name = to_string(field_type, field_declaration.getASTContext());
// The field name // The field name
const auto field_name = field_declaration.getNameAsString(); const auto field_name = field_declaration.getNameAsString();
// If for any reason clang reports the type as empty string, make sure it // If for any reason clang reports the type as empty string, make sure
// has some default name // it has some default name
if (type_name.empty()) if (type_name.empty())
type_name = "<<anonymous>>"; type_name = "<<anonymous>>";
@@ -1689,8 +1775,8 @@ void translation_unit_visitor::process_field(
// Check if this template instantiation should be added to the // Check if this template instantiation should be added to the
// current diagram. Even if the top level template type for // current diagram. Even if the top level template type for
// this instantiation should not be part of the diagram, e.g. it's // this instantiation should not be part of the diagram, e.g.
// a std::vector<>, it's nested types might be added // it's a std::vector<>, it's nested types might be added
bool add_template_instantiation_to_diargam{false}; bool add_template_instantiation_to_diargam{false};
if (diagram().should_include( if (diagram().should_include(
template_specialization.full_name(false))) { template_specialization.full_name(false))) {
@@ -1731,8 +1817,8 @@ void translation_unit_visitor::process_field(
}); });
} }
// Add any relationships to the class 'c' to the diagram, unless // Add any relationships to the class 'c' to the diagram,
// the top level type has been added as aggregation // unless the top level type has been added as aggregation
add_relationships(c, field, nested_relationships, add_relationships(c, field, nested_relationships,
/* break on first aggregation */ false); /* break on first aggregation */ false);
} }

View File

@@ -71,16 +71,16 @@ private:
void process_class_declaration(const clang::CXXRecordDecl &cls, void process_class_declaration(const clang::CXXRecordDecl &cls,
clanguml::class_diagram::model::class_ &c); clanguml::class_diagram::model::class_ &c);
std::unique_ptr<clanguml::class_diagram::model::class_>
process_template_specialization(
clang::ClassTemplateSpecializationDecl *cls);
void process_class_bases(const clang::CXXRecordDecl *cls, void process_class_bases(const clang::CXXRecordDecl *cls,
clanguml::class_diagram::model::class_ &c) const; clanguml::class_diagram::model::class_ &c) const;
void process_class_children(const clang::CXXRecordDecl *cls, void process_class_children(const clang::CXXRecordDecl *cls,
clanguml::class_diagram::model::class_ &c); clanguml::class_diagram::model::class_ &c);
std::unique_ptr<clanguml::class_diagram::model::class_>
process_template_specialization(
clang::ClassTemplateSpecializationDecl *cls);
void process_template_specialization_children( void process_template_specialization_children(
const clang::ClassTemplateSpecializationDecl *cls, const clang::ClassTemplateSpecializationDecl *cls,
clanguml::class_diagram::model::class_ &c); clanguml::class_diagram::model::class_ &c);
@@ -89,6 +89,12 @@ private:
const clang::ClassTemplateDecl &template_declaration, const clang::ClassTemplateDecl &template_declaration,
clanguml::class_diagram::model::class_ &c); clanguml::class_diagram::model::class_ &c);
void process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
model::class_ &template_instantiation,
const clang::TemplateArgument &arg, size_t argument_index,
bool in_parameter_pack = false);
void process_record_containment(const clang::TagDecl &record, void process_record_containment(const clang::TagDecl &record,
clanguml::common::model::element &c) const; clanguml::common::model::element &c) const;

View File

@@ -336,6 +336,7 @@ std::unique_ptr<DiagramModel> generate(
DiagramConfig &config, bool verbose = false) DiagramConfig &config, bool verbose = false)
{ {
LOG_INFO("Generating diagram {}.puml", name); LOG_INFO("Generating diagram {}.puml", name);
auto diagram = std::make_unique<DiagramModel>(); auto diagram = std::make_unique<DiagramModel>();
diagram->set_name(name); diagram->set_name(name);
diagram->set_filter( diagram->set_filter(
@@ -346,9 +347,13 @@ std::unique_ptr<DiagramModel> generate(
std::vector<std::string> translation_units{}; std::vector<std::string> translation_units{};
for (const auto &g : config.glob()) { for (const auto &g : config.glob()) {
LOG_DBG("Processing glob: {}", g); LOG_DBG("Processing glob: {}", g);
const auto matches = glob::rglob(g); const auto matches = glob::rglob(g);
std::copy(matches.begin(), matches.end(), std::copy(matches.begin(), matches.end(),
std::back_inserter(translation_units)); std::back_inserter(translation_units));
LOG_DBG(
"Found translation units: {}", fmt::join(translation_units, ", "));
} }
clang::tooling::ClangTool clang_tool(db, translation_units); clang::tooling::ClangTool clang_tool(db, translation_units);

View File

@@ -166,6 +166,14 @@ void class_diagram::initialize_template_aliases()
template_aliases().insert( template_aliases().insert(
{"std::basic_string<char32_t>", "std::u32string"}); {"std::basic_string<char32_t>", "std::u32string"});
} }
if (!template_aliases().count("std::integral_constant<bool,true>")) {
template_aliases().insert(
{"std::integral_constant<bool,true>", "std::true_type"});
}
if (!template_aliases().count("std::integral_constant<bool,false>")) {
template_aliases().insert(
{"std::integral_constant<bool,false>", "std::false_type"});
}
} }
template <> void append_value<plantuml>(plantuml &l, const plantuml &r) template <> void append_value<plantuml>(plantuml &l, const plantuml &r)

View File

@@ -41,17 +41,19 @@ std::pair<common::model::namespace_, std::string> split_ns(
std::vector<class_diagram::model::template_parameter> std::vector<class_diagram::model::template_parameter>
parse_unexposed_template_params(const std::string &params, parse_unexposed_template_params(const std::string &params,
std::function<std::string(const std::string &)> ns_resolve) std::function<std::string(const std::string &)> ns_resolve, int depth)
{ {
using class_diagram::model::template_parameter; using class_diagram::model::template_parameter;
std::vector<template_parameter> res; std::vector<template_parameter> res;
auto it = params.begin(); auto it = params.begin();
while (std::isspace(*it))
++it;
std::string type{}; std::string type{};
std::vector<template_parameter> nested_params; std::vector<template_parameter> nested_params;
bool complete_class_template{false}; bool complete_class_template_argument{false};
while (it != params.end()) { while (it != params.end()) {
if (*it == '<') { if (*it == '<') {
@@ -72,25 +74,32 @@ parse_unexposed_template_params(const std::string &params,
} }
bracket_match_end++; bracket_match_end++;
} }
std::string nested_params_str( std::string nested_params_str(
bracket_match_begin, bracket_match_end); bracket_match_begin, bracket_match_end);
nested_params =
parse_unexposed_template_params(nested_params_str, ns_resolve); nested_params = parse_unexposed_template_params(
nested_params_str, ns_resolve, depth + 1);
if (nested_params.empty()) if (nested_params.empty())
nested_params.emplace_back( nested_params.emplace_back(
template_parameter{nested_params_str}); template_parameter{nested_params_str});
it = bracket_match_end - 1; it = bracket_match_end - 1;
} }
else if (*it == '>') { else if (*it == '>') {
complete_class_template = true; complete_class_template_argument = true;
if (depth == 0) {
break;
}
} }
else if (*it == ',') { else if (*it == ',') {
complete_class_template = true; complete_class_template_argument = true;
} }
else { else {
type += *it; type += *it;
} }
if (complete_class_template) { if (complete_class_template_argument) {
template_parameter t; template_parameter t;
t.set_type(ns_resolve(clanguml::util::trim(type))); t.set_type(ns_resolve(clanguml::util::trim(type)));
type = ""; type = "";
@@ -98,7 +107,7 @@ parse_unexposed_template_params(const std::string &params,
t.add_template_param(std::move(param)); t.add_template_param(std::move(param));
res.emplace_back(std::move(t)); res.emplace_back(std::move(t));
complete_class_template = false; complete_class_template_argument = false;
} }
it++; it++;
} }
@@ -111,7 +120,6 @@ parse_unexposed_template_params(const std::string &params,
t.add_template_param(std::move(param)); t.add_template_param(std::move(param));
res.emplace_back(std::move(t)); res.emplace_back(std::move(t));
complete_class_template = false;
} }
return res; return res;

View File

@@ -32,6 +32,6 @@ std::pair<common::model::namespace_, std::string> split_ns(
std::vector<class_diagram::model::template_parameter> std::vector<class_diagram::model::template_parameter>
parse_unexposed_template_params(const std::string &params, parse_unexposed_template_params(const std::string &params,
std::function<std::string(const std::string &)> ns_resolve); std::function<std::string(const std::string &)> ns_resolve, int depth = 0);
} // namespace clanguml::cx::util } // namespace clanguml::cx::util

View File

@@ -41,15 +41,20 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
const auto from = m_config.using_namespace().relative(m.from); const auto from = m_config.using_namespace().relative(m.from);
const auto to = m_config.using_namespace().relative(m.to); const auto to = m_config.using_namespace().relative(m.to);
if (from.empty() || to.empty()) {
LOG_DBG("Skipping empty call from '{}' to '{}'", from, to);
return;
}
auto message = m.message;
if (!message.empty())
message += "()";
ostr << '"' << from << "\" " ostr << '"' << from << "\" "
<< common::generators::plantuml::to_plantuml(message_t::kCall) << " \"" << common::generators::plantuml::to_plantuml(message_t::kCall) << " \""
<< to << "\" : " << m.message << "()" << std::endl; << to << "\" : " << message << std::endl;
if (m.message == "add" && to == "A" && from == "A") LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from,
LOG_DBG("Generating call '{}' from {} [{}] to {} [{}]", m.message, from,
m.from_usr, to, m.to_usr);
else
LOG_DBG("Generating call '{}' from {} [{}] to {} [{}]", m.message, from,
m.from_usr, to, m.to_usr); m.from_usr, to, m.to_usr);
} }
@@ -71,11 +76,19 @@ void generator::generate_activity(const activity &a, std::ostream &ostr) const
{ {
for (const auto &m : a.messages) { for (const auto &m : a.messages) {
const auto to = m_config.using_namespace().relative(m.to); const auto to = m_config.using_namespace().relative(m.to);
if (to.empty())
continue;
generate_call(m, ostr); generate_call(m, ostr);
ostr << "activate " << '"' << to << '"' << std::endl; ostr << "activate " << '"' << to << '"' << std::endl;
if (m_model.sequences.find(m.to_usr) != m_model.sequences.end()) if (m_model.sequences.find(m.to_usr) != m_model.sequences.end())
generate_activity(m_model.sequences[m.to_usr], ostr); generate_activity(m_model.sequences[m.to_usr], ostr);
generate_return(m, ostr); generate_return(m, ostr);
ostr << "deactivate " << '"' << to << '"' << std::endl; ostr << "deactivate " << '"' << to << '"' << std::endl;
} }
} }

View File

@@ -263,5 +263,12 @@ template <> bool starts_with(const std::string &s, const std::string &prefix)
return s.rfind(prefix, 0) == 0; return s.rfind(prefix, 0) == 0;
} }
template <> bool ends_with(const std::string &value, const std::string &suffix)
{
if (suffix.size() > value.size())
return false;
return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
}
} }
} }

View File

@@ -170,6 +170,10 @@ bool starts_with(
template <> bool starts_with(const std::string &s, const std::string &prefix); template <> bool starts_with(const std::string &s, const std::string &prefix);
template <typename T> bool ends_with(const T &value, const T &suffix);
template <> bool ends_with(const std::string &value, const std::string &suffix);
template <typename T> template <typename T>
bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix) bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix)
{ {

View File

@@ -34,12 +34,12 @@ TEST_CASE("t00012", "[test-case][class]")
REQUIRE_THAT(puml, StartsWith("@startuml")); REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n")); REQUIRE_THAT(puml, EndsWith("@enduml\n"));
REQUIRE_THAT(puml, IsClassTemplate("A", "T,Ts...")); REQUIRE_THAT(puml, IsClassTemplate("A", "T,Ts..."));
REQUIRE_THAT(puml, IsClassTemplate("B", "int Is...")); REQUIRE_THAT(puml, IsClassTemplate("B", "int... Is"));
REQUIRE_THAT(puml, IsInstantiation(_A("B<int Is...>"), _A("B<3,2,1>"))); REQUIRE_THAT(puml, IsInstantiation(_A("B<int... Is>"), _A("B<3,2,1>")));
REQUIRE_THAT(puml, IsInstantiation(_A("B<int Is...>"), _A("B<1,1,1,1>"))); REQUIRE_THAT(puml, IsInstantiation(_A("B<int... Is>"), _A("B<1,1,1,1>")));
REQUIRE_THAT(puml, REQUIRE_THAT(puml,
IsInstantiation(_A("C<T,int Is...>"), IsInstantiation(_A("C<T,int... Is>"),
_A("C<std::map<int," _A("C<std::map<int,"
"std::vector<std::vector<std::vector<std::string>>>>,3,3,3>"))); "std::vector<std::vector<std::vector<std::string>>>>,3,3,3>")));

11
tests/t00047/.clang-uml Normal file
View File

@@ -0,0 +1,11 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t00047_class:
type: class
glob:
- ../../tests/t00047/t00047.cc
using_namespace: clanguml::t00047
include:
namespaces:
- clanguml::t00047

26
tests/t00047/t00047.cc Normal file
View File

@@ -0,0 +1,26 @@
#include <type_traits>
namespace clanguml {
namespace t00047 {
template <typename... Ts> struct conditional_t;
template <typename Else> struct conditional_t<Else> {
using type = Else;
};
template <typename Result, typename... Tail>
struct conditional_t<std::true_type, Result, Tail...> {
using type = Result;
};
template <typename Result, typename... Tail>
struct conditional_t<std::false_type, Result, Tail...> {
using type = typename conditional_t<Tail...>::type;
};
template <typename... Ts>
using conditional = typename conditional_t<Ts...>::type;
}
}

47
tests/t00047/test_case.h Normal file
View File

@@ -0,0 +1,47 @@
/**
* tests/t00047/test_case.h
*
* Copyright (c) 2021-2022 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("t00047", "[test-case][class]")
{
auto [config, db] = load_config("t00047");
auto diagram = config.diagrams["t00047_class"];
REQUIRE(diagram->name == "t00047_class");
auto model = generate_class_diagram(*db, diagram);
REQUIRE(model->name() == "t00047_class");
auto puml = generate_class_puml(diagram, *model);
AliasMatcher _A(puml);
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
// Check if class templates exist
REQUIRE_THAT(puml, IsClassTemplate("conditional_t", "Ts..."));
REQUIRE_THAT(puml, IsClassTemplate("conditional_t", "Else"));
REQUIRE_THAT(puml,
IsClassTemplate("conditional_t", "std::true_type,Result,Tail..."));
REQUIRE_THAT(puml,
IsClassTemplate("conditional_t", "std::false_type,Result,Tail..."));
save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
}

View File

@@ -235,6 +235,7 @@ using namespace clanguml::test::matchers;
#include "t00044/test_case.h" #include "t00044/test_case.h"
#include "t00045/test_case.h" #include "t00045/test_case.h"
#include "t00046/test_case.h" #include "t00046/test_case.h"
#include "t00047/test_case.h"
//// ////
//// Sequence diagram tests //// Sequence diagram tests

View File

@@ -135,6 +135,9 @@ test_cases:
- name: t00046 - name: t00046
title: Test case for root namespace handling with packages title: Test case for root namespace handling with packages
description: description:
- name: t00047
title: Test case for recursive variadic template
description:
Sequence diagrams: Sequence diagrams:
- name: t20001 - name: t20001
title: Basic sequence diagram test case title: Basic sequence diagram test case

View File

@@ -135,4 +135,37 @@ TEST_CASE("Test parse_unexposed_template_params", "[unit-test]")
CHECK(class2.template_params()[1].type() == "std::vector"); CHECK(class2.template_params()[1].type() == "std::vector");
CHECK(class2.template_params()[1].template_params()[0].type() == CHECK(class2.template_params()[1].template_params()[0].type() ==
"std::string"); "std::string");
const std::string empty_string = R"(
> {
using type = Result;
};)";
auto empty_template = parse_unexposed_template_params(
empty_string, [](const auto &n) { return n; });
CHECK(empty_template.size() == 0);
const std::string single_template_string = R"(Else> {
using type = Else;)";
auto single_template = parse_unexposed_template_params(
single_template_string, [](const auto &n) { return n; });
CHECK(single_template.size() == 1);
CHECK(single_template[0].type() == "Else");
const std::string declaration_string = R"(
std::true_type, Result, Tail> {
using type = Result;
};)";
auto declaration_template = parse_unexposed_template_params(
declaration_string, [](const auto &n) { return n; });
CHECK(declaration_template.size() == 3);
CHECK(declaration_template[0].type() == "std::true_type");
CHECK(declaration_template[1].type() == "Result");
CHECK(declaration_template[2].type() == "Tail");
} }

View File

@@ -24,7 +24,7 @@ TEST_CASE("{{ name }}", "[test-case][{{ type }}]")
REQUIRE(diagram->name == "{{ name }}_{{ type }}"); REQUIRE(diagram->name == "{{ name }}_{{ type }}");
auto model = generate_{{ type }}_diagram(db, diagram); auto model = generate_{{ type }}_diagram(*db, diagram);
REQUIRE(model->name() == "{{ name }}_{{ type }}"); REQUIRE(model->name() == "{{ name }}_{{ type }}");