From 7f9d698afcdf34de63ad1672912d31d4d42faa61 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 18 Apr 2023 23:56:09 +0200 Subject: [PATCH] Improved handling of method template deductions --- src/class_diagram/visitor/template_builder.cc | 145 ++++++++++++++++-- src/class_diagram/visitor/template_builder.h | 2 +- src/common/clang_utils.cc | 19 ++- src/common/clang_utils.h | 4 +- src/common/model/template_parameter.cc | 7 + src/common/model/template_parameter.h | 12 ++ tests/test_util.cc | 29 ++++ 7 files changed, 197 insertions(+), 21 deletions(-) diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index aacc6cb5..9e6dc911 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -72,8 +72,6 @@ std::unique_ptr template_builder::build(const clang::NamedDecl *cls, const clang::TemplateSpecializationType &template_type_decl, std::optional parent) { - // TODO: Make sure we only build instantiation once - // // Here we'll hold the template base class params to replace with the // instantiated values @@ -641,7 +639,6 @@ template_parameter template_builder::process_type_argument( argument.set_type(unexposed_type_name); } else if (type_name.find("type-parameter-") == 0) { - // argument = template_parameter::make_template_type({}); auto maybe_arg = get_template_argument_from_type_parameter_string( cls, type_name); @@ -651,7 +648,7 @@ template_parameter template_builder::process_type_argument( // Otherwise just set the name for the template argument to // whatever clang says - argument.set_name(type_name); + return template_parameter::make_template_type(type_name); } } @@ -732,22 +729,146 @@ bool template_builder::find_relationships_in_unexposed_template_params( std::optional template_builder::get_template_argument_from_type_parameter_string( - const clang::Decl *decl, const std::string &return_type_name) const + const clang::Decl *decl, const std::string &type_name) const { if (const auto *template_decl = llvm::dyn_cast(decl); - template_decl != nullptr && - return_type_name.find("type-parameter-") == 0) { + template_decl != nullptr && type_name.find("type-parameter-") == 0) { - [[maybe_unused]] const auto [depth, index] = - common::extract_template_parameter_index(return_type_name); + 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(' '))); - std::string param_name = return_type_name; + 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; + } + } + } + } + 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; + + const auto *template_decl_at_depth = alias_decl; + + template_decl_at_depth->dump(); for (auto i = 0U; - i < template_decl->getDescribedTemplateParams()->size(); i++) { + i < template_decl_at_depth->getTemplateParameters()->size(); i++) { const auto *param = - template_decl->getDescribedTemplateParams()->getParam(i); + template_decl_at_depth->getTemplateParameters()->getParam(i); if (i == index) { param_name = param->getNameAsString(); diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 2013e97b..408dce1e 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 &return_type_name) const; + const clang::Decl *decl, const std::string &type_name) const; common::visitor::ast_id_mapper &id_mapper(); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index e4bc4025..0a401149 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -269,17 +269,24 @@ std::string get_source_text( return get_source_text_raw(printable_range, sm); } -std::pair extract_template_parameter_index( - const std::string &type_parameter) +std::tuple +extract_template_parameter_index(const std::string &type_parameter) { assert(type_parameter.find("type-parameter-") == 0); - auto toks = - util::split(type_parameter.substr(strlen("type-parameter-")), "-"); + auto type_parameter_and_suffix = util::split(type_parameter, " "); - assert(toks.size() == 2); + auto toks = util::split( + type_parameter_and_suffix.front().substr(strlen("type-parameter-")), + "-"); - return {std::stoi(toks.at(0)), std::stoi(toks.at(1))}; + std::string qualifier; + + if (type_parameter_and_suffix.size() > 1) { + qualifier = type_parameter_and_suffix.at(1); + } + + return {std::stoi(toks.at(0)), std::stoi(toks.at(1)), std::move(qualifier)}; } bool is_subexpr_of(const clang::Stmt *parent_stmt, const clang::Stmt *sub_stmt) diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 2123388d..5954b5b8 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -98,8 +98,8 @@ std::string get_source_text_raw( std::string get_source_text( clang::SourceRange range, const clang::SourceManager &sm); -std::pair extract_template_parameter_index( - const std::string &type_parameter); +std::tuple +extract_template_parameter_index(const std::string &type_parameter); /** * @brief Check if an expression is contained in another expression diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index 9c9dc22b..00e6ba60 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -215,6 +215,13 @@ std::string template_parameter::to_string( "{}({})", return_type, fmt::join(function_args, ",")); } + if (is_method_template()) { + assert(template_params().size() == 2); + + return fmt::format("{} {}::*{}", template_params().at(0).name().value(), + template_params().at(1).name().value(), method_qualifier()); + } + std::string res; const auto maybe_type = type(); if (maybe_type) { diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index d99056d7..868480a0 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -182,6 +182,14 @@ public: bool is_function_template() const { return is_function_template_; } + void set_method_template(bool mt) { is_method_template_ = mt; } + + bool is_method_template() const { return is_method_template_; } + + void set_method_qualifier(const std::string &q) { method_qualifier_ = q; } + + const std::string &method_qualifier() const { return method_qualifier_; } + private: template_parameter() = default; @@ -210,6 +218,10 @@ private: bool is_function_template_{false}; + bool is_method_template_{false}; + + std::string method_qualifier_; + /// Stores optional fully qualified name of constraint for this template /// parameter std::optional concept_constraint_; diff --git a/tests/test_util.cc b/tests/test_util.cc index 2a1b26de..bf211059 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -94,6 +94,35 @@ TEST_CASE("Test replace_all", "[unit-test]") CHECK(text == orig); } +TEST_CASE("Test extract_template_parameter_index", "[unit-test]") +{ + using namespace clanguml::common; + + { + const auto [depth, index, qualifier] = + extract_template_parameter_index("type-parameter-0-0"); + CHECK(depth == 0); + CHECK(index == 0); + CHECK(qualifier.empty()); + } + + { + const auto [depth, index, qualifier] = + extract_template_parameter_index("type-parameter-0-0 &&"); + CHECK(depth == 0); + CHECK(index == 0); + CHECK(qualifier == "&&"); + } + + { + const auto [depth, index, qualifier] = + extract_template_parameter_index("type-parameter-12-678 const&"); + CHECK(depth == 12); + CHECK(index == 678); + CHECK(qualifier == "const&"); + } +} + TEST_CASE("Test parse_unexposed_template_params", "[unit-test]") { using namespace clanguml::common;