From ae7ef11e43f5fa5a1684f412eef67c0481dd4756 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 7 Aug 2022 23:08:37 +0200 Subject: [PATCH] Added test case for recursive variadic template specialization --- CMakeLists.txt | 2 +- src/class_diagram/model/template_parameter.cc | 30 +- src/class_diagram/model/template_parameter.h | 6 + .../visitor/translation_unit_visitor.cc | 284 ++++++++++++------ .../visitor/translation_unit_visitor.h | 14 +- src/common/generators/plantuml/generator.h | 5 + src/config/config.cc | 8 + src/cx/util.cc | 26 +- src/cx/util.h | 2 +- .../plantuml/sequence_diagram_generator.cc | 27 +- src/util/util.cc | 7 + src/util/util.h | 4 + tests/t00012/test_case.h | 8 +- tests/t00047/.clang-uml | 11 + tests/t00047/t00047.cc | 26 ++ tests/t00047/test_case.h | 47 +++ tests/test_cases.cc | 1 + tests/test_cases.yaml | 3 + tests/test_util.cc | 33 ++ util/templates/test_cases/test_case.h | 2 +- 20 files changed, 416 insertions(+), 130 deletions(-) create mode 100644 tests/t00047/.clang-uml create mode 100644 tests/t00047/t00047.cc create mode 100644 tests/t00047/test_case.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ac7b3005..a16751e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/class_diagram/model/template_parameter.cc b/src/class_diagram/model/template_parameter.cc index c313e306..0d3b31fe 100644 --- a/src/class_diagram/model/template_parameter.cc +++ b/src/class_diagram/model/template_parameter.cc @@ -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_; diff --git a/src/class_diagram/model/template_parameter.h b/src/class_diagram/model/template_parameter.h index 73bf8b5f..cbd79986 100644 --- a/src/class_diagram/model/template_parameter.h +++ b/src/class_diagram/model/template_parameter.h @@ -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_params_; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 81c8c4e2..1d8e32fb 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -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(cls) != @@ -414,6 +418,9 @@ std::unique_ptr 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()) { - const auto *nested_template_type = - arg.getAsType()->getAs(); - - 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(), - {&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' 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()) { + const auto *nested_template_type = + arg.getAsType()->getAs(); + + 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(), + {&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' should be + // simply 'std::string') + simplify_system_template(argument, + argument.to_string(config().using_namespace(), false)); + } + else if (arg.getAsType()->getAs()) { + 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 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()) template_type_ptr = template_type_decl.getAliasedType() ->getAs(); @@ -1402,8 +1487,8 @@ std::unique_ptr 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' should be - // simply 'std::string') + // template aliases such as 'std:basic_string' should + // be simply 'std::string') simplify_system_template(argument, argument.to_string(config().using_namespace(), false)); @@ -1455,7 +1540,8 @@ std::unique_ptr 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 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 = "<>"; @@ -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); } diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index d7bea1f5..28fbb212 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -71,16 +71,16 @@ private: void process_class_declaration(const clang::CXXRecordDecl &cls, clanguml::class_diagram::model::class_ &c); - std::unique_ptr - 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 + 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; diff --git a/src/common/generators/plantuml/generator.h b/src/common/generators/plantuml/generator.h index 71de76ca..44af1602 100644 --- a/src/common/generators/plantuml/generator.h +++ b/src/common/generators/plantuml/generator.h @@ -336,6 +336,7 @@ std::unique_ptr generate( DiagramConfig &config, bool verbose = false) { LOG_INFO("Generating diagram {}.puml", name); + auto diagram = std::make_unique(); diagram->set_name(name); diagram->set_filter( @@ -346,9 +347,13 @@ std::unique_ptr generate( std::vector 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); diff --git a/src/config/config.cc b/src/config/config.cc index 85041d43..121e9eaa 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -166,6 +166,14 @@ void class_diagram::initialize_template_aliases() template_aliases().insert( {"std::basic_string", "std::u32string"}); } + if (!template_aliases().count("std::integral_constant")) { + template_aliases().insert( + {"std::integral_constant", "std::true_type"}); + } + if (!template_aliases().count("std::integral_constant")) { + template_aliases().insert( + {"std::integral_constant", "std::false_type"}); + } } template <> void append_value(plantuml &l, const plantuml &r) diff --git a/src/cx/util.cc b/src/cx/util.cc index 7f80566e..56fcc7f0 100644 --- a/src/cx/util.cc +++ b/src/cx/util.cc @@ -41,17 +41,19 @@ std::pair split_ns( std::vector parse_unexposed_template_params(const std::string ¶ms, - std::function ns_resolve) + std::function ns_resolve, int depth) { using class_diagram::model::template_parameter; std::vector res; auto it = params.begin(); + while (std::isspace(*it)) + ++it; std::string type{}; std::vector 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; diff --git a/src/cx/util.h b/src/cx/util.h index c64ae55a..42751e94 100644 --- a/src/cx/util.h +++ b/src/cx/util.h @@ -32,6 +32,6 @@ std::pair split_ns( std::vector parse_unexposed_template_params(const std::string ¶ms, - std::function ns_resolve); + std::function ns_resolve, int depth = 0); } // namespace clanguml::cx::util diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 972000e2..fb45c5d2 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -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; } } diff --git a/src/util/util.cc b/src/util/util.cc index 6e40d347..5ab820b8 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -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()); +} + } } diff --git a/src/util/util.h b/src/util/util.h index 0c6cbe2d..a922a2c8 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -170,6 +170,10 @@ bool starts_with( template <> bool starts_with(const std::string &s, const std::string &prefix); +template bool ends_with(const T &value, const T &suffix); + +template <> bool ends_with(const std::string &value, const std::string &suffix); + template bool ends_with(const std::vector &col, const std::vector &suffix) { diff --git a/tests/t00012/test_case.h b/tests/t00012/test_case.h index 1d1033fe..bd76911f 100644 --- a/tests/t00012/test_case.h +++ b/tests/t00012/test_case.h @@ -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"), _A("B<3,2,1>"))); - REQUIRE_THAT(puml, IsInstantiation(_A("B"), _A("B<1,1,1,1>"))); + REQUIRE_THAT(puml, IsInstantiation(_A("B"), _A("B<3,2,1>"))); + REQUIRE_THAT(puml, IsInstantiation(_A("B"), _A("B<1,1,1,1>"))); REQUIRE_THAT(puml, - IsInstantiation(_A("C"), + IsInstantiation(_A("C"), _A("C>>>,3,3,3>"))); diff --git a/tests/t00047/.clang-uml b/tests/t00047/.clang-uml new file mode 100644 index 00000000..9ea39aed --- /dev/null +++ b/tests/t00047/.clang-uml @@ -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 \ No newline at end of file diff --git a/tests/t00047/t00047.cc b/tests/t00047/t00047.cc new file mode 100644 index 00000000..56ae8ca6 --- /dev/null +++ b/tests/t00047/t00047.cc @@ -0,0 +1,26 @@ +#include + +namespace clanguml { +namespace t00047 { + +template struct conditional_t; + +template struct conditional_t { + using type = Else; +}; + +template +struct conditional_t { + using type = Result; +}; + +template +struct conditional_t { + using type = typename conditional_t::type; +}; + +template +using conditional = typename conditional_t::type; + +} +} \ No newline at end of file diff --git a/tests/t00047/test_case.h b/tests/t00047/test_case.h new file mode 100644 index 00000000..042b1b9a --- /dev/null +++ b/tests/t00047/test_case.h @@ -0,0 +1,47 @@ +/** + * tests/t00047/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("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); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 00a51dfe..d44c7ab1 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -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 diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 4aa904f4..9ad39a21 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -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 diff --git a/tests/test_util.cc b/tests/test_util.cc index b5862d9e..453e9252 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -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"); } diff --git a/util/templates/test_cases/test_case.h b/util/templates/test_cases/test_case.h index 7f10d637..f70fdce8 100644 --- a/util/templates/test_cases/test_case.h +++ b/util/templates/test_cases/test_case.h @@ -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 }}");