diff --git a/.clang-uml b/.clang-uml index 0dacf720..964717a0 100644 --- a/.clang-uml +++ b/.clang-uml @@ -22,6 +22,8 @@ diagrams: include!: uml/sequence_diagram_visitor_sequence_diagram.yml class_diagram_generator_sequence: include!: uml/class_diagram_generator_sequence_diagram.yml + template_builder_sequence: + include!: uml/template_builder_sequence_diagram.yml package_model_class: include!: uml/package_model_class_diagram.yml include_graph: diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index 7414dfbf..aacc6cb5 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 "translation_unit_visitor.h" namespace clanguml::class_diagram::visitor { @@ -67,14 +68,14 @@ bool template_builder::simplify_system_template( return false; } -std::unique_ptr template_builder::build(const clang::Decl *cls, +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 params to replace with the + // Here we'll hold the template base class params to replace with the // instantiated values // std::deque template_builder::build(const clang::Decl *cls, process_template_arguments(parent, cls, template_base_params, template_type.template_arguments(), template_instantiation, - full_template_specialization_name, template_decl); + template_decl); // First try to find the best match for this template in partially // specialized templates @@ -264,14 +265,97 @@ std::unique_ptr template_builder::build(const clang::Decl *cls, return template_instantiation_ptr; } +std::unique_ptr +template_builder::build_from_class_template_specialization( + const clang::ClassTemplateSpecializationDecl &template_specialization, + std::optional parent) +{ + auto template_instantiation_ptr = + std::make_unique(config_.using_namespace()); + + // + // Here we'll hold the template base params to replace with the + // instantiated values + // + std::deque> + template_base_params{}; + + auto &template_instantiation = *template_instantiation_ptr; + template_instantiation.is_struct(template_specialization.isStruct()); + + const auto *template_decl = + template_specialization.getSpecializedTemplate(); + + auto qualified_name = template_decl->getQualifiedNameAsString(); + + namespace_ ns{qualified_name}; + ns.pop_back(); + template_instantiation.set_name(template_decl->getNameAsString()); + template_instantiation.set_namespace(ns); + + process_template_arguments(parent, &template_specialization, + template_base_params, + template_specialization.getTemplateArgs().asArray(), + template_instantiation, template_decl); + + // Update the id after the template parameters are processed + template_instantiation.set_id( + common::to_id(template_instantiation.full_name(false))); + + // First try to find the best match for this template in partially + // specialized templates + std::string destination{}; + std::string best_match_full_name{}; + auto full_template_name = template_instantiation.full_name(false); + int best_match{}; + common::model::diagram_element::id_t best_match_id{0}; + + for (const auto templ : diagram().classes()) { + if (templ.get() == template_instantiation) + continue; + + auto c_full_name = templ.get().full_name(false); + auto match = + template_instantiation.calculate_template_specialization_match( + templ.get()); + + if (match > best_match) { + best_match = match; + best_match_full_name = c_full_name; + best_match_id = templ.get().id(); + } + } + + auto templated_decl_id = template_specialization.getID(); + auto templated_decl_local_id = + id_mapper().get_global_id(templated_decl_id).value_or(0); + + if (best_match_id > 0) { + destination = best_match_full_name; + 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 + else if (diagram().has_element(templated_decl_local_id)) { + template_instantiation.add_relationship( + {relationship_t::kInstantiation, templated_decl_local_id}); + } + else if (diagram().should_include(qualified_name)) { + LOG_DBG("Skipping instantiation relationship from {} to {}", + template_instantiation_ptr->full_name(false), templated_decl_id); + } + + return template_instantiation_ptr; +} + void template_builder::process_template_arguments( std::optional &parent, - const clang::Decl *cls, + const clang::NamedDecl *cls, std::deque> &template_base_params, const clang::ArrayRef &template_args, - class_ &template_instantiation, - const std::string &full_template_specialization_name, - const clang::TemplateDecl *template_decl) + class_ &template_instantiation, const clang::TemplateDecl *template_decl) { auto arg_index = 0; for (const auto &arg : template_args) { @@ -280,7 +364,7 @@ void template_builder::process_template_arguments( std::vector arguments; argument_process_dispatch(parent, cls, template_instantiation, - full_template_specialization_name, template_decl, arg, arguments); + template_decl, arg, arg_index, arguments); if (arguments.empty()) { arg_index++; @@ -319,10 +403,9 @@ void template_builder::process_template_arguments( void template_builder::argument_process_dispatch( std::optional &parent, - const clang::Decl *cls, class_ &template_instantiation, - const std::string &full_template_specialization_name, + const clang::NamedDecl *cls, class_ &template_instantiation, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, + const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument) { switch (arg.getKind()) { @@ -330,9 +413,8 @@ void template_builder::argument_process_dispatch( argument.push_back(process_null_argument(arg)); break; case clang::TemplateArgument::Type: - argument.push_back(process_type_argument(parent, cls, - full_template_specialization_name, template_decl, arg, - template_instantiation)); + argument.push_back(process_type_argument(parent, cls, template_decl, + arg, template_instantiation, argument_index)); break; case clang::TemplateArgument::Declaration: break; @@ -351,9 +433,9 @@ void template_builder::argument_process_dispatch( argument.push_back(process_expression_argument(arg)); break; case clang::TemplateArgument::Pack: - for (auto &a : process_pack_argument(parent, cls, - template_instantiation, full_template_specialization_name, - template_decl, arg, argument)) { + for (auto &a : + process_pack_argument(parent, cls, template_instantiation, + template_decl, arg, argument_index, argument)) { argument.push_back(a); } break; @@ -363,6 +445,8 @@ void template_builder::argument_process_dispatch( template_parameter template_builder::process_template_argument( const clang::TemplateArgument &arg) { + LOG_DBG("Processing template argument: {}", common::to_string(arg)); + auto arg_name = arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); return template_parameter::make_template_type(arg_name); @@ -370,13 +454,17 @@ template_parameter template_builder::process_template_argument( template_parameter template_builder::process_type_argument( std::optional &parent, - const clang::Decl *cls, - const std::string &full_template_specialization_name, - const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, class_ &template_instantiation) + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, class_ &template_instantiation, + size_t argument_index) { assert(arg.getKind() == clang::TemplateArgument::Type); + auto type_name = common::to_string(arg, &cls->getASTContext()); + + LOG_DBG("Processing template {} type argument: {}", + template_decl->getQualifiedNameAsString(), type_name); + auto argument = template_parameter::make_argument({}); if (const auto *function_type = @@ -431,9 +519,9 @@ template_parameter template_builder::process_type_argument( // Read arg info as needed. auto nested_template_instantiation = build_from_class_template_specialization( - *classTemplateSpecialization, *param_record_type, + *classTemplateSpecialization, // *param_record_type, diagram().should_include( - full_template_specialization_name) + template_decl->getQualifiedNameAsString()) ? std::make_optional(&template_instantiation) : parent); @@ -457,14 +545,6 @@ template_parameter template_builder::process_type_argument( } } } - else if (const auto maybe_arg = - get_template_argument_from_type_parameter_string( - cls, arg.getAsType().getAsString()); - maybe_arg) { - // The type is only in the form 'type-parameter-X-Y' so we have - // to match it to a template parameter name in the 'cls' template - argument = *maybe_arg; - } else if (const auto *nested_template_type = arg.getAsType()->getAs(); nested_template_type != nullptr) { @@ -476,7 +556,7 @@ template_parameter template_builder::process_type_argument( argument.set_type(nested_type_name); auto nested_template_instantiation = build(cls, *nested_template_type, - diagram().should_include(full_template_specialization_name) + diagram().should_include(template_decl->getQualifiedNameAsString()) ? std::make_optional(&template_instantiation) : parent); @@ -491,10 +571,13 @@ template_parameter template_builder::process_type_argument( simplify_system_template( argument, argument.to_string(using_namespace(), false)); + const auto nested_template_instantiation_full_name = + nested_template_instantiation->full_name(false); + if (nested_template_instantiation && - diagram().should_include( - nested_template_instantiation->full_name(false))) { - if (diagram().should_include(full_template_specialization_name)) { + diagram().should_include(nested_template_instantiation_full_name)) { + if (diagram().should_include( + template_decl->getQualifiedNameAsString())) { template_instantiation.add_relationship( {relationship_t::kDependency, nested_template_instantiation->id()}); @@ -507,26 +590,146 @@ template_parameter template_builder::process_type_argument( } } - auto nested_template_instantiation_full_name = - nested_template_instantiation->full_name(false); if (diagram().should_include(nested_template_instantiation_full_name)) { diagram().add_class(std::move(nested_template_instantiation)); } } else if (arg.getAsType()->getAs() != nullptr) { - argument.is_template_parameter(true); - argument.set_type( - common::to_string(arg.getAsType(), template_decl->getASTContext())); + argument = template_parameter::make_template_type({}); + + auto parameter_name = + common::to_string(arg.getAsType(), cls->getASTContext()); + + ensure_lambda_type_is_relative(parameter_name); + + // clang does not provide declared template parameter/argument + // names in template specializations - so we have to extract + // them from raw source code... + if (parameter_name.find("type-parameter-") == 0) { + auto maybe_arg = get_template_argument_from_type_parameter_string( + cls, parameter_name); + + if (maybe_arg) { + return *maybe_arg; + } + } + + argument.set_name(parameter_name); + } + // Case for unexposed template + else if ((type_name.find('<') != std::string::npos) || + (type_name.find("type-parameter-") == 0)) { + + ensure_lambda_type_is_relative(type_name); + if (type_name.find('<') != std::string::npos) { + argument = template_parameter::make_argument({}); + + // 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); + + auto unexposed_type_name = type_name.substr(0, type_name.find('<')); + ensure_lambda_type_is_relative(unexposed_type_name); + + 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); + + if (maybe_arg) { + return *maybe_arg; + } + + // Otherwise just set the name for the template argument to + // whatever clang says + argument.set_name(type_name); + } + } + + else if (const auto maybe_arg = + get_template_argument_from_type_parameter_string( + cls, arg.getAsType().getAsString()); + maybe_arg) { + // The type is only in the form 'type-parameter-X-Y' so we have + // to match it to a template parameter name in the 'cls' template + argument = *maybe_arg; + } + else if (type_name.find("(lambda at ") == 0) { + // This is just a lambda reference + ensure_lambda_type_is_relative(type_name); + argument.set_type(type_name); } else { // This is just a regular record type - process_tag_argument(template_instantiation, - full_template_specialization_name, template_decl, arg, argument); + process_tag_argument( + template_instantiation, template_decl, arg, argument); } return argument; } +void template_builder::process_unexposed_template_specialization_parameters( + const std::string &type_name, template_parameter &tp, class_ &c) +{ + auto template_params = common::parse_unexposed_template_params( + type_name, [](const std::string &t) { return t; }); + + found_relationships_t relationships; + for (auto ¶m : template_params) { + find_relationships_in_unexposed_template_params(param, relationships); + tp.add_template_param(param); + } + + for (auto &r : relationships) { + c.add_relationship({std::get<1>(r), std::get<0>(r)}); + } +} + +bool template_builder::find_relationships_in_unexposed_template_params( + const template_parameter &ct, found_relationships_t &relationships) +{ + const auto &type = ct.type(); + + if (!type) + return false; + + bool found{false}; + LOG_DBG("Finding relationships in user defined type: {}", + ct.to_string(config().using_namespace(), false)); + + auto type_with_namespace = + std::make_optional(type.value()); + + if (!type_with_namespace.has_value()) { + // Couldn't find declaration of this type + type_with_namespace = common::model::namespace_{type.value()}; + } + + auto element_opt = diagram().get(type_with_namespace.value().to_string()); + if (element_opt) { + relationships.emplace_back( + element_opt.value().id(), relationship_t::kDependency); + found = true; + } + + for (const auto &nested_template_params : ct.template_params()) { + found = find_relationships_in_unexposed_template_params( + nested_template_params, relationships) || + found; + } + + return found; +} + std::optional template_builder::get_template_argument_from_type_parameter_string( const clang::Decl *decl, const std::string &return_type_name) const @@ -584,6 +787,8 @@ template_parameter template_builder::process_nullptr_argument( { assert(arg.getKind() == clang::TemplateArgument::NullPtr); + LOG_DBG("Processing nullptr argument: {}", common::to_string(arg)); + return template_parameter::make_argument("nullptr"); } @@ -597,26 +802,26 @@ template_parameter template_builder::process_expression_argument( std::vector template_builder::process_pack_argument( std::optional &parent, - const clang::Decl *cls, class_ &template_instantiation, - const std::string &full_template_specialization_name, + const clang::NamedDecl *cls, class_ &template_instantiation, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, + const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument) { assert(arg.getKind() == clang::TemplateArgument::Pack); std::vector res; + int pack_argument_index = argument_index; + for (const auto &a : arg.getPackAsArray()) { argument_process_dispatch(parent, cls, template_instantiation, - full_template_specialization_name, template_decl, a, res); + template_decl, a, pack_argument_index++, res); } return res; } void template_builder::process_tag_argument(class_ &template_instantiation, - const std::string &full_template_specialization_name, const clang::TemplateDecl *template_decl, const clang::TemplateArgument &arg, template_parameter &argument) { @@ -634,7 +839,8 @@ void template_builder::process_tag_argument(class_ &template_instantiation, record_type_decl != nullptr) { argument.set_id(common::to_id(arg)); - if (diagram().should_include(full_template_specialization_name)) { + if (diagram().should_include( + template_decl->getQualifiedNameAsString())) { // Add dependency relationship to the parent // template template_instantiation.add_relationship( @@ -652,7 +858,8 @@ void template_builder::process_tag_argument(class_ &template_instantiation, #if LLVM_VERSION_MAJOR >= 16 argument.set_type(record_type_decl->getQualifiedNameAsString()); #endif - if (diagram().should_include(full_template_specialization_name)) { + if (diagram().should_include( + template_decl->getQualifiedNameAsString())) { // Add dependency relationship to the parent // template template_instantiation.add_relationship( @@ -705,4 +912,40 @@ bool template_builder::add_base_classes(class_ &tinst, return variadic_params; } +void template_builder::ensure_lambda_type_is_relative( + std::string ¶meter_type) const +{ +#ifdef _MSC_VER + auto root_name = fmt::format( + "{}\\", std::filesystem::current_path().root_name().string()); + if (root_name.back() == '\\') { + root_name.pop_back(); + root_name.push_back('/'); + } +#else + auto root_name = std::string{"/"}; +#endif + + std::string lambda_prefix{fmt::format("(lambda at {}", root_name)}; + + while (parameter_type.find(lambda_prefix) != std::string::npos) { + auto lambda_begin = parameter_type.find(lambda_prefix); + + auto absolute_lambda_path_end = + parameter_type.find(':', lambda_begin + lambda_prefix.size()); + auto absolute_lambda_path = + parameter_type.substr(lambda_begin + lambda_prefix.size() - 1, + absolute_lambda_path_end - + (lambda_begin + lambda_prefix.size() - 1)); + + auto relative_lambda_path = util::path_to_url(std::filesystem::relative( + absolute_lambda_path, config().relative_to()) + .string()); + + parameter_type = fmt::format("{}(lambda at {}{}", + parameter_type.substr(0, lambda_begin), relative_lambda_path, + parameter_type.substr(absolute_lambda_path_end)); + } +} + } // namespace clanguml::class_diagram::visitor diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 3de8a297..2013e97b 100644 --- a/src/class_diagram/visitor/template_builder.h +++ b/src/class_diagram/visitor/template_builder.h @@ -30,6 +30,10 @@ using common::model::namespace_; using common::model::relationship_t; using common::model::template_parameter; +using found_relationships_t = + std::vector>; + class template_builder { public: template_builder(class_diagram::model::diagram &d, @@ -47,14 +51,13 @@ public: template_parameter &ct, const std::string &full_name) const; std::unique_ptr build( - const clang::Decl *cls, + const clang::NamedDecl *cls, const clang::TemplateSpecializationType &template_type_decl, std::optional parent = {}); std::unique_ptr build_from_class_template_specialization( const clang::ClassTemplateSpecializationDecl &template_specialization, - const clang::RecordType &record_type, std::optional parent = {}); bool add_base_classes(clanguml::class_diagram::model::class_ &tinst, @@ -64,23 +67,20 @@ public: void process_template_arguments( std::optional &parent, - const clang::Decl *cls, + const clang::NamedDecl *cls, std::deque> &template_base_params, const clang::ArrayRef &template_args, model::class_ &template_instantiation, - const std::string &full_template_specialization_name, const clang::TemplateDecl *template_decl); void argument_process_dispatch( std::optional &parent, - const clang::Decl *cls, class_ &template_instantiation, - const std::string &full_template_specialization_name, + const clang::NamedDecl *cls, class_ &template_instantiation, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, + const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument); void process_tag_argument(model::class_ &template_instantiation, - const std::string &full_template_specialization_name, const clang::TemplateDecl *template_decl, const clang::TemplateArgument &arg, common::model::template_parameter &argument); @@ -99,23 +99,27 @@ public: std::vector process_pack_argument( std::optional &parent, - const clang::Decl *cls, class_ &template_instantiation, - const std::string &full_template_specialization_name, + const clang::NamedDecl *cls, class_ &template_instantiation, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, + const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument); template_parameter process_type_argument( std::optional &parent, - const clang::Decl *cls, - const std::string &full_template_specialization_name, - const clang::TemplateDecl *template_decl, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, const clang::TemplateArgument &arg, - model::class_ &template_instantiation); + model::class_ &template_instantiation, size_t argument_index); common::model::template_parameter process_template_argument( const clang::TemplateArgument &arg); + void process_unexposed_template_specialization_parameters( + const std::string &type_name, template_parameter &tp, class_ &c); + + bool find_relationships_in_unexposed_template_params( + const template_parameter &ct, + class_diagram::visitor::found_relationships_t &relationships); + std::optional get_template_argument_from_type_parameter_string( const clang::Decl *decl, const std::string &return_type_name) const; @@ -125,6 +129,8 @@ public: clang::SourceManager &source_manager() const; private: + void ensure_lambda_type_is_relative(std::string ¶meter_type) const; + // Reference to the output diagram model clanguml::class_diagram::model::diagram &diagram_; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 07863efb..83e2e1bc 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -1715,7 +1715,8 @@ std::unique_ptr translation_unit_visitor::process_template_specialization( clang::ClassTemplateSpecializationDecl *cls) { - auto c_ptr{std::make_unique(config_.using_namespace())}; + auto c_ptr = tbuilder().build_from_class_template_specialization(*cls); + auto &template_instantiation = *c_ptr; template_instantiation.is_template(true); @@ -1745,374 +1746,11 @@ translation_unit_visitor::process_template_specialization( if (template_instantiation.skip()) return {}; - 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); - process_template_specialization_argument( - cls, template_instantiation, arg, arg_it); - } - - template_instantiation.set_id( - common::to_id(template_instantiation.full_name(false))); - id_mapper().add(cls->getID(), template_instantiation.id()); 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) { - std::optional argument; - - // If this is a nested template type - add nested templates as - // template arguments - if (const auto *function_type = - arg.getAsType()->getAs(); - function_type != nullptr) { - - auto a = template_parameter::make_template_type({}); - - a.set_function_template(true); - - // Set function template return type - const auto return_type_name = - function_type->getReturnType().getAsString(); - - // Try to match the return type to template parameter in case - // the type name is in the form 'type-parameter-X-Y' - auto maybe_return_arg = - tbuilder().get_template_argument_from_type_parameter_string( - cls, return_type_name); - - if (maybe_return_arg) - a.add_template_param(*maybe_return_arg); - else { - a.add_template_param( - template_parameter::make_argument(return_type_name)); - } - - // Set function template argument types - for (const auto ¶m_type : function_type->param_types()) { - auto maybe_arg = - tbuilder().get_template_argument_from_type_parameter_string( - cls, param_type.getAsString()); - - if (maybe_arg) { - a.add_template_param(*maybe_arg); - continue; - } - - if (param_type->isBuiltinType()) { - a.add_template_param(template_parameter::make_argument( - param_type.getAsString())); - continue; - } - - const auto *param_record_type = - param_type->getAs(); - if (param_record_type == nullptr) - continue; - } - argument = a; - } - else if (const auto *nested_template_type = - arg.getAsType() - ->getAs(); - nested_template_type != nullptr) { - argument = template_parameter::make_argument({}); - - const auto nested_template_name = - nested_template_type->getTemplateName() - .getAsTemplateDecl() - ->getQualifiedNameAsString(); - - argument->set_type(nested_template_name); - - auto nested_template_instantiation = tbuilder().build( - cls, *nested_template_type, {&template_instantiation}); - - argument->set_id(nested_template_instantiation->id()); - - for (const auto &t : - nested_template_instantiation->template_params()) - argument->add_template_param(t); - } - else if (arg.getAsType()->getAs() != - nullptr) { - argument = template_parameter::make_template_type({}); - - auto parameter_name = - common::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 (parameter_name.find("type-parameter-") == 0) { - auto declaration_text = common::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 = common::parse_unexposed_template_params( - declaration_text, [](const auto &t) { return t; }); - - if (template_params.size() > argument_index) - parameter_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", - parameter_name, argument_index, declaration_text); - } - } - - argument->set_name(parameter_name); - } - else { - auto type_name = - common::to_string(arg.getAsType(), cls->getASTContext()); - ensure_lambda_type_is_relative(type_name); - if (type_name.find('<') != std::string::npos) { - argument = template_parameter::make_argument({}); - - // 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); - - auto unexposed_type_name = - type_name.substr(0, type_name.find('<')); - ensure_lambda_type_is_relative(unexposed_type_name); - - argument->set_type(unexposed_type_name); - } - else if (type_name.find("type-parameter-") == 0) { - argument = template_parameter::make_template_type({}); - - auto declaration_text = common::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 = common::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 = template_parameter::make_argument({}); - argument->set_type(type_name); - } - } - - if (!argument) - return; - - LOG_DBG("Adding template instantiation argument {}", - argument.value().to_string(config().using_namespace(), false)); - - tbuilder().simplify_system_template(*argument, - argument.value().to_string(config().using_namespace(), false)); - - template_instantiation.add_template(std::move(argument.value())); - } - else if (argument_kind == clang::TemplateArgument::Integral) { - auto argument = template_parameter::make_argument( - std::to_string(arg.getAsIntegral().getExtValue())); - template_instantiation.add_template(std::move(argument)); - } - else if (argument_kind == clang::TemplateArgument::Expression) { - auto argument = - template_parameter::make_argument(common::get_source_text( - arg.getAsExpr()->getSourceRange(), source_manager())); - template_instantiation.add_template(std::move(argument)); - } - else if (argument_kind == clang::TemplateArgument::TemplateExpansion) { - // TODO - } - 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(), - cls->getLocation().printToString(source_manager())); - } -} - -void translation_unit_visitor:: - process_unexposed_template_specialization_parameters( - const std::string &type_name, template_parameter &tp, class_ &c) -{ - auto template_params = common::parse_unexposed_template_params( - type_name, [](const std::string &t) { return t; }); - - found_relationships_t relationships; - for (auto ¶m : template_params) { - find_relationships_in_unexposed_template_params(param, relationships); - tp.add_template_param(param); - } - - for (auto &r : relationships) { - c.add_relationship({std::get<1>(r), std::get<0>(r)}); - } -} - -bool translation_unit_visitor::find_relationships_in_unexposed_template_params( - const template_parameter &ct, found_relationships_t &relationships) -{ - const auto &type = ct.type(); - - if (!type) - return false; - - bool found{false}; - LOG_DBG("Finding relationships in user defined type: {}", - ct.to_string(config().using_namespace(), false)); - - auto type_with_namespace = - std::make_optional(type.value()); - - if (!type_with_namespace.has_value()) { - // Couldn't find declaration of this type - type_with_namespace = common::model::namespace_{type.value()}; - } - - auto element_opt = diagram().get(type_with_namespace.value().to_string()); - if (element_opt) { - relationships.emplace_back( - element_opt.value().id(), relationship_t::kDependency); - found = true; - } - - for (const auto &nested_template_params : ct.template_params()) { - found = find_relationships_in_unexposed_template_params( - nested_template_params, relationships) || - found; - } - - return found; -} - -std::unique_ptr -template_builder::build_from_class_template_specialization( - const clang::ClassTemplateSpecializationDecl &template_specialization, - const clang::RecordType &record_type, - std::optional parent) -{ - auto template_instantiation_ptr = - std::make_unique(config_.using_namespace()); - - // - // Here we'll hold the template base params to replace with the - // instantiated values - // - std::deque> - template_base_params{}; - - auto &template_instantiation = *template_instantiation_ptr; - std::string full_template_specialization_name = - common::to_string(record_type, template_specialization.getASTContext()); - - const auto *template_decl = - template_specialization.getSpecializedTemplate(); - - auto qualified_name = template_decl->getQualifiedNameAsString(); - - namespace_ ns{qualified_name}; - ns.pop_back(); - template_instantiation.set_name(template_decl->getNameAsString()); - template_instantiation.set_namespace(ns); - template_instantiation.set_id(template_decl->getID() + - static_cast( - std::hash{}(full_template_specialization_name) >> 4U)); - - process_template_arguments(parent, &template_specialization, - template_base_params, - template_specialization.getTemplateArgs().asArray(), - template_instantiation, full_template_specialization_name, - template_decl); - - // First try to find the best match for this template in partially - // specialized templates - std::string destination{}; - std::string best_match_full_name{}; - auto full_template_name = template_instantiation.full_name(false); - int best_match{}; - common::model::diagram_element::id_t best_match_id{0}; - - for (const auto templ : diagram().classes()) { - if (templ.get() == template_instantiation) - continue; - - auto c_full_name = templ.get().full_name(false); - auto match = - template_instantiation.calculate_template_specialization_match( - templ.get()); - - if (match > best_match) { - best_match = match; - best_match_full_name = c_full_name; - best_match_id = templ.get().id(); - } - } - - auto templated_decl_id = template_specialization.getID(); - auto templated_decl_local_id = - id_mapper().get_global_id(templated_decl_id).value_or(0); - - if (best_match_id > 0) { - destination = best_match_full_name; - 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 - else if (diagram().has_element(templated_decl_local_id)) { - template_instantiation.add_relationship( - {relationship_t::kInstantiation, templated_decl_local_id}); - } - else if (diagram().should_include(qualified_name)) { - LOG_DBG("Skipping instantiation relationship from {} to {}", - template_instantiation_ptr->full_name(false), templated_decl_id); - } - - return template_instantiation_ptr; -} - void translation_unit_visitor::process_field( const clang::FieldDecl &field_declaration, class_ &c) { @@ -2333,25 +1971,6 @@ void translation_unit_visitor::finalize() resolve_local_to_global_ids(); } -// -// void translation_unit_visitor::id_mapper().add( -// int64_t local_id, common::model::diagram_element::id_t global_id) -//{ -// LOG_DBG("== Setting local element mapping {} --> {}", local_id, -// global_id); -// -// local_ast_id_map_[local_id] = global_id; -//} -// -// std::optional -// translation_unit_visitor::id_mapper().get_global_id(int64_t local_id) const -//{ -// if (local_ast_id_map_.find(local_id) == local_ast_id_map_.end()) -// return {}; -// -// return local_ast_id_map_.at(local_id); -//} - void translation_unit_visitor::extract_constrained_template_param_name( const clang::ConceptSpecializationExpr *concept_specialization, const clang::ConceptDecl *cpt, diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index a4f78190..2af05e0a 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -20,6 +20,7 @@ #include "class_diagram/model/class.h" #include "class_diagram/model/concept.h" #include "class_diagram/model/diagram.h" +#include "class_diagram/visitor/template_builder.h" #include "common/model/enums.h" #include "common/model/template_trait.h" #include "common/visitor/ast_id_mapper.h" @@ -52,10 +53,6 @@ using clanguml::common::model::relationship_t; using clanguml::common::model::template_parameter; using clanguml::common::model::template_trait; -using found_relationships_t = - std::vector>; - /** * @brief Class diagram translation unit visitor * @@ -152,12 +149,6 @@ private: clanguml::common::model::template_trait &t, common::optional_ref templated_element = {}); - 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_method(const clang::CXXMethodDecl &mf, clanguml::class_diagram::model::class_ &c); @@ -203,22 +194,10 @@ private: void find_relationships_in_constraint_expression( clanguml::common::model::element &c, const clang::Expr *expr); - void process_unexposed_template_specialization_parameters( - const std::string &tspec, - clanguml::common::model::template_parameter &tp, - clanguml::class_diagram::model::class_ &c); - - bool find_relationships_in_unexposed_template_params( - const clanguml::common::model::template_parameter &ct, - found_relationships_t &relationships); - void add_incomplete_forward_declarations(); void resolve_local_to_global_ids(); - bool simplify_system_template(common::model::template_parameter &ct, - const std::string &full_name) const; - void process_constraint_requirements(const clang::ConceptDecl *cpt, const clang::Expr *expr, model::concept_ &concept_model) const; diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 4ff16e8e..e4bc4025 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -169,13 +169,14 @@ std::string to_string(const clang::RecordType &type, return to_string(type.desugar(), ctx, try_canonical); } -std::string to_string(const clang::TemplateArgument &arg) +std::string to_string( + const clang::TemplateArgument &arg, const clang::ASTContext *ctx) { switch (arg.getKind()) { case clang::TemplateArgument::Expression: return to_string(arg.getAsExpr()); case clang::TemplateArgument::Type: - return to_string(arg.getAsType()); + return to_string(arg.getAsType(), *ctx, false); case clang::TemplateArgument::Null: return ""; case clang::TemplateArgument::NullPtr: @@ -428,7 +429,7 @@ std::vector parse_unexposed_template_params( } if (complete_class_template_argument) { auto t = template_parameter::make_unexposed_argument( - ns_resolve(clanguml::util::trim(type))); + ns_resolve(clanguml::util::trim_typename(type))); type = ""; for (auto &¶m : nested_params) t.add_template_param(std::move(param)); @@ -441,7 +442,7 @@ std::vector parse_unexposed_template_params( if (!type.empty()) { auto t = template_parameter::make_unexposed_argument( - ns_resolve(clanguml::util::trim(type))); + ns_resolve(clanguml::util::trim_typename(type))); type = ""; for (auto &¶m : nested_params) t.add_template_param(std::move(param)); diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 38ac996f..2123388d 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -81,7 +81,8 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx, std::string to_string(const clang::RecordType &type, const clang::ASTContext &ctx, bool try_canonical = true); -std::string to_string(const clang::TemplateArgument &arg); +std::string to_string( + const clang::TemplateArgument &arg, const clang::ASTContext *ctx = nullptr); std::string to_string(const clang::Expr *expr); diff --git a/src/util/util.cc b/src/util/util.cc index 76de73ea..f307e6c3 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -136,6 +136,15 @@ std::string rtrim(const std::string &s) return (end == std::string::npos) ? "" : s.substr(0, end + 1); } +std::string trim_typename(const std::string &s) +{ + auto res = trim(s); + if (res.find("typename ") == 0) + return res.substr(strlen("typename ")); + + return res; +} + std::string trim(const std::string &s) { return rtrim(ltrim(s)); } std::vector split( diff --git a/src/util/util.h b/src/util/util.h index bfdbe29c..e992ef95 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -57,6 +57,7 @@ namespace clanguml::util { std::string ltrim(const std::string &s); std::string rtrim(const std::string &s); std::string trim(const std::string &s); +std::string trim_typename(const std::string &s); #define FILENAME_ \ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) diff --git a/tests/t00044/t00044.cc b/tests/t00044/t00044.cc index 24f391a5..33fb26d4 100644 --- a/tests/t00044/t00044.cc +++ b/tests/t00044/t00044.cc @@ -1,5 +1,4 @@ // Inspired by skypjack/entt signal handlers -// This test case checks that at least clang-uml does not crash on this code namespace clanguml::t00044 { template class sink; diff --git a/uml/template_builder_sequence_diagram.yml b/uml/template_builder_sequence_diagram.yml new file mode 100644 index 00000000..ea1d1cd3 --- /dev/null +++ b/uml/template_builder_sequence_diagram.yml @@ -0,0 +1,21 @@ +type: sequence +combine_free_functions_into_file_participants: true +generate_method_arguments: none +glob: + - src/class_diagram/visitor/template_builder.cc +include: + namespaces: + - clanguml + paths: + - src/class_diagram/visitor/template_builder.h + - src/class_diagram/visitor/template_builder.cc +exclude: + paths: + - src/common/model/source_location.h +using_namespace: + - clanguml +plantuml: + before: + - 'title clang-uml class_diagram::visitor::template_builder::build sequence diagram' +start_from: + - function: "clanguml::class_diagram::visitor::template_builder::build(const clang::NamedDecl *,const clang::TemplateSpecializationType &,std::optional)"