Added test case for recursive variadic template specialization
This commit is contained in:
@@ -10,7 +10,7 @@ project(clang-uml)
|
||||
#
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_VERBOSE_MAKEFILE OFF)
|
||||
|
||||
#
|
||||
# clang-uml custom defines
|
||||
|
||||
@@ -31,15 +31,37 @@ template_parameter::template_parameter(const std::string &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
|
||||
{
|
||||
if (is_variadic_)
|
||||
if (is_variadic_ && type_.empty())
|
||||
return name_ + "...";
|
||||
|
||||
return name_;
|
||||
|
||||
@@ -54,6 +54,9 @@ public:
|
||||
void is_variadic(bool is_variadic) 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;
|
||||
|
||||
friend bool operator==(
|
||||
@@ -118,6 +121,9 @@ private:
|
||||
/// Whether the template parameter is variadic
|
||||
bool is_variadic_{false};
|
||||
|
||||
/// Whether the argument specializes argument pack from parent template
|
||||
bool is_pack_{false};
|
||||
|
||||
// Nested template parameters
|
||||
std::vector<template_parameter> template_params_;
|
||||
|
||||
|
||||
@@ -321,8 +321,8 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
|
||||
return true;
|
||||
|
||||
// Skip forward declarations
|
||||
if (!cls->getTemplatedDecl()->isCompleteDefinition())
|
||||
return true;
|
||||
// if (!cls->getTemplatedDecl()->isCompleteDefinition())
|
||||
// return true;
|
||||
|
||||
auto c_ptr = create_class_declaration(cls->getTemplatedDecl());
|
||||
|
||||
@@ -337,14 +337,14 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
|
||||
|
||||
process_template_parameters(*cls, *c_ptr);
|
||||
|
||||
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
|
||||
|
||||
if (!cls->getTemplatedDecl()->isCompleteDefinition()) {
|
||||
forward_declarations_.emplace(id, std::move(c_ptr));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
else {
|
||||
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
|
||||
forward_declarations_.erase(id);
|
||||
}
|
||||
|
||||
if (diagram_.should_include(*c_ptr)) {
|
||||
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()))
|
||||
return true;
|
||||
|
||||
// Skip forward declarations
|
||||
if (!cls->isCompleteDefinition())
|
||||
return true;
|
||||
|
||||
// Templated records are handled by VisitClassTemplateDecl()
|
||||
if (cls->isTemplated() || cls->isTemplateDecl() ||
|
||||
(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()
|
||||
auto qualified_name = common::get_qualified_name(*cls);
|
||||
|
||||
if (!diagram().should_include(qualified_name))
|
||||
return {};
|
||||
|
||||
namespace_ ns{qualified_name};
|
||||
ns.pop_back();
|
||||
c.set_name(cls->getNameAsString());
|
||||
@@ -1070,98 +1077,174 @@ translation_unit_visitor::process_template_specialization(
|
||||
const auto template_args_count = cls->getTemplateArgs().size();
|
||||
for (auto arg_it = 0U; arg_it < template_args_count; arg_it++) {
|
||||
const auto arg = cls->getTemplateArgs().get(arg_it);
|
||||
const auto argument_kind = arg.getKind();
|
||||
if (argument_kind == clang::TemplateArgument::ArgKind::Type) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(false);
|
||||
|
||||
// If this is a nested template type - add nested templates as
|
||||
// template arguments
|
||||
if (arg.getAsType()->getAs<clang::TemplateSpecializationType>()) {
|
||||
const auto *nested_template_type =
|
||||
arg.getAsType()->getAs<clang::TemplateSpecializationType>();
|
||||
|
||||
const auto nested_template_name =
|
||||
nested_template_type->getTemplateName()
|
||||
.getAsTemplateDecl()
|
||||
->getQualifiedNameAsString();
|
||||
|
||||
argument.set_name(nested_template_name);
|
||||
|
||||
auto nested_template_instantiation =
|
||||
build_template_instantiation(
|
||||
*arg.getAsType()
|
||||
->getAs<clang::TemplateSpecializationType>(),
|
||||
{&template_instantiation});
|
||||
|
||||
argument.set_id(nested_template_instantiation->id());
|
||||
|
||||
for (const auto &t : nested_template_instantiation->templates())
|
||||
argument.add_template_param(t);
|
||||
|
||||
// Check if this template should be simplified (e.g. system
|
||||
// template aliases such as 'std:basic_string<char>' should be
|
||||
// simply 'std::string')
|
||||
simplify_system_template(argument,
|
||||
argument.to_string(config().using_namespace(), false));
|
||||
}
|
||||
else {
|
||||
auto type_name =
|
||||
to_string(arg.getAsType(), cls->getASTContext());
|
||||
if (type_name.find('<') != std::string::npos) {
|
||||
// Sometimes template instantiation is reported as
|
||||
// RecordType in the AST and getAs to
|
||||
// TemplateSpecializationType returns null pointer so we
|
||||
// have to at least make sure it's properly formatted
|
||||
// (e.g. std:integral_constant, or any template
|
||||
// specialization which contains it - see t00038)
|
||||
process_unexposed_template_specialization_parameters(
|
||||
type_name.substr(type_name.find('<') + 1,
|
||||
type_name.size() - (type_name.find('<') + 2)),
|
||||
argument, template_instantiation);
|
||||
|
||||
argument.set_name(type_name.substr(0, type_name.find('<')));
|
||||
}
|
||||
else
|
||||
// Otherwise just set the name for the template argument to
|
||||
// whatever clang says
|
||||
argument.set_name(type_name);
|
||||
}
|
||||
|
||||
LOG_DBG("Adding template instantiation argument {}",
|
||||
argument.to_string(config().using_namespace(), false));
|
||||
|
||||
template_instantiation.add_template(std::move(argument));
|
||||
}
|
||||
else if (argument_kind == clang::TemplateArgument::ArgKind::Integral) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(false);
|
||||
argument.set_type(
|
||||
std::to_string(arg.getAsIntegral().getExtValue()));
|
||||
template_instantiation.add_template(std::move(argument));
|
||||
}
|
||||
else if (argument_kind ==
|
||||
clang::TemplateArgument::ArgKind::Expression) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(false);
|
||||
argument.set_type(get_source_text(
|
||||
arg.getAsExpr()->getSourceRange(), source_manager_));
|
||||
template_instantiation.add_template(std::move(argument));
|
||||
}
|
||||
else {
|
||||
LOG_ERROR("UNSUPPORTED ARGUMENT KIND FOR ARG {}", arg.getKind());
|
||||
}
|
||||
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();
|
||||
|
||||
if (argument_kind == clang::TemplateArgument::Type) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(false);
|
||||
|
||||
// If this is a nested template type - add nested templates as
|
||||
// template arguments
|
||||
if (arg.getAsType()->getAs<clang::TemplateSpecializationType>()) {
|
||||
const auto *nested_template_type =
|
||||
arg.getAsType()->getAs<clang::TemplateSpecializationType>();
|
||||
|
||||
const auto nested_template_name =
|
||||
nested_template_type->getTemplateName()
|
||||
.getAsTemplateDecl()
|
||||
->getQualifiedNameAsString();
|
||||
|
||||
argument.set_name(nested_template_name);
|
||||
|
||||
auto nested_template_instantiation = build_template_instantiation(
|
||||
*arg.getAsType()->getAs<clang::TemplateSpecializationType>(),
|
||||
{&template_instantiation});
|
||||
|
||||
argument.set_id(nested_template_instantiation->id());
|
||||
|
||||
for (const auto &t : nested_template_instantiation->templates())
|
||||
argument.add_template_param(t);
|
||||
|
||||
// Check if this template should be simplified (e.g. system
|
||||
// template aliases such as 'std:basic_string<char>' should be
|
||||
// simply 'std::string')
|
||||
simplify_system_template(argument,
|
||||
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 {
|
||||
LOG_DBG("Failed to find type specialization for argument "
|
||||
"{} 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) {
|
||||
// Sometimes template instantiation is reported as
|
||||
// RecordType in the AST and getAs to
|
||||
// TemplateSpecializationType returns null pointer so we
|
||||
// have to at least make sure it's properly formatted
|
||||
// (e.g. std:integral_constant, or any template
|
||||
// specialization which contains it - see t00038)
|
||||
process_unexposed_template_specialization_parameters(
|
||||
type_name.substr(type_name.find('<') + 1,
|
||||
type_name.size() - (type_name.find('<') + 2)),
|
||||
argument, template_instantiation);
|
||||
|
||||
argument.set_name(type_name.substr(0, type_name.find('<')));
|
||||
}
|
||||
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
|
||||
// whatever clang says
|
||||
argument.set_name(type_name);
|
||||
}
|
||||
else
|
||||
argument.set_name(type_name);
|
||||
}
|
||||
|
||||
LOG_DBG("Adding template instantiation argument {}",
|
||||
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));
|
||||
}
|
||||
else if (argument_kind == clang::TemplateArgument::Integral) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(false);
|
||||
argument.set_type(std::to_string(arg.getAsIntegral().getExtValue()));
|
||||
template_instantiation.add_template(std::move(argument));
|
||||
}
|
||||
else if (argument_kind == clang::TemplateArgument::Expression) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(false);
|
||||
argument.set_type(get_source_text(
|
||||
arg.getAsExpr()->getSourceRange(), source_manager_));
|
||||
template_instantiation.add_template(std::move(argument));
|
||||
}
|
||||
else if (argument_kind == clang::TemplateArgument::TemplateExpansion) {
|
||||
template_parameter argument;
|
||||
argument.is_template_parameter(true);
|
||||
|
||||
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::
|
||||
process_unexposed_template_specialization_parameters(
|
||||
const std::string &type_name, template_parameter &tp, class_ &c)
|
||||
{
|
||||
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;
|
||||
for (auto ¶m : template_params) {
|
||||
@@ -1220,7 +1303,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
template_base_params{};
|
||||
|
||||
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()
|
||||
->getAs<clang::TemplateSpecializationType>();
|
||||
|
||||
@@ -1402,8 +1487,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
argument.add_template_param(t);
|
||||
|
||||
// Check if this template should be simplified (e.g. system
|
||||
// template aliases such as 'std:basic_string<char>' should be
|
||||
// simply 'std::string')
|
||||
// template aliases such as 'std:basic_string<char>' should
|
||||
// be simply 'std::string')
|
||||
simplify_system_template(argument,
|
||||
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(
|
||||
full_template_specialization_name)) {
|
||||
// Add dependency relationship to the parent template
|
||||
// Add dependency relationship to the parent
|
||||
// template
|
||||
template_instantiation.add_relationship(
|
||||
{relationship_t::kDependency,
|
||||
arg.getAsType()
|
||||
@@ -1546,8 +1632,8 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
|
||||
template_instantiation.add_relationship(
|
||||
{relationship_t::kInstantiation, best_match_id});
|
||||
}
|
||||
// If we can't find optimal match for parent template specialization, just
|
||||
// use whatever clang suggests
|
||||
// If we can't find optimal match for parent template specialization,
|
||||
// just use whatever clang suggests
|
||||
else if (diagram().has_element(template_type.getTemplateName()
|
||||
.getAsTemplateDecl()
|
||||
->getID())) {
|
||||
@@ -1609,8 +1695,8 @@ void translation_unit_visitor::process_field(
|
||||
auto type_name = to_string(field_type, field_declaration.getASTContext());
|
||||
// The field name
|
||||
const auto field_name = field_declaration.getNameAsString();
|
||||
// If for any reason clang reports the type as empty string, make sure it
|
||||
// has some default name
|
||||
// If for any reason clang reports the type as empty string, make sure
|
||||
// it has some default name
|
||||
if (type_name.empty())
|
||||
type_name = "<<anonymous>>";
|
||||
|
||||
@@ -1689,8 +1775,8 @@ void translation_unit_visitor::process_field(
|
||||
|
||||
// Check if this template instantiation should be added to the
|
||||
// current diagram. Even if the top level template type for
|
||||
// this instantiation should not be part of the diagram, e.g. it's
|
||||
// a std::vector<>, it's nested types might be added
|
||||
// this instantiation should not be part of the diagram, e.g.
|
||||
// it's a std::vector<>, it's nested types might be added
|
||||
bool add_template_instantiation_to_diargam{false};
|
||||
if (diagram().should_include(
|
||||
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
|
||||
// the top level type has been added as aggregation
|
||||
// Add any relationships to the class 'c' to the diagram,
|
||||
// unless the top level type has been added as aggregation
|
||||
add_relationships(c, field, nested_relationships,
|
||||
/* break on first aggregation */ false);
|
||||
}
|
||||
|
||||
@@ -71,16 +71,16 @@ private:
|
||||
void process_class_declaration(const clang::CXXRecordDecl &cls,
|
||||
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,
|
||||
clanguml::class_diagram::model::class_ &c) const;
|
||||
|
||||
void process_class_children(const clang::CXXRecordDecl *cls,
|
||||
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(
|
||||
const clang::ClassTemplateSpecializationDecl *cls,
|
||||
clanguml::class_diagram::model::class_ &c);
|
||||
@@ -89,6 +89,12 @@ private:
|
||||
const clang::ClassTemplateDecl &template_declaration,
|
||||
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,
|
||||
clanguml::common::model::element &c) const;
|
||||
|
||||
|
||||
@@ -336,6 +336,7 @@ std::unique_ptr<DiagramModel> generate(
|
||||
DiagramConfig &config, bool verbose = false)
|
||||
{
|
||||
LOG_INFO("Generating diagram {}.puml", name);
|
||||
|
||||
auto diagram = std::make_unique<DiagramModel>();
|
||||
diagram->set_name(name);
|
||||
diagram->set_filter(
|
||||
@@ -346,9 +347,13 @@ std::unique_ptr<DiagramModel> generate(
|
||||
std::vector<std::string> translation_units{};
|
||||
for (const auto &g : config.glob()) {
|
||||
LOG_DBG("Processing glob: {}", g);
|
||||
|
||||
const auto matches = glob::rglob(g);
|
||||
std::copy(matches.begin(), matches.end(),
|
||||
std::back_inserter(translation_units));
|
||||
|
||||
LOG_DBG(
|
||||
"Found translation units: {}", fmt::join(translation_units, ", "));
|
||||
}
|
||||
|
||||
clang::tooling::ClangTool clang_tool(db, translation_units);
|
||||
|
||||
@@ -166,6 +166,14 @@ void class_diagram::initialize_template_aliases()
|
||||
template_aliases().insert(
|
||||
{"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)
|
||||
|
||||
@@ -41,17 +41,19 @@ std::pair<common::model::namespace_, std::string> split_ns(
|
||||
|
||||
std::vector<class_diagram::model::template_parameter>
|
||||
parse_unexposed_template_params(const std::string ¶ms,
|
||||
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;
|
||||
|
||||
std::vector<template_parameter> res;
|
||||
|
||||
auto it = params.begin();
|
||||
while (std::isspace(*it))
|
||||
++it;
|
||||
|
||||
std::string type{};
|
||||
std::vector<template_parameter> nested_params;
|
||||
bool complete_class_template{false};
|
||||
bool complete_class_template_argument{false};
|
||||
|
||||
while (it != params.end()) {
|
||||
if (*it == '<') {
|
||||
@@ -72,25 +74,32 @@ parse_unexposed_template_params(const std::string ¶ms,
|
||||
}
|
||||
bracket_match_end++;
|
||||
}
|
||||
|
||||
std::string nested_params_str(
|
||||
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())
|
||||
nested_params.emplace_back(
|
||||
template_parameter{nested_params_str});
|
||||
|
||||
it = bracket_match_end - 1;
|
||||
}
|
||||
else if (*it == '>') {
|
||||
complete_class_template = true;
|
||||
complete_class_template_argument = true;
|
||||
if (depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (*it == ',') {
|
||||
complete_class_template = true;
|
||||
complete_class_template_argument = true;
|
||||
}
|
||||
else {
|
||||
type += *it;
|
||||
}
|
||||
if (complete_class_template) {
|
||||
if (complete_class_template_argument) {
|
||||
template_parameter t;
|
||||
t.set_type(ns_resolve(clanguml::util::trim(type)));
|
||||
type = "";
|
||||
@@ -98,7 +107,7 @@ parse_unexposed_template_params(const std::string ¶ms,
|
||||
t.add_template_param(std::move(param));
|
||||
|
||||
res.emplace_back(std::move(t));
|
||||
complete_class_template = false;
|
||||
complete_class_template_argument = false;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
@@ -111,7 +120,6 @@ parse_unexposed_template_params(const std::string ¶ms,
|
||||
t.add_template_param(std::move(param));
|
||||
|
||||
res.emplace_back(std::move(t));
|
||||
complete_class_template = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
@@ -32,6 +32,6 @@ std::pair<common::model::namespace_, std::string> split_ns(
|
||||
|
||||
std::vector<class_diagram::model::template_parameter>
|
||||
parse_unexposed_template_params(const std::string ¶ms,
|
||||
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
|
||||
|
||||
@@ -41,16 +41,21 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
|
||||
const auto from = m_config.using_namespace().relative(m.from);
|
||||
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 << "\" "
|
||||
<< 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("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);
|
||||
LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from,
|
||||
m.from_usr, to, m.to_usr);
|
||||
}
|
||||
|
||||
void generator::generate_return(const message &m, std::ostream &ostr) const
|
||||
@@ -71,11 +76,19 @@ void generator::generate_activity(const activity &a, std::ostream &ostr) const
|
||||
{
|
||||
for (const auto &m : a.messages) {
|
||||
const auto to = m_config.using_namespace().relative(m.to);
|
||||
|
||||
if (to.empty())
|
||||
continue;
|
||||
|
||||
generate_call(m, ostr);
|
||||
|
||||
ostr << "activate " << '"' << to << '"' << std::endl;
|
||||
|
||||
if (m_model.sequences.find(m.to_usr) != m_model.sequences.end())
|
||||
generate_activity(m_model.sequences[m.to_usr], ostr);
|
||||
|
||||
generate_return(m, ostr);
|
||||
|
||||
ostr << "deactivate " << '"' << to << '"' << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,5 +263,12 @@ template <> bool starts_with(const std::string &s, const std::string &prefix)
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,10 @@ bool starts_with(
|
||||
|
||||
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>
|
||||
bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix)
|
||||
{
|
||||
|
||||
@@ -34,12 +34,12 @@ TEST_CASE("t00012", "[test-case][class]")
|
||||
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||
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<1,1,1,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("C<T,int Is...>"),
|
||||
IsInstantiation(_A("C<T,int... Is>"),
|
||||
_A("C<std::map<int,"
|
||||
"std::vector<std::vector<std::vector<std::string>>>>,3,3,3>")));
|
||||
|
||||
|
||||
11
tests/t00047/.clang-uml
Normal file
11
tests/t00047/.clang-uml
Normal 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
26
tests/t00047/t00047.cc
Normal 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
47
tests/t00047/test_case.h
Normal 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);
|
||||
}
|
||||
@@ -235,6 +235,7 @@ using namespace clanguml::test::matchers;
|
||||
#include "t00044/test_case.h"
|
||||
#include "t00045/test_case.h"
|
||||
#include "t00046/test_case.h"
|
||||
#include "t00047/test_case.h"
|
||||
|
||||
////
|
||||
//// Sequence diagram tests
|
||||
|
||||
@@ -135,6 +135,9 @@ test_cases:
|
||||
- name: t00046
|
||||
title: Test case for root namespace handling with packages
|
||||
description:
|
||||
- name: t00047
|
||||
title: Test case for recursive variadic template
|
||||
description:
|
||||
Sequence diagrams:
|
||||
- name: t20001
|
||||
title: Basic sequence diagram test case
|
||||
|
||||
@@ -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].template_params()[0].type() ==
|
||||
"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");
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ TEST_CASE("{{ name }}", "[test-case][{{ 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 }}");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user