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