From 0aa4eb732dc913d017c9cb7a23ed27df88c27855 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Apr 2023 19:29:02 +0200 Subject: [PATCH] Improved unexposed template parameter tokenization --- src/class_diagram/model/class.h | 12 + src/class_diagram/visitor/template_builder.cc | 322 ++++++++++-------- src/class_diagram/visitor/template_builder.h | 2 +- .../visitor/translation_unit_visitor.cc | 14 +- src/common/clang_utils.cc | 121 +++++++ src/common/clang_utils.h | 18 + src/common/model/template_parameter.cc | 51 ++- src/common/model/template_parameter.h | 6 +- tests/test_cases.cc | 1 + tests/test_model.cc | 34 ++ tests/test_util.cc | 104 ++++++ 11 files changed, 530 insertions(+), 155 deletions(-) diff --git a/src/class_diagram/model/class.h b/src/class_diagram/model/class.h index 143b0645..3e0888fc 100644 --- a/src/class_diagram/model/class.h +++ b/src/class_diagram/model/class.h @@ -72,6 +72,16 @@ public: int calculate_template_specialization_match(const class_ &other) const; + void template_specialization_found(bool found) + { + template_specialization_found_ = found; + } + + bool template_specialization_found() const + { + return template_specialization_found_; + } + private: bool is_struct_{false}; bool is_template_{false}; @@ -82,6 +92,8 @@ private: std::string base_template_full_name_; std::string full_name_; + + bool template_specialization_found_{false}; }; } // namespace clanguml::class_diagram::model diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index 9e6dc911..c4f83fbf 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -17,6 +17,7 @@ */ #include "template_builder.h" +#include "common/clang_utils.h" #include "translation_unit_visitor.h" namespace clanguml::class_diagram::visitor { @@ -241,12 +242,18 @@ std::unique_ptr template_builder::build(const clang::NamedDecl *cls, destination = best_match_full_name; template_instantiation.add_relationship( {relationship_t::kInstantiation, best_match_id}); + template_instantiation.template_specialization_found(true); } // If we can't find optimal match for parent template specialization, // just use whatever clang suggests else if (diagram().has_element(templated_decl_local_id)) { template_instantiation.add_relationship( {relationship_t::kInstantiation, templated_decl_local_id}); + template_instantiation.template_specialization_found(true); + } + else if (diagram().should_include(full_template_specialization_name)) { + LOG_DBG("Skipping instantiation relationship from {} to {}", + template_instantiation_ptr->full_name(false), templated_decl_id); } else { LOG_DBG("== Cannot determine global id for specialization template {} " @@ -333,12 +340,14 @@ template_builder::build_from_class_template_specialization( destination = best_match_full_name; template_instantiation.add_relationship( {relationship_t::kInstantiation, best_match_id}); + template_instantiation.template_specialization_found(true); } - // If we can't find optimal match for parent template specialization, - // just use whatever clang suggests else if (diagram().has_element(templated_decl_local_id)) { + // If we can't find optimal match for parent template specialization, + // just use whatever clang suggests template_instantiation.add_relationship( {relationship_t::kInstantiation, templated_decl_local_id}); + template_instantiation.template_specialization_found(true); } else if (diagram().should_include(qualified_name)) { LOG_DBG("Skipping instantiation relationship from {} to {}", @@ -727,165 +736,192 @@ bool template_builder::find_relationships_in_unexposed_template_params( return found; } -std::optional -template_builder::get_template_argument_from_type_parameter_string( - const clang::Decl *decl, const std::string &type_name) const +using token_it = std::vector::const_iterator; + +namespace detail { + +std::string map_type_parameter_to_template_parameter( + const clang::ClassTemplateSpecializationDecl *decl, const std::string &tp) +{ + const auto [depth0, index0, qualifier0] = + common::extract_template_parameter_index(tp); + + for (auto i = 0U; i < decl->getDescribedTemplateParams()->size(); i++) { + const auto *param = decl->getDescribedTemplateParams()->getParam(i); + + if (i == index0) { + return param->getNameAsString(); + } + } + + return tp; +} + +std::string map_type_parameter_to_template_parameter( + const clang::TypeAliasTemplateDecl *decl, const std::string &tp) +{ + const auto [depth0, index0, qualifier0] = + common::extract_template_parameter_index(tp); + + for (auto i = 0U; i < decl->getTemplateParameters()->size(); i++) { + const auto *param = decl->getTemplateParameters()->getParam(i); + + if (i == index0) { + return param->getNameAsString(); + } + } + + return tp; +} + +} + +template_parameter map_type_parameter_to_template_parameter( + const clang::Decl *decl, const std::string &tp) { if (const auto *template_decl = llvm::dyn_cast(decl); - template_decl != nullptr && type_name.find("type-parameter-") == 0) { - - if (type_name.rfind("type-parameter-") > 0 && - type_name.find("::*") != std::string::npos) { - if (type_name.find("::*)") == std::string::npos) { - // This is a method invocation template, e.g. - // template - // struct invocable - // {}; - const auto [depth0, index0, qualifier0] = - common::extract_template_parameter_index( - type_name.substr(0, type_name.find(' '))); - - const auto [depth1, index1, qualifier1] = - common::extract_template_parameter_index(type_name.substr( - type_name.find(' ') + 1, type_name.find(':'))); - - auto template_param = - template_parameter::make_template_type({}); - template_param.set_method_template(true); - - std::string param_name = type_name; - - for (auto i = 0U; - i < template_decl->getDescribedTemplateParams()->size(); - i++) { - const auto *param = - template_decl->getDescribedTemplateParams()->getParam( - i); - - if (i == index0) { - param_name = param->getNameAsString(); - - template_param.add_template_param( - template_parameter::make_template_type(param_name)); - } - - if (i == index1) { - param_name = param->getNameAsString(); - - template_param.add_template_param( - template_parameter::make_template_type(param_name)); - } - } - - template_param.set_method_qualifier( - type_name.substr(type_name.find("::*") + 3)); - - return template_param; - } - else { - // Here we're dealing with method type with args, e.g.: - // type-parameter-0-0 - // (type-parameter-0-1::*)(type-parameter-0-2) - const auto [depth0, index0, qualifier0] = - common::extract_template_parameter_index( - type_name.substr(0, type_name.find(' '))); - - const auto [depth1, index1, qualifier1] = - common::extract_template_parameter_index(type_name.substr( - type_name.find(" (") + 2, type_name.find(':'))); - - auto template_param = - template_parameter::make_template_type({}); - template_param.set_method_template(true); - - // TODO: Handle args - for (auto i = 0U; - i < template_decl->getDescribedTemplateParams()->size(); - i++) { - const auto *param = - template_decl->getDescribedTemplateParams()->getParam( - i); - - if (i == index0) { - template_param.add_template_param( - template_parameter::make_template_type( - param->getNameAsString())); - } - - if (i == index1) { - template_param.add_template_param( - template_parameter::make_template_type( - param->getNameAsString())); - } - } - - return template_param; - } - } - else { - const auto [depth, index, qualifier] = - common::extract_template_parameter_index(type_name); - - std::string param_name = type_name; - - clang::ClassTemplateSpecializationDecl *template_decl_at_depth = - const_cast( - template_decl); - - for (auto i = 0U; i < - template_decl_at_depth->getDescribedTemplateParams()->size(); - i++) { - const auto *param = - template_decl_at_depth->getDescribedTemplateParams() - ->getParam(i); - - if (i == index) { - param_name = param->getNameAsString(); - - auto template_param = - template_parameter::make_template_type(param_name); - - template_param.is_variadic(param->isParameterPack()); - - return template_param; - } - } - } + template_decl != nullptr && tp.find("type-parameter-") == 0) { + return template_parameter::make_template_type( + detail::map_type_parameter_to_template_parameter( + template_decl, tp)); } - else if (const auto *alias_decl = - llvm::dyn_cast(decl); - alias_decl != nullptr && type_name.find("type-parameter-") == 0) { - const auto [depth, index, qualifier] = - common::extract_template_parameter_index(type_name); - std::string param_name = type_name; + if (const auto *alias_decl = + llvm::dyn_cast(decl); + alias_decl != nullptr && + (tp.find("type-parameter-") != std::string::npos)) { + return template_parameter::make_template_type( + detail::map_type_parameter_to_template_parameter(alias_decl, tp)); + } - const auto *template_decl_at_depth = alias_decl; + return template_parameter::make_argument(tp); +} - template_decl_at_depth->dump(); +std::optional build_template_parameter( + const clang::Decl *decl, token_it begin, token_it end) +{ + if (decl == nullptr) + return {}; - for (auto i = 0U; - i < template_decl_at_depth->getTemplateParameters()->size(); i++) { - const auto *param = - template_decl_at_depth->getTemplateParameters()->getParam(i); + auto res = template_parameter::make_template_type({}); - if (i == index) { - param_name = param->getNameAsString(); + std::string param_qualifier{}; // e.g. const& or && - auto template_param = - template_parameter::make_template_type(param_name); + auto it = begin; + auto it_next = it; + it_next++; - template_param.is_variadic(param->isParameterPack()); + if (*it == "const") { + param_qualifier = "const"; + it++; + it_next++; + } - return template_param; - } + if (it == end) + return {}; + + // simple template param without qualifiers + if (common::is_type_token(*it) && it_next == end) { + res = map_type_parameter_to_template_parameter(decl, *it); + return res; + } + // template parameter with qualifier at the end + else if (common::is_type_token(*it) && common::is_qualifier(*it_next)) { + res = map_type_parameter_to_template_parameter(decl, *it); + res.set_qualifier(*it_next); + return res; + } + // method template parameter + else if (common::is_type_token(*it) && common::is_type_token(*it_next)) { + res.add_template_param( + map_type_parameter_to_template_parameter(decl, *it)); + res.add_template_param( + map_type_parameter_to_template_parameter(decl, *it_next)); + it = it_next; + it++; + if (it != end && *it == "::") { + res.set_method_template(true); + it++; + it++; } + + if (it != end && common::is_qualifier(*it)) { + res.set_qualifier(*it); + } + + return res; + } + else if (common::is_type_token(*it) && *it_next == "(") { + res.add_template_param( + map_type_parameter_to_template_parameter(decl, *it)); + it_next++; + res.add_template_param( + map_type_parameter_to_template_parameter(decl, *it_next)); + + it = it_next; + it++; + if (*it == "::") { + res.set_method_template(true); + it++; + it++; + it++; + } + + if (it != end) { + // handle args + if (*it == "(") { + it++; + while (true) { + auto arg_separator = std::find(it, end, ","); + if (arg_separator == end) { + // just one arg + auto args_end = std::find(it, end, ")"); + auto arg = build_template_parameter(decl, it, args_end); + if (arg) + res.add_template_param(*arg); + it++; + break; + } + else { + auto arg = + build_template_parameter(decl, it, arg_separator); + if (arg) + res.add_template_param(*arg); + it = arg_separator; + it++; + } + } + + assert(*it == ")"); + + it++; + + if (it != end && common::is_qualifier(*it)) { + res.set_qualifier(*it); + } + } + + return res; + } + else + return res; } return {}; } +std::optional +template_builder::get_template_argument_from_type_parameter_string( + const clang::Decl *decl, std::string type_name) const +{ + type_name = util::trim(type_name); + + auto toks = common::tokenize_unexposed_template_parameter(type_name); + + return build_template_parameter(decl, toks.begin(), toks.end()); +} + template_parameter template_builder::process_integral_argument( const clang::TemplateArgument &arg) { diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 408dce1e..5f3a2cd1 100644 --- a/src/class_diagram/visitor/template_builder.h +++ b/src/class_diagram/visitor/template_builder.h @@ -122,7 +122,7 @@ public: std::optional get_template_argument_from_type_parameter_string( - const clang::Decl *decl, const std::string &type_name) const; + const clang::Decl *decl, std::string type_name) const; common::visitor::ast_id_mapper &id_mapper(); diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 83e2e1bc..9cff7ec3 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -210,11 +210,15 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( // Process template specialization bases process_class_bases(cls, template_specialization); - const auto maybe_id = - id_mapper().get_global_id(cls->getSpecializedTemplate()->getID()); - if (maybe_id.has_value()) - template_specialization.add_relationship( - {relationship_t::kInstantiation, maybe_id.value()}); + if (!template_specialization.template_specialization_found()) { + // Only do this if we haven't found a bettern specialization during + // construction of the template specialization + const auto maybe_id = + id_mapper().get_global_id(cls->getSpecializedTemplate()->getID()); + if (maybe_id.has_value()) + template_specialization.add_relationship( + {relationship_t::kInstantiation, maybe_id.value()}); + } if (diagram_.should_include(template_specialization)) { const auto full_name = template_specialization.full_name(false); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 0a401149..a6999acd 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -459,4 +459,125 @@ std::vector parse_unexposed_template_params( return res; } + +bool is_type_parameter(const std::string &t) +{ + return t.find("type-parameter-") == 0; +} + +bool is_qualifier(const std::string &q) +{ + return q == "&" || q == "&&" || q == "const&"; +} + +bool is_bracket(const std::string &b) +{ + return b == "(" || b == ")" || b == "[" || b == "]"; +} + +bool is_identifier_character(char c) { return std::isalnum(c) || c == '_'; } + +bool is_identifier(const std::string &t) +{ + return std::isalpha(t.at(0)) && + std::all_of(t.begin(), t.end(), + [](const char c) { return is_identifier_character(c); }); +} + +bool is_keyword(const std::string &t) +{ + static std::vector keywords {"alignas", "alignof", "asm", + "auto", "bool", "break", "case", "catch", "char", "char16_t", + "char32_t", "class", "concept", "const", "constexpr", "const_cast", + "continue", "decltype", "default", "delete", "do", "double", + "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", + "float", "for", "friend", "goto", "if", "inline", "int", "long", + "mutable", "namespace", "new", "noexcept", "nullptr", "operator", + "private", "protected", "public", "register", "reinterpret_cast", + "return", "requires", "short", "signed", "sizeof", "static", + "static_assert", "static_cast", "struct", "switch", "template", "this", + "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", + "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", + "while"}; + + return util::contains(keywords, t); +} + +bool is_qualified_identifier(const std::string &t) +{ + return std::isalpha(t.at(0)) && + std::all_of(t.begin(), t.end(), [](const char c) { + return is_identifier_character(c) || c == ':'; + }); +} + +bool is_type_token(const std::string &t) +{ + return is_type_parameter(t) || + (is_identifier(t) && !is_qualifier(t) && !is_bracket(t)); +} + +std::vector tokenize_unexposed_template_parameter( + const std::string &t) +{ + std::vector result; + + auto spaced_out = util::split(t, " "); + + for (const auto &word : spaced_out) { + if (is_qualified_identifier(word)) { + if (word != "class" && word != "templated" && word != "struct") + result.push_back(word); + continue; + } + + std::string tok; + + for (const char c : word) { + if (c == '(' || c == ')' || c == '[' || c == ']') { + if (!tok.empty()) + result.push_back(tok); + result.push_back(std::string{c}); + tok.clear(); + } + else if (c == ':') { + if (!tok.empty() && tok != ":") { + result.push_back(tok); + tok = ":"; + } + else { + tok += ':'; + } + } + else if (c == ',') { + if (!tok.empty()) { + result.push_back(tok); + } + result.push_back(","); + tok.clear(); + } + else if (c == '*') { + if (!tok.empty()) { + result.push_back(tok); + } + result.push_back("*"); + tok.clear(); + } + else { + tok += c; + } + } + + tok = util::trim(tok); + + if (!tok.empty()) { + if (tok != "class" && tok != "typename" && word != "struct") + result.push_back(tok); + tok.clear(); + } + } + + return result; +} + } // namespace clanguml::common diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 5954b5b8..0c643692 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -153,6 +153,9 @@ std::vector parse_unexposed_template_params( const std::function &ns_resolve, int depth = 0); +std::vector tokenize_unexposed_template_parameter( + const std::string &t); + template void if_dyn_cast(P pointer, F &&func) { @@ -164,4 +167,19 @@ void if_dyn_cast(P pointer, F &&func) std::forward(func)(dyn_cast_value); } } + +bool is_type_parameter(const std::string &t); + +bool is_qualifier(const std::string &q); + +bool is_bracket(const std::string &b); + +bool is_identifier_character(char c); + +bool is_identifier(const std::string &t); + +bool is_qualified_identifier(const std::string &t); + +bool is_type_token(const std::string &t); + } // namespace clanguml::common diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index 00e6ba60..7ed55175 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -113,6 +113,20 @@ int template_parameter::calculate_specialization_match( { int res{0}; + if (qualifier() != base_template_parameter.qualifier()) + return 0; + + if (is_template_parameter() && + base_template_parameter.is_template_parameter() && + template_params().empty() && + base_template_parameter.template_params().empty() && + is_variadic() == is_variadic() && + is_function_template() == + base_template_parameter.is_function_template() && + is_method_template() == base_template_parameter.is_method_template()) { + return 1; + } + auto maybe_base_template_parameter_type = base_template_parameter.type(); auto maybe_template_parameter_type = type(); @@ -132,6 +146,9 @@ int template_parameter::calculate_specialization_match( !is_function_template()) return 0; + if (base_template_parameter.is_method_template() && !is_method_template()) + return 0; + if (!base_template_parameter.template_params().empty() && !template_params().empty()) { auto params_match = calculate_template_params_specialization_match( @@ -216,10 +233,29 @@ std::string template_parameter::to_string( } if (is_method_template()) { - assert(template_params().size() == 2); + assert(template_params().size() > 1); - return fmt::format("{} {}::*{}", template_params().at(0).name().value(), - template_params().at(1).name().value(), method_qualifier()); + if (template_params().size() == 2) { + return fmt::format("{} {}::*{}", + template_params().at(0).to_string(using_namespace, relative), + template_params().at(1).to_string(using_namespace, relative), + qualifier()); + } + else { + auto it = template_params().begin(); + auto return_type = it->to_string(using_namespace, relative); + it++; + auto class_type = it->to_string(using_namespace, relative); + it++; + std::vector args; + + for (; it != template_params().end(); it++) { + args.push_back(it->to_string(using_namespace, relative)); + } + + return fmt::format("{} ({}::*)({}){}", return_type, class_type, + fmt::join(args, ","), qualifier()); + } } std::string res; @@ -271,6 +307,9 @@ std::string template_parameter::to_string( res += fmt::format("<{}>", fmt::join(params, ",")); } + if (!qualifier().empty()) + res += " " + qualifier(); + const auto &maybe_default_value = default_value(); if (maybe_default_value) { res += "="; @@ -342,6 +381,12 @@ int calculate_template_params_specialization_match( { int res{0}; + if (specialization_params.size() != template_params.size() && + !std::any_of(template_params.begin(), template_params.end(), + [](const auto &t) { return t.is_variadic(); })) { + return 0; + } + if (!specialization_params.empty() && !template_params.empty()) { auto template_index{0U}; auto arg_index{0U}; diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index 868480a0..0811dca6 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -186,9 +186,9 @@ public: bool is_method_template() const { return is_method_template_; } - void set_method_qualifier(const std::string &q) { method_qualifier_ = q; } + void set_qualifier(const std::string &q) { qualifier_ = q; } - const std::string &method_qualifier() const { return method_qualifier_; } + const std::string &qualifier() const { return qualifier_; } private: template_parameter() = default; @@ -220,7 +220,7 @@ private: bool is_method_template_{false}; - std::string method_qualifier_; + std::string qualifier_; /// Stores optional fully qualified name of constraint for this template /// parameter diff --git a/tests/test_cases.cc b/tests/test_cases.cc index b5cc7897..980272a7 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -301,6 +301,7 @@ using namespace clanguml::test::matchers; #endif #include "t00060/test_case.h" #include "t00061/test_case.h" +#include "t00062/test_case.h" /// /// Sequence diagram tests diff --git a/tests/test_model.cc b/tests/test_model.cc index ae6a7b06..6e5521f0 100644 --- a/tests/test_model.cc +++ b/tests/test_model.cc @@ -286,4 +286,38 @@ TEST_CASE( CHECK(sink_s.calculate_specialization_match(sink_t)); } + + { + auto tp1 = template_parameter::make_template_type({}); + tp1.set_method_template(true); + tp1.add_template_param(template_parameter::make_template_type("Ret")); + tp1.add_template_param(template_parameter::make_template_type("C")); + tp1.add_template_param( + template_parameter::make_template_type("Arg0")); + + auto tp2 = template_parameter::make_template_type({}); + tp2.set_method_template(true); + tp2.add_template_param(template_parameter::make_argument("char")); + tp2.add_template_param(template_parameter::make_template_type("C")); + tp2.add_template_param(template_parameter::make_argument("double")); + + CHECK(tp2.calculate_specialization_match(tp1)); + } + + { + auto tp1 = template_parameter::make_template_type({}); + tp1.set_method_template(true); + tp1.add_template_param(template_parameter::make_template_type("Ret")); + tp1.add_template_param(template_parameter::make_template_type("C")); + tp1.add_template_param( + template_parameter::make_template_type("Arg0")); + + auto tp2 = template_parameter::make_template_type({}); + tp2.set_method_template(true); + tp2.add_template_param(template_parameter::make_argument("char")); + tp2.add_template_param(template_parameter::make_template_type("C")); + tp2.add_template_param(template_parameter::make_argument("double")); + + CHECK(tp2.calculate_specialization_match(tp1)); + } } diff --git a/tests/test_util.cc b/tests/test_util.cc index bf211059..450f20ed 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -285,3 +285,107 @@ TEST_CASE("Test hash_seed", "[unit-test]") CHECK(hash_seed(1) == hash_seed(1)); CHECK(hash_seed(1) != hash_seed(2)); } + +TEST_CASE("Test tokenize_unexposed_template_parameter", "[unit-test]") +{ + using namespace clanguml::common; + + { + auto r = tokenize_unexposed_template_parameter("type-parameter-0-1"); + CHECK(r[0] == "type-parameter-0-1"); + } + + { + int i = 0; + + auto r = + tokenize_unexposed_template_parameter("const type-parameter-0-1 &"); + CHECK(r[i++] == "const"); + CHECK(r[i++] == "type-parameter-0-1"); + CHECK(r[i++] == "&"); + } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter( + "const type-parameter-0-0 type-parameter-0-1::* &&"); + CHECK(r[i++] == "const"); + CHECK(r[i++] == "type-parameter-0-0"); + CHECK(r[i++] == "type-parameter-0-1"); + CHECK(r[i++] == "::"); + CHECK(r[i++] == "*"); + CHECK(r[i++] == "&&"); + } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter( + "type-parameter-0-0 [type-parameter-0-1]"); + CHECK(r[i++] == "type-parameter-0-0"); + CHECK(r[i++] == "["); + CHECK(r[i++] == "type-parameter-0-1"); + CHECK(r[i++] == "]"); + } + + { + int i = 0; + auto r = tokenize_unexposed_template_parameter( + "type-parameter-0-0 (type-parameter-0-1::*)(type-parameter-0-2, " + "type-parameter-0-3, type-parameter-0-4)"); + CHECK(r[i++] == "type-parameter-0-0"); + CHECK(r[i++] == "("); + CHECK(r[i++] == "type-parameter-0-1"); + CHECK(r[i++] == "::"); + CHECK(r[i++] == "*"); + CHECK(r[i++] == ")"); + CHECK(r[i++] == "("); + CHECK(r[i++] == "type-parameter-0-2"); + CHECK(r[i++] == ","); + CHECK(r[i++] == "type-parameter-0-3"); + CHECK(r[i++] == ","); + CHECK(r[i++] == "type-parameter-0-4"); + CHECK(r[i++] == ")"); + } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter( + "const ns1::ns2::A &"); + CHECK(r[i++] == "const"); + CHECK(r[i++] == "ns1::ns2::A"); + CHECK(r[i++] == "&"); + } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter( + "class ns1::ns2::A &"); + CHECK(r[i++] == "ns1::ns2::A"); + CHECK(r[i++] == "&"); + } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter( + "ns1::ns2::A C::* &&"); + CHECK(r[i++] == "ns1::ns2::A"); + CHECK(r[i++] == "C"); + CHECK(r[i++] == "::"); + CHECK(r[i++] == "*"); + CHECK(r[i++] == "&&"); + } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter( + "unsigned int"); + CHECK(r[i++] == "unsigned"); + CHECK(r[i++] == "int"); + } +}