From 0d524b38a4bd627c813d893e50aad249a48120bc Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Fri, 14 Apr 2023 21:05:48 +0200 Subject: [PATCH 01/22] Fixed handling of template template default arguments --- .../visitor/translation_unit_visitor.cc | 6 +++--- src/common/clang_utils.cc | 18 ++++++++++++++++++ src/common/clang_utils.h | 2 ++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 836ea600..f63a1347 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -1006,11 +1006,11 @@ bool translation_unit_visitor::process_template_parameters( clang::dyn_cast_or_null( parameter); std::optional default_arg; - if (template_template_parameter->hasDefaultArgument()) + if (template_template_parameter->hasDefaultArgument()) { default_arg = common::to_string( template_template_parameter->getDefaultArgument() - .getArgument() - .getAsExpr()); + .getArgument()); + } auto ct = template_parameter::make_template_template_type( template_template_parameter->getNameAsString(), default_arg, template_template_parameter->isParameterPack()); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 6d203502..4ff16e8e 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -169,6 +169,24 @@ std::string to_string(const clang::RecordType &type, return to_string(type.desugar(), ctx, try_canonical); } +std::string to_string(const clang::TemplateArgument &arg) +{ + switch (arg.getKind()) { + case clang::TemplateArgument::Expression: + return to_string(arg.getAsExpr()); + case clang::TemplateArgument::Type: + return to_string(arg.getAsType()); + case clang::TemplateArgument::Null: + return ""; + case clang::TemplateArgument::NullPtr: + return "nullptr"; + case clang::TemplateArgument::Integral: + return std::to_string(arg.getAsIntegral().getExtValue()); + default: + return ""; + } +} + std::string to_string(const clang::Expr *expr) { const clang::LangOptions lang_options; diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 18b6c548..38ac996f 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -81,6 +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::Expr *expr); std::string to_string(const clang::Stmt *stmt); From f034b554de3d738c9945be14ba5d4179b61f557c Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 15 Apr 2023 11:48:21 +0200 Subject: [PATCH 02/22] Added case handling of template argument kinds --- .../visitor/translation_unit_visitor.cc | 491 ++++++++++-------- .../visitor/translation_unit_visitor.h | 9 + 2 files changed, 285 insertions(+), 215 deletions(-) diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index f63a1347..97223ced 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -2370,28 +2370,49 @@ void translation_unit_visitor:: { auto arg_index = 0; for (const auto &arg : template_args) { - const auto argument_kind = arg.getKind(); - std::optional argument; - if (argument_kind == clang::TemplateArgument::Template) { - argument = - build_template_instantiation_process_template_argument(arg); + // Argument can be a parameter pack in which case it gives multiple + // arguments + std::vector argument; + + switch (arg.getKind()) { + case clang::TemplateArgument::Null: + argument.push_back( + build_template_instantiation_process_null_argument(arg)); + break; + case clang::TemplateArgument::Type: + argument.push_back( + build_template_instantiation_process_type_argument(parent, cls, + full_template_specialization_name, template_decl, arg, + template_instantiation)); + break; + case clang::TemplateArgument::Declaration: + break; + case clang::TemplateArgument::NullPtr: + argument.push_back( + build_template_instantiation_process_nullptr_argument(arg)); + break; + case clang::TemplateArgument::Integral: + argument.push_back( + build_template_instantiation_process_integral_argument(arg)); + break; + case clang::TemplateArgument::Template: + argument.push_back( + build_template_instantiation_process_template_argument(arg)); + break; + case clang::TemplateArgument::TemplateExpansion: + break; + case clang::TemplateArgument::Expression: + argument.push_back( + build_template_instantiation_process_expression_argument(arg)); + break; + case clang::TemplateArgument::Pack: + build_template_instantiation_process_pack_argument(arg, argument)); + break; } - else if (argument_kind == clang::TemplateArgument::Type) { - argument = build_template_instantiation_process_type_argument( - parent, cls, full_template_specialization_name, template_decl, - arg, template_instantiation); - } - else if (argument_kind == clang::TemplateArgument::Integral) { - argument = - build_template_instantiation_process_integral_argument(arg); - } - else if (argument_kind == clang::TemplateArgument::Expression) { - argument = - build_template_instantiation_process_expression_argument(arg); - } - else { - LOG_ERROR("Unsupported argument type {}", arg.getKind()); - continue; + + if (argument.empty()) { + arg_index++; + continue; } // We can figure this only when we encounter variadic param in @@ -2403,20 +2424,22 @@ void translation_unit_visitor:: // In case any of the template arguments are base classes, add // them as parents of the current template instantiation class if (!template_base_params.empty()) { - variadic_params = build_template_instantiation_add_base_classes( - template_instantiation, template_base_params, arg_index, - variadic_params, argument.value()); + variadic_params = build_template_instantiation_add_base_classes( + template_instantiation, template_base_params, arg_index, + variadic_params, argument.front()); } - LOG_DBG("Adding template argument {} to template " - "specialization/instantiation {}", - argument.value().to_string(config().using_namespace(), false), - template_instantiation.name()); + for (auto &arg : argument) { + simplify_system_template( + arg, arg.to_string(config().using_namespace(), false)); - simplify_system_template(argument.value(), - argument.value().to_string(config().using_namespace(), false)); + LOG_DBG("Adding template argument {} to template " + "specialization/instantiation {}", + arg.to_string(config().using_namespace(), false), + template_instantiation.name()); - template_instantiation.add_template(std::move(argument.value())); + template_instantiation.add_template(std::move(arg)); + } arg_index++; } @@ -2460,65 +2483,67 @@ translation_unit_visitor::build_template_instantiation_process_type_argument( cls, return_type_name); if (maybe_return_arg) - argument.add_template_param(*maybe_return_arg); + argument.add_template_param(*maybe_return_arg); else { - argument.add_template_param( - template_parameter::make_argument(return_type_name)); + argument.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 = get_template_argument_from_type_parameter_string( - cls, param_type.getAsString()); + auto maybe_arg = + get_template_argument_from_type_parameter_string( + cls, param_type.getAsString()); - if (maybe_arg) { - argument.add_template_param(*maybe_arg); - continue; - } - - if (param_type->isBuiltinType()) { - argument.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; - - auto *classTemplateSpecialization = - llvm::dyn_cast( - param_type->getAsRecordDecl()); - - if (classTemplateSpecialization != nullptr) { - // Read arg info as needed. - auto nested_template_instantiation = - build_template_instantiation_from_class_template_specialization( - *classTemplateSpecialization, *param_record_type, - diagram().should_include( - full_template_specialization_name) - ? std::make_optional(&template_instantiation) - : parent); - - const auto nested_template_name = - classTemplateSpecialization->getQualifiedNameAsString(); - - if (nested_template_instantiation) { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); + if (maybe_arg) { + argument.add_template_param(*maybe_arg); + continue; } - 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)); + if (param_type->isBuiltinType()) { + argument.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; + + auto *classTemplateSpecialization = + llvm::dyn_cast( + param_type->getAsRecordDecl()); + + if (classTemplateSpecialization != nullptr) { + // Read arg info as needed. + auto nested_template_instantiation = + build_template_instantiation_from_class_template_specialization( + *classTemplateSpecialization, *param_record_type, + diagram().should_include( + full_template_specialization_name) + ? std::make_optional(&template_instantiation) + : parent); + + const auto nested_template_name = + classTemplateSpecialization->getQualifiedNameAsString(); + + if (nested_template_instantiation) { + if (parent.has_value()) + parent.value()->add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } + + 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 (const auto maybe_arg = @@ -2548,7 +2573,7 @@ translation_unit_visitor::build_template_instantiation_process_type_argument( argument.set_id(nested_template_instantiation->id()); for (const auto &t : nested_template_instantiation->template_params()) - argument.add_template_param(t); + argument.add_template_param(t); // Check if this template should be simplified (e.g. system // template aliases such as 'std:basic_string' should @@ -2559,23 +2584,24 @@ translation_unit_visitor::build_template_instantiation_process_type_argument( if (nested_template_instantiation && diagram().should_include( nested_template_instantiation->full_name(false))) { - if (diagram().should_include(full_template_specialization_name)) { - template_instantiation.add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - else { - if (parent.has_value()) - parent.value()->add_relationship( + if (diagram().should_include( + full_template_specialization_name)) { + template_instantiation.add_relationship( {relationship_t::kDependency, nested_template_instantiation->id()}); - } + } + else { + if (parent.has_value()) + parent.value()->add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } } 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)); + diagram().add_class(std::move(nested_template_instantiation)); } } else if (arg.getAsType()->getAs() != nullptr) { @@ -2609,19 +2635,19 @@ translation_unit_visitor::get_template_argument_from_type_parameter_string( for (auto i = 0U; i < template_decl->getDescribedTemplateParams()->size(); i++) { - const auto *param = - template_decl->getDescribedTemplateParams()->getParam(i); + const auto *param = + template_decl->getDescribedTemplateParams()->getParam(i); - if (i == index) { - param_name = param->getNameAsString(); + if (i == index) { + param_name = param->getNameAsString(); - auto template_param = - template_parameter::make_template_type(param_name); + auto template_param = + template_parameter::make_template_type(param_name); - template_param.is_variadic(param->isParameterPack()); + template_param.is_variadic(param->isParameterPack()); - return template_param; - } + return template_param; + } } } @@ -2638,6 +2664,24 @@ template_parameter translation_unit_visitor:: std::to_string(arg.getAsIntegral().getExtValue())); } +template_parameter +translation_unit_visitor::build_template_instantiation_process_null_argument( + const clang::TemplateArgument &arg) const +{ + assert(arg.getKind() == clang::TemplateArgument::Null); + + return template_parameter::make_argument(""); +} + +template_parameter +translation_unit_visitor::build_template_instantiation_process_nullptr_argument( + const clang::TemplateArgument &arg) const +{ + assert(arg.getKind() == clang::TemplateArgument::NullPtr); + + return template_parameter::make_argument("nullptr"); +} + template_parameter translation_unit_visitor:: build_template_instantiation_process_expression_argument( const clang::TemplateArgument &arg) const @@ -2647,6 +2691,16 @@ template_parameter translation_unit_visitor:: arg.getAsExpr()->getSourceRange(), source_manager())); } +template_parameter +translation_unit_visitor::build_template_instantiation_process_pack_argument( + const clang::TemplateArgument &arg) const +{ + assert(arg.getKind() == clang::TemplateArgument::Pack); + + arg.getPackAsArray().front().return template_parameter::make_argument( + std::to_string(arg.get)); +} + void translation_unit_visitor:: build_template_instantiation_process_tag_argument( class_ &template_instantiation, @@ -2667,13 +2721,14 @@ void translation_unit_visitor:: if (const auto *record_type_decl = tsp->getAsRecordDecl(); record_type_decl != nullptr) { - argument.set_id(common::to_id(arg)); - if (diagram().should_include(full_template_specialization_name)) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); - } + argument.set_id(common::to_id(arg)); + if (diagram().should_include( + full_template_specialization_name)) { + // Add dependency relationship to the parent + // template + template_instantiation.add_relationship( + {relationship_t::kDependency, common::to_id(arg)}); + } } } else if (const auto *record_type = @@ -2682,23 +2737,24 @@ void translation_unit_visitor:: if (const auto *record_type_decl = record_type->getAsRecordDecl(); record_type_decl != nullptr) { - argument.set_id(common::to_id(arg)); + argument.set_id(common::to_id(arg)); #if LLVM_VERSION_MAJOR >= 16 - argument.set_type(record_type_decl->getQualifiedNameAsString()); + argument.set_type(record_type_decl->getQualifiedNameAsString()); #endif - if (diagram().should_include(full_template_specialization_name)) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); - } + if (diagram().should_include( + full_template_specialization_name)) { + // Add dependency relationship to the parent + // template + template_instantiation.add_relationship( + {relationship_t::kDependency, common::to_id(arg)}); + } } } else if (const auto *enum_type = arg.getAsType()->getAs(); enum_type != nullptr) { if (enum_type->getAsTagDecl() != nullptr) { - template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); + template_instantiation.add_relationship( + {relationship_t::kDependency, common::to_id(arg)}); } } } @@ -2716,11 +2772,11 @@ bool translation_unit_visitor::build_template_instantiation_add_base_classes( else { variadic_params = is_variadic; if ((arg_index == index) || (is_variadic && arg_index >= index)) { - add_template_argument_as_base_class = true; - if (!is_variadic) { - // Don't remove the remaining variadic parameter - template_base_params.pop_front(); - } + add_template_argument_as_base_class = true; + if (!is_variadic) { + // Don't remove the remaining variadic parameter + template_base_params.pop_front(); + } } } @@ -2802,22 +2858,22 @@ void translation_unit_visitor::process_field( // TODO: Refactor to an unalias_type() method if (template_field_type != nullptr) if (template_field_type->isTypeAlias()) - template_field_type = - template_field_type->getAliasedType() - ->getAs(); + template_field_type = + template_field_type->getAliasedType() + ->getAs(); bool field_type_is_template_template_parameter{false}; if (template_field_type != nullptr) { // Skip types which are template template parameters of the parent // template for (const auto &class_template_param : c.template_params()) { - if (class_template_param.name() == - template_field_type->getTemplateName() - .getAsTemplateDecl() - ->getNameAsString() + - "<>") { - field_type_is_template_template_parameter = true; - } + if (class_template_param.name() == + template_field_type->getTemplateName() + .getAsTemplateDecl() + ->getNameAsString() + + "<>") { + field_type_is_template_template_parameter = true; + } } } @@ -2830,62 +2886,65 @@ void translation_unit_visitor::process_field( &field_declaration, *template_field_type, {&c}); if (!field.skip_relationship() && template_specialization_ptr) { - const auto &template_specialization = *template_specialization_ptr; + const auto &template_specialization = + *template_specialization_ptr; - // 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 - bool add_template_instantiation_to_diargam{false}; - if (diagram().should_include( - template_specialization.full_name(false))) { + // 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 + bool add_template_instantiation_to_diargam{false}; + if (diagram().should_include( + template_specialization.full_name(false))) { - found_relationships_t::value_type r{ - template_specialization.id(), relationship_hint}; + found_relationships_t::value_type r{ + template_specialization.id(), relationship_hint}; - add_template_instantiation_to_diargam = true; - - // If the template instantiation for the build type has been - // added as aggregation, skip its nested templates - template_instantiation_added_as_aggregation = - relationship_hint == relationship_t::kAggregation; - relationships.emplace_back(std::move(r)); - } - - // Try to find relationships to types nested in the template - // instantiation - found_relationships_t nested_relationships; - if (!template_instantiation_added_as_aggregation) { - for (const auto &template_argument : - template_specialization.template_params()) { - - LOG_DBG("Looking for nested relationships from {}::{} in " - "template {}", - c.full_name(false), field_name, - template_argument.to_string( - config().using_namespace(), false)); + add_template_instantiation_to_diargam = true; + // If the template instantiation for the build type has been + // added as aggregation, skip its nested templates template_instantiation_added_as_aggregation = - template_argument.find_nested_relationships( - nested_relationships, relationship_hint, - [&d = diagram()](const std::string &full_name) { - if (full_name.empty()) - return false; - auto [ns, name] = common::split_ns(full_name); - return d.should_include(ns, name); - }); + relationship_hint == relationship_t::kAggregation; + relationships.emplace_back(std::move(r)); } - // 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); - } + // Try to find relationships to types nested in the template + // instantiation + found_relationships_t nested_relationships; + if (!template_instantiation_added_as_aggregation) { + for (const auto &template_argument : + template_specialization.template_params()) { - // Add the template instantiation object to the diagram if it - // matches the include pattern - if (add_template_instantiation_to_diargam) - diagram().add_class(std::move(template_specialization_ptr)); + LOG_DBG( + "Looking for nested relationships from {}::{} in " + "template {}", + c.full_name(false), field_name, + template_argument.to_string( + config().using_namespace(), false)); + + template_instantiation_added_as_aggregation = + template_argument.find_nested_relationships( + nested_relationships, relationship_hint, + [&d = diagram()](const std::string &full_name) { + if (full_name.empty()) + return false; + auto [ns, name] = + common::split_ns(full_name); + return d.should_include(ns, name); + }); + } + + // 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); + } + + // Add the template instantiation object to the diagram if it + // matches the include pattern + if (add_template_instantiation_to_diargam) + diagram().add_class(std::move(template_specialization_ptr)); } } @@ -2893,18 +2952,18 @@ void translation_unit_visitor::process_field( // Find relationship for the type if the type has not been added // as aggregation if (!template_instantiation_added_as_aggregation) { - if ((field_type->getAsRecordDecl() != nullptr) && - field_type->getAsRecordDecl()->getNameAsString().empty()) { - // Relationships to fields whose type is an anonymous nested - // struct have to be handled separately here - anonymous_struct_relationships_[field_type->getAsRecordDecl() - ->getID()] = - std::make_tuple( - field.name(), relationship_hint, field.access()); - } - else - find_relationships( - field_type, relationships, relationship_hint); + if ((field_type->getAsRecordDecl() != nullptr) && + field_type->getAsRecordDecl()->getNameAsString().empty()) { + // Relationships to fields whose type is an anonymous nested + // struct have to be handled separately here + anonymous_struct_relationships_ + [field_type->getAsRecordDecl()->getID()] = + std::make_tuple(field.name(), relationship_hint, + field.access()); + } + else + find_relationships( + field_type, relationships, relationship_hint); } add_relationships(c, field, relationships); @@ -2917,7 +2976,7 @@ void translation_unit_visitor::add_incomplete_forward_declarations() { for (auto &[id, c] : forward_declarations_) { if (diagram().should_include(c->full_name(false))) { - diagram().add_class(std::move(c)); + diagram().add_class(std::move(c)); } } forward_declarations_.clear(); @@ -2929,7 +2988,20 @@ void translation_unit_visitor::resolve_local_to_global_ids() // to elements for (const auto &cls : diagram().classes()) { for (auto &rel : cls.get().relationships()) { - if (rel.type() == relationship_t::kInstantiation) { + if (rel.type() == relationship_t::kInstantiation) { + const auto maybe_id = get_ast_local_id(rel.destination()); + if (maybe_id) { + LOG_DBG( + "= Resolved instantiation destination from local " + "id {} to global id {}", + rel.destination(), *maybe_id); + rel.set_destination(*maybe_id); + } + } + } + } + for (const auto &cpt : diagram().concepts()) { + for (auto &rel : cpt.get().relationships()) { const auto maybe_id = get_ast_local_id(rel.destination()); if (maybe_id) { LOG_DBG("= Resolved instantiation destination from local " @@ -2937,18 +3009,6 @@ void translation_unit_visitor::resolve_local_to_global_ids() rel.destination(), *maybe_id); rel.set_destination(*maybe_id); } - } - } - } - for (const auto &cpt : diagram().concepts()) { - for (auto &rel : cpt.get().relationships()) { - const auto maybe_id = get_ast_local_id(rel.destination()); - if (maybe_id) { - LOG_DBG("= Resolved instantiation destination from local " - "id {} to global id {}", - rel.destination(), *maybe_id); - rel.set_destination(*maybe_id); - } } } } @@ -3002,16 +3062,17 @@ void translation_unit_visitor::extract_constrained_template_param_name( if (!full_declaration_text.empty()) { // Handle typename constraint in requires clause if (type_name.find("type-parameter-") == 0) { - const auto concept_declaration_text = full_declaration_text.substr( - full_declaration_text.find(cpt->getNameAsString()) + - cpt->getNameAsString().size() + 1); + const auto concept_declaration_text = + full_declaration_text.substr( + full_declaration_text.find(cpt->getNameAsString()) + + cpt->getNameAsString().size() + 1); - auto template_params = common::parse_unexposed_template_params( - concept_declaration_text, [](const auto &t) { return t; }); + auto template_params = common::parse_unexposed_template_params( + concept_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); + if (template_params.size() > argument_index) + type_name = template_params[argument_index].to_string( + config().using_namespace(), false); } constrained_template_params.push_back(type_name); } diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index b64bad17..f18a8f2e 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -225,6 +225,15 @@ private: template_parameter build_template_instantiation_process_integral_argument( const clang::TemplateArgument &arg) const; + template_parameter build_template_instantiation_process_nullptr_argument( + const clang::TemplateArgument &arg) const; + + template_parameter build_template_instantiation_process_null_argument( + const clang::TemplateArgument &arg) const; + + template_parameter build_template_instantiation_process_pack_argument( + const clang::TemplateArgument &arg) const; + template_parameter build_template_instantiation_process_type_argument( std::optional &parent, const clang::Decl *cls, From 758c1418a69dc77a9192ec6a09d2d1775b1cd7a4 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 15 Apr 2023 22:11:45 +0200 Subject: [PATCH 03/22] Refactored template instantiation builder methods to a separate class --- src/class_diagram/visitor/template_builder.cc | 708 +++++++++++++ src/class_diagram/visitor/template_builder.h | 139 +++ .../visitor/translation_unit_visitor.cc | 987 +++--------------- .../visitor/translation_unit_visitor.h | 89 +- src/common/visitor/ast_id_mapper.cc | 36 + src/common/visitor/ast_id_mapper.h | 43 + 6 files changed, 1084 insertions(+), 918 deletions(-) create mode 100644 src/class_diagram/visitor/template_builder.cc create mode 100644 src/class_diagram/visitor/template_builder.h create mode 100644 src/common/visitor/ast_id_mapper.cc create mode 100644 src/common/visitor/ast_id_mapper.h diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc new file mode 100644 index 00000000..7414dfbf --- /dev/null +++ b/src/class_diagram/visitor/template_builder.cc @@ -0,0 +1,708 @@ +/** + * src/class_diagram/visitor/template_builder.cc + * + * Copyright (c) 2021-2023 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. + */ + +#include "template_builder.h" + +namespace clanguml::class_diagram::visitor { + +template_builder::template_builder(class_diagram::model::diagram &d, + const config::class_diagram &config, + common::visitor::ast_id_mapper &id_mapper, + clang::SourceManager &source_manager) + : diagram_{d} + , config_{config} + , id_mapper_{id_mapper} + , source_manager_{source_manager} +{ +} + +class_diagram::model::diagram &template_builder::diagram() { return diagram_; } + +const config::class_diagram &template_builder::config() const +{ + return config_; +} + +const namespace_ &template_builder::using_namespace() const +{ + return config_.using_namespace(); +} + +common::visitor::ast_id_mapper &template_builder::id_mapper() +{ + return id_mapper_; +} + +clang::SourceManager &template_builder::source_manager() const +{ + return source_manager_; +} + +bool template_builder::simplify_system_template( + template_parameter &ct, const std::string &full_name) const +{ + auto simplified = config().simplify_template_type(full_name); + + if (simplified != full_name) { + ct.set_type(simplified); + ct.clear_params(); + return true; + } + + return false; +} + +std::unique_ptr template_builder::build(const clang::Decl *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 + // instantiated values + // + std::deque> + template_base_params{}; + + const auto *template_type_ptr = &template_type_decl; + + if (template_type_decl.isTypeAlias()) { + if (const auto *tsp = + template_type_decl.getAliasedType() + ->template getAs(); + tsp != nullptr) + template_type_ptr = tsp; + } + + const auto &template_type = *template_type_ptr; + + // + // Create class_ instance to hold the template instantiation + // + auto template_instantiation_ptr = + std::make_unique(using_namespace()); + + auto &template_instantiation = *template_instantiation_ptr; + template_instantiation.is_template(true); + + std::string full_template_specialization_name = common::to_string( + template_type.desugar(), + template_type.getTemplateName().getAsTemplateDecl()->getASTContext()); + + auto *template_decl{template_type.getTemplateName().getAsTemplateDecl()}; + + auto template_decl_qualified_name = + template_decl->getQualifiedNameAsString(); + + auto *class_template_decl{ + clang::dyn_cast(template_decl)}; + + if ((class_template_decl != nullptr) && + (class_template_decl->getTemplatedDecl() != nullptr) && + (class_template_decl->getTemplatedDecl()->getParent() != nullptr) && + class_template_decl->getTemplatedDecl()->getParent()->isRecord()) { + + namespace_ ns{ + common::get_tag_namespace(*class_template_decl->getTemplatedDecl() + ->getParent() + ->getOuterLexicalRecordContext())}; + + std::string ns_str = ns.to_string(); + std::string name = template_decl->getQualifiedNameAsString(); + if (!ns_str.empty()) { + name = name.substr(ns_str.size() + 2); + } + + util::replace_all(name, "::", "##"); + template_instantiation.set_name(name); + + template_instantiation.set_namespace(ns); + } + else { + namespace_ ns{template_decl_qualified_name}; + ns.pop_back(); + template_instantiation.set_name(template_decl->getNameAsString()); + template_instantiation.set_namespace(ns); + } + + // TODO: Refactor handling of base parameters to a separate method + + // We need this to match any possible base classes coming from template + // arguments + std::vector< + std::pair> + template_parameter_names{}; + + for (const auto *parameter : *template_decl->getTemplateParameters()) { + if (parameter->isTemplateParameter() && + (parameter->isTemplateParameterPack() || + parameter->isParameterPack())) { + template_parameter_names.emplace_back( + parameter->getNameAsString(), true); + } + else + template_parameter_names.emplace_back( + parameter->getNameAsString(), false); + } + + // Check if the primary template has any base classes + int base_index = 0; + + const auto *templated_class_decl = + clang::dyn_cast_or_null( + template_decl->getTemplatedDecl()); + + if ((templated_class_decl != nullptr) && + templated_class_decl->hasDefinition()) + for (const auto &base : templated_class_decl->bases()) { + const auto base_class_name = common::to_string( + base.getType(), templated_class_decl->getASTContext(), false); + + LOG_DBG("Found template instantiation base: {}, {}", + base_class_name, base_index); + + // Check if any of the primary template arguments has a + // parameter equal to this type + auto it = std::find_if(template_parameter_names.begin(), + template_parameter_names.end(), + [&base_class_name]( + const auto &p) { return p.first == base_class_name; }); + + if (it != template_parameter_names.end()) { + const auto ¶meter_name = it->first; + const bool is_variadic = it->second; + // Found base class which is a template parameter + LOG_DBG("Found base class which is a template parameter " + "{}, {}, {}", + parameter_name, is_variadic, + std::distance(template_parameter_names.begin(), it)); + + template_base_params.emplace_back(parameter_name, + std::distance(template_parameter_names.begin(), it), + is_variadic); + } + else { + // This is a regular base class - it is handled by + // process_template + } + base_index++; + } + + process_template_arguments(parent, cls, template_base_params, + template_type.template_arguments(), 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_type.getTemplateName().getAsTemplateDecl()->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 { + LOG_DBG("== Cannot determine global id for specialization template {} " + "- delaying until the translation unit is complete ", + templated_decl_id); + + template_instantiation.add_relationship( + {relationship_t::kInstantiation, templated_decl_id}); + } + + template_instantiation.set_id( + common::to_id(template_instantiation_ptr->full_name(false))); + + return template_instantiation_ptr; +} + +void template_builder::process_template_arguments( + std::optional &parent, + const clang::Decl *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) +{ + auto arg_index = 0; + for (const auto &arg : template_args) { + // Argument can be a parameter pack in which case it gives multiple + // arguments + std::vector arguments; + + argument_process_dispatch(parent, cls, template_instantiation, + full_template_specialization_name, template_decl, arg, arguments); + + if (arguments.empty()) { + arg_index++; + continue; + } + + // We can figure this only when we encounter variadic param in + // the list of template params, from then this variable is true + // and we can process following template parameters as belonging + // to the variadic tuple + [[maybe_unused]] auto variadic_params{false}; + + // In case any of the template arguments are base classes, add + // them as parents of the current template instantiation class + if (!template_base_params.empty()) { + variadic_params = + add_base_classes(template_instantiation, template_base_params, + arg_index, variadic_params, arguments.front()); + } + + for (auto &argument : arguments) { + simplify_system_template( + argument, argument.to_string(using_namespace(), false)); + + LOG_DBG("Adding template argument {} to template " + "specialization/instantiation {}", + argument.to_string(using_namespace(), false), + template_instantiation.name()); + + template_instantiation.add_template(std::move(argument)); + } + + arg_index++; + } +} + +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::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + std::vector &argument) +{ + switch (arg.getKind()) { + case clang::TemplateArgument::Null: + 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)); + break; + case clang::TemplateArgument::Declaration: + break; + case clang::TemplateArgument::NullPtr: + argument.push_back(process_nullptr_argument(arg)); + break; + case clang::TemplateArgument::Integral: + argument.push_back(process_integral_argument(arg)); + break; + case clang::TemplateArgument::Template: + argument.push_back(process_template_argument(arg)); + break; + case clang::TemplateArgument::TemplateExpansion: + break; + case clang::TemplateArgument::Expression: + 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)) { + argument.push_back(a); + } + break; + } +} + +template_parameter template_builder::process_template_argument( + const clang::TemplateArgument &arg) +{ + auto arg_name = + arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); + return template_parameter::make_template_type(arg_name); +} + +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) +{ + assert(arg.getKind() == clang::TemplateArgument::Type); + + auto argument = template_parameter::make_argument({}); + + if (const auto *function_type = + arg.getAsType()->getAs(); + function_type != nullptr) { + + argument.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 = + get_template_argument_from_type_parameter_string( + cls, return_type_name); + + if (maybe_return_arg) + argument.add_template_param(*maybe_return_arg); + else { + argument.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 = get_template_argument_from_type_parameter_string( + cls, param_type.getAsString()); + + if (maybe_arg) { + argument.add_template_param(*maybe_arg); + continue; + } + + if (param_type->isBuiltinType()) { + argument.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; + + auto *classTemplateSpecialization = + llvm::dyn_cast( + param_type->getAsRecordDecl()); + + if (classTemplateSpecialization != nullptr) { + // Read arg info as needed. + auto nested_template_instantiation = + build_from_class_template_specialization( + *classTemplateSpecialization, *param_record_type, + diagram().should_include( + full_template_specialization_name) + ? std::make_optional(&template_instantiation) + : parent); + + const auto nested_template_name = + classTemplateSpecialization->getQualifiedNameAsString(); + + if (nested_template_instantiation) { + if (parent.has_value()) + parent.value()->add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } + + 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 (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) { + + const auto nested_type_name = nested_template_type->getTemplateName() + .getAsTemplateDecl() + ->getQualifiedNameAsString(); + + argument.set_type(nested_type_name); + + auto nested_template_instantiation = build(cls, *nested_template_type, + diagram().should_include(full_template_specialization_name) + ? std::make_optional(&template_instantiation) + : parent); + + argument.set_id(nested_template_instantiation->id()); + + for (const auto &t : nested_template_instantiation->template_params()) + 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(using_namespace(), false)); + + if (nested_template_instantiation && + diagram().should_include( + nested_template_instantiation->full_name(false))) { + if (diagram().should_include(full_template_specialization_name)) { + template_instantiation.add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } + else { + if (parent.has_value()) + parent.value()->add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } + } + + 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())); + } + else { + // This is just a regular record type + process_tag_argument(template_instantiation, + full_template_specialization_name, template_decl, arg, argument); + } + + return argument; +} + +std::optional +template_builder::get_template_argument_from_type_parameter_string( + const clang::Decl *decl, const std::string &return_type_name) const +{ + if (const auto *template_decl = + llvm::dyn_cast(decl); + template_decl != nullptr && + return_type_name.find("type-parameter-") == 0) { + + [[maybe_unused]] const auto [depth, index] = + common::extract_template_parameter_index(return_type_name); + + std::string param_name = return_type_name; + + for (auto i = 0U; + i < template_decl->getDescribedTemplateParams()->size(); i++) { + const auto *param = + template_decl->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; + } + } + } + + return {}; +} + +template_parameter template_builder::process_integral_argument( + const clang::TemplateArgument &arg) +{ + assert(arg.getKind() == clang::TemplateArgument::Integral); + + return template_parameter::make_argument( + std::to_string(arg.getAsIntegral().getExtValue())); +} + +template_parameter template_builder::process_null_argument( + const clang::TemplateArgument &arg) +{ + assert(arg.getKind() == clang::TemplateArgument::Null); + + return template_parameter::make_argument(""); +} + +template_parameter template_builder::process_nullptr_argument( + const clang::TemplateArgument &arg) +{ + assert(arg.getKind() == clang::TemplateArgument::NullPtr); + + return template_parameter::make_argument("nullptr"); +} + +template_parameter template_builder::process_expression_argument( + const clang::TemplateArgument &arg) +{ + assert(arg.getKind() == clang::TemplateArgument::Expression); + return template_parameter::make_argument(common::get_source_text( + arg.getAsExpr()->getSourceRange(), source_manager())); +} + +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::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + std::vector &argument) +{ + assert(arg.getKind() == clang::TemplateArgument::Pack); + + std::vector res; + + for (const auto &a : arg.getPackAsArray()) { + argument_process_dispatch(parent, cls, template_instantiation, + full_template_specialization_name, template_decl, a, 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) +{ + assert(arg.getKind() == clang::TemplateArgument::Type); + + argument.is_template_parameter(false); + + argument.set_type( + common::to_string(arg.getAsType(), template_decl->getASTContext())); + + if (const auto *tsp = + arg.getAsType()->getAs(); + tsp != nullptr) { + if (const auto *record_type_decl = tsp->getAsRecordDecl(); + record_type_decl != nullptr) { + + argument.set_id(common::to_id(arg)); + if (diagram().should_include(full_template_specialization_name)) { + // Add dependency relationship to the parent + // template + template_instantiation.add_relationship( + {relationship_t::kDependency, common::to_id(arg)}); + } + } + } + else if (const auto *record_type = + arg.getAsType()->getAs(); + record_type != nullptr) { + if (const auto *record_type_decl = record_type->getAsRecordDecl(); + record_type_decl != nullptr) { + + argument.set_id(common::to_id(arg)); +#if LLVM_VERSION_MAJOR >= 16 + argument.set_type(record_type_decl->getQualifiedNameAsString()); +#endif + if (diagram().should_include(full_template_specialization_name)) { + // Add dependency relationship to the parent + // template + template_instantiation.add_relationship( + {relationship_t::kDependency, common::to_id(arg)}); + } + } + } + else if (const auto *enum_type = arg.getAsType()->getAs(); + enum_type != nullptr) { + if (enum_type->getAsTagDecl() != nullptr) { + template_instantiation.add_relationship( + {relationship_t::kDependency, common::to_id(arg)}); + } + } +} + +bool template_builder::add_base_classes(class_ &tinst, + std::deque> &template_base_params, + int arg_index, bool variadic_params, const template_parameter &ct) +{ + bool add_template_argument_as_base_class = false; + + auto [arg_name, index, is_variadic] = template_base_params.front(); + if (variadic_params) + add_template_argument_as_base_class = true; + else { + variadic_params = is_variadic; + if ((arg_index == index) || (is_variadic && arg_index >= index)) { + add_template_argument_as_base_class = true; + if (!is_variadic) { + // Don't remove the remaining variadic parameter + template_base_params.pop_front(); + } + } + } + + const auto maybe_id = ct.id(); + if (add_template_argument_as_base_class && maybe_id) { + LOG_DBG("Adding template argument as base class '{}'", + ct.to_string({}, false)); + + model::class_parent cp; + cp.set_access(common::model::access_t::kPublic); + cp.set_name(ct.to_string({}, false)); + cp.set_id(maybe_id.value()); + + tinst.add_parent(std::move(cp)); + } + + return variadic_params; +} + +} // namespace clanguml::class_diagram::visitor diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h new file mode 100644 index 00000000..3de8a297 --- /dev/null +++ b/src/class_diagram/visitor/template_builder.h @@ -0,0 +1,139 @@ +/** + * src/class_diagram/visitor/template_builder.h + * + * Copyright (c) 2021-2023 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. + */ +#pragma once + +#include "class_diagram/model/class.h" +#include "class_diagram/model/concept.h" +#include "class_diagram/model/diagram.h" +#include "common/visitor/ast_id_mapper.h" +#include "config/config.h" + +namespace clanguml::class_diagram::visitor { + +using class_diagram::model::class_; +using common::model::namespace_; +using common::model::relationship_t; +using common::model::template_parameter; + +class template_builder { +public: + template_builder(class_diagram::model::diagram &d, + const config::class_diagram &config, + common::visitor::ast_id_mapper &id_mapper, + clang::SourceManager &source_manager); + + class_diagram::model::diagram &diagram(); + + const config::class_diagram &config() const; + + const namespace_ &using_namespace() const; + + bool simplify_system_template( + template_parameter &ct, const std::string &full_name) const; + + std::unique_ptr build( + const clang::Decl *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, + std::deque> &template_base_params, + int arg_index, bool variadic_params, + const clanguml::common::model::template_parameter &ct); + + void process_template_arguments( + std::optional &parent, + const clang::Decl *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::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + 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); + + template_parameter process_expression_argument( + const clang::TemplateArgument &arg); + + template_parameter process_integral_argument( + const clang::TemplateArgument &arg); + + template_parameter process_nullptr_argument( + const clang::TemplateArgument &arg); + + template_parameter process_null_argument( + const clang::TemplateArgument &arg); + + std::vector process_pack_argument( + std::optional &parent, + const clang::Decl *cls, class_ &template_instantiation, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + 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::TemplateArgument &arg, + model::class_ &template_instantiation); + + common::model::template_parameter process_template_argument( + const clang::TemplateArgument &arg); + + std::optional + get_template_argument_from_type_parameter_string( + const clang::Decl *decl, const std::string &return_type_name) const; + + common::visitor::ast_id_mapper &id_mapper(); + + clang::SourceManager &source_manager() const; + +private: + // Reference to the output diagram model + clanguml::class_diagram::model::diagram &diagram_; + + // Reference to class diagram config + const clanguml::config::class_diagram &config_; + + common::visitor::ast_id_mapper &id_mapper_; + + clang::SourceManager &source_manager_; +}; + +} \ No newline at end of file diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 97223ced..07863efb 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -32,6 +32,7 @@ translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, : common::visitor::translation_unit_visitor{sm, config} , diagram_{diagram} , config_{config} + , template_builder_{diagram_, config_, id_mapper_, sm} { } @@ -64,7 +65,7 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns) p->set_name(name); p->set_namespace(package_parent); p->set_id(common::to_id(*ns)); - set_ast_local_id(ns->getID(), p->id()); + id_mapper().add(ns->getID(), p->id()); if (diagram().should_include(*p) && !diagram().get(p->id())) { process_comment(*ns, *p); @@ -123,14 +124,14 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) // First check if the parent has been added to the diagram as // regular class - id_opt = get_ast_local_id(local_id); + id_opt = id_mapper().get_global_id(local_id); // If not, check if the parent template declaration is in the model if (!id_opt) { if (parent_record_decl->getDescribedTemplate() != nullptr) { local_id = parent_record_decl->getDescribedTemplate()->getID(); - id_opt = get_ast_local_id(local_id); + id_opt = id_mapper().get_global_id(local_id); } } } @@ -153,7 +154,7 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) e.set_id(common::to_id(e.full_name(false))); } - set_ast_local_id(enm->getID(), e.id()); + id_mapper().add(enm->getID(), e.id()); process_comment(*enm, e); set_source_location(*enm, e); @@ -210,7 +211,7 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( process_class_bases(cls, template_specialization); const auto maybe_id = - get_ast_local_id(cls->getSpecializedTemplate()->getID()); + id_mapper().get_global_id(cls->getSpecializedTemplate()->getID()); if (maybe_id.has_value()) template_specialization.add_relationship( {relationship_t::kInstantiation, maybe_id.value()}); @@ -250,7 +251,7 @@ bool translation_unit_visitor::VisitTypeAliasTemplateDecl( return true; auto template_specialization_ptr = - build_template_instantiation(cls, *template_type_specialization_ptr); + tbuilder().build(cls, *template_type_specialization_ptr); if (!template_specialization_ptr) return true; @@ -298,7 +299,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl( c_ptr->set_id(id); c_ptr->is_template(true); - set_ast_local_id(cls->getID(), id); + id_mapper().add(cls->getID(), id); constexpr auto kMaxConstraintCount = 24U; llvm::SmallVector constraints{}; @@ -352,7 +353,7 @@ bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *rec) const auto rec_id = record_ptr->id(); - set_ast_local_id(rec->getID(), rec_id); + id_mapper().add(rec->getID(), rec_id); auto &record_model = diagram().get_class(rec_id).has_value() ? *diagram().get_class(rec_id).get() @@ -404,7 +405,7 @@ bool translation_unit_visitor::TraverseConceptDecl(clang::ConceptDecl *cpt) const auto concept_id = concept_model->id(); - set_ast_local_id(cpt->getID(), concept_id); + id_mapper().add(cpt->getID(), concept_id); process_template_parameters(*cpt, *concept_model); @@ -621,7 +622,7 @@ void translation_unit_visitor::process_concept_specialization_relationships( const auto cpt_name = cpt->getNameAsString(); - const auto maybe_id = get_ast_local_id(cpt->getID()); + const auto maybe_id = id_mapper().get_global_id(cpt->getID()); if (!maybe_id) return; @@ -702,7 +703,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) if (cls->isTemplated() && (cls->getDescribedTemplate() != nullptr)) { // If the described templated of this class is already in the model // skip it: - if (get_ast_local_id(cls->getDescribedTemplate()->getID())) + if (id_mapper().get_global_id(cls->getDescribedTemplate()->getID())) return true; } @@ -717,7 +718,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) const auto cls_id = c_ptr->id(); - set_ast_local_id(cls->getID(), cls_id); + id_mapper().add(cls->getID(), cls_id); auto &class_model = diagram().get_class(cls_id).has_value() ? *diagram().get_class(cls_id).get() @@ -863,7 +864,7 @@ void translation_unit_visitor::process_record_parent( // First check if the parent has been added to the diagram as // regular class - id_opt = get_ast_local_id(local_id); + id_opt = id_mapper().get_global_id(local_id); // If not, check if the parent template declaration is in the // model @@ -871,7 +872,7 @@ void translation_unit_visitor::process_record_parent( if (parent_record_decl->getDescribedTemplate() != nullptr) { local_id = parent_record_decl->getDescribedTemplate()->getID(); - id_opt = get_ast_local_id(local_id); + id_opt = id_mapper().get_global_id(local_id); } } } @@ -972,7 +973,8 @@ bool translation_unit_visitor::process_template_parameters( should_include(named_concept)) { templated_element.value().add_relationship( {relationship_t::kConstraint, - get_ast_local_id(named_concept->getID()) + id_mapper() + .get_global_id(named_concept->getID()) .value(), access_t::kNone, ct.name().value()}); } @@ -1025,53 +1027,6 @@ bool translation_unit_visitor::process_template_parameters( return false; } -void translation_unit_visitor::process_template_record_containment( - const clang::TagDecl &record, - clanguml::common::model::element &element) const -{ - assert(record.getParent()->isRecord()); - - const auto *parent = record.getParent(); - - if (parent != nullptr) { - if (const auto *record_decl = - clang::dyn_cast(parent); - record_decl != nullptr) { - if (const auto *described_template = - record_decl->getDescribedTemplate(); - described_template != nullptr) { - auto id_opt = get_ast_local_id(described_template->getID()); - - if (id_opt) { - element.add_relationship( - {relationship_t::kContainment, *id_opt}); - } - } - } - } -} - -void translation_unit_visitor::process_record_containment( - const clang::TagDecl &record, - clanguml::common::model::element &element) const -{ - assert(record.getParent()->isRecord()); - - const auto *parent = record.getParent()->getOuterLexicalRecordContext(); - auto parent_name = static_cast(parent) - ->getQualifiedNameAsString(); - - auto namespace_declaration = common::get_tag_namespace(record); - element.set_namespace(namespace_declaration); - - if (const auto *record_decl = - clang::dyn_cast(record.getParent()); - record_decl != nullptr) { - element.add_relationship( - {relationship_t::kContainment, common::to_id(*record_decl)}); - } -} - void translation_unit_visitor::process_class_bases( const clang::CXXRecordDecl *cls, class_ &c) { @@ -1091,8 +1046,7 @@ void translation_unit_visitor::process_class_bases( else if (const auto *tsp = base.getType()->getAs(); tsp != nullptr) { - auto template_specialization_ptr = - build_template_instantiation(cls, *tsp, {}); + auto template_specialization_ptr = tbuilder().build(cls, *tsp, {}); if (template_specialization_ptr) { cp.set_id(template_specialization_ptr->id()); } @@ -1665,7 +1619,7 @@ void translation_unit_visitor:: template_instantiation_type.getTemplateName().getAsTemplateDecl())) return; - auto template_specialization_ptr = build_template_instantiation( + auto template_specialization_ptr = tbuilder().build( template_instantiation_type.getTemplateName().getAsTemplateDecl(), template_instantiation_type, &c); @@ -1801,7 +1755,7 @@ translation_unit_visitor::process_template_specialization( template_instantiation.set_id( common::to_id(template_instantiation.full_name(false))); - set_ast_local_id(cls->getID(), template_instantiation.id()); + id_mapper().add(cls->getID(), template_instantiation.id()); return c_ptr; } @@ -1833,7 +1787,7 @@ void translation_unit_visitor::process_template_specialization_argument( // 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 = - get_template_argument_from_type_parameter_string( + tbuilder().get_template_argument_from_type_parameter_string( cls, return_type_name); if (maybe_return_arg) @@ -1846,7 +1800,7 @@ void translation_unit_visitor::process_template_specialization_argument( // Set function template argument types for (const auto ¶m_type : function_type->param_types()) { auto maybe_arg = - get_template_argument_from_type_parameter_string( + tbuilder().get_template_argument_from_type_parameter_string( cls, param_type.getAsString()); if (maybe_arg) { @@ -1880,7 +1834,7 @@ void translation_unit_visitor::process_template_specialization_argument( argument->set_type(nested_template_name); - auto nested_template_instantiation = build_template_instantiation( + auto nested_template_instantiation = tbuilder().build( cls, *nested_template_type, {&template_instantiation}); argument->set_id(nested_template_instantiation->id()); @@ -1984,7 +1938,7 @@ void translation_unit_visitor::process_template_specialization_argument( LOG_DBG("Adding template instantiation argument {}", argument.value().to_string(config().using_namespace(), false)); - simplify_system_template(*argument, + tbuilder().simplify_system_template(*argument, argument.value().to_string(config().using_namespace(), false)); template_instantiation.add_template(std::move(argument.value())); @@ -2072,11 +2026,11 @@ bool translation_unit_visitor::find_relationships_in_unexposed_template_params( return found; } -std::unique_ptr translation_unit_visitor:: - build_template_instantiation_from_class_template_specialization( - const clang::ClassTemplateSpecializationDecl &template_specialization, - const clang::RecordType &record_type, - std::optional parent) +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()); @@ -2106,8 +2060,8 @@ std::unique_ptr translation_unit_visitor:: static_cast( std::hash{}(full_template_specialization_name) >> 4U)); - build_template_instantiation_process_template_arguments(parent, - &template_specialization, template_base_params, + process_template_arguments(parent, &template_specialization, + template_base_params, template_specialization.getTemplateArgs().asArray(), template_instantiation, full_template_specialization_name, template_decl); @@ -2138,7 +2092,7 @@ std::unique_ptr translation_unit_visitor:: auto templated_decl_id = template_specialization.getID(); auto templated_decl_local_id = - get_ast_local_id(templated_decl_id).value_or(0); + id_mapper().get_global_id(templated_decl_id).value_or(0); if (best_match_id > 0) { destination = best_match_full_name; @@ -2159,643 +2113,6 @@ std::unique_ptr translation_unit_visitor:: return template_instantiation_ptr; } -std::unique_ptr translation_unit_visitor::build_template_instantiation( - const clang::Decl *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 - // instantiated values - // - std::deque> - template_base_params{}; - - const auto *template_type_ptr = &template_type_decl; - - if (template_type_decl.isTypeAlias()) { - if (const auto *tsp = - template_type_decl.getAliasedType() - ->template getAs(); - tsp != nullptr) - template_type_ptr = tsp; - } - - const auto &template_type = *template_type_ptr; - - // - // Create class_ instance to hold the template instantiation - // - auto template_instantiation_ptr = - std::make_unique(config_.using_namespace()); - - auto &template_instantiation = *template_instantiation_ptr; - template_instantiation.is_template(true); - - std::string full_template_specialization_name = common::to_string( - template_type.desugar(), - template_type.getTemplateName().getAsTemplateDecl()->getASTContext()); - - auto *template_decl{template_type.getTemplateName().getAsTemplateDecl()}; - - auto template_decl_qualified_name = - template_decl->getQualifiedNameAsString(); - - auto *class_template_decl{ - clang::dyn_cast(template_decl)}; - - if ((class_template_decl != nullptr) && - (class_template_decl->getTemplatedDecl() != nullptr) && - (class_template_decl->getTemplatedDecl()->getParent() != nullptr) && - class_template_decl->getTemplatedDecl()->getParent()->isRecord()) { - - namespace_ ns{ - common::get_tag_namespace(*class_template_decl->getTemplatedDecl() - ->getParent() - ->getOuterLexicalRecordContext())}; - - std::string ns_str = ns.to_string(); - std::string name = template_decl->getQualifiedNameAsString(); - if (!ns_str.empty()) { - name = name.substr(ns_str.size() + 2); - } - - util::replace_all(name, "::", "##"); - template_instantiation.set_name(name); - - template_instantiation.set_namespace(ns); - } - else { - namespace_ ns{template_decl_qualified_name}; - ns.pop_back(); - template_instantiation.set_name(template_decl->getNameAsString()); - template_instantiation.set_namespace(ns); - } - - // TODO: Refactor handling of base parameters to a separate method - - // We need this to match any possible base classes coming from template - // arguments - std::vector< - std::pair> - template_parameter_names{}; - - for (const auto *parameter : *template_decl->getTemplateParameters()) { - if (parameter->isTemplateParameter() && - (parameter->isTemplateParameterPack() || - parameter->isParameterPack())) { - template_parameter_names.emplace_back( - parameter->getNameAsString(), true); - } - else - template_parameter_names.emplace_back( - parameter->getNameAsString(), false); - } - - // Check if the primary template has any base classes - int base_index = 0; - - const auto *templated_class_decl = - clang::dyn_cast_or_null( - template_decl->getTemplatedDecl()); - - if ((templated_class_decl != nullptr) && - templated_class_decl->hasDefinition()) - for (const auto &base : templated_class_decl->bases()) { - const auto base_class_name = common::to_string( - base.getType(), templated_class_decl->getASTContext(), false); - - LOG_DBG("Found template instantiation base: {}, {}", - base_class_name, base_index); - - // Check if any of the primary template arguments has a - // parameter equal to this type - auto it = std::find_if(template_parameter_names.begin(), - template_parameter_names.end(), - [&base_class_name]( - const auto &p) { return p.first == base_class_name; }); - - if (it != template_parameter_names.end()) { - const auto ¶meter_name = it->first; - const bool is_variadic = it->second; - // Found base class which is a template parameter - LOG_DBG("Found base class which is a template parameter " - "{}, {}, {}", - parameter_name, is_variadic, - std::distance(template_parameter_names.begin(), it)); - - template_base_params.emplace_back(parameter_name, - std::distance(template_parameter_names.begin(), it), - is_variadic); - } - else { - // This is a regular base class - it is handled by - // process_template - } - base_index++; - } - - build_template_instantiation_process_template_arguments(parent, cls, - template_base_params, template_type.template_arguments(), - 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_type.getTemplateName().getAsTemplateDecl()->getID(); - auto templated_decl_local_id = - get_ast_local_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 { - LOG_DBG("== Cannot determine global id for specialization template {} " - "- delaying until the translation unit is complete ", - templated_decl_id); - - template_instantiation.add_relationship( - {relationship_t::kInstantiation, templated_decl_id}); - } - - template_instantiation.set_id( - common::to_id(template_instantiation_ptr->full_name(false))); - - return template_instantiation_ptr; -} - -void translation_unit_visitor:: - build_template_instantiation_process_template_arguments( - std::optional &parent, - const clang::Decl *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) -{ - auto arg_index = 0; - for (const auto &arg : template_args) { - // Argument can be a parameter pack in which case it gives multiple - // arguments - std::vector argument; - - switch (arg.getKind()) { - case clang::TemplateArgument::Null: - argument.push_back( - build_template_instantiation_process_null_argument(arg)); - break; - case clang::TemplateArgument::Type: - argument.push_back( - build_template_instantiation_process_type_argument(parent, cls, - full_template_specialization_name, template_decl, arg, - template_instantiation)); - break; - case clang::TemplateArgument::Declaration: - break; - case clang::TemplateArgument::NullPtr: - argument.push_back( - build_template_instantiation_process_nullptr_argument(arg)); - break; - case clang::TemplateArgument::Integral: - argument.push_back( - build_template_instantiation_process_integral_argument(arg)); - break; - case clang::TemplateArgument::Template: - argument.push_back( - build_template_instantiation_process_template_argument(arg)); - break; - case clang::TemplateArgument::TemplateExpansion: - break; - case clang::TemplateArgument::Expression: - argument.push_back( - build_template_instantiation_process_expression_argument(arg)); - break; - case clang::TemplateArgument::Pack: - build_template_instantiation_process_pack_argument(arg, argument)); - break; - } - - if (argument.empty()) { - arg_index++; - continue; - } - - // We can figure this only when we encounter variadic param in - // the list of template params, from then this variable is true - // and we can process following template parameters as belonging - // to the variadic tuple - [[maybe_unused]] auto variadic_params{false}; - - // In case any of the template arguments are base classes, add - // them as parents of the current template instantiation class - if (!template_base_params.empty()) { - variadic_params = build_template_instantiation_add_base_classes( - template_instantiation, template_base_params, arg_index, - variadic_params, argument.front()); - } - - for (auto &arg : argument) { - simplify_system_template( - arg, arg.to_string(config().using_namespace(), false)); - - LOG_DBG("Adding template argument {} to template " - "specialization/instantiation {}", - arg.to_string(config().using_namespace(), false), - template_instantiation.name()); - - template_instantiation.add_template(std::move(arg)); - } - - arg_index++; - } -} - -template_parameter translation_unit_visitor:: - build_template_instantiation_process_template_argument( - const clang::TemplateArgument &arg) const -{ - auto arg_name = - arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); - return template_parameter::make_template_type(arg_name); -} - -template_parameter -translation_unit_visitor::build_template_instantiation_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) -{ - assert(arg.getKind() == clang::TemplateArgument::Type); - - auto argument = template_parameter::make_argument({}); - - if (const auto *function_type = - arg.getAsType()->getAs(); - function_type != nullptr) { - - argument.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 = - get_template_argument_from_type_parameter_string( - cls, return_type_name); - - if (maybe_return_arg) - argument.add_template_param(*maybe_return_arg); - else { - argument.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 = - get_template_argument_from_type_parameter_string( - cls, param_type.getAsString()); - - if (maybe_arg) { - argument.add_template_param(*maybe_arg); - continue; - } - - if (param_type->isBuiltinType()) { - argument.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; - - auto *classTemplateSpecialization = - llvm::dyn_cast( - param_type->getAsRecordDecl()); - - if (classTemplateSpecialization != nullptr) { - // Read arg info as needed. - auto nested_template_instantiation = - build_template_instantiation_from_class_template_specialization( - *classTemplateSpecialization, *param_record_type, - diagram().should_include( - full_template_specialization_name) - ? std::make_optional(&template_instantiation) - : parent); - - const auto nested_template_name = - classTemplateSpecialization->getQualifiedNameAsString(); - - if (nested_template_instantiation) { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - - 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 (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) { - - const auto nested_type_name = nested_template_type->getTemplateName() - .getAsTemplateDecl() - ->getQualifiedNameAsString(); - - argument.set_type(nested_type_name); - - auto nested_template_instantiation = - build_template_instantiation(cls, *nested_template_type, - diagram().should_include(full_template_specialization_name) - ? std::make_optional(&template_instantiation) - : parent); - - argument.set_id(nested_template_instantiation->id()); - - for (const auto &t : nested_template_instantiation->template_params()) - 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)); - - if (nested_template_instantiation && - diagram().should_include( - nested_template_instantiation->full_name(false))) { - if (diagram().should_include( - full_template_specialization_name)) { - template_instantiation.add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - else { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - } - - 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())); - } - else { - // This is just a regular record type - build_template_instantiation_process_tag_argument( - template_instantiation, full_template_specialization_name, - template_decl, arg, argument); - } - - return argument; -} - -std::optional -translation_unit_visitor::get_template_argument_from_type_parameter_string( - const clang::Decl *decl, const std::string &return_type_name) const -{ - if (const auto *template_decl = - llvm::dyn_cast(decl); - template_decl != nullptr && - return_type_name.find("type-parameter-") == 0) { - - [[maybe_unused]] const auto [depth, index] = - common::extract_template_parameter_index(return_type_name); - - std::string param_name = return_type_name; - - for (auto i = 0U; - i < template_decl->getDescribedTemplateParams()->size(); i++) { - const auto *param = - template_decl->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; - } - } - } - - return {}; -} - -template_parameter translation_unit_visitor:: - build_template_instantiation_process_integral_argument( - const clang::TemplateArgument &arg) const -{ - assert(arg.getKind() == clang::TemplateArgument::Integral); - - return template_parameter::make_argument( - std::to_string(arg.getAsIntegral().getExtValue())); -} - -template_parameter -translation_unit_visitor::build_template_instantiation_process_null_argument( - const clang::TemplateArgument &arg) const -{ - assert(arg.getKind() == clang::TemplateArgument::Null); - - return template_parameter::make_argument(""); -} - -template_parameter -translation_unit_visitor::build_template_instantiation_process_nullptr_argument( - const clang::TemplateArgument &arg) const -{ - assert(arg.getKind() == clang::TemplateArgument::NullPtr); - - return template_parameter::make_argument("nullptr"); -} - -template_parameter translation_unit_visitor:: - build_template_instantiation_process_expression_argument( - const clang::TemplateArgument &arg) const -{ - assert(arg.getKind() == clang::TemplateArgument::Expression); - return template_parameter::make_argument(common::get_source_text( - arg.getAsExpr()->getSourceRange(), source_manager())); -} - -template_parameter -translation_unit_visitor::build_template_instantiation_process_pack_argument( - const clang::TemplateArgument &arg) const -{ - assert(arg.getKind() == clang::TemplateArgument::Pack); - - arg.getPackAsArray().front().return template_parameter::make_argument( - std::to_string(arg.get)); -} - -void translation_unit_visitor:: - build_template_instantiation_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) -{ - assert(arg.getKind() == clang::TemplateArgument::Type); - - argument.is_template_parameter(false); - - argument.set_type( - common::to_string(arg.getAsType(), template_decl->getASTContext())); - - if (const auto *tsp = - arg.getAsType()->getAs(); - tsp != nullptr) { - if (const auto *record_type_decl = tsp->getAsRecordDecl(); - record_type_decl != nullptr) { - - argument.set_id(common::to_id(arg)); - if (diagram().should_include( - full_template_specialization_name)) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); - } - } - } - else if (const auto *record_type = - arg.getAsType()->getAs(); - record_type != nullptr) { - if (const auto *record_type_decl = record_type->getAsRecordDecl(); - record_type_decl != nullptr) { - - argument.set_id(common::to_id(arg)); -#if LLVM_VERSION_MAJOR >= 16 - argument.set_type(record_type_decl->getQualifiedNameAsString()); -#endif - if (diagram().should_include( - full_template_specialization_name)) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); - } - } - } - else if (const auto *enum_type = arg.getAsType()->getAs(); - enum_type != nullptr) { - if (enum_type->getAsTagDecl() != nullptr) { - template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); - } - } -} - -bool translation_unit_visitor::build_template_instantiation_add_base_classes( - class_ &tinst, - std::deque> &template_base_params, - int arg_index, bool variadic_params, const template_parameter &ct) const -{ - bool add_template_argument_as_base_class = false; - - auto [arg_name, index, is_variadic] = template_base_params.front(); - if (variadic_params) - add_template_argument_as_base_class = true; - else { - variadic_params = is_variadic; - if ((arg_index == index) || (is_variadic && arg_index >= index)) { - add_template_argument_as_base_class = true; - if (!is_variadic) { - // Don't remove the remaining variadic parameter - template_base_params.pop_front(); - } - } - } - - const auto maybe_id = ct.id(); - if (add_template_argument_as_base_class && maybe_id) { - LOG_DBG("Adding template argument as base class '{}'", - ct.to_string({}, false)); - - class_parent cp; - cp.set_access(access_t::kPublic); - cp.set_name(ct.to_string({}, false)); - cp.set_id(maybe_id.value()); - - tinst.add_parent(std::move(cp)); - } - - return variadic_params; -} - void translation_unit_visitor::process_field( const clang::FieldDecl &field_declaration, class_ &c) { @@ -2858,22 +2175,22 @@ void translation_unit_visitor::process_field( // TODO: Refactor to an unalias_type() method if (template_field_type != nullptr) if (template_field_type->isTypeAlias()) - template_field_type = - template_field_type->getAliasedType() - ->getAs(); + template_field_type = + template_field_type->getAliasedType() + ->getAs(); bool field_type_is_template_template_parameter{false}; if (template_field_type != nullptr) { // Skip types which are template template parameters of the parent // template for (const auto &class_template_param : c.template_params()) { - if (class_template_param.name() == - template_field_type->getTemplateName() - .getAsTemplateDecl() - ->getNameAsString() + - "<>") { - field_type_is_template_template_parameter = true; - } + if (class_template_param.name() == + template_field_type->getTemplateName() + .getAsTemplateDecl() + ->getNameAsString() + + "<>") { + field_type_is_template_template_parameter = true; + } } } @@ -2882,69 +2199,66 @@ void translation_unit_visitor::process_field( !field_type_is_template_template_parameter) { // Build the template instantiation for the field type - auto template_specialization_ptr = build_template_instantiation( - &field_declaration, *template_field_type, {&c}); + auto template_specialization_ptr = + tbuilder().build(&field_declaration, *template_field_type, {&c}); if (!field.skip_relationship() && template_specialization_ptr) { - const auto &template_specialization = - *template_specialization_ptr; + const auto &template_specialization = *template_specialization_ptr; - // 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 - bool add_template_instantiation_to_diargam{false}; - if (diagram().should_include( - template_specialization.full_name(false))) { + // 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 + bool add_template_instantiation_to_diargam{false}; + if (diagram().should_include( + template_specialization.full_name(false))) { - found_relationships_t::value_type r{ - template_specialization.id(), relationship_hint}; + found_relationships_t::value_type r{ + template_specialization.id(), relationship_hint}; - add_template_instantiation_to_diargam = true; + add_template_instantiation_to_diargam = true; - // If the template instantiation for the build type has been - // added as aggregation, skip its nested templates - template_instantiation_added_as_aggregation = - relationship_hint == relationship_t::kAggregation; - relationships.emplace_back(std::move(r)); - } + // If the template instantiation for the build type has been + // added as aggregation, skip its nested templates + template_instantiation_added_as_aggregation = + relationship_hint == relationship_t::kAggregation; + relationships.emplace_back(std::move(r)); + } - // Try to find relationships to types nested in the template - // instantiation - found_relationships_t nested_relationships; - if (!template_instantiation_added_as_aggregation) { - for (const auto &template_argument : - template_specialization.template_params()) { + // Try to find relationships to types nested in the template + // instantiation + found_relationships_t nested_relationships; + if (!template_instantiation_added_as_aggregation) { + for (const auto &template_argument : + template_specialization.template_params()) { - LOG_DBG( - "Looking for nested relationships from {}::{} in " + LOG_DBG("Looking for nested relationships from {}::{} in " "template {}", - c.full_name(false), field_name, - template_argument.to_string( - config().using_namespace(), false)); + c.full_name(false), field_name, + template_argument.to_string( + config().using_namespace(), false)); - template_instantiation_added_as_aggregation = - template_argument.find_nested_relationships( - nested_relationships, relationship_hint, - [&d = diagram()](const std::string &full_name) { - if (full_name.empty()) - return false; - auto [ns, name] = - common::split_ns(full_name); - return d.should_include(ns, name); - }); - } - - // 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); + template_instantiation_added_as_aggregation = + template_argument.find_nested_relationships( + nested_relationships, relationship_hint, + [&d = diagram()](const std::string &full_name) { + if (full_name.empty()) + return false; + auto [ns, name] = common::split_ns(full_name); + return d.should_include(ns, name); + }); } - // Add the template instantiation object to the diagram if it - // matches the include pattern - if (add_template_instantiation_to_diargam) - diagram().add_class(std::move(template_specialization_ptr)); + // 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); + } + + // Add the template instantiation object to the diagram if it + // matches the include pattern + if (add_template_instantiation_to_diargam) + diagram().add_class(std::move(template_specialization_ptr)); } } @@ -2952,18 +2266,18 @@ void translation_unit_visitor::process_field( // Find relationship for the type if the type has not been added // as aggregation if (!template_instantiation_added_as_aggregation) { - if ((field_type->getAsRecordDecl() != nullptr) && - field_type->getAsRecordDecl()->getNameAsString().empty()) { - // Relationships to fields whose type is an anonymous nested - // struct have to be handled separately here - anonymous_struct_relationships_ - [field_type->getAsRecordDecl()->getID()] = - std::make_tuple(field.name(), relationship_hint, - field.access()); - } - else - find_relationships( - field_type, relationships, relationship_hint); + if ((field_type->getAsRecordDecl() != nullptr) && + field_type->getAsRecordDecl()->getNameAsString().empty()) { + // Relationships to fields whose type is an anonymous nested + // struct have to be handled separately here + anonymous_struct_relationships_[field_type->getAsRecordDecl() + ->getID()] = + std::make_tuple( + field.name(), relationship_hint, field.access()); + } + else + find_relationships( + field_type, relationships, relationship_hint); } add_relationships(c, field, relationships); @@ -2976,7 +2290,7 @@ void translation_unit_visitor::add_incomplete_forward_declarations() { for (auto &[id, c] : forward_declarations_) { if (diagram().should_include(c->full_name(false))) { - diagram().add_class(std::move(c)); + diagram().add_class(std::move(c)); } } forward_declarations_.clear(); @@ -2988,27 +2302,27 @@ void translation_unit_visitor::resolve_local_to_global_ids() // to elements for (const auto &cls : diagram().classes()) { for (auto &rel : cls.get().relationships()) { - if (rel.type() == relationship_t::kInstantiation) { - const auto maybe_id = get_ast_local_id(rel.destination()); - if (maybe_id) { - LOG_DBG( - "= Resolved instantiation destination from local " - "id {} to global id {}", - rel.destination(), *maybe_id); - rel.set_destination(*maybe_id); - } - } - } - } - for (const auto &cpt : diagram().concepts()) { - for (auto &rel : cpt.get().relationships()) { - const auto maybe_id = get_ast_local_id(rel.destination()); + if (rel.type() == relationship_t::kInstantiation) { + const auto maybe_id = + id_mapper().get_global_id(rel.destination()); if (maybe_id) { LOG_DBG("= Resolved instantiation destination from local " "id {} to global id {}", rel.destination(), *maybe_id); rel.set_destination(*maybe_id); } + } + } + } + for (const auto &cpt : diagram().concepts()) { + for (auto &rel : cpt.get().relationships()) { + const auto maybe_id = id_mapper().get_global_id(rel.destination()); + if (maybe_id) { + LOG_DBG("= Resolved instantiation destination from local " + "id {} to global id {}", + rel.destination(), *maybe_id); + rel.set_destination(*maybe_id); + } } } } @@ -3019,36 +2333,24 @@ void translation_unit_visitor::finalize() resolve_local_to_global_ids(); } -bool translation_unit_visitor::simplify_system_template( - template_parameter &ct, const std::string &full_name) const -{ - auto simplified = config().simplify_template_type(full_name); - - if (simplified != full_name) { - ct.set_type(simplified); - ct.clear_params(); - return true; - } - - return false; -} - -void translation_unit_visitor::set_ast_local_id( - 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::get_ast_local_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::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, @@ -3062,17 +2364,16 @@ void translation_unit_visitor::extract_constrained_template_param_name( if (!full_declaration_text.empty()) { // Handle typename constraint in requires clause if (type_name.find("type-parameter-") == 0) { - const auto concept_declaration_text = - full_declaration_text.substr( - full_declaration_text.find(cpt->getNameAsString()) + - cpt->getNameAsString().size() + 1); + const auto concept_declaration_text = full_declaration_text.substr( + full_declaration_text.find(cpt->getNameAsString()) + + cpt->getNameAsString().size() + 1); - auto template_params = common::parse_unexposed_template_params( - concept_declaration_text, [](const auto &t) { return t; }); + auto template_params = common::parse_unexposed_template_params( + concept_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); + if (template_params.size() > argument_index) + type_name = template_params[argument_index].to_string( + config().using_namespace(), false); } constrained_template_params.push_back(type_name); } diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index f18a8f2e..a4f78190 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -22,8 +22,10 @@ #include "class_diagram/model/diagram.h" #include "common/model/enums.h" #include "common/model/template_trait.h" +#include "common/visitor/ast_id_mapper.h" #include "common/visitor/translation_unit_visitor.h" #include "config/config.h" +#include "template_builder.h" #include #include @@ -93,6 +95,11 @@ public: */ clanguml::class_diagram::model::diagram &diagram() { return diagram_; } + const clanguml::class_diagram::model::diagram &diagram() const + { + return diagram_; + } + /** * @brief Get diagram config instance * @@ -151,12 +158,6 @@ private: const clang::TemplateArgument &arg, size_t argument_index, bool in_parameter_pack = false); - void process_template_record_containment(const clang::TagDecl &record, - clanguml::common::model::element &c) const; - - void process_record_containment(const clang::TagDecl &record, - clanguml::common::model::element &c) const; - void process_method(const clang::CXXMethodDecl &mf, clanguml::class_diagram::model::class_ &c); @@ -186,66 +187,6 @@ private: const found_relationships_t &relationships, bool break_on_first_aggregation = false); - std::unique_ptr - build_template_instantiation(const clang::Decl *cls, - const clang::TemplateSpecializationType &template_type, - std::optional parent = {}); - - std::unique_ptr - build_template_instantiation_from_class_template_specialization( - const clang::ClassTemplateSpecializationDecl &template_specialization, - const clang::RecordType &record_type, - std::optional parent = {}); - - bool build_template_instantiation_add_base_classes( - clanguml::class_diagram::model::class_ &tinst, - std::deque> &template_base_params, - int arg_index, bool variadic_params, - const clanguml::common::model::template_parameter &ct) const; - - void build_template_instantiation_process_template_arguments( - std::optional &parent, - const clang::Decl *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 build_template_instantiation_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); - - template_parameter build_template_instantiation_process_expression_argument( - const clang::TemplateArgument &arg) const; - - template_parameter build_template_instantiation_process_integral_argument( - const clang::TemplateArgument &arg) const; - - template_parameter build_template_instantiation_process_nullptr_argument( - const clang::TemplateArgument &arg) const; - - template_parameter build_template_instantiation_process_null_argument( - const clang::TemplateArgument &arg) const; - - template_parameter build_template_instantiation_process_pack_argument( - const clang::TemplateArgument &arg) const; - - template_parameter build_template_instantiation_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, - model::class_ &template_instantiation); - - common::model::template_parameter - build_template_instantiation_process_template_argument( - const clang::TemplateArgument &arg) const; - void ensure_lambda_type_is_relative(std::string ¶meter_type) const; void process_record_parent( @@ -290,10 +231,6 @@ private: std::vector &constrained_template_params, size_t argument_index, std::string &type_name) const; - std::optional - get_template_argument_from_type_parameter_string( - const clang::Decl *decl, const std::string &return_type_name) const; - /// Store the mapping from local clang entity id (obtained using /// getID()) method to clang-uml global id void set_ast_local_id( @@ -303,9 +240,9 @@ private: bool has_processed_template_class(const std::string &qualified_name) const; - /// Retrieve the global clang-uml entity id based on the clang local id - std::optional get_ast_local_id( - int64_t local_id) const; + common::visitor::ast_id_mapper &id_mapper() const { return id_mapper_; } + + template_builder &tbuilder() { return template_builder_; } // Reference to the output diagram model clanguml::class_diagram::model::diagram &diagram_; @@ -313,12 +250,14 @@ private: // Reference to class diagram config const clanguml::config::class_diagram &config_; + mutable common::visitor::ast_id_mapper id_mapper_; + + template_builder template_builder_; + std::map> forward_declarations_; - std::map local_ast_id_map_; - std::map> diff --git a/src/common/visitor/ast_id_mapper.cc b/src/common/visitor/ast_id_mapper.cc new file mode 100644 index 00000000..2bc6ad45 --- /dev/null +++ b/src/common/visitor/ast_id_mapper.cc @@ -0,0 +1,36 @@ +/** + * src/class_diagram/visitor/ast_id_mapper.cc + * + * Copyright (c) 2021-2023 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. + */ + +#include "ast_id_mapper.h" + +namespace clanguml::common::visitor { + +void ast_id_mapper::add(int64_t ast_id, id_t global_id) +{ + id_map_.emplace(ast_id, global_id); +} + +std::optional ast_id_mapper::get_global_id(int64_t ast_id) +{ + if (id_map_.count(ast_id) == 0) + return {}; + + return id_map_.at(ast_id); +} + +} // namespace clanguml::common::visitor \ No newline at end of file diff --git a/src/common/visitor/ast_id_mapper.h b/src/common/visitor/ast_id_mapper.h new file mode 100644 index 00000000..daedb141 --- /dev/null +++ b/src/common/visitor/ast_id_mapper.h @@ -0,0 +1,43 @@ +/** + * src/class_diagram/visitor/ast_id_mapper.h + * + * Copyright (c) 2021-2023 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. + */ +#pragma once + +#include "common/model/diagram_element.h" + +#include +#include + +namespace clanguml::common::visitor { + +class ast_id_mapper { +public: + using id_t = common::model::diagram_element::id_t; + + ast_id_mapper() = default; + + void add(int64_t ast_id, id_t global_id); + + std::optional get_global_id(int64_t ast_id); + +private: + std::map + id_map_; +}; + +} // namespace clanguml::class_diagram::visitor \ No newline at end of file From 6323ce8a9219605f17dcc55407362d7e896c8459 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 18 Apr 2023 00:13:29 +0200 Subject: [PATCH 04/22] Fixed template_builder handling of nested template specializations --- .clang-uml | 2 + src/class_diagram/visitor/template_builder.cc | 341 +++++++++++++--- src/class_diagram/visitor/template_builder.h | 36 +- .../visitor/translation_unit_visitor.cc | 385 +----------------- .../visitor/translation_unit_visitor.h | 23 +- src/common/clang_utils.cc | 9 +- src/common/clang_utils.h | 3 +- src/util/util.cc | 9 + src/util/util.h | 1 + tests/t00044/t00044.cc | 1 - uml/template_builder_sequence_diagram.yml | 21 + 11 files changed, 356 insertions(+), 475 deletions(-) create mode 100644 uml/template_builder_sequence_diagram.yml 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)" From 7f9d698afcdf34de63ad1672912d31d4d42faa61 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 18 Apr 2023 23:56:09 +0200 Subject: [PATCH 05/22] 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; From 0aa4eb732dc913d017c9cb7a23ed27df88c27855 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Apr 2023 19:29:02 +0200 Subject: [PATCH 06/22] 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"); + } +} From 192e1993e86986decc31f63d28a480cadda71e90 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Apr 2023 19:29:30 +0200 Subject: [PATCH 07/22] Added initial unexposed template parameter deduction test case --- tests/t00062/.clang-uml | 12 ++++++ tests/t00062/t00062.cc | 62 +++++++++++++++++++++++++++++ tests/t00062/test_case.h | 86 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 tests/t00062/.clang-uml create mode 100644 tests/t00062/t00062.cc create mode 100644 tests/t00062/test_case.h diff --git a/tests/t00062/.clang-uml b/tests/t00062/.clang-uml new file mode 100644 index 00000000..84979caa --- /dev/null +++ b/tests/t00062/.clang-uml @@ -0,0 +1,12 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00062_class: + type: class + glob: + - ../../tests/t00062/t00062.cc + include: + namespaces: + - clanguml::t00062 + using_namespace: + - clanguml::t00062 \ No newline at end of file diff --git a/tests/t00062/t00062.cc b/tests/t00062/t00062.cc new file mode 100644 index 00000000..68b9da1f --- /dev/null +++ b/tests/t00062/t00062.cc @@ -0,0 +1,62 @@ +#include + +namespace clanguml { +namespace t00062 { +template struct A; + +template struct A { + U &u; +}; + +template struct A { + U &&u; +}; + +template struct A { + U const &u; +}; + +template struct A { + C &c; + M C::*m; +}; + +template struct A { + C &&c; + M C::*m; +}; + +template struct A { + C &c; + M C::*m; +}; + +template struct A { + C &c; +}; + +template struct A { + C &&c; + M C::*m; +}; + +template struct A { + C &&c; + float C::*mf; +}; + +template +struct A { + C &c; + M C::*m; +}; + +template struct A { + char n[N]; +}; + +template <> struct A { + std::vector n; +}; +} +} \ No newline at end of file diff --git a/tests/t00062/test_case.h b/tests/t00062/test_case.h new file mode 100644 index 00000000..87028856 --- /dev/null +++ b/tests/t00062/test_case.h @@ -0,0 +1,86 @@ +/** + * tests/t00062/test_case.h + * + * Copyright (c) 2021-2023 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("t00062", "[test-case][class]") +{ + auto [config, db] = load_config("t00062"); + + auto diagram = config.diagrams["t00062_class"]; + + REQUIRE(diagram->name == "t00062_class"); + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00062_class"); + + { + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + + // Check if all classes exist + REQUIRE_THAT(puml, IsClass(_A("AAA"))); + + // Check if class templates exist + //REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); + + // Check concepts + //REQUIRE_THAT(puml, IsConcept(_A("AConcept"))); + //REQUIRE_THAT(puml, + // IsConceptRequirement( + // _A("AConcept"), "sizeof (T) > sizeof (P)")); + + // Check if all enums exist + //REQUIRE_THAT(puml, IsEnum(_A("Lights"))); + + // Check if all inner classes exist + //REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + + // Check if all inheritance relationships exist + //REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child"))); + + // Check if all methods exist + //REQUIRE_THAT(puml, (IsMethod("foo"))); + + // Check if all fields exist + //REQUIRE_THAT(puml, (IsField("private_member", "int"))); + + // Check if all relationships exist + //REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); + //REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); + //REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag")); + //REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac")); + //REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); + + + save_puml( + config.output_directory() + "/" + diagram->name + ".puml", puml); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); + } + +} \ No newline at end of file From 3ed50ba3b20773d44a83358b21f855c1f768cfd2 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Apr 2023 19:29:46 +0200 Subject: [PATCH 08/22] Fixed formatting --- src/common/clang_utils.cc | 2 +- tests/t00062/t00062.cc | 2 +- tests/t00062/test_case.h | 63 +++++++++++++++++++-------------------- tests/test_model.cc | 6 ++-- tests/test_util.cc | 12 +++----- 5 files changed, 38 insertions(+), 47 deletions(-) diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index a6999acd..2bb2e2b0 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -486,7 +486,7 @@ bool is_identifier(const std::string &t) bool is_keyword(const std::string &t) { - static std::vector keywords {"alignas", "alignof", "asm", + 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", diff --git a/tests/t00062/t00062.cc b/tests/t00062/t00062.cc index 68b9da1f..d9ccd5ef 100644 --- a/tests/t00062/t00062.cc +++ b/tests/t00062/t00062.cc @@ -21,7 +21,7 @@ template struct A { M C::*m; }; -template struct A { +template struct A { C &&c; M C::*m; }; diff --git a/tests/t00062/test_case.h b/tests/t00062/test_case.h index 87028856..116dbc9c 100644 --- a/tests/t00062/test_case.h +++ b/tests/t00062/test_case.h @@ -35,41 +35,39 @@ TEST_CASE("t00062", "[test-case][class]") REQUIRE_THAT(puml, StartsWith("@startuml")); REQUIRE_THAT(puml, EndsWith("@enduml\n")); - - // Check if all classes exist - REQUIRE_THAT(puml, IsClass(_A("AAA"))); - - // Check if class templates exist - //REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); - - // Check concepts - //REQUIRE_THAT(puml, IsConcept(_A("AConcept"))); - //REQUIRE_THAT(puml, - // IsConceptRequirement( - // _A("AConcept"), "sizeof (T) > sizeof (P)")); + // Check if all classes exist + REQUIRE_THAT(puml, IsClass(_A("AAA"))); - // Check if all enums exist - //REQUIRE_THAT(puml, IsEnum(_A("Lights"))); - - // Check if all inner classes exist - //REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + // Check if class templates exist + // REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); - // Check if all inheritance relationships exist - //REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child"))); - - // Check if all methods exist - //REQUIRE_THAT(puml, (IsMethod("foo"))); - - // Check if all fields exist - //REQUIRE_THAT(puml, (IsField("private_member", "int"))); - - // Check if all relationships exist - //REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); - //REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); - //REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag")); - //REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac")); - //REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); + // Check concepts + // REQUIRE_THAT(puml, IsConcept(_A("AConcept"))); + // REQUIRE_THAT(puml, + // IsConceptRequirement( + // _A("AConcept"), "sizeof (T) > sizeof (P)")); + // Check if all enums exist + // REQUIRE_THAT(puml, IsEnum(_A("Lights"))); + + // Check if all inner classes exist + // REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + + // Check if all inheritance relationships exist + // REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child"))); + + // Check if all methods exist + // REQUIRE_THAT(puml, (IsMethod("foo"))); + + // Check if all fields exist + // REQUIRE_THAT(puml, (IsField("private_member", "int"))); + + // Check if all relationships exist + // REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); + // REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); + // REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag")); + // REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac")); + // REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); save_puml( config.output_directory() + "/" + diagram->name + ".puml", puml); @@ -82,5 +80,4 @@ TEST_CASE("t00062", "[test-case][class]") save_json(config.output_directory() + "/" + diagram->name + ".json", j); } - } \ No newline at end of file diff --git a/tests/test_model.cc b/tests/test_model.cc index 6e5521f0..6b075c19 100644 --- a/tests/test_model.cc +++ b/tests/test_model.cc @@ -292,8 +292,7 @@ TEST_CASE( 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")); + tp1.add_template_param(template_parameter::make_template_type("Arg0")); auto tp2 = template_parameter::make_template_type({}); tp2.set_method_template(true); @@ -309,8 +308,7 @@ TEST_CASE( 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")); + tp1.add_template_param(template_parameter::make_template_type("Arg0")); auto tp2 = template_parameter::make_template_type({}); tp2.set_method_template(true); diff --git a/tests/test_util.cc b/tests/test_util.cc index 450f20ed..406919c8 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -352,8 +352,7 @@ TEST_CASE("Test tokenize_unexposed_template_parameter", "[unit-test]") { int i = 0; - auto r = tokenize_unexposed_template_parameter( - "const ns1::ns2::A &"); + auto r = tokenize_unexposed_template_parameter("const ns1::ns2::A &"); CHECK(r[i++] == "const"); CHECK(r[i++] == "ns1::ns2::A"); CHECK(r[i++] == "&"); @@ -362,8 +361,7 @@ TEST_CASE("Test tokenize_unexposed_template_parameter", "[unit-test]") { int i = 0; - auto r = tokenize_unexposed_template_parameter( - "class ns1::ns2::A &"); + auto r = tokenize_unexposed_template_parameter("class ns1::ns2::A &"); CHECK(r[i++] == "ns1::ns2::A"); CHECK(r[i++] == "&"); } @@ -371,8 +369,7 @@ TEST_CASE("Test tokenize_unexposed_template_parameter", "[unit-test]") { int i = 0; - auto r = tokenize_unexposed_template_parameter( - "ns1::ns2::A C::* &&"); + auto r = tokenize_unexposed_template_parameter("ns1::ns2::A C::* &&"); CHECK(r[i++] == "ns1::ns2::A"); CHECK(r[i++] == "C"); CHECK(r[i++] == "::"); @@ -383,8 +380,7 @@ TEST_CASE("Test tokenize_unexposed_template_parameter", "[unit-test]") { int i = 0; - auto r = tokenize_unexposed_template_parameter( - "unsigned int"); + auto r = tokenize_unexposed_template_parameter("unsigned int"); CHECK(r[i++] == "unsigned"); CHECK(r[i++] == "int"); } From 47ccb561c98c60c3e81b29f0f78a5c35de33638a Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Mon, 24 Apr 2023 00:28:38 +0200 Subject: [PATCH 09/22] Fixed handling of unexposed variadic template params --- src/class_diagram/visitor/template_builder.cc | 6 ++++++ src/common/clang_utils.cc | 15 +++++++++++++++ tests/t00062/test_case.h | 15 ++++++++++++++- tests/test_util.cc | 11 +++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index c4f83fbf..d55b537e 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -852,6 +852,12 @@ std::optional build_template_parameter( return res; } + else if (common::is_type_token(*it) && *it_next == "...") { + // Variadic template parameter + auto parm = map_type_parameter_to_template_parameter(decl, *it); + parm.is_variadic(true); + return parm; + } else if (common::is_type_token(*it) && *it_next == "(") { res.add_template_param( map_type_parameter_to_template_parameter(decl, *it)); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 2bb2e2b0..be50439a 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -563,6 +563,21 @@ std::vector tokenize_unexposed_template_parameter( result.push_back("*"); tok.clear(); } + else if (c == '.') { + // This can only be the case if we have a variadic template, + // right? + if (tok == "..") { + result.push_back("..."); + tok.clear(); + } + else if (tok == ".") { + tok = ".."; + } + else if (!tok.empty()) { + result.push_back(tok); + tok = "."; + } + } else { tok += c; } diff --git a/tests/t00062/test_case.h b/tests/t00062/test_case.h index 116dbc9c..58ee6e81 100644 --- a/tests/t00062/test_case.h +++ b/tests/t00062/test_case.h @@ -36,7 +36,20 @@ TEST_CASE("t00062", "[test-case][class]") REQUIRE_THAT(puml, EndsWith("@enduml\n")); // Check if all classes exist - REQUIRE_THAT(puml, IsClass(_A("AAA"))); + REQUIRE_THAT(puml, IsClassTemplate("A", "T")); + REQUIRE_THAT(puml, IsClassTemplate("A", "U &")); + REQUIRE_THAT(puml, IsClassTemplate("A", "U &&")); + // REQUIRE_THAT(puml, IsClassTemplate("A", "U const&")); + REQUIRE_THAT(puml, IsClassTemplate("A", "M C::*")); + REQUIRE_THAT(puml, IsClassTemplate("A", "M C::*&&")); + REQUIRE_THAT(puml, IsClassTemplate("A", "M (C::*)(Arg)")); + // REQUIRE_THAT(puml, IsClassTemplate("A", "int (C::*)(bool)")); + REQUIRE_THAT(puml, IsClassTemplate("A", "M (C::*)(Arg)&&")); + // REQUIRE_THAT(puml, IsClassTemplate("A", "int + // (C::*)(bool)&&")); + REQUIRE_THAT(puml, IsClassTemplate("A", "M (C::*)(Arg1,Arg2,Arg3)")); + REQUIRE_THAT(puml, IsClassTemplate("A", "char[N]")); + REQUIRE_THAT(puml, IsClassTemplate("A", "char[1000]")); // Check if class templates exist // REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); diff --git a/tests/test_util.cc b/tests/test_util.cc index 406919c8..413cbb33 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -384,4 +384,15 @@ TEST_CASE("Test tokenize_unexposed_template_parameter", "[unit-test]") CHECK(r[i++] == "unsigned"); CHECK(r[i++] == "int"); } + + { + int i = 0; + + auto r = tokenize_unexposed_template_parameter("Ret(Args...)"); + CHECK(r[i++] == "Ret"); + CHECK(r[i++] == "("); + CHECK(r[i++] == "Args"); + CHECK(r[i++] == "..."); + CHECK(r[i++] == ")"); + } } From 0f4a2e1f9c658962e5f1ae13d6a45dd13cee1a19 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 25 Apr 2023 01:08:05 +0200 Subject: [PATCH 10/22] Fixed handling of qualifiers in unexposed template parameters --- src/class_diagram/visitor/template_builder.cc | 28 ++++++++++++------- src/common/clang_utils.cc | 5 ++-- tests/t00062/test_case.h | 8 +++--- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index d55b537e..68611eea 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -795,7 +795,10 @@ template_parameter map_type_parameter_to_template_parameter( detail::map_type_parameter_to_template_parameter(alias_decl, tp)); } - return template_parameter::make_argument(tp); + std::string arg = tp; + if (arg == "_Bool") + arg = "bool"; + return template_parameter::make_argument(arg); } std::optional build_template_parameter( @@ -822,14 +825,15 @@ std::optional build_template_parameter( return {}; // simple template param without qualifiers - if (common::is_type_token(*it) && it_next == end) { + if (common::is_type_token(*it) && (it_next == end || *it_next == ")")) { 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); + param_qualifier += *it_next; + res.set_qualifier(param_qualifier); return res; } // method template parameter @@ -847,7 +851,8 @@ std::optional build_template_parameter( } if (it != end && common::is_qualifier(*it)) { - res.set_qualifier(*it); + param_qualifier += *it; + res.set_qualifier(param_qualifier); } return res; @@ -861,17 +866,18 @@ std::optional build_template_parameter( else if (common::is_type_token(*it) && *it_next == "(") { res.add_template_param( map_type_parameter_to_template_parameter(decl, *it)); - it_next++; + it_next++; // skip '(' res.add_template_param( map_type_parameter_to_template_parameter(decl, *it_next)); it = it_next; it++; - if (*it == "::") { + it_next = it; + it_next++; + + if (*it == "::" && *it_next == "*") { res.set_method_template(true); - it++; - it++; - it++; + std::advance(it, 3); } if (it != end) { @@ -879,6 +885,7 @@ std::optional build_template_parameter( if (*it == "(") { it++; while (true) { + // This will break on more complex args auto arg_separator = std::find(it, end, ","); if (arg_separator == end) { // just one arg @@ -904,7 +911,8 @@ std::optional build_template_parameter( it++; if (it != end && common::is_qualifier(*it)) { - res.set_qualifier(*it); + param_qualifier += *it; + res.set_qualifier(param_qualifier); } } diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index be50439a..874a4812 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -479,9 +479,8 @@ 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); }); + return std::all_of(t.begin(), t.end(), + [](const char c) { return is_identifier_character(c); }); } bool is_keyword(const std::string &t) diff --git a/tests/t00062/test_case.h b/tests/t00062/test_case.h index 58ee6e81..335b43a2 100644 --- a/tests/t00062/test_case.h +++ b/tests/t00062/test_case.h @@ -39,15 +39,15 @@ TEST_CASE("t00062", "[test-case][class]") REQUIRE_THAT(puml, IsClassTemplate("A", "T")); REQUIRE_THAT(puml, IsClassTemplate("A", "U &")); REQUIRE_THAT(puml, IsClassTemplate("A", "U &&")); - // REQUIRE_THAT(puml, IsClassTemplate("A", "U const&")); + REQUIRE_THAT(puml, IsClassTemplate("A", "U const&")); REQUIRE_THAT(puml, IsClassTemplate("A", "M C::*")); REQUIRE_THAT(puml, IsClassTemplate("A", "M C::*&&")); REQUIRE_THAT(puml, IsClassTemplate("A", "M (C::*)(Arg)")); - // REQUIRE_THAT(puml, IsClassTemplate("A", "int (C::*)(bool)")); + REQUIRE_THAT(puml, IsClassTemplate("A", "int (C::*)(bool)")); REQUIRE_THAT(puml, IsClassTemplate("A", "M (C::*)(Arg)&&")); - // REQUIRE_THAT(puml, IsClassTemplate("A", "int - // (C::*)(bool)&&")); REQUIRE_THAT(puml, IsClassTemplate("A", "M (C::*)(Arg1,Arg2,Arg3)")); + REQUIRE_THAT(puml, IsClassTemplate("A", "float (C::*)(int)&&")); + REQUIRE_THAT(puml, IsClassTemplate("A", "char[N]")); REQUIRE_THAT(puml, IsClassTemplate("A", "char[1000]")); From 6ebdc8ab77de24e97a9905e244116cb319b5ca73 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Fri, 28 Apr 2023 22:46:36 +0200 Subject: [PATCH 11/22] WIP --- src/class_diagram/visitor/template_builder.cc | 189 ++++++++++++------ src/class_diagram/visitor/template_builder.h | 2 +- .../visitor/translation_unit_visitor.cc | 2 +- .../visitor/translation_unit_visitor.h | 4 + src/common/clang_utils.cc | 10 +- src/common/clang_utils.h | 2 + src/common/model/diagram_element.cc | 7 +- src/common/model/template_parameter.cc | 61 +++++- src/common/model/template_parameter.h | 35 +++- tests/CMakeLists.txt | 1 + tests/t00051/test_case.h | 12 +- tests/t00062/t00062.cc | 14 ++ tests/t00062/test_case.h | 2 +- 13 files changed, 254 insertions(+), 87 deletions(-) diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index 68611eea..e93f40a2 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -370,6 +370,21 @@ void template_builder::process_template_arguments( // arguments std::vector arguments; + // For now ignore the default template arguments to make the system + // templates 'nicer' - i.e. skipping the allocators and comparators + // TODO: Change this to ignore only when the arguments are set to + // default values, and add them when they are specifically + // overriden + const auto *maybe_type_parm_decl = + clang::dyn_cast( + template_decl->getTemplateParameters()->getParam( + std::min(arg_index, + template_decl->getTemplateParameters()->size() - 1))); + if (maybe_type_parm_decl && + maybe_type_parm_decl->hasDefaultArgument()) { + continue; + } + argument_process_dispatch(parent, cls, template_instantiation, template_decl, arg, arg_index, arguments); @@ -394,7 +409,7 @@ void template_builder::process_template_arguments( for (auto &argument : arguments) { simplify_system_template( - argument, argument.to_string(using_namespace(), false)); + argument, argument.to_string(using_namespace(), false, true)); LOG_DBG("Adding template argument {} to template " "specialization/instantiation {}", @@ -469,13 +484,19 @@ template_parameter template_builder::process_type_argument( 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 = - arg.getAsType()->getAs(); + auto type = arg.getAsType().getNonReferenceType().getUnqualifiedType(); + if (type->isPointerType()) + type = type->getPointeeType(); +// if (type->isMemberPointerType()) +// type = type->getPointeeType(); + + LOG_DBG("Processing template {} type argument: {}, {}, {}", + template_decl->getQualifiedNameAsString(), type_name, + type->getTypeClassName(), common::to_string(type, cls->getASTContext())); + + if (const auto *function_type = type->getAs(); function_type != nullptr) { argument.set_function_template(true); @@ -553,7 +574,7 @@ template_parameter template_builder::process_type_argument( } } else if (const auto *nested_template_type = - arg.getAsType()->getAs(); + type->getAs(); nested_template_type != nullptr) { const auto nested_type_name = nested_template_type->getTemplateName() @@ -601,11 +622,10 @@ template_parameter template_builder::process_type_argument( diagram().add_class(std::move(nested_template_instantiation)); } } - else if (arg.getAsType()->getAs() != nullptr) { + else if (type->getAs() != nullptr) { argument = template_parameter::make_template_type({}); - auto parameter_name = - common::to_string(arg.getAsType(), cls->getASTContext()); + auto parameter_name = common::to_string(type, cls->getASTContext()); ensure_lambda_type_is_relative(parameter_name); @@ -617,14 +637,16 @@ template_parameter template_builder::process_type_argument( cls, parameter_name); if (maybe_arg) { - return *maybe_arg; + argument = *maybe_arg; } } - - argument.set_name(parameter_name); + else { + argument.set_name(parameter_name); + } } + /* // Case for unexposed template - else if ((type_name.find('<') != std::string::npos) || + else if ((type_name.find('<') != std::string::npos) && (type_name.find("type-parameter-") == 0)) { ensure_lambda_type_is_relative(type_name); @@ -660,14 +682,24 @@ template_parameter template_builder::process_type_argument( return template_parameter::make_template_type(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("type-parameter-") != std::string::npos) { + // This is some sort of template parameter with unexposed type + // parameters in the form 'type-parameter-X-Y' + 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 { + // fallback - just put whatever clang returns + argument.is_template_parameter(false); + argument.set_type( + common::to_string(type, template_decl->getASTContext())); + } } else if (type_name.find("(lambda at ") == 0) { // This is just a lambda reference @@ -677,7 +709,26 @@ template_parameter template_builder::process_type_argument( else { // This is just a regular record type process_tag_argument( - template_instantiation, template_decl, arg, argument); + template_instantiation, template_decl, type, argument); + } + + if (arg.getAsType()->isLValueReferenceType()) { + argument.is_lvalue_reference(true); + } + if (arg.getAsType()->isRValueReferenceType()) { + argument.is_rvalue_reference(true); + } + if (arg.getAsType()->isPointerType()) { + argument.is_pointer(true); + } + if (arg.getAsType()->isMemberPointerType()) { + argument.set_method_template(true); + } + if (arg.getAsType().isConstQualified()) { + argument.set_qualifier(template_parameter::cvqualifier::kConst); + } + if (arg.getAsType().isVolatileQualified()) { + argument.set_qualifier(template_parameter::cvqualifier::kVolatile); } return argument; @@ -798,6 +849,7 @@ template_parameter map_type_parameter_to_template_parameter( std::string arg = tp; if (arg == "_Bool") arg = "bool"; + return template_parameter::make_argument(arg); } @@ -809,14 +861,12 @@ std::optional build_template_parameter( auto res = template_parameter::make_template_type({}); - std::string param_qualifier{}; // e.g. const& or && - auto it = begin; auto it_next = it; it_next++; if (*it == "const") { - param_qualifier = "const"; + res.set_qualifier(template_parameter::cvqualifier::kConst); it++; it_next++; } @@ -832,8 +882,8 @@ std::optional build_template_parameter( // 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); - param_qualifier += *it_next; - res.set_qualifier(param_qualifier); + // param_qualifier += *it_next; + // res.set_qualifier(param_qualifier); return res; } // method template parameter @@ -851,8 +901,8 @@ std::optional build_template_parameter( } if (it != end && common::is_qualifier(*it)) { - param_qualifier += *it; - res.set_qualifier(param_qualifier); + // param_qualifier += *it; + // res.set_qualifier(param_qualifier); } return res; @@ -884,6 +934,13 @@ std::optional build_template_parameter( // handle args if (*it == "(") { it++; + + if(it != end && *it == "...") { + // handle elipssis arg + res.is_elipssis(true); + return res; + } + while (true) { // This will break on more complex args auto arg_separator = std::find(it, end, ","); @@ -906,14 +963,15 @@ std::optional build_template_parameter( } } - assert(*it == ")"); + //if(it!=end) + //assert(*it == ")"); it++; - if (it != end && common::is_qualifier(*it)) { - param_qualifier += *it; - res.set_qualifier(param_qualifier); - } + //if (it != end && common::is_qualifier(*it)) { + // param_qualifier += *it; + // res.set_qualifier(param_qualifier); + //} } return res; @@ -941,8 +999,11 @@ template_parameter template_builder::process_integral_argument( { assert(arg.getKind() == clang::TemplateArgument::Integral); - return template_parameter::make_argument( - std::to_string(arg.getAsIntegral().getExtValue())); + std::string result; + llvm::raw_string_ostream ostream(result); + arg.dump(ostream); + + return template_parameter::make_argument(result); } template_parameter template_builder::process_null_argument( @@ -993,56 +1054,70 @@ std::vector template_builder::process_pack_argument( } void template_builder::process_tag_argument(class_ &template_instantiation, - const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, template_parameter &argument) + const clang::TemplateDecl *template_decl, const clang::QualType type, + template_parameter &argument) { - assert(arg.getKind() == clang::TemplateArgument::Type); + [[maybe_unused]] auto current_instantiation_name = + template_instantiation.full_name(false); argument.is_template_parameter(false); + auto type_name = common::to_string(type, template_decl->getASTContext()); + argument.set_type(type_name); + const auto type_id = common::to_id(type_name); + argument.set_id(type_id); - argument.set_type( - common::to_string(arg.getAsType(), template_decl->getASTContext())); - - if (const auto *tsp = - arg.getAsType()->getAs(); + if (const auto *tsp = type->getAs(); tsp != nullptr) { if (const auto *record_type_decl = tsp->getAsRecordDecl(); record_type_decl != nullptr) { - argument.set_id(common::to_id(arg)); if (diagram().should_include( template_decl->getQualifiedNameAsString())) { // Add dependency relationship to the parent // template template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); + {relationship_t::kDependency, type_id}); } } } - else if (const auto *record_type = - arg.getAsType()->getAs(); + else if (const auto *record_type = type->getAs(); record_type != nullptr) { - if (const auto *record_type_decl = record_type->getAsRecordDecl(); - record_type_decl != nullptr) { - argument.set_id(common::to_id(arg)); + const auto *class_template_specialization = + clang::dyn_cast( + record_type->getAsRecordDecl()); + + if (class_template_specialization != nullptr) { + auto tag_argument = build_from_class_template_specialization( + *class_template_specialization); + + if (tag_argument) { + argument.set_type(tag_argument->name_and_ns()); + for (const auto &p : tag_argument->template_params()) + argument.add_template_param(p); + for (auto &r : tag_argument->relationships()) { + template_instantiation.add_relationship(std::move(r)); + } + } + } + else if (const auto *record_type_decl = record_type->getAsRecordDecl(); + record_type_decl != nullptr) { #if LLVM_VERSION_MAJOR >= 16 argument.set_type(record_type_decl->getQualifiedNameAsString()); #endif - if (diagram().should_include( - template_decl->getQualifiedNameAsString())) { + if (diagram().should_include(type_name)) { // Add dependency relationship to the parent // template template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); + {relationship_t::kDependency, type_id}); } } } - else if (const auto *enum_type = arg.getAsType()->getAs(); + else if (const auto *enum_type = type->getAs(); enum_type != nullptr) { if (enum_type->getAsTagDecl() != nullptr) { template_instantiation.add_relationship( - {relationship_t::kDependency, common::to_id(arg)}); + {relationship_t::kDependency, type_id}); } } } diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 5f3a2cd1..0c8b14ca 100644 --- a/src/class_diagram/visitor/template_builder.h +++ b/src/class_diagram/visitor/template_builder.h @@ -82,7 +82,7 @@ public: void process_tag_argument(model::class_ &template_instantiation, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, + const clang::QualType type, common::model::template_parameter &argument); template_parameter process_expression_argument( diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 9cff7ec3..7f957783 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -1875,7 +1875,7 @@ void translation_unit_visitor::process_field( template_specialization.template_params()) { LOG_DBG("Looking for nested relationships from {}::{} in " - "template {}", + "template argument {}", c.full_name(false), field_name, template_argument.to_string( config().using_namespace(), false)); diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 2af05e0a..01b98387 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -67,6 +67,10 @@ public: clanguml::class_diagram::model::diagram &diagram, const clanguml::config::class_diagram &config); + bool shouldVisitTemplateInstantiations() const { return false; } + + bool shouldVisitImplicitCode() const { return false; } + virtual bool VisitNamespaceDecl(clang::NamespaceDecl *ns); virtual bool VisitRecordDecl(clang::RecordDecl *D); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 874a4812..bb03f840 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -306,6 +306,10 @@ template <> id_t to_id(const std::string &full_name) return static_cast(std::hash{}(full_name) >> 3U); } +id_t to_id(const clang::QualType &type, const clang::ASTContext &ctx) { + return to_id(common::to_string(type, ctx)); +} + template <> id_t to_id(const clang::NamespaceDecl &declaration) { return to_id(get_qualified_name(declaration)); @@ -533,7 +537,7 @@ std::vector tokenize_unexposed_template_parameter( std::string tok; for (const char c : word) { - if (c == '(' || c == ')' || c == '[' || c == ']') { + if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' || c == '>') { if (!tok.empty()) result.push_back(tok); result.push_back(std::string{c}); @@ -544,6 +548,10 @@ std::vector tokenize_unexposed_template_parameter( result.push_back(tok); tok = ":"; } + else if (tok == ":") { + result.push_back("::"); + tok = ""; + } else { tok += ':'; } diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 0c643692..32218304 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -129,6 +129,8 @@ template id_t to_id(const T &declaration); template <> id_t to_id(const std::string &full_name); +id_t to_id(const clang::QualType &type, const clang::ASTContext &ctx); + template <> id_t to_id(const clang::NamespaceDecl &declaration); template <> id_t to_id(const clang::CXXRecordDecl &declaration); diff --git a/src/common/model/diagram_element.cc b/src/common/model/diagram_element.cc index e8392e28..bf76c0c4 100644 --- a/src/common/model/diagram_element.cc +++ b/src/common/model/diagram_element.cc @@ -46,11 +46,12 @@ void diagram_element::add_relationship(relationship &&cr) return; } - LOG_DBG("Adding relationship from: '{}' ({}) - {} - '{}'", id(), - full_name(true), to_string(cr.type()), cr.destination()); + if (!util::contains(relationships_, cr)) { + LOG_DBG("Adding relationship from: '{}' ({}) - {} - '{}'", id(), + full_name(true), to_string(cr.type()), cr.destination()); - if (!util::contains(relationships_, cr)) relationships_.emplace_back(std::move(cr)); + } } std::vector &diagram_element::relationships() diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index 7ed55175..52457da0 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -113,7 +113,10 @@ int template_parameter::calculate_specialization_match( { int res{0}; - if (qualifier() != base_template_parameter.qualifier()) + // If the potential base template has a qualifier, the current template + // must match it + if (!base_template_parameter.qualifiers().empty() && + (qualifiers() != base_template_parameter.qualifiers())) return 0; if (is_template_parameter() && @@ -211,10 +214,33 @@ bool operator!=(const template_parameter &l, const template_parameter &r) return !(l == r); } -std::string template_parameter::to_string( - const clanguml::common::model::namespace_ &using_namespace, - bool relative) const +std::string template_parameter::qualifiers_str() const { + std::string res; + if (qualifiers_.count(cvqualifier::kConst) == 1) + res += "const"; + + if (is_rvalue_reference()) + res += "&&"; + else if (is_lvalue_reference()) + res += "&"; + + if (is_pointer()) + res += "*"; + + if(res.empty()) + return res; + + return " " + res; +} + +std::string template_parameter::to_string( + const clanguml::common::model::namespace_ &using_namespace, bool relative, + bool skip_qualifiers) const +{ + if(is_elipssis()) + return "..."; + using clanguml::common::model::namespace_; assert(!(type().has_value() && concept_constraint().has_value())); @@ -235,11 +261,11 @@ std::string template_parameter::to_string( if (is_method_template()) { assert(template_params().size() > 1); + std::string unqualified; if (template_params().size() == 2) { - return fmt::format("{} {}::*{}", + unqualified = fmt::format("{} {}::*", template_params().at(0).to_string(using_namespace, relative), - template_params().at(1).to_string(using_namespace, relative), - qualifier()); + template_params().at(1).to_string(using_namespace, relative)); } else { auto it = template_params().begin(); @@ -253,9 +279,14 @@ std::string template_parameter::to_string( args.push_back(it->to_string(using_namespace, relative)); } - return fmt::format("{} ({}::*)({}){}", return_type, class_type, - fmt::join(args, ","), qualifier()); + unqualified = fmt::format("{} ({}::*)({})", return_type, class_type, + fmt::join(args, ",")); } + + if (skip_qualifiers) + return unqualified; + else + return unqualified + qualifiers_str(); } std::string res; @@ -307,8 +338,8 @@ std::string template_parameter::to_string( res += fmt::format("<{}>", fmt::join(params, ",")); } - if (!qualifier().empty()) - res += " " + qualifier(); + if (!skip_qualifiers) + res += qualifiers_str(); const auto &maybe_default_value = default_value(); if (maybe_default_value) { @@ -332,6 +363,9 @@ bool template_parameter::find_nested_relationships( // just add it and skip recursion (e.g. this is a user defined type) const auto maybe_type = type(); if (maybe_type && should_include(maybe_type.value())) { + if (is_pointer() || is_lvalue_reference() || is_rvalue_reference()) + hint = common::model::relationship_t::kAssociation; + const auto maybe_id = id(); if (maybe_id) { nested_relationships.emplace_back(maybe_id.value(), hint); @@ -349,6 +383,11 @@ bool template_parameter::find_nested_relationships( if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) { + if (template_argument.is_pointer() || + template_argument.is_lvalue_reference() || + template_argument.is_rvalue_reference()) + hint = common::model::relationship_t::kAssociation; + nested_relationships.emplace_back(maybe_id.value(), hint); added_aggregation_relationship = diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index 0811dca6..cf17a2e1 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -21,6 +21,7 @@ #include "common/model/namespace.h" #include +#include #include #include @@ -44,6 +45,13 @@ std::string to_string(template_parameter_kind_t k); /// nested templates class template_parameter { public: + enum class cvqualifier { + kConst, + kVolatile, + kLValueReference, + kRValueReference + }; + static template_parameter make_template_type(const std::string &name, const std::optional &default_value = {}, bool is_variadic = false) @@ -149,7 +157,7 @@ public: std::string to_string( const clanguml::common::model::namespace_ &using_namespace, - bool relative) const; + bool relative, bool skip_qualifiers = false) const; void add_template_param(template_parameter &&ct); @@ -186,13 +194,26 @@ public: bool is_method_template() const { return is_method_template_; } - void set_qualifier(const std::string &q) { qualifier_ = q; } + void set_qualifier(const cvqualifier q) { qualifiers_.emplace(q); } - const std::string &qualifier() const { return qualifier_; } + const std::set &qualifiers() const { return qualifiers_; } + void is_pointer(bool p) { is_pointer_ = p; } + bool is_pointer() const { return is_pointer_; } + + void is_lvalue_reference(bool p) { is_lvalue_reference_ = p; } + bool is_lvalue_reference() const { return is_lvalue_reference_; } + + void is_rvalue_reference(bool p) { is_rvalue_reference_ = p; } + bool is_rvalue_reference() const { return is_rvalue_reference_; } + + void is_elipssis(bool e) { is_elipssis_ = e; } + bool is_elipssis() const { return is_elipssis_; } private: template_parameter() = default; + std::string qualifiers_str() const; + template_parameter_kind_t kind_{template_parameter_kind_t::template_type}; /// Represents the type of non-type template parameters @@ -213,14 +234,20 @@ private: /// Can only be true when is_template_parameter_ is true bool is_template_template_parameter_{false}; + bool is_elipssis_{false}; + /// Whether the template parameter is variadic bool is_variadic_{false}; + bool is_pointer_{false}; + bool is_lvalue_reference_{false}; + bool is_rvalue_reference_{false}; + bool is_function_template_{false}; bool is_method_template_{false}; - std::string qualifier_; + std::set qualifiers_; /// Stores optional fully qualified name of constraint for this template /// parameter diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c17b96e..9491e375 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,7 @@ set(TEST_CASES test_config test_cli_handler test_filters + test_template_parser test_thread_pool_executor) foreach(TEST_NAME ${TEST_CASES}) diff --git a/tests/t00051/test_case.h b/tests/t00051/test_case.h index 6fb6d73a..a19d6067 100644 --- a/tests/t00051/test_case.h +++ b/tests/t00051/test_case.h @@ -60,20 +60,16 @@ TEST_CASE("t00051", "[test-case][class]") REQUIRE_THAT(puml, IsClassTemplate("B", - "(lambda at ../../tests/t00051/t00051.cc:43:18),(lambda at " - "../../tests/t00051/t00051.cc:43:27)")); + "(lambda at ../../tests/t00051/t00051.cc:43:18)")); + //,(lambda at ../../tests/t00051/t00051.cc:43:27)")); REQUIRE_THAT(puml, IsInstantiation(_A("B"), - _A("B<(lambda at ../../tests/t00051/t00051.cc:43:18),(lambda " - "at " - "../../tests/t00051/t00051.cc:43:27)>"))); + _A("B<(lambda at ../../tests/t00051/t00051.cc:43:18)>"))); REQUIRE_THAT(puml, IsDependency(_A("A"), - _A("B<(lambda at ../../tests/t00051/t00051.cc:43:18),(lambda " - "at " - "../../tests/t00051/t00051.cc:43:27)>"))); + _A("B<(lambda at ../../tests/t00051/t00051.cc:43:18)>"))); save_puml( config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/t00062/t00062.cc b/tests/t00062/t00062.cc index d9ccd5ef..7858d7f2 100644 --- a/tests/t00062/t00062.cc +++ b/tests/t00062/t00062.cc @@ -1,3 +1,5 @@ +#include +#include #include namespace clanguml { @@ -8,6 +10,17 @@ template struct A { U &u; }; +template struct A &> { + U &u; +}; + +template <> +struct A> &> { }; + +template struct A { + U ***u; +}; + template struct A { U &&u; }; @@ -58,5 +71,6 @@ template struct A { template <> struct A { std::vector n; }; + } } \ No newline at end of file diff --git a/tests/t00062/test_case.h b/tests/t00062/test_case.h index 335b43a2..194020cc 100644 --- a/tests/t00062/test_case.h +++ b/tests/t00062/test_case.h @@ -36,7 +36,7 @@ TEST_CASE("t00062", "[test-case][class]") REQUIRE_THAT(puml, EndsWith("@enduml\n")); // Check if all classes exist - REQUIRE_THAT(puml, IsClassTemplate("A", "T")); + REQUIRE_THAT(puml, IsClassTemplate("A", "TTTT")); REQUIRE_THAT(puml, IsClassTemplate("A", "U &")); REQUIRE_THAT(puml, IsClassTemplate("A", "U &&")); REQUIRE_THAT(puml, IsClassTemplate("A", "U const&")); From 68f067f76a71d8dd7874970696b4b0eacdc6372d Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Mon, 1 May 2023 01:40:46 +0200 Subject: [PATCH 12/22] Refactored template builder with try_as methods for different types --- src/class_diagram/visitor/template_builder.cc | 903 +++++++++--------- src/class_diagram/visitor/template_builder.h | 68 +- src/common/clang_utils.cc | 22 +- src/common/clang_utils.h | 2 + src/common/model/template_parameter.cc | 114 +-- src/common/model/template_parameter.h | 99 +- src/util/util.h | 17 + tests/CMakeLists.txt | 1 - tests/t00051/test_case.h | 6 +- tests/t00062/t00062.cc | 12 +- tests/t00062/test_case.h | 8 +- tests/test_model.cc | 24 +- 12 files changed, 691 insertions(+), 585 deletions(-) diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index e93f40a2..e66efe0b 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -436,7 +436,7 @@ void template_builder::argument_process_dispatch( break; case clang::TemplateArgument::Type: argument.push_back(process_type_argument(parent, cls, template_decl, - arg, template_instantiation, argument_index)); + arg.getAsType(), template_instantiation, argument_index)); break; case clang::TemplateArgument::Declaration: break; @@ -450,6 +450,7 @@ void template_builder::argument_process_dispatch( argument.push_back(process_template_argument(arg)); break; case clang::TemplateArgument::TemplateExpansion: + argument.push_back(process_template_expansion(arg)); break; case clang::TemplateArgument::Expression: argument.push_back(process_expression_argument(arg)); @@ -471,284 +472,124 @@ template_parameter template_builder::process_template_argument( auto arg_name = arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); + return template_parameter::make_template_type(arg_name); } +template_parameter template_builder::process_template_expansion( + const clang::TemplateArgument &arg) +{ + LOG_DBG( + "Processing template expansion argument: {}", common::to_string(arg)); + + auto arg_name = + arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); + + auto param = template_parameter::make_template_type(arg_name); + param.is_variadic(true); + + return param; +} + +clang::QualType template_builder::consume_context( + clang::QualType type, template_parameter &tp) const +{ + while (true) { + bool try_again{false}; + common::model::context ctx; + + ctx.is_const = type.isConstQualified(); + ctx.is_volatile = type.isVolatileQualified(); + + if (type->isLValueReferenceType()) { + ctx.pr = common::model::rpqualifier::kLValueReference; + try_again = true; + } + else if (type->isRValueReferenceType()) { + ctx.pr = common::model::rpqualifier::kRValueReference; + try_again = true; + } + else if (type->isPointerType()) { + ctx.pr = common::model::rpqualifier::kPointer; + try_again = true; + } + + if (type.isConstQualified() || type.isVolatileQualified()) { + ctx.is_const = type.isConstQualified(); + ctx.is_volatile = type.isVolatileQualified(); + + try_again = true; + } + + if (try_again) { + type = type.getNonReferenceType().getUnqualifiedType(); + if (type->isPointerType()) + type = type->getPointeeType(); + + tp.push_context(std::move(ctx)); + } + else + return type; + } +} + template_parameter template_builder::process_type_argument( std::optional &parent, const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, class_ &template_instantiation, - size_t argument_index) + // const clang::TemplateArgument &arg, + clang::QualType type, class_ &template_instantiation, size_t argument_index) { - assert(arg.getKind() == clang::TemplateArgument::Type); + auto type_name = common::to_string(type, &cls->getASTContext()); - auto type_name = common::to_string(arg, &cls->getASTContext()); - - auto argument = template_parameter::make_argument({}); - - auto type = arg.getAsType().getNonReferenceType().getUnqualifiedType(); - if (type->isPointerType()) - type = type->getPointeeType(); -// if (type->isMemberPointerType()) -// type = type->getPointeeType(); + std::optional argument; LOG_DBG("Processing template {} type argument: {}, {}, {}", template_decl->getQualifiedNameAsString(), type_name, - type->getTypeClassName(), common::to_string(type, cls->getASTContext())); + type->getTypeClassName(), + common::to_string(type, cls->getASTContext())); - if (const auto *function_type = type->getAs(); - function_type != nullptr) { + argument = try_as_function_prototype(parent, cls, template_decl, type, + template_instantiation, argument_index); - argument.set_function_template(true); + if (argument) + return *argument; - // Set function template return type - const auto return_type_name = - function_type->getReturnType().getAsString(); + argument = try_as_member_pointer(parent, cls, template_decl, type, + template_instantiation, argument_index); - // 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 = - get_template_argument_from_type_parameter_string( - cls, return_type_name); + if (argument) + return *argument; - if (maybe_return_arg) - argument.add_template_param(*maybe_return_arg); - else { - argument.add_template_param( - template_parameter::make_argument(return_type_name)); - } + argument = try_as_template_specialization_type(parent, cls, template_decl, + type, template_instantiation, argument_index); - // Set function template argument types - for (const auto ¶m_type : function_type->param_types()) { - auto maybe_arg = get_template_argument_from_type_parameter_string( - cls, param_type.getAsString()); + if (argument) + return *argument; - if (maybe_arg) { - argument.add_template_param(*maybe_arg); - continue; - } + argument = try_as_template_parm_type(cls, template_decl, type); - if (param_type->isBuiltinType()) { - argument.add_template_param(template_parameter::make_argument( - param_type.getAsString())); - continue; - } + if (argument) + return *argument; - const auto *param_record_type = - param_type->getAs(); - if (param_record_type == nullptr) - continue; + argument = try_as_lamda(cls, template_decl, type); - auto *classTemplateSpecialization = - llvm::dyn_cast( - param_type->getAsRecordDecl()); + if (argument) + return *argument; - if (classTemplateSpecialization != nullptr) { - // Read arg info as needed. - auto nested_template_instantiation = - build_from_class_template_specialization( - *classTemplateSpecialization, // *param_record_type, - diagram().should_include( - template_decl->getQualifiedNameAsString()) - ? std::make_optional(&template_instantiation) - : parent); + argument = try_as_record_type(parent, cls, template_decl, type, + template_instantiation, argument_index); - const auto nested_template_name = - classTemplateSpecialization->getQualifiedNameAsString(); + if (argument) + return *argument; - if (nested_template_instantiation) { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } + argument = try_as_enum_type( + parent, cls, template_decl, type, template_instantiation); - 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 (const auto *nested_template_type = - type->getAs(); - nested_template_type != nullptr) { + if (argument) + return *argument; - const auto nested_type_name = nested_template_type->getTemplateName() - .getAsTemplateDecl() - ->getQualifiedNameAsString(); - - argument.set_type(nested_type_name); - - auto nested_template_instantiation = build(cls, *nested_template_type, - diagram().should_include(template_decl->getQualifiedNameAsString()) - ? std::make_optional(&template_instantiation) - : parent); - - argument.set_id(nested_template_instantiation->id()); - - for (const auto &t : nested_template_instantiation->template_params()) - 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(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)) { - if (diagram().should_include( - template_decl->getQualifiedNameAsString())) { - template_instantiation.add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - else { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - } - - if (diagram().should_include(nested_template_instantiation_full_name)) { - diagram().add_class(std::move(nested_template_instantiation)); - } - } - else if (type->getAs() != nullptr) { - argument = template_parameter::make_template_type({}); - - auto parameter_name = common::to_string(type, 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) { - argument = *maybe_arg; - } - } - else { - 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) { - 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 - return template_parameter::make_template_type(type_name); - } - } - */ - else if (type_name.find("type-parameter-") != std::string::npos) { - // This is some sort of template parameter with unexposed type - // parameters in the form 'type-parameter-X-Y' - 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 { - // fallback - just put whatever clang returns - argument.is_template_parameter(false); - argument.set_type( - common::to_string(type, template_decl->getASTContext())); - } - } - 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, template_decl, type, argument); - } - - if (arg.getAsType()->isLValueReferenceType()) { - argument.is_lvalue_reference(true); - } - if (arg.getAsType()->isRValueReferenceType()) { - argument.is_rvalue_reference(true); - } - if (arg.getAsType()->isPointerType()) { - argument.is_pointer(true); - } - if (arg.getAsType()->isMemberPointerType()) { - argument.set_method_template(true); - } - if (arg.getAsType().isConstQualified()) { - argument.set_qualifier(template_parameter::cvqualifier::kConst); - } - if (arg.getAsType().isVolatileQualified()) { - argument.set_qualifier(template_parameter::cvqualifier::kVolatile); - } - - 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)}); - } + // fallback + return template_parameter::make_argument(type_name); } bool template_builder::find_relationships_in_unexposed_template_params( @@ -787,7 +628,7 @@ bool template_builder::find_relationships_in_unexposed_template_params( return found; } -using token_it = std::vector::const_iterator; +// using token_it = std::vector::const_iterator; namespace detail { @@ -827,171 +668,28 @@ std::string map_type_parameter_to_template_parameter( } -template_parameter map_type_parameter_to_template_parameter( - const clang::Decl *decl, const std::string &tp) +std::string map_type_parameter_to_template_parameter_name( + const clang::Decl *decl, const std::string &type_parameter) { + if (type_parameter.find("type-parameter-") != 0) + return type_parameter; + if (const auto *template_decl = llvm::dyn_cast(decl); - template_decl != nullptr && tp.find("type-parameter-") == 0) { - return template_parameter::make_template_type( - detail::map_type_parameter_to_template_parameter( - template_decl, tp)); + template_decl != nullptr) { + return detail::map_type_parameter_to_template_parameter( + template_decl, type_parameter); } 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)); + alias_decl != nullptr) { + return detail::map_type_parameter_to_template_parameter( + alias_decl, type_parameter); } - std::string arg = tp; - if (arg == "_Bool") - arg = "bool"; - - return template_parameter::make_argument(arg); -} - -std::optional build_template_parameter( - const clang::Decl *decl, token_it begin, token_it end) -{ - if (decl == nullptr) - return {}; - - auto res = template_parameter::make_template_type({}); - - auto it = begin; - auto it_next = it; - it_next++; - - if (*it == "const") { - res.set_qualifier(template_parameter::cvqualifier::kConst); - it++; - it_next++; - } - - if (it == end) - return {}; - - // simple template param without qualifiers - if (common::is_type_token(*it) && (it_next == end || *it_next == ")")) { - 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); - // param_qualifier += *it_next; - // res.set_qualifier(param_qualifier); - 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)) { - // param_qualifier += *it; - // res.set_qualifier(param_qualifier); - } - - return res; - } - else if (common::is_type_token(*it) && *it_next == "...") { - // Variadic template parameter - auto parm = map_type_parameter_to_template_parameter(decl, *it); - parm.is_variadic(true); - return parm; - } - else if (common::is_type_token(*it) && *it_next == "(") { - res.add_template_param( - map_type_parameter_to_template_parameter(decl, *it)); - it_next++; // skip '(' - res.add_template_param( - map_type_parameter_to_template_parameter(decl, *it_next)); - - it = it_next; - it++; - it_next = it; - it_next++; - - if (*it == "::" && *it_next == "*") { - res.set_method_template(true); - std::advance(it, 3); - } - - if (it != end) { - // handle args - if (*it == "(") { - it++; - - if(it != end && *it == "...") { - // handle elipssis arg - res.is_elipssis(true); - return res; - } - - while (true) { - // This will break on more complex args - 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++; - } - } - - //if(it!=end) - //assert(*it == ")"); - - it++; - - //if (it != end && common::is_qualifier(*it)) { - // param_qualifier += *it; - // res.set_qualifier(param_qualifier); - //} - } - - 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()); + // Fallback + return type_parameter; } template_parameter template_builder::process_integral_argument( @@ -1035,7 +733,7 @@ template_parameter template_builder::process_expression_argument( std::vector template_builder::process_pack_argument( std::optional &parent, const clang::NamedDecl *cls, class_ &template_instantiation, - const clang::TemplateDecl *template_decl, + const clang::TemplateDecl *base_template_decl, const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument) { @@ -1047,79 +745,360 @@ std::vector template_builder::process_pack_argument( for (const auto &a : arg.getPackAsArray()) { argument_process_dispatch(parent, cls, template_instantiation, - template_decl, a, pack_argument_index++, res); + base_template_decl, a, pack_argument_index++, res); } return res; } -void template_builder::process_tag_argument(class_ &template_instantiation, - const clang::TemplateDecl *template_decl, const clang::QualType type, - template_parameter &argument) +std::optional template_builder::try_as_member_pointer( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) { - [[maybe_unused]] auto current_instantiation_name = - template_instantiation.full_name(false); + const auto *mp_type = + common::dereference(type)->getAs(); + if (mp_type == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + type = consume_context(type, argument); + + // Handle a pointer to a data member of a class + if (mp_type->isMemberDataPointer()) { + argument.is_member_pointer(false); + argument.is_data_pointer(true); + + auto pointee_arg = process_type_argument(parent, cls, template_decl, + mp_type->getPointeeType(), template_instantiation, argument_index); + + argument.add_template_param(std::move(pointee_arg)); + + const auto *member_class_type = mp_type->getClass(); + + if (member_class_type == nullptr) + return {}; + + auto class_type_arg = process_type_argument(parent, cls, template_decl, + mp_type->getClass()->getCanonicalTypeUnqualified(), + template_instantiation, argument_index); + + argument.add_template_param(std::move(class_type_arg)); + } + // Handle pointer to class method member + else { + argument.is_member_pointer(true); + argument.is_data_pointer(false); + + const auto *function_type = + mp_type->getPointeeType()->getAs(); + + assert(function_type != nullptr); + + auto return_type_arg = process_type_argument(parent, cls, template_decl, + function_type->getReturnType(), template_instantiation, + argument_index); + + // Add return type argument + argument.add_template_param(std::move(return_type_arg)); + + const auto *member_class_type = mp_type->getClass(); + + if (member_class_type == nullptr) + return {}; + + auto class_type_arg = process_type_argument(parent, cls, template_decl, + mp_type->getClass()->getCanonicalTypeUnqualified(), + template_instantiation, argument_index); + + // Add class type argument + argument.add_template_param(std::move(class_type_arg)); + + // Add argument types + for (const auto ¶m_type : function_type->param_types()) { + argument.add_template_param( + process_type_argument(parent, cls, template_decl, param_type, + template_instantiation, argument_index)); + } + } + + return argument; +} + +std::optional template_builder::try_as_array( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *array_type = common::dereference(type)->getAsArrayTypeUnsafe(); + if (array_type == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + + type = consume_context(type, argument); + + argument.is_array(true); + + // Set function template return type + auto element_type = process_type_argument(parent, cls, template_decl, + array_type->getElementType(), template_instantiation, argument_index); + + argument.add_template_param(element_type); + + if (array_type->isDependentSizedArrayType() && + array_type->getDependence() == + clang::TypeDependence::DependentInstantiation) { + argument.add_template_param(template_parameter::make_template_type( + common::to_string(((clang::DependentSizedArrayType *)array_type) + ->getSizeExpr()))); + } + else if (array_type->isConstantArrayType()) { + argument.add_template_param(template_parameter::make_argument( + std::to_string(((clang::ConstantArrayType *)array_type) + ->getSize() + .getLimitedValue()))); + } + // TODO: Handle variable sized arrays + + return argument; +} + +std::optional template_builder::try_as_function_prototype( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *function_type = type->getAs(); + if (function_type == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + + type = consume_context(type, argument); + + argument.is_function_template(true); + + // Set function template return type + auto return_arg = process_type_argument(parent, cls, template_decl, + function_type->getReturnType(), template_instantiation, argument_index); + + argument.add_template_param(return_arg); + + // Set function template argument types + for (const auto ¶m_type : function_type->param_types()) { + argument.add_template_param(process_type_argument(parent, cls, + template_decl, param_type, template_instantiation, argument_index)); + } + + return argument; +} + +std::optional +template_builder::try_as_template_specialization_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *nested_template_type = + common::dereference(type)->getAs(); + if (nested_template_type == nullptr) + return {}; + + auto argument = template_parameter::make_argument(""); + type = consume_context(type, argument); + + const auto nested_type_name = nested_template_type->getTemplateName() + .getAsTemplateDecl() + ->getQualifiedNameAsString(); + + argument.set_type(nested_type_name); + + auto nested_template_instantiation = build(cls, *nested_template_type, + diagram().should_include(template_decl->getQualifiedNameAsString()) + ? std::make_optional(&template_instantiation) + : parent); + + argument.set_id(nested_template_instantiation->id()); + + for (const auto &t : nested_template_instantiation->template_params()) + 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(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)) { + if (diagram().should_include( + template_decl->getQualifiedNameAsString())) { + template_instantiation.add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } + else { + if (parent.has_value()) + parent.value()->add_relationship({relationship_t::kDependency, + nested_template_instantiation->id()}); + } + } + + if (diagram().should_include(nested_template_instantiation_full_name)) { + diagram().add_class(std::move(nested_template_instantiation)); + } + + return argument; +} + +std::optional template_builder::try_as_template_parm_type( + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type) +{ + auto is_variadic{false}; + + auto type_parameter = + common::dereference(type)->getAs(); + + if (type_parameter == nullptr) { + if (common::dereference(type)->getAs()) { + is_variadic = true; + type_parameter = common::dereference(type) + ->getAs() + ->getPattern() + ->getAs(); + } + } + + if (type_parameter == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + type = consume_context(type, argument); + + argument.is_variadic(is_variadic); + + auto type_name = common::to_string(type, &cls->getASTContext()); + + auto type_parameter_name = common::to_string(type, cls->getASTContext()); + + ensure_lambda_type_is_relative(type_parameter_name); + + argument.set_name(map_type_parameter_to_template_parameter_name( + cls, type_parameter_name)); + + return argument; +} + +std::optional template_builder::try_as_lamda( + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type) +{ + auto type_name = common::to_string(type, &cls->getASTContext()); + + if (type_name.find("(lambda at ") != 0) + return {}; + + auto argument = template_parameter::make_argument(""); + type = consume_context(type, argument); + + ensure_lambda_type_is_relative(type_name); + argument.set_type(type_name); + + return argument; +} + +std::optional template_builder::try_as_record_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *record_type = + common::dereference(type)->getAs(); + if (record_type == nullptr) + return {}; + + auto argument = template_parameter::make_argument({}); + type = consume_context(type, argument); - argument.is_template_parameter(false); auto type_name = common::to_string(type, template_decl->getASTContext()); argument.set_type(type_name); const auto type_id = common::to_id(type_name); argument.set_id(type_id); - if (const auto *tsp = type->getAs(); - tsp != nullptr) { - if (const auto *record_type_decl = tsp->getAsRecordDecl(); - record_type_decl != nullptr) { + const auto *class_template_specialization = + clang::dyn_cast( + record_type->getAsRecordDecl()); - if (diagram().should_include( - template_decl->getQualifiedNameAsString())) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, type_id}); + if (class_template_specialization != nullptr) { + auto tag_argument = build_from_class_template_specialization( + *class_template_specialization); + + if (tag_argument) { + argument.set_type(tag_argument->name_and_ns()); + for (const auto &p : tag_argument->template_params()) + argument.add_template_param(p); + for (auto &r : tag_argument->relationships()) { + template_instantiation.add_relationship(std::move(r)); + } + + if (diagram().should_include(tag_argument->full_name(false))) { + if (parent.has_value()) + parent.value()->add_relationship( + {relationship_t::kDependency, tag_argument->id()}); + + diagram().add_class(std::move(tag_argument)); } } } - else if (const auto *record_type = type->getAs(); - record_type != nullptr) { - - const auto *class_template_specialization = - clang::dyn_cast( - record_type->getAsRecordDecl()); - - if (class_template_specialization != nullptr) { - auto tag_argument = build_from_class_template_specialization( - *class_template_specialization); - - if (tag_argument) { - argument.set_type(tag_argument->name_and_ns()); - for (const auto &p : tag_argument->template_params()) - argument.add_template_param(p); - for (auto &r : tag_argument->relationships()) { - template_instantiation.add_relationship(std::move(r)); - } - } - } - else if (const auto *record_type_decl = record_type->getAsRecordDecl(); - record_type_decl != nullptr) { + else if (const auto *record_type_decl = record_type->getAsRecordDecl(); + record_type_decl != nullptr) { #if LLVM_VERSION_MAJOR >= 16 - argument.set_type(record_type_decl->getQualifiedNameAsString()); + argument.set_type(record_type_decl->getQualifiedNameAsString()); #endif - if (diagram().should_include(type_name)) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, type_id}); - } - } - } - else if (const auto *enum_type = type->getAs(); - enum_type != nullptr) { - if (enum_type->getAsTagDecl() != nullptr) { + if (diagram().should_include(type_name)) { + // Add dependency relationship to the parent + // template template_instantiation.add_relationship( {relationship_t::kDependency, type_id}); } } + + return argument; +} + +std::optional template_builder::try_as_enum_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation) +{ + const auto *enum_type = type->getAs(); + if (enum_type == nullptr) + return {}; + + auto argument = template_parameter::make_argument({}); + type = consume_context(type, argument); + + auto type_name = common::to_string(type, template_decl->getASTContext()); + argument.set_type(type_name); + const auto type_id = common::to_id(type_name); + argument.set_id(type_id); + + if (enum_type->getAsTagDecl() != nullptr) { + template_instantiation.add_relationship( + {relationship_t::kDependency, type_id}); + } + + return argument; } bool template_builder::add_base_classes(class_ &tinst, diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 0c8b14ca..999a024a 100644 --- a/src/class_diagram/visitor/template_builder.h +++ b/src/class_diagram/visitor/template_builder.h @@ -80,11 +80,6 @@ public: const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument); - void process_tag_argument(model::class_ &template_instantiation, - const clang::TemplateDecl *template_decl, - const clang::QualType type, - common::model::template_parameter &argument); - template_parameter process_expression_argument( const clang::TemplateArgument &arg); @@ -100,30 +95,73 @@ public: std::vector process_pack_argument( std::optional &parent, const clang::NamedDecl *cls, class_ &template_instantiation, - const clang::TemplateDecl *template_decl, + const clang::TemplateDecl *base_template_decl, const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument); template_parameter process_type_argument( std::optional &parent, - const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, - model::class_ &template_instantiation, size_t argument_index); + const clang::NamedDecl *cls, + const clang::TemplateDecl *base_template_decl, + // const clang::TemplateArgument &arg, + clang::QualType type, 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); + common::model::template_parameter process_template_expansion( + const clang::TemplateArgument &arg); + + std::optional try_as_function_prototype( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + + std::optional try_as_array( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + + std::optional try_as_template_specialization_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + + std::optional try_as_template_parm_type( + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type); + + std::optional try_as_lamda(const clang::NamedDecl *cls, + const clang::TemplateDecl *template_decl, clang::QualType &type); + + std::optional try_as_record_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + + std::optional try_as_enum_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation); + + std::optional try_as_member_pointer( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + + clang::QualType consume_context( + clang::QualType type, template_parameter &tp) const; 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, std::string type_name) const; - common::visitor::ast_id_mapper &id_mapper(); clang::SourceManager &source_manager() const; diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index bb03f840..89b5869e 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -306,7 +306,8 @@ template <> id_t to_id(const std::string &full_name) return static_cast(std::hash{}(full_name) >> 3U); } -id_t to_id(const clang::QualType &type, const clang::ASTContext &ctx) { +id_t to_id(const clang::QualType &type, const clang::ASTContext &ctx) +{ return to_id(common::to_string(type, ctx)); } @@ -520,6 +521,22 @@ bool is_type_token(const std::string &t) (is_identifier(t) && !is_qualifier(t) && !is_bracket(t)); } +clang::QualType dereference(clang::QualType type) +{ + auto res = type; + + while (true) { + if (res->isReferenceType()) + res = res.getNonReferenceType(); + else if (res->isPointerType()) + res = res->getPointeeType(); + else + break; + } + + return res; +} + std::vector tokenize_unexposed_template_parameter( const std::string &t) { @@ -537,7 +554,8 @@ std::vector tokenize_unexposed_template_parameter( std::string tok; for (const char c : word) { - if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' || c == '>') { + if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' || + c == '>') { if (!tok.empty()) result.push_back(tok); result.push_back(std::string{c}); diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 32218304..e8cede7e 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -184,4 +184,6 @@ bool is_qualified_identifier(const std::string &t); bool is_type_token(const std::string &t); +clang::QualType dereference(clang::QualType type); + } // namespace clanguml::common diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index 52457da0..386858f3 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -69,8 +69,9 @@ void template_parameter::set_name(const std::string &name) { assert(kind_ != template_parameter_kind_t::argument); - if (name.empty()) + if (name.empty()) { return; + } if (util::ends_with(name, std::string{"..."})) { name_ = name.substr(0, name.size() - 3); @@ -113,10 +114,11 @@ int template_parameter::calculate_specialization_match( { int res{0}; - // If the potential base template has a qualifier, the current template - // must match it - if (!base_template_parameter.qualifiers().empty() && - (qualifiers() != base_template_parameter.qualifiers())) + // If the potential base template has a deduction context (e.g. const&), + // the specialization must have the same and possibly more + if (!base_template_parameter.deduced_context().empty() && + !util::starts_with( + deduced_context(), base_template_parameter.deduced_context())) return 0; if (is_template_parameter() && @@ -126,7 +128,7 @@ int template_parameter::calculate_specialization_match( is_variadic() == is_variadic() && is_function_template() == base_template_parameter.is_function_template() && - is_method_template() == base_template_parameter.is_method_template()) { + is_member_pointer() == base_template_parameter.is_member_pointer()) { return 1; } @@ -137,7 +139,6 @@ int template_parameter::calculate_specialization_match( maybe_template_parameter_type.has_value() && !base_template_parameter.is_template_parameter() && !is_template_parameter()) { - if (maybe_base_template_parameter_type.value() != maybe_template_parameter_type.value()) return 0; @@ -145,11 +146,20 @@ int template_parameter::calculate_specialization_match( res++; } + if (base_template_parameter.is_array() && !is_array()) + return 0; + + if (base_template_parameter.is_array() && is_array()) + res++; + if (base_template_parameter.is_function_template() && !is_function_template()) return 0; - if (base_template_parameter.is_method_template() && !is_method_template()) + if (base_template_parameter.is_member_pointer() && !is_member_pointer()) + return 0; + + if (base_template_parameter.is_data_pointer() && !is_data_pointer()) return 0; if (!base_template_parameter.template_params().empty() && @@ -214,31 +224,22 @@ bool operator!=(const template_parameter &l, const template_parameter &r) return !(l == r); } -std::string template_parameter::qualifiers_str() const +std::string template_parameter::deduced_context_str() const { - std::string res; - if (qualifiers_.count(cvqualifier::kConst) == 1) - res += "const"; + std::vector deduced_contexts; - if (is_rvalue_reference()) - res += "&&"; - else if (is_lvalue_reference()) - res += "&"; + for (const auto &c : deduced_context()) { + deduced_contexts.push_back(c.to_string()); + } - if (is_pointer()) - res += "*"; - - if(res.empty()) - return res; - - return " " + res; + return fmt::format("{}", fmt::join(deduced_contexts, " ")); } std::string template_parameter::to_string( const clanguml::common::model::namespace_ &using_namespace, bool relative, bool skip_qualifiers) const { - if(is_elipssis()) + if (is_elipssis()) return "..."; using clanguml::common::model::namespace_; @@ -258,35 +259,41 @@ std::string template_parameter::to_string( "{}({})", return_type, fmt::join(function_args, ",")); } - if (is_method_template()) { - assert(template_params().size() > 1); + if (is_data_pointer()) { + assert(template_params().size() == 2); - std::string unqualified; - if (template_params().size() == 2) { - unqualified = fmt::format("{} {}::*", - template_params().at(0).to_string(using_namespace, relative), - template_params().at(1).to_string(using_namespace, relative)); - } - 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)); - } - - unqualified = fmt::format("{} ({}::*)({})", return_type, class_type, - fmt::join(args, ",")); - } + std::string unqualified = fmt::format("{} {}::*", + template_params().at(0).to_string(using_namespace, relative), + template_params().at(1).to_string(using_namespace, relative)); if (skip_qualifiers) return unqualified; - else - return unqualified + qualifiers_str(); + else { + return util::join(" ", unqualified, deduced_context_str()); + } + } + + if (is_member_pointer()) { + assert(template_params().size() > 1); + + 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)); + } + + std::string unqualified = fmt::format( + "{} ({}::*)({})", return_type, class_type, fmt::join(args, ",")); + if (skip_qualifiers) + return unqualified; + else { + return util::join(" ", unqualified, deduced_context_str()); + } } std::string res; @@ -339,7 +346,7 @@ std::string template_parameter::to_string( } if (!skip_qualifiers) - res += qualifiers_str(); + res = util::join(" ", res, deduced_context_str()); const auto &maybe_default_value = default_value(); if (maybe_default_value) { @@ -362,8 +369,9 @@ bool template_parameter::find_nested_relationships( // If this type argument should be included in the relationship // just add it and skip recursion (e.g. this is a user defined type) const auto maybe_type = type(); + if (maybe_type && should_include(maybe_type.value())) { - if (is_pointer() || is_lvalue_reference() || is_rvalue_reference()) + if (is_association()) hint = common::model::relationship_t::kAssociation; const auto maybe_id = id(); @@ -383,9 +391,7 @@ bool template_parameter::find_nested_relationships( if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) { - if (template_argument.is_pointer() || - template_argument.is_lvalue_reference() || - template_argument.is_rvalue_reference()) + if (template_argument.is_association()) hint = common::model::relationship_t::kAssociation; nested_relationships.emplace_back(maybe_id.value(), hint); diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index cf17a2e1..5729f9ff 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -20,6 +20,7 @@ #include "common/model/enums.h" #include "common/model/namespace.h" +#include #include #include #include @@ -31,10 +32,49 @@ enum class template_parameter_kind_t { template_type, template_template_type, non_type_template, - argument, // a.k.a. type parameter specialization + argument, concept_constraint }; +// TODO: rename to include the pointer and reference +enum class cvqualifier { kConst, kVolatile }; + +enum class rpqualifier { kLValueReference, kRValueReference, kPointer, kNone }; + +struct context { + bool is_const; + bool is_volatile; + rpqualifier pr{rpqualifier::kNone}; + + std::string to_string() const + { + std::vector cv_qualifiers; + if (is_const) + cv_qualifiers.push_back("const"); + if (is_volatile) + cv_qualifiers.push_back("volatile"); + + auto res = fmt::format("{}", fmt::join(cv_qualifiers, " ")); + + if (pr == rpqualifier::kPointer) + res += "*"; + else if (pr == rpqualifier::kLValueReference) + res += "&"; + else if (pr == rpqualifier::kRValueReference) + res += "&&"; + + return res; + } + + bool operator==(const context &rhs) const + { + return is_const == rhs.is_const && is_volatile == rhs.is_volatile && + pr == rhs.pr; + } + + bool operator!=(const context &rhs) const { return !(rhs == *this); } +}; + std::string to_string(template_parameter_kind_t k); /// @brief Represents template parameter, template arguments or concept @@ -45,13 +85,6 @@ std::string to_string(template_parameter_kind_t k); /// nested templates class template_parameter { public: - enum class cvqualifier { - kConst, - kVolatile, - kLValueReference, - kRValueReference - }; - static template_parameter make_template_type(const std::string &name, const std::optional &default_value = {}, bool is_variadic = false) @@ -167,6 +200,15 @@ public: void clear_params() { template_params_.clear(); } + bool is_association() const + { + return std::any_of(deduced_context().begin(), deduced_context().end(), + [](const auto &c) { + return c.pr == rpqualifier::kPointer || + c.pr == rpqualifier::kLValueReference; + }); + } + bool find_nested_relationships( std::vector> &nested_relationships, @@ -186,33 +228,29 @@ public: void set_unexposed(bool unexposed) { is_unexposed_ = unexposed; } - void set_function_template(bool ft) { is_function_template_ = ft; } - + void is_function_template(bool ft) { is_function_template_ = ft; } bool is_function_template() const { return is_function_template_; } - void set_method_template(bool mt) { is_method_template_ = mt; } + void is_member_pointer(bool m) { is_member_pointer_ = m; } + bool is_member_pointer() const { return is_member_pointer_; } - bool is_method_template() const { return is_method_template_; } + void is_data_pointer(bool m) { is_data_pointer_ = m; } + bool is_data_pointer() const { return is_data_pointer_; } - void set_qualifier(const cvqualifier q) { qualifiers_.emplace(q); } + void is_array(bool a) { is_array_ = a; } + bool is_array() const { return is_array_; } - const std::set &qualifiers() const { return qualifiers_; } - - void is_pointer(bool p) { is_pointer_ = p; } - bool is_pointer() const { return is_pointer_; } - - void is_lvalue_reference(bool p) { is_lvalue_reference_ = p; } - bool is_lvalue_reference() const { return is_lvalue_reference_; } - - void is_rvalue_reference(bool p) { is_rvalue_reference_ = p; } - bool is_rvalue_reference() const { return is_rvalue_reference_; } + void push_context(const context q) { context_.push_front(q); } + const std::deque &deduced_context() const { return context_; } + void deduced_context(const std::deque &c) { context_ = c; } void is_elipssis(bool e) { is_elipssis_ = e; } bool is_elipssis() const { return is_elipssis_; } + private: template_parameter() = default; - std::string qualifiers_str() const; + std::string deduced_context_str() const; template_parameter_kind_t kind_{template_parameter_kind_t::template_type}; @@ -239,15 +277,16 @@ private: /// Whether the template parameter is variadic bool is_variadic_{false}; - bool is_pointer_{false}; - bool is_lvalue_reference_{false}; - bool is_rvalue_reference_{false}; - bool is_function_template_{false}; - bool is_method_template_{false}; + bool is_data_pointer_{false}; - std::set qualifiers_; + bool is_member_pointer_{false}; + + bool is_array_{false}; + + /// Stores the template parameter/argument deduction context e.g. const& + std::deque context_; /// Stores optional fully qualified name of constraint for this template /// parameter diff --git a/src/util/util.h b/src/util/util.h index e992ef95..19892657 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -92,9 +92,26 @@ std::string get_git_toplevel_dir(); std::vector split( std::string str, std::string_view delimiter, bool skip_empty = true); +template void erase_if(std::vector &v, F &&f) +{ + v.erase(std::remove_if(v.begin(), v.end(), std::forward(f)), v.end()); +} + std::string join( const std::vector &toks, std::string_view delimiter); +template +std::string join(std::string_view delimiter, Args... args) +{ + std::vector coll{args...}; + + erase_if(coll, [](const auto &s) { + return s.find_first_not_of(" \t") == std::string::npos; + }); + + return fmt::format("{}", fmt::join(coll, delimiter)); +} + /** * @brief Abbreviate string to max_length, and replace last 3 characters * with ellipsis. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9491e375..4c17b96e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,7 +40,6 @@ set(TEST_CASES test_config test_cli_handler test_filters - test_template_parser test_thread_pool_executor) foreach(TEST_NAME ${TEST_CASES}) diff --git a/tests/t00051/test_case.h b/tests/t00051/test_case.h index a19d6067..14269b36 100644 --- a/tests/t00051/test_case.h +++ b/tests/t00051/test_case.h @@ -59,9 +59,9 @@ TEST_CASE("t00051", "[test-case][class]") REQUIRE_THAT(puml, (IsMethod("ff", "void"))); REQUIRE_THAT(puml, - IsClassTemplate("B", - "(lambda at ../../tests/t00051/t00051.cc:43:18)")); - //,(lambda at ../../tests/t00051/t00051.cc:43:27)")); + IsClassTemplate( + "B", "(lambda at ../../tests/t00051/t00051.cc:43:18)")); + //,(lambda at ../../tests/t00051/t00051.cc:43:27)")); REQUIRE_THAT(puml, IsInstantiation(_A("B"), diff --git a/tests/t00062/t00062.cc b/tests/t00062/t00062.cc index 7858d7f2..d1f81e85 100644 --- a/tests/t00062/t00062.cc +++ b/tests/t00062/t00062.cc @@ -17,7 +17,7 @@ template struct A &> { template <> struct A> &> { }; -template struct A { +template struct A { U ***u; }; @@ -71,6 +71,14 @@ template struct A { template <> struct A { std::vector n; }; - +// +// template struct eval; +// +// template