/** * @file src/class_diagram/visitor/translation_unit_visitor.cc * * Copyright (c) 2021-2024 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 "translation_unit_visitor.h" #include "common/clang_utils.h" #include #include #include #include namespace clanguml::class_diagram::visitor { translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, clanguml::class_diagram::model::diagram &diagram, const clanguml::config::class_diagram &config) : visitor_specialization_t{sm, diagram, config} , template_builder_{diagram, config, *this} { } std::unique_ptr translation_unit_visitor::create_element( const clang::NamedDecl *decl) const { auto cls = std::make_unique(config().using_namespace()); cls->is_struct(common::is_struct(decl)); return cls; } bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns) { assert(ns != nullptr); if (config().package_type() == config::package_type_t::kDirectory) return true; if (ns->isAnonymousNamespace() || ns->isInline()) return true; LOG_DBG("= Visiting namespace declaration {} at {}", ns->getQualifiedNameAsString(), ns->getLocation().printToString(source_manager())); auto package_path = namespace_{common::get_qualified_name(*ns)}; auto package_parent = package_path; std::string name; if (!package_path.is_empty()) name = package_path.name(); if (!package_parent.is_empty()) package_parent.pop_back(); const auto usn = config().using_namespace(); auto p = std::make_unique(usn); package_path = package_path.relative_to(usn); p->set_name(name); p->set_namespace(package_parent); p->set_id(common::to_id(*ns)); id_mapper().add(ns->getID(), p->id()); if (diagram().should_include(*p) && !diagram().get(p->id())) { process_comment(*ns, *p); set_source_location(*ns, *p); p->set_style(p->style_spec()); for (const auto *attr : ns->attrs()) { if (attr->getKind() == clang::attr::Kind::Deprecated) { p->set_deprecated(true); break; } } if (!p->skip()) { diagram().add(package_path, std::move(p)); } } return true; } bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) { assert(enm != nullptr); // Anonymous enum values should be rendered as class fields // with type enum if (enm->getNameAsString().empty()) return true; if (!should_include(enm)) return true; LOG_DBG("= Visiting enum declaration {} at {}", enm->getQualifiedNameAsString(), enm->getLocation().printToString(source_manager())); auto e_ptr = std::make_unique(config().using_namespace()); auto &e = *e_ptr; std::string qualified_name = common::get_qualified_name(*enm); auto ns{common::get_tag_namespace(*enm)}; const auto *parent = enm->getParent(); // Id of parent class or struct in which this enum is potentially nested std::optional parent_id_opt; if (parent != nullptr) { const auto *parent_record_decl = clang::dyn_cast(parent); if (parent_record_decl != nullptr) { common::id_t local_id{parent_record_decl->getID()}; // First check if the parent has been added to the diagram as // regular class parent_id_opt = id_mapper().get_global_id(local_id); // If not, check if the parent template declaration is in the model if (!parent_id_opt) { if (parent_record_decl->getDescribedTemplate() != nullptr) { local_id = parent_record_decl->getDescribedTemplate()->getID(); parent_id_opt = id_mapper().get_global_id(local_id); } } } } if (parent_id_opt && diagram().find(*parent_id_opt)) { auto parent_class = diagram().find(*parent_id_opt); e.set_namespace(ns); e.set_name(parent_class.value().name() + "##" + enm->getNameAsString()); e.set_id(common::to_id(e.full_name(false))); e.add_relationship({relationship_t::kContainment, *parent_id_opt}); e.nested(true); } else { e.set_name(common::get_tag_name(*enm)); e.set_namespace(ns); e.set_id(common::to_id(e.full_name(false))); } id_mapper().add(enm->getID(), e.id()); process_comment(*enm, e); set_source_location(*enm, e); set_owning_module(*enm, e); if (e.skip()) return true; e.set_style(e.style_spec()); for (const auto &ev : enm->enumerators()) { e.constants().push_back(ev->getNameAsString()); } add_enum(std::move(e_ptr)); return true; } bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( clang::ClassTemplateSpecializationDecl *cls) { if (!should_include(cls)) return true; LOG_DBG("= Visiting template specialization declaration {} at {} " "(described class id {})", cls->getQualifiedNameAsString(), cls->getLocation().printToString(source_manager()), cls->getSpecializedTemplate() ? cls->getSpecializedTemplate()->getTemplatedDecl()->getID() : 0); // TODO: Add support for classes defined in function/method bodies if (cls->isLocalClass() != nullptr) return true; auto template_specialization_ptr = process_template_specialization(cls); if (!template_specialization_ptr) return true; auto &template_specialization = *template_specialization_ptr; if (cls->hasBody()) { process_template_specialization_children(cls, template_specialization); } if (cls->hasDefinition()) { // Process template specialization bases process_class_bases(cls, template_specialization); // Process class child entities process_class_children(cls, template_specialization); } if (!template_specialization.template_specialization_found()) { // Only do this if we haven't found a better specialization during // construction of the template specialization const common::id_t ast_id{cls->getSpecializedTemplate()->getID()}; const auto maybe_id = id_mapper().get_global_id(ast_id); if (maybe_id.has_value()) template_specialization.add_relationship( {relationship_t::kInstantiation, maybe_id.value()}); } if (diagram().should_include(template_specialization)) { const auto full_name = template_specialization.full_name(false); const auto id = template_specialization.id(); LOG_DBG("Adding class template specialization {} with id {}", full_name, id); add_class(std::move(template_specialization_ptr)); } return true; } bool translation_unit_visitor::VisitTypeAliasTemplateDecl( clang::TypeAliasTemplateDecl *cls) { if (!should_include(cls)) return true; LOG_DBG("= Visiting template type alias declaration {} at {}", cls->getQualifiedNameAsString(), cls->getLocation().printToString(source_manager())); const auto *template_type_specialization_ptr = cls->getTemplatedDecl() ->getUnderlyingType() ->getAs(); if (template_type_specialization_ptr == nullptr) return true; auto template_specialization_ptr = std::make_unique(config().using_namespace()); tbuilder().build_from_template_specialization_type( *template_specialization_ptr, cls, *template_type_specialization_ptr); if (diagram().should_include(*template_specialization_ptr)) { const auto name = template_specialization_ptr->full_name(); const auto id = template_specialization_ptr->id(); LOG_DBG("Adding class {} with id {}", name, id); set_source_location(*cls, *template_specialization_ptr); set_owning_module(*cls, *template_specialization_ptr); add_class(std::move(template_specialization_ptr)); } return true; } bool translation_unit_visitor::VisitClassTemplateDecl( clang::ClassTemplateDecl *cls) { if (!should_include(cls)) return true; LOG_DBG("= Visiting class template declaration {} at {}", cls->getQualifiedNameAsString(), cls->getLocation().printToString(source_manager())); auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); if (!c_ptr) return true; add_processed_template_class(cls->getQualifiedNameAsString()); tbuilder().build_from_template_declaration(*c_ptr, *cls, *c_ptr); // Override the id with the template id, for now we don't care about the // underlying templated class id const auto cls_full_name = c_ptr->full_name(false); const auto id = common::to_id(cls_full_name); c_ptr->set_id(id); c_ptr->is_template(true); id_mapper().add(cls->getID(), id); constexpr auto kMaxConstraintCount = 24U; llvm::SmallVector constraints{}; if (cls->hasAssociatedConstraints()) { cls->getAssociatedConstraints(constraints); } for (const auto *expr : constraints) { find_relationships_in_constraint_expression(*c_ptr, expr); } if (!cls->getTemplatedDecl()->isCompleteDefinition()) { forward_declarations_.emplace(id, std::move(c_ptr)); return true; } process_class_declaration(*cls->getTemplatedDecl(), *c_ptr); forward_declarations_.erase(id); if (diagram().should_include(*c_ptr)) { const auto name = c_ptr->full_name(); LOG_DBG("Adding class template {} with id {}", name, id); add_class(std::move(c_ptr)); } return true; } bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *rec) { if (clang::dyn_cast_or_null(rec) != nullptr) // This is handled by VisitCXXRecordDecl() return true; // It seems we are in a C (not C++) translation unit if (!should_include(rec)) return true; LOG_DBG("= Visiting record declaration {} at {}", rec->getQualifiedNameAsString(), rec->getLocation().printToString(source_manager())); auto record_ptr = create_record_declaration(rec); if (!record_ptr) return true; const auto rec_id = record_ptr->id(); id_mapper().add(rec->getID(), rec_id); auto &record_model = diagram().find(rec_id).has_value() ? *diagram().find(rec_id).get() : *record_ptr; if (rec->isCompleteDefinition() && !record_model.complete()) { process_record_members(rec, record_model); record_model.complete(true); } auto id = record_model.id(); if (!rec->isCompleteDefinition()) { forward_declarations_.emplace(id, std::move(record_ptr)); return true; } forward_declarations_.erase(id); if (diagram().should_include(record_model)) { LOG_DBG("Adding struct/union {} with id {}", record_model.full_name(false), record_model.id()); add_class(std::move(record_ptr)); } else { LOG_DBG("Skipping struct/union {} with id {}", record_model.full_name(), record_model.id()); } return true; } bool translation_unit_visitor::TraverseConceptDecl(clang::ConceptDecl *cpt) { if (!should_include(cpt)) return true; LOG_DBG("= Visiting concept (isType: {}) declaration {} at {}", cpt->isTypeConcept(), cpt->getQualifiedNameAsString(), cpt->getLocation().printToString(source_manager())); auto concept_model = create_concept_declaration(cpt); if (!concept_model) return true; const auto concept_id = concept_model->id(); id_mapper().add(cpt->getID(), concept_id); tbuilder().build_from_template_declaration(*concept_model, *cpt); constexpr auto kMaxConstraintCount = 24U; llvm::SmallVector constraints{}; if (cpt->hasAssociatedConstraints()) { cpt->getAssociatedConstraints(constraints); } for (const auto *expr : constraints) { find_relationships_in_constraint_expression(*concept_model, expr); } if (cpt->getConstraintExpr() != nullptr) { process_constraint_requirements( cpt, cpt->getConstraintExpr(), *concept_model); find_relationships_in_constraint_expression( *concept_model, cpt->getConstraintExpr()); } if (diagram().should_include(*concept_model)) { LOG_DBG("Adding concept {} with id {}", concept_model->full_name(false), concept_model->id()); add_concept(std::move(concept_model)); } else { LOG_DBG("Skipping concept {} with id {}", concept_model->full_name(), concept_model->id()); } return true; } void translation_unit_visitor::process_constraint_requirements( const clang::ConceptDecl *cpt, const clang::Expr *expr, model::concept_ &concept_model) const { if (const auto *constraint = llvm::dyn_cast(expr); constraint) { auto constraint_source = common::to_string(constraint); LOG_DBG("== Processing constraint: '{}'", constraint_source); for (const auto *requirement : constraint->getRequirements()) { LOG_DBG("== Processing requirement: '{}'", requirement->getKind()); } // process 'requires (...)' declaration for (const auto *decl : constraint->getBody()->decls()) { if (const auto *parm_var_decl = llvm::dyn_cast(decl); parm_var_decl) { parm_var_decl->getQualifiedNameAsString(); auto param_name = parm_var_decl->getNameAsString(); auto param_type = common::to_string( parm_var_decl->getType(), cpt->getASTContext()); LOG_DBG("=== Processing parameter variable declaration: {}, {}", param_type, param_name); concept_model.add_parameter( {std::move(param_type), std::move(param_name)}); } else { LOG_DBG("=== Processing some other concept declaration: {}", decl->getID()); } } // process concept body requirements '{ }' if any for (const auto *req : constraint->getRequirements()) { if (req->getKind() == clang::concepts::Requirement::RK_Simple) { const auto *simple_req = llvm::dyn_cast(req); if (simple_req != nullptr) { util::if_not_null( simple_req->getExpr(), [&concept_model](const auto *e) { auto simple_expr = common::to_string(e); LOG_DBG("=== Processing expression requirement: {}", simple_expr); concept_model.add_statement(std::move(simple_expr)); }); } } else if (req->getKind() == clang::concepts::Requirement::RK_Type) { util::if_not_null( llvm::dyn_cast(req), [&concept_model, cpt](const auto *t) { auto type_name = common::to_string( t->getType()->getType(), cpt->getASTContext()); LOG_DBG( "=== Processing type requirement: {}", type_name); concept_model.add_statement(std::move(type_name)); }); } else if (req->getKind() == clang::concepts::Requirement::RK_Nested) { const auto *nested_req = llvm::dyn_cast(req); if (nested_req != nullptr) { util::if_not_null( nested_req->getConstraintExpr(), [](const auto *e) { LOG_DBG("=== Processing nested requirement: {}", common::to_string(e)); }); } } else if (req->getKind() == clang::concepts::Requirement::RK_Compound) { const auto *compound_req = llvm::dyn_cast(req); if (compound_req != nullptr) { const auto *compound_expr_ptr = compound_req->getExpr(); if (compound_expr_ptr != nullptr) { auto compound_expr = common::to_string(compound_expr_ptr); auto req_return_type = compound_req->getReturnTypeRequirement(); if (!req_return_type.isEmpty()) { compound_expr = fmt::format("{{{}}} -> {}", compound_expr, common::to_string( req_return_type.getTypeConstraint())); } else if (compound_req->hasNoexceptRequirement()) { compound_expr = fmt::format("{{{}}} noexcept", compound_expr); } LOG_DBG("=== Processing compound requirement: {}", compound_expr); concept_model.add_statement(std::move(compound_expr)); } } } } } else if (const auto *binop = llvm::dyn_cast(expr); binop) { process_constraint_requirements(cpt, binop->getLHS(), concept_model); process_constraint_requirements(cpt, binop->getRHS(), concept_model); } else if (const auto *unop = llvm::dyn_cast(expr); unop) { process_constraint_requirements(cpt, unop->getSubExpr(), concept_model); } } void translation_unit_visitor::find_relationships_in_constraint_expression( clanguml::common::model::element &c, const clang::Expr *expr) { if (expr == nullptr) return; found_relationships_t relationships; common::if_dyn_cast(expr, [&](const auto *ul) { for (const auto ta : ul->template_arguments()) { find_relationships(ta.getArgument().getAsType(), relationships, relationship_t::kConstraint); } }); common::if_dyn_cast( expr, [&](const auto *cs) { process_concept_specialization_relationships(c, cs); }); common::if_dyn_cast(expr, [&](const auto *re) { // TODO }); common::if_dyn_cast(expr, [&](const auto *op) { find_relationships_in_constraint_expression(c, op->getLHS()); find_relationships_in_constraint_expression(c, op->getRHS()); }); common::if_dyn_cast(expr, [&](const auto *op) { find_relationships_in_constraint_expression(c, op->getSubExpr()); }); for (const auto &[type_element_id, relationship_type] : relationships) { if (type_element_id != c.id() && (relationship_type != relationship_t::kNone)) { relationship r{relationship_type, type_element_id}; c.add_relationship(std::move(r)); } } } void translation_unit_visitor::process_concept_specialization_relationships( common::model::element &c, const clang::ConceptSpecializationExpr *concept_specialization) { if (const auto *cpt = concept_specialization->getNamedConcept(); should_include(cpt)) { const auto cpt_name = cpt->getNameAsString(); const common::id_t ast_id{cpt->getID()}; const auto maybe_id = id_mapper().get_global_id(ast_id); if (!maybe_id) return; const auto target_id = maybe_id.value(); std::vector constrained_template_params; size_t argument_index{}; for (const auto ta : concept_specialization->getTemplateArguments()) { if (ta.getKind() == clang::TemplateArgument::Type) { auto type_name = common::to_string(ta.getAsType(), cpt->getASTContext()); extract_constrained_template_param_name(concept_specialization, cpt, constrained_template_params, argument_index, type_name); } else if (ta.getKind() == clang::TemplateArgument::Pack) { if (!ta.getPackAsArray().empty() && ta.getPackAsArray().front().isPackExpansion()) { const auto &pack_head = ta.getPackAsArray().front().getAsType(); auto type_name = common::to_string(pack_head, cpt->getASTContext()); extract_constrained_template_param_name( concept_specialization, cpt, constrained_template_params, argument_index, type_name); } } else { auto type_name = common::to_string(ta.getAsType(), cpt->getASTContext()); LOG_DBG( "=== Unsupported concept type parameter: {}", type_name); } argument_index++; } if (!constrained_template_params.empty()) c.add_relationship( {relationship_t::kConstraint, target_id, access_t::kNone, fmt::format( "{}", fmt::join(constrained_template_params, ","))}); } } bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) { // Skip system headers if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) return true; if (!should_include(cls)) return true; LOG_DBG("= Visiting class declaration {} at {}", cls->getQualifiedNameAsString(), cls->getLocation().printToString(source_manager())); LOG_DBG( "== getQualifiedNameAsString() = {}", cls->getQualifiedNameAsString()); if (cls->getOwningModule() != nullptr) LOG_DBG( "== getOwningModule()->Name = {}", cls->getOwningModule()->Name); LOG_DBG("== getID() = {}", cls->getID()); LOG_DBG("== isTemplateDecl() = {}", cls->isTemplateDecl()); LOG_DBG("== isTemplated() = {}", cls->isTemplated()); LOG_DBG("== getParent()->isRecord()() = {}", cls->getParent()->isRecord()); if (const auto *parent_record = clang::dyn_cast(cls->getParent()); parent_record != nullptr) { LOG_DBG("== getParent()->getQualifiedNameAsString() = {}", parent_record->getQualifiedNameAsString()); } if (has_processed_template_class(cls->getQualifiedNameAsString())) // If we have already processed the template of this class // skip it return true; if (cls->isTemplated() && (cls->getDescribedTemplate() != nullptr)) { // If the described templated of this class is already in the model // skip it: const common::id_t ast_id{cls->getDescribedTemplate()->getID()}; if (id_mapper().get_global_id(ast_id)) return true; } // TODO: Add support for classes defined in function/method bodies if (cls->isLocalClass() != nullptr) return true; auto c_ptr = create_class_declaration(cls); if (!c_ptr) return true; const auto cls_id = c_ptr->id(); id_mapper().add(cls->getID(), cls_id); auto &class_model = diagram().find(cls_id).has_value() ? *diagram().find(cls_id).get() : *c_ptr; if (cls->isCompleteDefinition() && !class_model.complete()) process_class_declaration(*cls, class_model); auto id = class_model.id(); if (!cls->isCompleteDefinition()) { forward_declarations_.emplace(id, std::move(c_ptr)); return true; } forward_declarations_.erase(id); if (diagram().should_include(class_model)) { LOG_DBG("Adding class {} with id {}", class_model.full_name(false), class_model.id()); add_class(std::move(c_ptr)); } else { LOG_DBG("Skipping class {} with id {}", class_model.full_name(), class_model.id()); } return true; } std::unique_ptr translation_unit_visitor::create_concept_declaration(clang::ConceptDecl *cpt) { assert(cpt != nullptr); if (!should_include(cpt)) return {}; auto concept_ptr{ std::make_unique(config().using_namespace())}; auto &concept_model = *concept_ptr; auto ns = common::get_template_namespace(*cpt); concept_model.set_name(cpt->getNameAsString()); concept_model.set_namespace(ns); concept_model.set_id(common::to_id(concept_model.full_name(false))); process_comment(*cpt, concept_model); set_source_location(*cpt, concept_model); set_owning_module(*cpt, concept_model); if (concept_model.skip()) return {}; concept_model.set_style(concept_model.style_spec()); return concept_ptr; } std::unique_ptr translation_unit_visitor::create_record_declaration( clang::RecordDecl *rec) { assert(rec != nullptr); if (!should_include(rec)) return {}; auto record_ptr{std::make_unique(config().using_namespace())}; auto &record = *record_ptr; process_record_parent(rec, record, namespace_{}); if (!record.is_nested()) { auto record_name = rec->getQualifiedNameAsString(); #if LLVM_VERSION_MAJOR < 16 if (record_name == "(anonymous)") { util::if_not_null(rec->getTypedefNameForAnonDecl(), [&record_name](const clang::TypedefNameDecl *name) { record_name = name->getNameAsString(); }); } #endif record.set_name(record_name); record.set_id(common::to_id(record.full_name(false))); } process_comment(*rec, record); set_source_location(*rec, record); set_owning_module(*rec, record); const auto record_full_name = record_ptr->full_name(false); record.is_struct(rec->isStruct()); record.is_union(rec->isUnion()); if (record.skip()) return {}; record.set_style(record.style_spec()); return record_ptr; } std::unique_ptr translation_unit_visitor::create_class_declaration( clang::CXXRecordDecl *cls) { assert(cls != nullptr); if (!should_include(cls)) return {}; auto c_ptr{std::make_unique(config().using_namespace())}; auto &c = *c_ptr; auto ns{common::get_tag_namespace(*cls)}; process_record_parent(cls, c, ns); if (!c.is_nested()) { c.set_name(common::get_tag_name(*cls)); c.set_namespace(ns); c.set_id(common::to_id(c.full_name(false))); } c.is_struct(cls->isStruct()); process_comment(*cls, c); set_source_location(*cls, c); set_owning_module(*cls, c); if (c.skip()) return {}; c.set_style(c.style_spec()); return c_ptr; } void translation_unit_visitor::process_record_parent( clang::RecordDecl *cls, class_ &c, const namespace_ &ns) { const auto *parent = cls->getParent(); std::optional id_opt; auto parent_ns = ns; if (parent != nullptr) { const auto *parent_record_decl = clang::dyn_cast(parent); if (parent_record_decl != nullptr) { parent_ns = common::get_tag_namespace(*parent_record_decl); common::id_t ast_id{parent_record_decl->getID()}; // First check if the parent has been added to the diagram as // regular class id_opt = id_mapper().get_global_id(ast_id); // If not, check if the parent template declaration is in the // model if (!id_opt) { if (parent_record_decl->getDescribedTemplate() != nullptr) { ast_id = parent_record_decl->getDescribedTemplate()->getID(); id_opt = id_mapper().get_global_id(ast_id); } } } } if (id_opt && diagram().find(*id_opt)) { // Here we have 2 options, either: // - the parent is a regular C++ class/struct // - the parent is a class template declaration/specialization auto parent_class = diagram().find(*id_opt); c.set_namespace(parent_ns); const auto cls_name = cls->getNameAsString(); if (cls_name.empty()) { // Nested structs can be anonymous if (anonymous_struct_relationships_.count(cls->getID()) > 0) { const auto &[label, hint, access, destination_multiplicity] = anonymous_struct_relationships_[cls->getID()]; c.set_name(parent_class.value().name() + "##" + fmt::format("({})", label)); std::string destination_multiplicity_str{}; if (destination_multiplicity.has_value()) { destination_multiplicity_str = std::to_string(*destination_multiplicity); } parent_class.value().add_relationship( {hint, common::to_id(c.full_name(false)), access, label, "", destination_multiplicity_str}); } else c.set_name(parent_class.value().name() + "##" + fmt::format( "(anonymous_{})", std::to_string(cls->getID()))); } else { c.set_name( parent_class.value().name() + "##" + cls->getNameAsString()); } c.set_id(common::to_id(c.full_name(false))); if (!cls->getNameAsString().empty()) { // Don't add anonymous structs as contained in the class // as they are already added as aggregations c.add_relationship({relationship_t::kContainment, *id_opt}); } c.nested(true); } } void translation_unit_visitor::process_class_declaration( const clang::CXXRecordDecl &cls, class_ &c) { // Process class child entities process_class_children(&cls, c); // Process class bases process_class_bases(&cls, c); c.complete(true); } void translation_unit_visitor::process_class_bases( const clang::CXXRecordDecl *cls, class_ &c) { for (const auto &base : cls->bases()) { class_parent cp; auto name_and_ns = common::model::namespace_{ common::to_string(base.getType(), cls->getASTContext())}; cp.set_name(name_and_ns.to_string()); if (const auto *tsp = base.getType()->getAs(); tsp != nullptr) { auto template_specialization_ptr = std::make_unique(config().using_namespace()); tbuilder().build_from_template_specialization_type( *template_specialization_ptr, cls, *tsp, {}); cp.set_id(template_specialization_ptr->id()); cp.set_name(template_specialization_ptr->full_name(false)); if (diagram().should_include(*template_specialization_ptr)) { add_class(std::move(template_specialization_ptr)); } } else if (const auto *record_type = base.getType()->getAs(); record_type != nullptr) { cp.set_name(record_type->getDecl()->getQualifiedNameAsString()); cp.set_id(common::to_id(*record_type->getDecl())); } else // This could be a template parameter - we don't want it here continue; cp.is_virtual(base.isVirtual()); cp.set_access( common::access_specifier_to_access_t(base.getAccessSpecifier())); LOG_DBG("Found base class {} [{}] for class {}", cp.name(), cp.id(), c.name()); c.add_parent(std::move(cp)); } } void translation_unit_visitor::process_template_specialization_children( const clang::ClassTemplateSpecializationDecl *cls, class_ &c) { assert(cls != nullptr); // Iterate over class methods (both regular and static) for (const auto *method : cls->methods()) { if (method != nullptr) { process_method(*method, c); } } // Iterate over class template methods if (const auto *cls_decl_context = clang::dyn_cast_or_null(cls); cls_decl_context != nullptr) { for (auto const *decl_iterator : clang::dyn_cast_or_null(cls)->decls()) { auto const *method_template = llvm::dyn_cast_or_null( decl_iterator); if (method_template == nullptr) continue; process_template_method(*method_template, c); } } // Iterate over regular class fields for (const auto *field : cls->fields()) { if (field != nullptr) process_field(*field, c); } // Static fields have to be processed by iterating over variable // declarations for (const auto *decl : cls->decls()) { if (decl->getKind() == clang::Decl::Var) { const clang::VarDecl *variable_declaration{ dynamic_cast(decl)}; if ((variable_declaration != nullptr) && variable_declaration->isStaticDataMember()) { process_static_field(*variable_declaration, c); } } else if (decl->getKind() == clang::Decl::Enum) { const auto *enum_decl = clang::dyn_cast_or_null(decl); if (enum_decl == nullptr) continue; if (enum_decl->getNameAsString().empty()) { for (const auto *enum_const : enum_decl->enumerators()) { class_member m{common::access_specifier_to_access_t( enum_decl->getAccess()), enum_const->getNameAsString(), "enum"}; c.add_member(std::move(m)); } } } } if (cls->hasFriends()) { for (const auto *friend_declaration : cls->friends()) { process_friend(*friend_declaration, c); } } } void translation_unit_visitor::process_record_members( const clang::RecordDecl *cls, class_ &c) { // Iterate over regular class fields for (const auto *field : cls->fields()) { if (field != nullptr) process_field(*field, c); } } void translation_unit_visitor::process_class_children( const clang::CXXRecordDecl *cls, class_ &c) { assert(cls != nullptr); // Iterate over class methods (both regular and static) for (const auto *method : cls->methods()) { if (method != nullptr) { process_method(*method, c); } } // Iterate over class template methods if (const auto *cls_decl_context = clang::dyn_cast_or_null(cls); cls_decl_context != nullptr) { for (auto const *decl_iterator : cls_decl_context->decls()) { auto const *method_template = llvm::dyn_cast_or_null( decl_iterator); if (method_template == nullptr) continue; process_template_method(*method_template, c); } } // Iterate over regular class fields for (const auto *field : cls->fields()) { if (field != nullptr) process_field(*field, c); } // Static fields have to be processed by iterating over variable // declarations for (const auto *decl : cls->decls()) { if (decl->getKind() == clang::Decl::Var) { const clang::VarDecl *variable_declaration{ clang::dyn_cast_or_null(decl)}; if ((variable_declaration != nullptr) && variable_declaration->isStaticDataMember()) { process_static_field(*variable_declaration, c); } } else if (decl->getKind() == clang::Decl::Enum) { const auto *enum_decl = clang::dyn_cast_or_null(decl); if (enum_decl == nullptr) continue; if (enum_decl->getNameAsString().empty()) { for (const auto *enum_const : enum_decl->enumerators()) { class_member m{common::access_specifier_to_access_t( enum_decl->getAccess()), enum_const->getNameAsString(), "enum"}; c.add_member(std::move(m)); } } } } if (cls->isCompleteDefinition()) for (const auto *friend_declaration : cls->friends()) { if (friend_declaration != nullptr) process_friend(*friend_declaration, c); } } void translation_unit_visitor::process_friend( const clang::FriendDecl &f, class_ &c) { if (const auto *friend_type_info = f.getFriendType()) { const auto friend_type = friend_type_info->getType(); if (friend_type->getAs() != nullptr) { // TODO: handle template friend } else if (friend_type->getAs() != nullptr) { if (should_include(friend_type->getAsRecordDecl())) { relationship r{relationship_t::kFriendship, common::to_id(*friend_type->getAsRecordDecl()), common::access_specifier_to_access_t(f.getAccess()), "<>"}; c.add_relationship(std::move(r)); } } } } void translation_unit_visitor::process_method( const clang::CXXMethodDecl &mf, class_ &c) { // TODO: For now skip implicitly default methods // in the future, add config option to choose if (mf.isDefaulted() && !mf.isExplicitlyDefaulted()) return; auto method_return_type = common::to_string(mf.getReturnType(), mf.getASTContext()); common::ensure_lambda_type_is_relative(config(), method_return_type); auto method_name = mf.getNameAsString(); if (mf.isTemplated()) { // Sometimes in template specializations method names contain the // template parameters for some reason - drop them // Is there a better way to do this? method_name = method_name.substr(0, method_name.find('<')); } class_method method{common::access_specifier_to_access_t(mf.getAccess()), util::trim(method_name), config().simplify_template_type(method_return_type)}; process_method_properties(mf, c, method_name, method); process_comment(mf, method); // Register the source location of the field declaration set_source_location(mf, method); if (method.skip()) return; for (const auto *param : mf.parameters()) { if (param != nullptr) process_function_parameter(*param, method, c); } // find relationship for return type found_relationships_t relationships; // Move dereferencing to build() method of template_builder if (const auto *templ = mf.getReturnType() .getNonReferenceType() .getUnqualifiedType() ->getAs(); templ != nullptr) { const auto *unaliased_type = templ; if (unaliased_type->isTypeAlias()) unaliased_type = unaliased_type->getAliasedType() ->getAs(); if (unaliased_type != nullptr) { auto template_specialization_ptr = std::make_unique(config().using_namespace()); tbuilder().build_from_template_specialization_type( *template_specialization_ptr, unaliased_type->getTemplateName().getAsTemplateDecl(), *unaliased_type, &c); if (diagram().should_include(*template_specialization_ptr)) { relationships.emplace_back(template_specialization_ptr->id(), relationship_t::kDependency); add_class(std::move(template_specialization_ptr)); } } } find_relationships( mf.getReturnType(), relationships, relationship_t::kDependency); for (const auto &[type_element_id, relationship_type] : relationships) { if (type_element_id != c.id() && (relationship_type != relationship_t::kNone)) { relationship r{relationship_t::kDependency, type_element_id}; LOG_DBG("Adding method return type relationship from {}::{} to " "{}: {}", c.full_name(), mf.getNameAsString(), clanguml::common::model::to_string(r.type()), r.label()); c.add_relationship(std::move(r)); } } // Also consider the container itself if it is a template // instantiation it's arguments could count as reference to relevant // types auto underlying_type = mf.getReturnType(); if (underlying_type->isReferenceType()) underlying_type = underlying_type.getNonReferenceType(); if (underlying_type->isPointerType()) underlying_type = underlying_type->getPointeeType(); if (const auto *atsp = underlying_type->getAs(); atsp != nullptr) { process_function_parameter_find_relationships_in_autotype(c, atsp); } method.update(config().using_namespace()); if (diagram().should_include(method)) { LOG_DBG("Adding method: {}", method.name()); c.add_method(std::move(method)); } } void translation_unit_visitor::process_method_properties( const clang::CXXMethodDecl &mf, const class_ &c, const std::string &method_name, class_method &method) const { const bool is_constructor = c.name() == method_name; const bool is_destructor = fmt::format("~{}", c.name()) == method_name; #if LLVM_VERSION_MAJOR > 17 method.is_pure_virtual(mf.isPureVirtual()); #else method.is_pure_virtual(mf.isPure()); #endif method.is_virtual(mf.isVirtual()); method.is_const(mf.isConst()); method.is_defaulted(mf.isDefaulted()); method.is_deleted(mf.isDeleted()); method.is_static(mf.isStatic()); method.is_operator(mf.isOverloadedOperator()); method.is_constexpr(mf.isConstexprSpecified() && !is_constructor); method.is_consteval(mf.isConsteval()); method.is_constructor(is_constructor); method.is_destructor(is_destructor); method.is_move_assignment(mf.isMoveAssignmentOperator()); method.is_copy_assignment(mf.isCopyAssignmentOperator()); method.is_noexcept(isNoexceptExceptionSpec(mf.getExceptionSpecType())); method.is_coroutine(common::is_coroutine(mf)); } void translation_unit_visitor:: process_function_parameter_find_relationships_in_autotype( class_ &c, const clang::AutoType *atsp) { auto desugared_atsp = atsp->getDeducedType(); if (atsp->isSugared()) { const auto *deduced_type = atsp->desugar()->getAs(); if (deduced_type != nullptr) desugared_atsp = deduced_type->getDeducedType(); } if (desugared_atsp.isNull()) return; const auto *deduced_record_type = desugared_atsp->isRecordType() ? desugared_atsp->getAs() : nullptr; if (deduced_record_type != nullptr) { if (auto *deduced_auto_decl = llvm::dyn_cast_or_null( deduced_record_type->getDecl()); deduced_auto_decl != nullptr) { const auto diagram_class_count_before_visit = diagram().classes().size(); VisitClassTemplateSpecializationDecl(deduced_auto_decl); const bool visitor_added_new_template_specialization = (diagram().classes().size() - diagram_class_count_before_visit) > 0; if (visitor_added_new_template_specialization) { const auto &template_specialization_model = diagram().classes().back(); if (should_include(deduced_auto_decl)) { relationship r{relationship_t::kDependency, template_specialization_model.get().id()}; c.add_relationship(std::move(r)); } } } } } void translation_unit_visitor::process_template_method( const clang::FunctionTemplateDecl &mf, class_ &c) { // TODO: For now skip implicitly default methods // in the future, add config option to choose if (mf.getTemplatedDecl()->isDefaulted() && !mf.getTemplatedDecl()->isExplicitlyDefaulted()) return; class_method method{common::access_specifier_to_access_t(mf.getAccess()), util::trim(mf.getNameAsString()), mf.getTemplatedDecl()->getReturnType().getAsString()}; auto method_name = mf.getNameAsString(); if (mf.isTemplated()) { // Sometimes in template specializations method names contain the // template parameters for some reason - drop them // Is there a better way to do this? method_name = method_name.substr(0, method_name.find('<')); } util::if_not_null( clang::dyn_cast(mf.getTemplatedDecl()), [&](const auto *decl) { process_method_properties(*decl, c, method_name, method); }); tbuilder().build_from_template_declaration(method, mf); process_comment(mf, method); if (method.skip()) return; for (const auto *param : mf.getTemplatedDecl()->parameters()) { if (param != nullptr) process_function_parameter(*param, method, c); } method.update(config().using_namespace()); if (diagram().should_include(method)) { LOG_DBG("Adding method: {}", method.name()); c.add_method(std::move(method)); } } bool translation_unit_visitor::find_relationships(const clang::QualType &type, found_relationships_t &relationships, clanguml::common::model::relationship_t relationship_hint) { bool result{false}; if (type->isPointerType()) { relationship_hint = relationship_t::kAssociation; find_relationships( type->getPointeeType(), relationships, relationship_hint); } else if (type->isRValueReferenceType()) { relationship_hint = relationship_t::kAggregation; find_relationships( type.getNonReferenceType(), relationships, relationship_hint); } else if (type->isLValueReferenceType()) { relationship_hint = relationship_t::kAssociation; find_relationships( type.getNonReferenceType(), relationships, relationship_hint); } else if (type->isArrayType()) { find_relationships(type->getAsArrayTypeUnsafe()->getElementType(), relationships, relationship_t::kAggregation); } else if (type->isEnumeralType()) { if (const auto *enum_type = type->getAs(); enum_type != nullptr) { // Use AST's local ID here for relationship target, as we can't // calculate here properly the ID for nested enums. It will be // resolved properly in finalize(). relationships.emplace_back( enum_type->getDecl()->getID(), relationship_hint); } } else if (type->isRecordType()) { const auto *type_instantiation_decl = type->getAs(); if (type_instantiation_decl != nullptr) { // If this template should be included in the diagram // add it - and then process recursively its arguments if (should_include(type_instantiation_decl->getTemplateName() .getAsTemplateDecl())) { relationships.emplace_back( type_instantiation_decl->getTemplateName() .getAsTemplateDecl() ->getID(), relationship_hint); } for (const auto &template_argument : type_instantiation_decl->template_arguments()) { const auto template_argument_kind = template_argument.getKind(); if (template_argument_kind == clang::TemplateArgument::ArgKind::Integral) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Null) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Expression) { // pass } else if (template_argument.getKind() == clang::TemplateArgument::ArgKind::NullPtr) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Template) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::TemplateExpansion) { // pass } else if (const auto *function_type = template_argument.getAsType() ->getAs(); function_type != nullptr) { for (const auto ¶m_type : function_type->param_types()) { result = find_relationships(param_type, relationships, relationship_t::kDependency); } } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Type) { result = find_relationships(template_argument.getAsType(), relationships, relationship_hint); } } } else if (type->getAsCXXRecordDecl() != nullptr) { const auto target_id = common::to_id(*type->getAsCXXRecordDecl()); relationships.emplace_back(target_id, relationship_hint); result = true; } else { const auto target_id = common::to_id(*type->getAsRecordDecl()); relationships.emplace_back(target_id, relationship_hint); result = true; } } else if (const auto *template_specialization_type = type->getAs(); template_specialization_type != nullptr) { if (should_include(template_specialization_type->getTemplateName() .getAsTemplateDecl())) { relationships.emplace_back( template_specialization_type->getTemplateName() .getAsTemplateDecl() ->getID(), relationship_hint); } for (const auto &template_argument : template_specialization_type->template_arguments()) { const auto template_argument_kind = template_argument.getKind(); if (template_argument_kind == clang::TemplateArgument::ArgKind::Integral) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Null) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Expression) { // pass } else if (template_argument.getKind() == clang::TemplateArgument::ArgKind::NullPtr) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Template) { // pass } else if (template_argument_kind == clang::TemplateArgument::ArgKind::TemplateExpansion) { // pass } else if (const auto *function_type = template_argument.getAsType() ->getAs(); function_type != nullptr) { for (const auto ¶m_type : function_type->param_types()) { result = find_relationships( param_type, relationships, relationship_t::kDependency); } } else if (template_argument_kind == clang::TemplateArgument::ArgKind::Type) { result = find_relationships(template_argument.getAsType(), relationships, relationship_hint); } } } return result; } void translation_unit_visitor::process_function_parameter( const clang::ParmVarDecl &p, class_method &method, class_ &c, const std::set & /*template_parameter_names*/) { method_parameter parameter; parameter.set_name(p.getNameAsString()); process_comment(p, parameter); if (parameter.skip()) return; auto parameter_type = common::to_string(p.getType(), p.getASTContext()); // Is there no better way to determine that 'type' is a lambda? common::ensure_lambda_type_is_relative(config(), parameter_type); parameter.set_type(parameter_type); if (p.hasDefaultArg()) { const auto *default_arg = p.getDefaultArg(); if (default_arg != nullptr) { auto default_arg_str = common::get_source_text( default_arg->getSourceRange(), source_manager()); parameter.set_default_value(default_arg_str); } } if (!parameter.skip_relationship()) { // find relationship for the type found_relationships_t relationships; LOG_DBG("Looking for relationships in type: {}", common::to_string(p.getType(), p.getASTContext())); if (const auto *templ = p.getType() .getNonReferenceType() .getUnqualifiedType() ->getAs(); templ != nullptr) { auto template_specialization_ptr = std::make_unique(config().using_namespace()); tbuilder().build_from_template_specialization_type( *template_specialization_ptr, templ->getTemplateName().getAsTemplateDecl(), *templ, &c); if (diagram().should_include(*template_specialization_ptr)) { relationships.emplace_back(template_specialization_ptr->id(), relationship_t::kDependency); add_class(std::move(template_specialization_ptr)); } } find_relationships( p.getType(), relationships, relationship_t::kDependency); for (const auto &[type_element_id, relationship_type] : relationships) { if (type_element_id != c.id() && (relationship_type != relationship_t::kNone)) { relationship r{relationship_t::kDependency, type_element_id}; LOG_DBG("Adding function parameter relationship from {} to " "{}: {}", c.full_name(), clanguml::common::model::to_string(r.type()), r.label()); c.add_relationship(std::move(r)); } } } method.add_parameter(std::move(parameter)); } void translation_unit_visitor::add_relationships(class_ &c, const class_member &field, const found_relationships_t &relationships, bool break_on_first_aggregation) { auto [decorator_rtype, decorator_rmult] = field.get_relationship(); for (const auto &[target, relationship_type] : relationships) { if (relationship_type != relationship_t::kNone) { relationship r{relationship_type, target}; r.set_label(field.name()); r.set_access(field.access()); bool mulitplicity_provided_in_comment{false}; if (decorator_rtype != relationship_t::kNone) { r.set_type(decorator_rtype); auto mult = util::split(decorator_rmult, ":", false); if (mult.size() == 2) { mulitplicity_provided_in_comment = true; r.set_multiplicity_source(mult[0]); r.set_multiplicity_destination(mult[1]); } } if (!mulitplicity_provided_in_comment && field.destination_multiplicity().has_value()) { r.set_multiplicity_destination( std::to_string(*field.destination_multiplicity())); } r.set_style(field.style_spec()); LOG_DBG("Adding relationship from {} to {} with label {}", c.full_name(false), r.destination(), clanguml::common::model::to_string(r.type()), r.label()); c.add_relationship(std::move(r)); if (break_on_first_aggregation && relationship_type == relationship_t::kAggregation) break; } } } void translation_unit_visitor::process_static_field( const clang::VarDecl &field_declaration, class_ &c) { const auto field_type = field_declaration.getType(); auto type_name = common::to_string(field_type, field_declaration.getASTContext()); if (type_name.empty()) type_name = "<>"; class_member field{ common::access_specifier_to_access_t(field_declaration.getAccess()), field_declaration.getNameAsString(), config().simplify_template_type(type_name)}; field.is_static(true); process_comment(field_declaration, field); set_source_location(field_declaration, field); if (field.skip()) return; if (!field.skip_relationship()) { found_relationships_t relationships; // find relationship for the type find_relationships(field_declaration.getType(), relationships, relationship_t::kAssociation); add_relationships(c, field, relationships); } c.add_member(std::move(field)); } std::unique_ptr translation_unit_visitor::process_template_specialization( clang::ClassTemplateSpecializationDecl *cls) { auto c_ptr = std::make_unique(config().using_namespace()); tbuilder().build_from_class_template_specialization(*c_ptr, *cls); auto &template_instantiation = *c_ptr; template_instantiation.is_template(true); // TODO: refactor to method get_qualified_name() auto qualified_name = cls->getQualifiedNameAsString(); util::replace_all(qualified_name, "(anonymous namespace)", ""); util::replace_all(qualified_name, "::::", "::"); namespace_ ns{qualified_name}; ns.pop_back(); template_instantiation.set_name(cls->getNameAsString()); template_instantiation.set_namespace(ns); template_instantiation.is_struct(cls->isStruct()); process_record_parent(cls, template_instantiation, ns); if (!template_instantiation.is_nested()) { template_instantiation.set_name(common::get_tag_name(*cls)); template_instantiation.set_id( common::to_id(template_instantiation.full_name(false))); } process_comment(*cls, template_instantiation); set_source_location(*cls, template_instantiation); set_owning_module(*cls, template_instantiation); if (template_instantiation.skip()) return {}; id_mapper().add(cls->getID(), template_instantiation.id()); return c_ptr; } void translation_unit_visitor::process_field( const clang::FieldDecl &field_declaration, class_ &c) { LOG_DBG( "== Visiting record member {}", field_declaration.getNameAsString()); // Default hint for relationship is aggregation auto relationship_hint = relationship_t::kAggregation; // If the first type of the template instantiation of this field type // has been added as aggregation relationship with class 'c', don't // add it's nested template types as aggregation [[maybe_unused]] bool template_instantiation_added_as_aggregation{false}; // The actual field type auto field_type = field_declaration.getType(); // String representation of the field type auto type_name = common::to_string(field_type, field_declaration.getASTContext()); // The field name const auto field_name = field_declaration.getNameAsString(); auto field_type_str = common::to_string(field_type, field_declaration.getASTContext(), false); common::ensure_lambda_type_is_relative(config(), field_type_str); class_member field{ common::access_specifier_to_access_t(field_declaration.getAccess()), field_name, config().simplify_template_type(field_type_str)}; // Parse the field comment process_comment(field_declaration, field); // Register the source location of the field declaration set_source_location(field_declaration, field); // If the comment contains a skip directive, just return if (field.skip()) return; if (field_type->isPointerType()) { relationship_hint = relationship_t::kAssociation; field_type = field_type->getPointeeType(); } else if (field_type->isLValueReferenceType()) { relationship_hint = relationship_t::kAssociation; field_type = field_type.getNonReferenceType(); } else if (field_type->isArrayType()) { relationship_hint = relationship_t::kAggregation; while (field_type->isArrayType()) { auto current_multiplicity = field.destination_multiplicity(); if (!current_multiplicity) field.set_destination_multiplicity(common::get_array_size( *field_type->getAsArrayTypeUnsafe())); else { auto maybe_array_size = common::get_array_size(*field_type->getAsArrayTypeUnsafe()); if (maybe_array_size.has_value()) { field.set_destination_multiplicity( current_multiplicity.value() * maybe_array_size.value()); } } field_type = field_type->getAsArrayTypeUnsafe()->getElementType(); } } else if (field_type->isRValueReferenceType()) { field_type = field_type.getNonReferenceType(); } if (type_name.find("std::shared_ptr") == 0) relationship_hint = relationship_t::kAssociation; if (type_name.find("std::weak_ptr") == 0) relationship_hint = relationship_t::kAssociation; found_relationships_t relationships; const auto *template_field_type = field_type->getAs(); // 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(); 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; } } } // Process the type which is template instantiation of some sort if (template_field_type != nullptr && !field_type_is_template_template_parameter) { // Build the template instantiation for the field type auto template_specialization_ptr = std::make_unique(config().using_namespace()); tbuilder().build_from_template_specialization_type( *template_specialization_ptr, field_type->getAs() ->getTemplateName() .getAsTemplateDecl(), *template_field_type, {&c}); if (!field.skip_relationship() && 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_diagram{false}; if (diagram().should_include( template_specialization.get_namespace())) { found_relationships_t::value_type r{ template_specialization.id(), relationship_hint}; add_template_instantiation_to_diagram = true; // If the template instantiation for the build type has been // added as aggregation, skip its nested templates template_instantiation_added_as_aggregation = relationship_hint == relationship_t::kAggregation; relationships.emplace_back(std::move(r)); } // Try to find relationships to types nested in the template // instantiation found_relationships_t nested_relationships; if (!template_instantiation_added_as_aggregation) { for (const auto &template_argument : template_specialization.template_params()) { LOG_DBG("Looking for nested relationships from {}::{} in " "template argument {}", c.full_name(false), field_name, template_argument.to_string( config().using_namespace(), false)); template_instantiation_added_as_aggregation = template_argument.find_nested_relationships( nested_relationships, relationship_hint, [&d = diagram()](const std::string &full_name) { if (full_name.empty()) return false; auto [ns, name] = common::split_ns(full_name); return d.should_include(ns, name); }); } // Add any relationships to the class 'c' to the diagram, // unless the top level type has been added as aggregation add_relationships(c, field, nested_relationships, /* break on first aggregation */ false); } // Add the template instantiation object to the diagram if it // matches the include pattern if (add_template_instantiation_to_diagram) add_class(std::move(template_specialization_ptr)); } } if (!field.skip_relationship()) { // 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(), field.destination_multiplicity()); } else find_relationships( field_type, relationships, relationship_hint); } add_relationships(c, field, relationships); } // If this is an anonymous struct - replace the anonymous_XYZ part with // field name if ((field_type->getAsRecordDecl() != nullptr) && field_type->getAsRecordDecl()->getNameAsString().empty()) { if (util::contains(field.type(), "(anonymous_")) { std::regex anonymous_re("anonymous_(\\d*)"); field.set_type( std::regex_replace(field.type(), anonymous_re, field_name)); } } c.add_member(std::move(field)); } void translation_unit_visitor::add_incomplete_forward_declarations() { for (auto &[id, c] : forward_declarations_) { if (diagram().should_include(c->get_namespace())) { add_class(std::move(c)); } } forward_declarations_.clear(); } void translation_unit_visitor::resolve_local_to_global_ids() { // TODO: Refactor to a map with relationships attached to references // to elements for (const auto &cls : diagram().classes()) { for (auto &rel : cls.get().relationships()) { if (!rel.destination().is_global()) { 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()) { if (!rel.destination().is_global()) { 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 &enm : diagram().enums()) { for (auto &rel : enm.get().relationships()) { if (!rel.destination().is_global()) { 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); } } } } } void translation_unit_visitor::finalize() { add_incomplete_forward_declarations(); resolve_local_to_global_ids(); if (config().skip_redundant_dependencies()) { diagram().remove_redundant_dependencies(); } } void translation_unit_visitor::extract_constrained_template_param_name( const clang::ConceptSpecializationExpr *concept_specialization, const clang::ConceptDecl *cpt, std::vector &constrained_template_params, size_t argument_index, std::string &type_name) const { const auto full_declaration_text = common::get_source_text_raw( concept_specialization->getSourceRange(), source_manager()); 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); 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); } constrained_template_params.push_back(type_name); } } void translation_unit_visitor::add_processed_template_class( std::string qualified_name) { processed_template_qualified_names_.emplace(std::move(qualified_name)); } bool translation_unit_visitor::has_processed_template_class( const std::string &qualified_name) const { return util::contains(processed_template_qualified_names_, qualified_name); } void translation_unit_visitor::add_diagram_element( std::unique_ptr element) { add_class(util::unique_pointer_cast(std::move(element))); } void translation_unit_visitor::add_class(std::unique_ptr &&c) { if ((config().generate_packages() && config().package_type() == config::package_type_t::kDirectory)) { assert(!c->file().empty()); const auto file = config().make_path_relative(c->file()); common::model::path p{ file.string(), common::model::path_type::kFilesystem}; p.pop_back(); diagram().add(p, std::move(c)); } else if ((config().generate_packages() && config().package_type() == config::package_type_t::kModule)) { const auto module_path = config().make_module_relative(c->module()); common::model::path p{module_path, common::model::path_type::kModule}; diagram().add(p, std::move(c)); } else { diagram().add(c->path(), std::move(c)); } } void translation_unit_visitor::add_enum(std::unique_ptr &&e) { if ((config().generate_packages() && config().package_type() == config::package_type_t::kDirectory)) { assert(!e->file().empty()); const auto file = config().make_path_relative(e->file()); common::model::path p{ file.string(), common::model::path_type::kFilesystem}; p.pop_back(); diagram().add(p, std::move(e)); } else if ((config().generate_packages() && config().package_type() == config::package_type_t::kModule)) { const auto module_path = config().make_module_relative(e->module()); common::model::path p{module_path, common::model::path_type::kModule}; diagram().add(p, std::move(e)); } else { diagram().add(e->path(), std::move(e)); } } void translation_unit_visitor::add_concept(std::unique_ptr &&c) { if ((config().generate_packages() && config().package_type() == config::package_type_t::kDirectory)) { assert(!c->file().empty()); const auto file = config().make_path_relative(c->file()); common::model::path p{ file.string(), common::model::path_type::kFilesystem}; p.pop_back(); diagram().add(p, std::move(c)); } else if ((config().generate_packages() && config().package_type() == config::package_type_t::kModule)) { const auto module_path = config().make_module_relative(c->module()); common::model::path p{module_path, common::model::path_type::kModule}; diagram().add(p, std::move(c)); } else { diagram().add(c->path(), std::move(c)); } } void translation_unit_visitor::find_instantiation_relationships( common::model::template_element &template_instantiation_base, const std::string &full_name, common::id_t templated_decl_id) { auto &template_instantiation = dynamic_cast( template_instantiation_base); // 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::id_t best_match_id{}; 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_global_id = id_mapper().get_global_id(templated_decl_id).value_or(common::id_t{}); if (best_match_id.value() > 0) { destination = best_match_full_name; template_instantiation.add_relationship( {common::model::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_global_id)) { template_instantiation.add_relationship( {common::model::relationship_t::kInstantiation, templated_decl_global_id}); template_instantiation.template_specialization_found(true); } else if (diagram().should_include(common::model::namespace_{full_name})) { LOG_DBG("Skipping instantiation relationship from {} to {}", template_instantiation.full_name(false), templated_decl_global_id); } else { LOG_DBG("== Cannot determine global id for specialization template {} " "- delaying until the translation unit is complete ", templated_decl_global_id); template_instantiation.add_relationship( {common::model::relationship_t::kInstantiation, templated_decl_id}); } } } // namespace clanguml::class_diagram::visitor