/** * 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" #include "common/clang_utils.h" #include "translation_unit_visitor.h" #include 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::NamedDecl *cls, const clang::TemplateSpecializationType &template_type_decl, std::optional parent) { // // Here we'll hold the template base class 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, 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}); 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 {} " "- 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; } 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 clang::ClassTemplateDecl *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}); template_instantiation.template_specialization_found(true); } 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 {}", template_instantiation_ptr->full_name(false), templated_decl_id); } return template_instantiation_ptr; } void template_builder::process_template_arguments( std::optional &parent, const clang::NamedDecl *cls, std::deque> &template_base_params, const clang::ArrayRef &template_args, class_ &template_instantiation, 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; // For now ignore the default template arguments of templates which // do not match the inclusion filters, 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 // overridden if (!diagram().should_include( template_decl->getQualifiedNameAsString())) { const auto *maybe_type_parm_decl = clang::dyn_cast( template_decl->getTemplateParameters()->getParam( std::min(arg_index, static_cast( template_decl->getTemplateParameters() ->size()) - 1))); if (maybe_type_parm_decl != nullptr && maybe_type_parm_decl->hasDefaultArgument()) { continue; } } // // Handle the template parameter/argument based on its kind // argument_process_dispatch(parent, cls, template_instantiation, template_decl, arg, arg_index, 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, true)); 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::NamedDecl *cls, class_ &template_instantiation, const clang::TemplateDecl *template_decl, const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument) { LOG_DBG("Processing argument {} in template class: {}", argument_index, cls->getQualifiedNameAsString()); switch (arg.getKind()) { case clang::TemplateArgument::Null: argument.push_back(process_null_argument(arg)); break; case clang::TemplateArgument::Template: argument.push_back(process_template_argument(arg)); break; case clang::TemplateArgument::Type: argument.push_back(process_type_argument(parent, cls, template_decl, arg.getAsType(), template_instantiation, argument_index)); 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::TemplateExpansion: argument.push_back(process_template_expansion(arg)); 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, template_decl, arg, argument_index, argument)) { argument.push_back(a); } break; } } 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); } 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; if (type->isPointerType() || type->isReferenceType()) { if (type.isConstQualified() || type.isVolatileQualified()) { ctx.is_ref_const = type.isConstQualified(); ctx.is_ref_volatile = type.isVolatileQualified(); try_again = true; } } 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->isMemberFunctionPointerType() && type->getPointeeType()->getAs() != nullptr) { const auto ref_qualifier = type->getPointeeType() // NOLINT ->getAs() // NOLINT ->getRefQualifier(); if (ref_qualifier == clang::RefQualifierKind::RQ_RValue) { ctx.pr = common::model::rpqualifier::kRValueReference; try_again = true; } else if (ref_qualifier == clang::RefQualifierKind::RQ_LValue) { ctx.pr = common::model::rpqualifier::kLValueReference; try_again = true; } } else if (type->isPointerType()) { ctx.pr = common::model::rpqualifier::kPointer; try_again = true; } if (try_again) { if (type->isPointerType()) { if (type->getPointeeType().isConstQualified()) ctx.is_const = true; if (type->getPointeeType().isVolatileQualified()) ctx.is_volatile = true; type = type->getPointeeType().getUnqualifiedType(); } else if (type->isReferenceType()) { if (type.getNonReferenceType().isConstQualified()) ctx.is_const = true; if (type.getNonReferenceType().isVolatileQualified()) ctx.is_volatile = true; type = type.getNonReferenceType().getUnqualifiedType(); } else if (type.isConstQualified() || type.isVolatileQualified()) { ctx.is_const = type.isConstQualified(); ctx.is_volatile = type.isVolatileQualified(); } tp.push_context(ctx); if (type->isMemberFunctionPointerType()) return type; } else return type; } } template_parameter template_builder::process_type_argument( std::optional &parent, const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, clang::QualType type, class_ &template_instantiation, size_t argument_index) { std::optional argument; if (type->getAs() != nullptr) { type = type->getAs()->getNamedType(); // NOLINT } auto type_name = common::to_string(type, &cls->getASTContext()); LOG_DBG("Processing template {} type argument {}: {}, {}, {}", template_decl->getQualifiedNameAsString(), argument_index, type_name, type->getTypeClassName(), common::to_string(type, cls->getASTContext())); argument = try_as_function_prototype(parent, cls, template_decl, type, template_instantiation, argument_index); if (argument) return *argument; argument = try_as_member_pointer(parent, cls, template_decl, type, template_instantiation, argument_index); if (argument) return *argument; argument = try_as_array(parent, cls, template_decl, type, template_instantiation, argument_index); if (argument) return *argument; argument = try_as_template_parm_type(cls, template_decl, type); if (argument) return *argument; argument = try_as_template_specialization_type(parent, cls, template_decl, type, template_instantiation, argument_index); if (argument) return *argument; argument = try_as_lambda(cls, template_decl, type); if (argument) return *argument; argument = try_as_record_type(parent, cls, template_decl, type, template_instantiation, argument_index); if (argument) return *argument; argument = try_as_enum_type( parent, cls, template_decl, type, template_instantiation); if (argument) return *argument; argument = try_as_builtin_type(parent, type, template_decl); if (argument) return *argument; // fallback return template_parameter::make_argument(type_name); } 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; } 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; } } // namespace detail 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) { return detail::map_type_parameter_to_template_parameter( template_decl, type_parameter); } if (const auto *alias_decl = llvm::dyn_cast(decl); alias_decl != nullptr) { return detail::map_type_parameter_to_template_parameter( alias_decl, type_parameter); } // Fallback return type_parameter; } template_parameter template_builder::process_integral_argument( const clang::TemplateArgument &arg) { assert(arg.getKind() == clang::TemplateArgument::Integral); 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( 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); LOG_DBG("Processing nullptr argument: {}", common::to_string(arg)); 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::NamedDecl *cls, class_ &template_instantiation, const clang::TemplateDecl *base_template_decl, const clang::TemplateArgument &arg, size_t argument_index, std::vector & /*argument*/) { assert(arg.getKind() == clang::TemplateArgument::Pack); std::vector res; auto pack_argument_index = argument_index; for (const auto &a : arg.getPackAsArray()) { argument_process_dispatch(parent, cls, template_instantiation, base_template_decl, a, pack_argument_index++, res); } return res; } 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) { 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) // NOLINT ->getSizeExpr()))); } else if (array_type->isConstantArrayType()) { argument.add_template_param(template_parameter::make_argument( std::to_string(((clang::ConstantArrayType *)array_type) // NOLINT ->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 if (function_type->isVariadic() && function_type->param_types().empty()) { auto fallback_arg = template_parameter::make_argument({}); fallback_arg.is_ellipsis(true); argument.add_template_param(std::move(fallback_arg)); } else { 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); auto nested_type_name = nested_template_type->getTemplateName() .getAsTemplateDecl() ->getQualifiedNameAsString(); if (clang::dyn_cast( nested_template_type->getTemplateName().getAsTemplateDecl()) != nullptr) { if (const auto *template_specialization_decl = clang::dyn_cast(cls); template_specialization_decl != nullptr) { nested_type_name = template_specialization_decl->getDescribedTemplateParams() ->getParam(argument_index) ->getNameAsString(); } else { // fallback nested_type_name = "template"; } argument.is_template_template_parameter(true); } 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}; const auto *type_parameter = common::dereference(type)->getAs(); auto type_name = common::to_string(type, &cls->getASTContext()); if (type_parameter == nullptr) { if (const auto *pet = common::dereference(type)->getAs(); pet != nullptr) { is_variadic = true; type_parameter = pet->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_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_lambda( 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); 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); 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)); } 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_decl = record_type->getAsRecordDecl(); record_type_decl != 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; } std::optional template_builder::try_as_builtin_type( std::optional & /*parent*/, clang::QualType &type, const clang::TemplateDecl *template_decl) { const auto *builtin_type = type->getAs(); if (builtin_type == nullptr) return {}; auto type_name = common::to_string(type, template_decl->getASTContext()); auto argument = template_parameter::make_argument(type_name); type = consume_context(type, argument); argument.set_type(type_name); return argument; } 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; } 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