0.6.2
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
translation_unit_visitor.cc
Go to the documentation of this file.
1/**
2 * @file src/class_diagram/visitor/translation_unit_visitor.cc
3 *
4 * Copyright (c) 2021-2025 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
20#include "common/clang_utils.h"
21
22#include <clang/AST/ExprConcepts.h>
23#include <clang/Basic/FileManager.h>
24#include <clang/Lex/Preprocessor.h>
25#include <spdlog/spdlog.h>
26
28
33 , template_builder_{diagram, config, *this}
34{
35}
36
38 const clang::NamedDecl *decl) const
39{
40 auto cls = std::make_unique<class_>(config().using_namespace());
41 cls->is_struct(common::is_struct(decl));
42 return cls;
43}
44
46{
47 assert(ns != nullptr);
48
49 if (config().package_type() == config::package_type_t::kDirectory)
50 return true;
51
52 if (ns->isAnonymousNamespace() || ns->isInline())
53 return true;
54
55 LOG_DBG("= Visiting namespace declaration {} at {}",
56 ns->getQualifiedNameAsString(),
57 ns->getLocation().printToString(source_manager()));
58
59 auto package_path = namespace_{common::get_qualified_name(*ns)};
60
61 auto package_parent = package_path;
62 bool is_root =
63 (package_path.size() == 1) && !config().using_namespace().is_empty();
64 package_path.is_root(is_root);
65
66 std::string name;
67 if (!package_path.is_empty())
68 name = package_path.name();
69
70 if (!package_parent.is_empty())
71 package_parent.pop_back();
72
73 const auto usn = config().using_namespace();
74
75 auto p = std::make_unique<common::model::package>(usn);
76 package_path = package_path.relative_to(usn);
77
78 p->set_name(name);
79 p->set_namespace(package_parent);
80 p->set_id(common::to_id(*ns));
81 p->is_root(is_root);
82 id_mapper().add(ns->getID(), p->id());
83
84 if (config().filter_mode() == config::filter_mode_t::advanced ||
85 (diagram().should_include(*p) && !diagram().get(p->id()))) {
86 process_comment(*ns, *p);
87 set_source_location(*ns, *p);
88
89 p->set_style(p->style_spec());
90
91 for (const auto *attr : ns->attrs()) {
92 if (attr->getKind() == clang::attr::Kind::Deprecated) {
93 p->set_deprecated(true);
94 break;
95 }
96 }
97
98 if (!p->skip()) {
99 diagram().add(package_path, std::move(p));
100 }
101 }
102
103 return true;
104}
105
106bool translation_unit_visitor::VisitTypedefDecl(clang::TypedefDecl *decl)
107{
108 if (const auto *enm = common::get_typedef_enum_decl(decl)) {
109 // Associate a typedef with an anonymous declaration so that it can
110 // be later used to assign proper name to enum
111 if (!should_include(enm))
112 return true;
113
114 LOG_DBG("= Visiting typedef enum declaration {} at {}",
115 enm->getQualifiedNameAsString(),
116 enm->getLocation().printToString(source_manager()));
117
118 auto e_ptr = create_declaration(enm, decl);
119
120 if (!e_ptr)
121 return true;
122
123 if (enm->isComplete()) {
124 process_declaration(*enm, *e_ptr);
125 }
126
127 if (e_ptr && diagram().should_include(*e_ptr))
128 add_enum(std::move(e_ptr));
129 }
130
131 return true; // Continue traversing
132}
133
135{
136 assert(enm != nullptr);
137
138 // Anonymous enum values are either class fields with type enum or
139 // `typedef enum` declarations
140 if (enm->getNameAsString().empty()) {
141 return true;
142 }
143
144 if (!should_include(enm))
145 return true;
146
147 LOG_DBG("= Visiting enum declaration {} at {}",
148 enm->getQualifiedNameAsString(),
149 enm->getLocation().printToString(source_manager()));
150
151 auto e_ptr = create_declaration(enm, nullptr);
152 if (!e_ptr)
153 return true;
154
155 return add_or_update(enm, std::move(e_ptr));
156}
157
158std::unique_ptr<clanguml::class_diagram::model::enum_>
160 const clang::EnumDecl *enm, const clang::TypedefDecl *typedef_decl)
161{
162 auto e_ptr = std::make_unique<enum_>(config().using_namespace());
163 auto &e = *e_ptr;
164
165 auto ns{common::get_tag_namespace(*enm)};
166
167 // Id of parent class or struct in which this enum is potentially nested
168 std::optional<eid_t> parent_id_opt;
169 [[maybe_unused]] namespace_ parent_ns;
170 find_record_parent_id(enm, parent_id_opt, parent_ns);
171
172 std::string enm_name;
173 if (enm->getNameAsString().empty() && typedef_decl != nullptr)
174 enm_name = typedef_decl->getNameAsString();
175 else if (parent_id_opt)
176 enm_name = enm->getNameAsString();
177 else
178 enm_name = common::get_tag_name(*enm);
179
180 if (parent_id_opt && diagram().find<class_>(*parent_id_opt)) {
181 auto parent_class = diagram().find<class_>(*parent_id_opt);
182
183 e.set_namespace(ns);
184 e.set_name(parent_class.value().name(), enm_name);
185 e.set_id(common::to_id(e.full_name(false)));
186 e.add_relationship({relationship_t::kContainment, *parent_id_opt});
187 e.nested(true);
188 }
189 else if (parent_id_opt && diagram().find<objc_interface>(*parent_id_opt)) {
190 auto parent_class = diagram().find<objc_interface>(*parent_id_opt);
191
192 e.set_namespace(ns);
193 e.set_name(parent_class.value().name(), enm_name);
194 e.set_id(common::to_id(e.full_name(false)));
195 e.add_relationship({relationship_t::kContainment, *parent_id_opt});
196 e.nested(true);
197 }
198 else {
199 e.set_name(enm_name);
200 e.set_namespace(ns);
201 e.set_id(common::to_id(e.full_name(false)));
202 }
203
204 id_mapper().add(enm->getID(), e.id());
205
206 process_comment(*enm, e);
207 set_source_location(*enm, e);
208 set_owning_module(*enm, e);
209
210 if (e.skip())
211 return {};
212
213 e.set_style(e.style_spec());
214
215 return e_ptr;
216}
217
219 const clang::EnumDecl &enm, clanguml::class_diagram::model::enum_ &e)
220{
221 for (const auto &ev : enm.enumerators()) {
222 e.constants().push_back(ev->getNameAsString());
223 }
224
225 e.complete(true);
226}
227
229 clang::ClassTemplateSpecializationDecl *cls)
230{
231 if (!should_include(cls))
232 return true;
233
234 LOG_DBG("= Visiting template specialization declaration {} at {} "
235 "(described class id {})",
236 cls->getQualifiedNameAsString(),
237 cls->getLocation().printToString(source_manager()),
238 cls->getSpecializedTemplate()
239 ? cls->getSpecializedTemplate()->getTemplatedDecl()->getID()
240 : 0);
241
242 // TODO: Add support for classes defined in function/method bodies
243 if (cls->isLocalClass() != nullptr)
244 return true;
245
246 auto template_specialization_ptr = process_template_specialization(cls);
247
248 if (!template_specialization_ptr)
249 return true;
250
251 return add_or_update(cls, std::move(template_specialization_ptr));
252}
253
255 clang::TypeAliasTemplateDecl *cls)
256{
257 if (!should_include(cls))
258 return true;
259
260 LOG_DBG("= Visiting template type alias declaration {} at {}",
261 cls->getQualifiedNameAsString(),
262 cls->getLocation().printToString(source_manager()));
263
264 const auto *template_type_specialization_ptr =
265 cls->getTemplatedDecl()
266 ->getUnderlyingType()
267 ->getAs<clang::TemplateSpecializationType>();
268
269 if (template_type_specialization_ptr == nullptr)
270 return true;
271
272 auto template_specialization_ptr =
273 std::make_unique<class_>(config().using_namespace());
274 tbuilder().build_from_template_specialization_type(*cls->getTemplatedDecl(),
275 *template_specialization_ptr, cls, *template_type_specialization_ptr);
276
277 template_specialization_ptr->is_template(true);
278
279 if (diagram().should_include(*template_specialization_ptr)) {
280 const auto name = template_specialization_ptr->full_name(true);
281 const auto id = template_specialization_ptr->id();
282
283 LOG_DBG("Adding class {} with id {}", name, id);
284
285 set_source_location(*cls, *template_specialization_ptr);
286 set_owning_module(*cls, *template_specialization_ptr);
287
288 add_class(std::move(template_specialization_ptr));
289 }
290
291 return true;
292}
293
295 clang::ClassTemplateDecl *cls)
296{
297 if (!should_include(cls))
298 return true;
299
300 LOG_DBG("= Visiting class template declaration {} at {}",
301 cls->getQualifiedNameAsString(),
302 cls->getLocation().printToString(source_manager()));
303
304 auto c_ptr = create_declaration(cls->getTemplatedDecl());
305
306 if (!c_ptr)
307 return true;
308
309 add_processed_template_class(cls->getQualifiedNameAsString());
310
311 tbuilder().build_from_template_declaration(*c_ptr, *cls, *c_ptr);
312
313 // Override the id with the template id, for now we don't care about the
314 // underlying templated class id
315 const auto cls_full_name = c_ptr->full_name(false);
316 const auto id = common::to_id(cls_full_name);
317
318 c_ptr->set_id(id);
319 c_ptr->is_template(true);
320
321 id_mapper().add(cls->getID(), id);
322
323 constexpr auto kMaxConstraintCount = 24U;
324
325#if LLVM_VERSION_MAJOR < 21
326 llvm::SmallVector<const clang::Expr *, kMaxConstraintCount> constraints{};
327 if (cls->hasAssociatedConstraints()) {
328 cls->getAssociatedConstraints(constraints);
329 }
330 for (const auto *expr : constraints) {
332 }
333#else
334 llvm::SmallVector<clang::AssociatedConstraint, kMaxConstraintCount>
335 constraints{};
336 if (cls->hasAssociatedConstraints()) {
337 cls->getAssociatedConstraints(constraints);
338 }
339 for (const auto &constraint : constraints) {
341 *c_ptr, constraint.ConstraintExpr);
342 }
343#endif
344
345 return add_or_update(cls->getTemplatedDecl(), std::move(c_ptr));
346}
347
349{
350 if (rec == nullptr)
351 return true;
352
353 if (clang::dyn_cast_or_null<clang::CXXRecordDecl>(rec) != nullptr)
354 // This is handled by VisitCXXRecordDecl()
355 return true;
356
357 // It seems we are in a C (not C++) translation unit
358 if (!should_include(rec))
359 return true;
360
361 LOG_DBG("= Visiting record declaration {} at {}",
362 rec->getQualifiedNameAsString(),
363 rec->getLocation().printToString(source_manager()));
364
365 auto record_ptr = create_declaration(rec);
366
367 if (!record_ptr)
368 return true;
369
370 return add_or_update(rec, std::move(record_ptr));
371}
372
374 clang::ObjCCategoryDecl *decl)
375{
376 if (!should_include(decl))
377 return true;
378
379 LOG_DBG("= Visiting ObjC category declaration {} at {}",
380 decl->getQualifiedNameAsString(),
381 decl->getLocation().printToString(source_manager()));
382
383 auto category_ptr = create_objc_category_declaration(decl);
384
385 if (!category_ptr)
386 return true;
387
388 const auto category_id = category_ptr->id();
389
390 id_mapper().add(decl->getID(), category_id);
391
392 auto &category_model =
393 diagram().find<objc_interface>(category_id).has_value()
394 ? *diagram().find<objc_interface>(category_id).get()
395 : *category_ptr;
396
397 process_objc_category_declaration(*decl, category_model);
398
399 if (diagram().should_include(category_model)) {
400 LOG_DBG("Adding ObjC category {} with id {}",
401 category_model.full_name(false), category_model.id());
402
403 add_objc_interface(std::move(category_ptr));
404 }
405 else {
406 LOG_DBG("Skipping ObjC category {} with id {}",
407 category_model.full_name(true), category_model.id());
408 }
409
410 return true;
411}
412
414 clang::ObjCProtocolDecl *decl)
415{
416 if (!should_include(decl))
417 return true;
418
419 LOG_DBG("= Visiting ObjC protocol declaration {} at {}",
420 decl->getQualifiedNameAsString(),
421 decl->getLocation().printToString(source_manager()));
422
423 auto protocol_ptr = create_objc_protocol_declaration(decl);
424
425 if (!protocol_ptr)
426 return true;
427
428 const auto protocol_id = protocol_ptr->id();
429
430 id_mapper().add(decl->getID(), protocol_id);
431
432 auto &protocol_model =
433 diagram().find<objc_interface>(protocol_id).has_value()
434 ? *diagram().find<objc_interface>(protocol_id).get()
435 : *protocol_ptr;
436
437 process_objc_protocol_declaration(*decl, protocol_model);
438
439 if (diagram().should_include(protocol_model)) {
440 LOG_DBG("Adding ObjC protocol {} with id {}",
441 protocol_model.full_name(false), protocol_model.id());
442
443 add_objc_interface(std::move(protocol_ptr));
444 }
445 else {
446 LOG_DBG("Skipping ObjC protocol {} with id {}",
447 protocol_model.full_name(true), protocol_model.id());
448 }
449
450 return true;
451}
452
454 clang::ObjCInterfaceDecl *decl)
455{
456 if (!should_include(decl))
457 return true;
458
459 LOG_DBG("= Visiting ObjC interface declaration {} at {}",
460 decl->getQualifiedNameAsString(),
461 decl->getLocation().printToString(source_manager()));
462
463 auto interface_ptr = create_objc_interface_declaration(decl);
464
465 if (!interface_ptr)
466 return true;
467
468 const auto protocol_id = interface_ptr->id();
469
470 id_mapper().add(decl->getID(), protocol_id);
471
472 auto &interface_model =
473 diagram().find<objc_interface>(protocol_id).has_value()
474 ? *diagram().find<objc_interface>(protocol_id).get()
475 : *interface_ptr;
476
477 if (!interface_model.complete())
478 process_objc_interface_declaration(*decl, interface_model);
479
480 if (diagram().should_include(interface_model)) {
481 LOG_DBG("Adding ObjC interface {} with id {}",
482 interface_model.full_name(false), interface_model.id());
483
484 add_objc_interface(std::move(interface_ptr));
485 }
486 else {
487 LOG_DBG("Skipping ObjC interface {} with id {}",
488 interface_model.full_name(true), interface_model.id());
489 }
490
491 return true;
492}
493
495{
496 if (!should_include(cpt))
497 return true;
498
499 LOG_DBG("= Visiting concept (isType: {}) declaration {} at {}",
500 cpt->isTypeConcept(), cpt->getQualifiedNameAsString(),
501 cpt->getLocation().printToString(source_manager()));
502
503 auto concept_model = create_concept_declaration(cpt);
504
505 if (!concept_model)
506 return true;
507
508 const auto concept_id = concept_model->id();
509
510 id_mapper().add(cpt->getID(), concept_id);
511
512 tbuilder().build_from_template_declaration(*concept_model, *cpt);
513
514 constexpr auto kMaxConstraintCount = 24U;
515
516#if LLVM_VERSION_MAJOR < 21
517 llvm::SmallVector<const clang::Expr *, kMaxConstraintCount> constraints{};
518 if (cpt->hasAssociatedConstraints()) {
519 cpt->getAssociatedConstraints(constraints);
520 }
521
522 for (const auto *expr : constraints) {
524 }
525#else
526 llvm::SmallVector<clang::AssociatedConstraint, kMaxConstraintCount>
527 constraints{};
528 if (cpt->hasAssociatedConstraints()) {
529 cpt->getAssociatedConstraints(constraints);
530 }
531 for (const auto &constraint : constraints) {
533 *concept_model, constraint.ConstraintExpr);
534 }
535#endif
536
537 if (cpt->getConstraintExpr() != nullptr) {
539 cpt, cpt->getConstraintExpr(), *concept_model);
540
542 *concept_model, cpt->getConstraintExpr());
543 }
544
545 if (diagram().should_include(*concept_model)) {
546 LOG_DBG("Adding concept {} with id {}", concept_model->full_name(false),
547 concept_model->id());
548
549 add_concept(std::move(concept_model));
550 }
551 else {
552 LOG_DBG("Skipping concept {} with id {}",
553 concept_model->full_name(true), concept_model->id());
554 }
555
556 return true;
557}
558
560 const clang::ConceptDecl *cpt, const clang::Expr *expr,
561 model::concept_ &concept_model) const
562{
563 if (const auto *constraint = llvm::dyn_cast<clang::RequiresExpr>(expr);
564 constraint) {
565
566 auto constraint_source = common::to_string(constraint);
567
568 LOG_DBG("== Processing constraint: '{}'", constraint_source);
569
570 for ([[maybe_unused]] const auto *requirement :
571 constraint->getRequirements()) {
572 // TODO
573 }
574
575 // process 'requires (...)' declaration
576 for (const auto *decl : constraint->getBody()->decls()) {
577 if (const auto *parm_var_decl =
578 llvm::dyn_cast<clang::ParmVarDecl>(decl);
579 parm_var_decl) {
580 parm_var_decl->getQualifiedNameAsString();
581
582 auto param_name = parm_var_decl->getNameAsString();
583 auto param_type = common::to_string(
584 parm_var_decl->getType(), cpt->getASTContext());
585
586 LOG_DBG("=== Processing parameter variable declaration: {}, {}",
587 param_type, param_name);
588
589 concept_model.add_parameter(
590 {std::move(param_type), std::move(param_name)});
591 }
592 else {
593 LOG_DBG("=== Processing some other concept declaration: {}",
594 decl->getID());
595 }
596 }
597
598 // process concept body requirements '{ }' if any
599 for (const auto *req : constraint->getRequirements()) {
600 if (req->getKind() == clang::concepts::Requirement::RK_Simple) {
601 const auto *simple_req =
602 llvm::dyn_cast<clang::concepts::ExprRequirement>(req);
603
604 if (simple_req != nullptr) {
606 simple_req->getExpr(), [&concept_model](const auto *e) {
607 auto simple_expr = common::to_string(e);
608
609 LOG_DBG("=== Processing expression requirement: {}",
610 simple_expr);
611
612 concept_model.add_statement(std::move(simple_expr));
613 });
614 }
615 }
616 else if (req->getKind() == clang::concepts::Requirement::RK_Type) {
618 llvm::dyn_cast<clang::concepts::TypeRequirement>(req),
619 [&concept_model, cpt](const auto *t) {
620 auto type_name = common::to_string(
621 t->getType()->getType(), cpt->getASTContext());
622
623 LOG_DBG(
624 "=== Processing type requirement: {}", type_name);
625
626 concept_model.add_statement(std::move(type_name));
627 });
628 }
629 else if (req->getKind() ==
630 clang::concepts::Requirement::RK_Nested) {
631 const auto *nested_req =
632 llvm::dyn_cast<clang::concepts::NestedRequirement>(req);
633
634 if (nested_req != nullptr) {
636 nested_req->getConstraintExpr(), [](const auto *e) {
637 LOG_DBG("=== Processing nested requirement: {}",
638 common::to_string(e));
639 });
640 }
641 }
642 else if (req->getKind() ==
643 clang::concepts::Requirement::RK_Compound) {
644 const auto *compound_req =
645 llvm::dyn_cast<clang::concepts::ExprRequirement>(req);
646
647 if (compound_req != nullptr) {
648 const auto *compound_expr_ptr = compound_req->getExpr();
649
650 if (compound_expr_ptr != nullptr) {
651 auto compound_expr =
652 common::to_string(compound_expr_ptr);
653
654 auto req_return_type =
655 compound_req->getReturnTypeRequirement();
656
657 if (!req_return_type.isEmpty()) {
658 compound_expr =
659 fmt::format("{{{}}} -> {}", compound_expr,
661 req_return_type.getTypeConstraint()));
662 }
663 else if (compound_req->hasNoexceptRequirement()) {
664 compound_expr =
665 fmt::format("{{{}}} noexcept", compound_expr);
666 }
667
668 LOG_DBG("=== Processing compound requirement: {}",
669 compound_expr);
670
671 concept_model.add_statement(std::move(compound_expr));
672 }
673 }
674 }
675 }
676 }
677 else if (const auto *binop = llvm::dyn_cast<clang::BinaryOperator>(expr);
678 binop) {
679 process_constraint_requirements(cpt, binop->getLHS(), concept_model);
680 process_constraint_requirements(cpt, binop->getRHS(), concept_model);
681 }
682 else if (const auto *unop = llvm::dyn_cast<clang::UnaryOperator>(expr);
683 unop) {
684 process_constraint_requirements(cpt, unop->getSubExpr(), concept_model);
685 }
686}
687
689 clanguml::common::model::element &c, const clang::Expr *expr)
690{
691 if (expr == nullptr)
692 return;
693 found_relationships_t relationships;
694
695 common::if_dyn_cast<clang::UnresolvedLookupExpr>(
696 expr, [&](const clang::UnresolvedLookupExpr *ul) {
697 for (const auto ta : ul->template_arguments()) {
698 if (ta.getArgument().getKind() !=
699 clang::TemplateArgument::ArgKind::Type)
700 continue;
701 find_relationships({}, ta.getArgument().getAsType(),
702 relationships, relationship_t::kConstraint);
703 }
704 });
705
706 common::if_dyn_cast<clang::ConceptSpecializationExpr>(
707 expr, [&](const auto *cs) {
709 });
710
711 common::if_dyn_cast<clang::RequiresExpr>(expr, [&](const auto *re) {
712 // TODO
713 });
714
715 common::if_dyn_cast<clang::BinaryOperator>(expr, [&](const auto *op) {
718 });
719
720 common::if_dyn_cast<clang::UnaryOperator>(expr, [&](const auto *op) {
722 });
723
724 for (const auto &[type_element_id, relationship_type, source_decl] :
725 relationships) {
726 if (type_element_id != c.id() &&
727 (relationship_type != relationship_t::kNone)) {
728
729 relationship r{relationship_type, type_element_id};
730
731 if (source_decl != nullptr) {
732 set_source_location(*source_decl, r);
733 }
734
735 c.add_relationship(std::move(r));
736 }
737 }
738}
739
742 const clang::ConceptSpecializationExpr *concept_specialization)
743{
744
745 if (const auto *cpt = concept_specialization->getNamedConcept();
746 should_include(cpt)) {
747
748 const auto cpt_name = cpt->getNameAsString();
749 const eid_t ast_id{cpt->getID()};
750 const auto maybe_id = id_mapper().get_global_id(ast_id);
751 if (!maybe_id)
752 return;
753
754 const auto target_id = maybe_id.value();
755
756 std::vector<std::string> constrained_template_params;
757
758 size_t argument_index{};
759
760 for (const auto ta : concept_specialization->getTemplateArguments()) {
761 if (ta.getKind() == clang::TemplateArgument::Type) {
762 auto type_name =
763 common::to_string(ta.getAsType(), cpt->getASTContext());
764 extract_constrained_template_param_name(concept_specialization,
765 cpt, constrained_template_params, argument_index,
766 type_name);
767 }
768 else if (ta.getKind() == clang::TemplateArgument::Pack) {
769 if (!ta.getPackAsArray().empty() &&
770 ta.getPackAsArray().front().isPackExpansion()) {
771 const auto &pack_head =
772 ta.getPackAsArray().front().getAsType();
773 auto type_name =
774 common::to_string(pack_head, cpt->getASTContext());
776 concept_specialization, cpt,
777 constrained_template_params, argument_index, type_name);
778 }
779 }
780 else {
781 LOG_DBG("Unsupported concept type parameter in concept: {}",
782 cpt_name);
783 }
784 argument_index++;
785 }
786
787 if (!constrained_template_params.empty())
789 {relationship_t::kConstraint, target_id, access_t::kNone,
790 fmt::format(
791 "{}", fmt::join(constrained_template_params, ","))});
792 }
793}
794
795bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
796{
797 if (!should_include(cls))
798 return true;
799
800 LOG_DBG("= Visiting class declaration {} at {}",
801 cls->getQualifiedNameAsString(),
802 cls->getLocation().printToString(source_manager()));
803
804 LOG_DBG(
805 "== getQualifiedNameAsString() = {}", cls->getQualifiedNameAsString());
806 if (cls->getOwningModule() != nullptr)
807 LOG_DBG(
808 "== getOwningModule()->Name = {}", cls->getOwningModule()->Name);
809 LOG_DBG("== getID() = {}", cls->getID());
810 LOG_DBG("== isTemplateDecl() = {}", cls->isTemplateDecl());
811 LOG_DBG("== isTemplated() = {}", cls->isTemplated());
812 LOG_DBG("== getParent()->isRecord()() = {}", cls->getParent()->isRecord());
813 LOG_DBG("== isCompleteDefinition() = {}", cls->isCompleteDefinition());
814
815 if (const auto *parent_record =
816 clang::dyn_cast<clang::RecordDecl>(cls->getParent());
817 parent_record != nullptr) {
818 LOG_DBG("== getParent()->getQualifiedNameAsString() = {}",
819 parent_record->getQualifiedNameAsString());
820 }
821
822 if (has_processed_template_class(cls->getQualifiedNameAsString()))
823 // If we have already processed the template of this class
824 // skip it
825 return true;
826
827 if (cls->isTemplated() && (cls->getDescribedTemplate() != nullptr)) {
828 // If the described templated of this class is already in the model
829 // skip it:
830 const eid_t ast_id{cls->getDescribedTemplate()->getID()};
831 if (id_mapper().get_global_id(ast_id))
832 return true;
833 }
834
835 // TODO: Add support for classes defined in function/method bodies
836 if (cls->isLocalClass() != nullptr)
837 return true;
838
839 auto c_ptr = create_declaration(cls);
840
841 if (!c_ptr)
842 return true;
843
844 return add_or_update(cls, std::move(c_ptr));
845}
846
847std::unique_ptr<clanguml::class_diagram::model::concept_>
849{
850 assert(cpt != nullptr);
851
852 if (!should_include(cpt))
853 return {};
854
855 auto concept_ptr{
856 std::make_unique<model::concept_>(config().using_namespace())};
857 auto &concept_model = *concept_ptr;
858
859 auto ns = common::get_template_namespace(*cpt);
860
861 concept_model.set_name(cpt->getNameAsString());
862 concept_model.set_namespace(ns);
863 concept_model.set_id(common::to_id(concept_model.full_name(false)));
864
865 process_comment(*cpt, concept_model);
866 set_source_location(*cpt, concept_model);
867 set_owning_module(*cpt, concept_model);
868
869 if (concept_model.skip())
870 return {};
871
872 concept_model.set_style(concept_model.style_spec());
873
874 return concept_ptr;
875}
876
878 clang::RecordDecl *rec)
879{
880 assert(rec != nullptr);
881
882 if (!should_include(rec))
883 return {};
884
885 auto record_ptr{std::make_unique<class_>(config().using_namespace())};
886 auto &record = *record_ptr;
887
888 process_record_parent(rec, record, namespace_{});
889
890 if (!record.is_nested()) {
891 auto record_name = rec->getQualifiedNameAsString();
892
893#if LLVM_VERSION_MAJOR < 16
894 if (record_name == "(anonymous)") {
895 util::if_not_null(rec->getTypedefNameForAnonDecl(),
896 [&record_name](const clang::TypedefNameDecl *name) {
897 record_name = name->getNameAsString();
898 });
899 }
900#endif
901
902 record.set_name(record_name);
903 record.set_id(common::to_id(record.full_name(false)));
904 }
905
906 process_comment(*rec, record);
907 set_source_location(*rec, record);
908 set_owning_module(*rec, record);
909
910 const auto record_full_name = record_ptr->full_name(false);
911
912 record.is_struct(rec->isStruct());
913 record.is_union(rec->isUnion());
914
915 if (record.skip())
916 return {};
917
918 record.set_style(record.style_spec());
919
920 return record_ptr;
921}
922
924 clang::CXXRecordDecl *cls)
925{
926 assert(cls != nullptr);
927
928 if (!should_include(cls))
929 return {};
930
931 auto c_ptr{std::make_unique<class_>(config().using_namespace())};
932 auto &c = *c_ptr;
933
934 auto ns{common::get_tag_namespace(*cls)};
935
936 process_record_parent(cls, c, ns);
937
938 if (!c.is_nested()) {
939 c.set_name(common::get_tag_name(*cls));
940 c.set_namespace(ns);
941 c.set_id(common::to_id(c.full_name(false)));
942 }
943
944 c.is_struct(cls->isStruct());
945
946 process_comment(*cls, c);
947 set_source_location(*cls, c);
948 set_owning_module(*cls, c);
949
950 if (c.skip())
951 return {};
952
953 c.set_style(c.style_spec());
954
955 return c_ptr;
956}
957
958std::unique_ptr<clanguml::class_diagram::model::objc_interface>
960 clang::ObjCCategoryDecl *decl)
961{
962 assert(decl != nullptr);
963
964 if (!should_include(decl))
965 return {};
966
967 auto c_ptr{std::make_unique<class_diagram::model::objc_interface>(
968 config().using_namespace())};
969 auto &c = *c_ptr;
970
971 decl->getClassInterface()->getNameAsString();
972 c.set_name(fmt::format("{}({})",
973 decl->getClassInterface()->getNameAsString(), decl->getNameAsString()));
974 c.set_id(common::to_id(fmt::format("__objc__category__{}", c.name())));
975 c.is_category(true);
976
977 process_comment(*decl, c);
978 set_source_location(*decl, c);
979
980 if (c.skip())
981 return {};
982
983 c.set_style(c.style_spec());
984
985 return c_ptr;
986}
987
988std::unique_ptr<clanguml::class_diagram::model::objc_interface>
990 clang::ObjCProtocolDecl *decl)
991{
992 assert(decl != nullptr);
993
994 if (!should_include(decl))
995 return {};
996
997 auto c_ptr{std::make_unique<class_diagram::model::objc_interface>(
998 config().using_namespace())};
999 auto &c = *c_ptr;
1000
1001 c.set_name(decl->getNameAsString());
1002 c.set_id(common::to_id(*decl));
1003 c.is_protocol(true);
1004
1005 process_comment(*decl, c);
1006 set_source_location(*decl, c);
1007
1008 if (c.skip())
1009 return {};
1010
1011 c.set_style(c.style_spec());
1012
1013 return c_ptr;
1014}
1015
1016std::unique_ptr<clanguml::class_diagram::model::objc_interface>
1018 clang::ObjCInterfaceDecl *decl)
1019{
1020 assert(decl != nullptr);
1021
1022 if (!should_include(decl))
1023 return {};
1024
1025 auto c_ptr{std::make_unique<class_diagram::model::objc_interface>(
1026 config().using_namespace())};
1027 auto &c = *c_ptr;
1028
1029 c.set_name(decl->getNameAsString());
1030 c.set_id(common::to_id(*decl));
1031
1032 process_comment(*decl, c);
1033 set_source_location(*decl, c);
1034
1035 if (c.skip())
1036 return {};
1037
1038 c.set_style(c.style_spec());
1039
1040 return c_ptr;
1041}
1042
1044 clang::RecordDecl *cls, class_ &c, const namespace_ &ns)
1045{
1046 std::optional<eid_t> id_opt;
1047 namespace_ parent_ns = ns;
1048
1049 find_record_parent_id(cls, id_opt, parent_ns);
1050
1051 if (id_opt && diagram().find<class_>(*id_opt)) {
1052 process_record_parent_by_type<class_>(*id_opt, c, parent_ns, cls);
1053 }
1054
1055 if (id_opt && diagram().find<objc_interface>(*id_opt)) {
1056 process_record_parent_by_type<objc_interface>(
1057 *id_opt, c, parent_ns, cls);
1058 }
1059}
1060
1062 const clang::CXXRecordDecl &cls, clanguml::class_diagram::model::class_ &c)
1063{
1064 // Process class child entities
1065 process_children(&cls, c);
1066
1067 // Process class bases
1068 process_class_bases(&cls, c);
1069
1070 c.complete(true);
1071}
1072
1074 const clang::ObjCCategoryDecl &cls, objc_interface &c)
1075{
1076 assert(c.is_category());
1077
1078 // Iterate over class methods (both regular and static)
1079 for (const auto *method : cls.methods()) {
1080 if (method != nullptr) {
1081 process_objc_method(*method, c);
1082 }
1083 }
1084
1085 // Add relationship to the ObjC Interface being extended by this
1086 // category
1087 if (cls.getClassInterface() != nullptr) {
1088 eid_t objc_interface_id = common::to_id(*cls.getClassInterface());
1090 relationship_t::kInstantiation, objc_interface_id, access_t::kNone};
1091
1092 LOG_DBG("Found protocol {} [{}] for ObjC interface {}",
1093 cls.getClassInterface()->getNameAsString(),
1094 objc_interface_id.value(), c.name());
1095
1096 c.add_relationship(std::move(r));
1097 }
1098
1099 c.complete(true);
1100}
1101
1103 const clang::ObjCProtocolDecl &cls, objc_interface &c)
1104{
1105 assert(c.is_protocol());
1106
1107 // Iterate over class methods (both regular and static)
1108 for (const auto *method : cls.methods()) {
1109 if (method != nullptr) {
1110 process_objc_method(*method, c);
1111 }
1112 }
1113
1114 c.complete(true);
1115}
1116
1118 const clang::ObjCInterfaceDecl &cls, objc_interface &c)
1119{
1120 assert(!c.is_protocol() && !c.is_category());
1121
1122 // Iterate over class methods (both regular and static)
1123 for (const auto *method : cls.methods()) {
1124 if (method != nullptr) {
1125 process_objc_method(*method, c);
1126 }
1127 }
1128
1129 for (const auto *ivar : cls.ivars()) {
1130 if (ivar != nullptr) {
1131 process_objc_ivar(*ivar, c);
1132 }
1133 }
1134
1136
1137 c.complete(true);
1138}
1140 const clang::ObjCInterfaceDecl &cls, objc_interface &c)
1141{
1142 if (const auto *base = cls.getSuperClass(); base != nullptr) {
1143 eid_t parent_id = common::to_id(*base);
1144 common::model::relationship cp{parent_id, access_t::kNone, false};
1145
1146 LOG_DBG("Found base class {} [{}] for ObjC interface {}",
1147 base->getNameAsString(), parent_id.value(), c.name());
1148
1149 c.add_relationship(std::move(cp));
1150 }
1151
1152 for (const auto *protocol : cls.protocols()) {
1153 eid_t parent_id = common::to_id(*protocol);
1155 relationship_t::kInstantiation, parent_id, access_t::kNone};
1156
1157 LOG_DBG("Found protocol {} [{}] for ObjC interface {}",
1158 protocol->getNameAsString(), parent_id.value(), c.name());
1159
1160 c.add_relationship(std::move(cp));
1161 }
1162}
1163
1165 const clang::ObjCIvarDecl &ivar, objc_interface &c)
1166{
1167 LOG_DBG("== Visiting ObjC ivar {}", ivar.getNameAsString());
1168
1169 // Default hint for relationship is aggregation
1170 auto relationship_hint = relationship_t::kAggregation;
1171
1172 auto field_type = ivar.getType();
1173 auto field_type_str =
1174 common::to_string(field_type, ivar.getASTContext(), false);
1175
1176 auto type_name = common::to_string(field_type, ivar.getASTContext());
1177
1178 const auto field_name = ivar.getNameAsString();
1179
1180 objc_member field{
1181 common::access_specifier_to_access_t(ivar.getAccessControl()),
1182 field_name, field_type_str};
1183
1184 field.set_qualified_name(ivar.getQualifiedNameAsString());
1185
1186 process_comment(ivar, field);
1187 set_source_location(ivar, field);
1188
1189 if (field.skip())
1190 return;
1191
1192 if (field_type->isObjCObjectPointerType()) {
1193 relationship_hint = relationship_t::kAggregation;
1194 field_type = field_type->getPointeeType();
1195 }
1196 else if (field_type->isPointerType()) {
1197 relationship_hint = relationship_t::kAssociation;
1198 field_type = field_type->getPointeeType();
1199 }
1200 else if (field_type->isLValueReferenceType()) {
1201 relationship_hint = relationship_t::kAssociation;
1202 field_type = field_type.getNonReferenceType();
1203 }
1204 else if (field_type->isArrayType()) {
1205 relationship_hint = relationship_t::kAggregation;
1206 while (field_type->isArrayType()) {
1207 auto current_multiplicity = field.destination_multiplicity();
1208 if (!current_multiplicity)
1209 field.set_destination_multiplicity(common::get_array_size(
1210 *field_type->getAsArrayTypeUnsafe()));
1211 else {
1212 auto maybe_array_size =
1213 common::get_array_size(*field_type->getAsArrayTypeUnsafe());
1214 if (maybe_array_size.has_value()) {
1215 field.set_destination_multiplicity(
1216 current_multiplicity.value() *
1217 maybe_array_size.value());
1218 }
1219 }
1220
1221 field_type = field_type->getAsArrayTypeUnsafe()->getElementType();
1222 }
1223 }
1224 else if (field_type->isRValueReferenceType()) {
1225 field_type = field_type.getNonReferenceType();
1226 }
1227
1228 found_relationships_t relationships;
1229
1230 if (!field.skip_relationship()) {
1231 // Find relationship for the type if the type has not been added
1232 // as aggregation
1233 if (field_type->getAsObjCInterfaceType() != nullptr &&
1234 field_type->getAsObjCInterfaceType()->getInterface() != nullptr) {
1235 const auto *objc_iface =
1236 field_type->getAsObjCInterfaceType()->getInterface();
1237
1238 relationships.emplace_back(common::to_id(*objc_iface),
1239 relationship_t::kAggregation, &ivar);
1240 }
1241 else if ((field_type->getAsRecordDecl() != nullptr) &&
1242 field_type->getAsRecordDecl()->getNameAsString().empty()) {
1243 // Relationships to fields whose type is an anonymous and nested
1244 // struct have to be handled separately here
1245 anonymous_struct_relationships_[field_type->getAsRecordDecl()
1246 ->getID()] =
1247 std::make_tuple(field.name(), relationship_hint, field.access(),
1248 field.destination_multiplicity());
1249 }
1250 else
1252 &ivar, field_type, relationships, relationship_hint);
1253
1254 add_relationships(c, field, relationships);
1255 }
1256
1257 // If this is an anonymous struct - replace the anonymous_XYZ part with
1258 // field name
1259 if ((field_type->getAsRecordDecl() != nullptr) &&
1260 field_type->getAsRecordDecl()->getNameAsString().empty()) {
1261 if (util::contains(field.type(), "(anonymous_")) {
1262 std::regex anonymous_re("anonymous_(\\d*)");
1263 field.set_type(
1264 std::regex_replace(field.type(), anonymous_re, field_name));
1265 }
1266 }
1267
1268 c.add_member(std::move(field));
1269}
1270
1272 const clang::CXXRecordDecl *cls, class_ &c)
1273{
1274 for (const auto &base : cls->bases()) {
1275 eid_t parent_id;
1276 if (const auto *tsp =
1277 base.getType()->getAs<clang::TemplateSpecializationType>();
1278 tsp != nullptr) {
1279 auto template_specialization_ptr =
1280 std::make_unique<class_>(config().using_namespace());
1282 *cls, *template_specialization_ptr, cls, *tsp, {});
1283
1284 parent_id = template_specialization_ptr->id();
1285
1286 if (diagram().should_include(*template_specialization_ptr)) {
1287 add_class(std::move(template_specialization_ptr));
1288 }
1289 }
1290 else if (const auto *record_type =
1291 base.getType()->getAs<clang::RecordType>();
1292 record_type != nullptr) {
1293 parent_id = common::to_id(*record_type->getDecl());
1294 }
1295 else
1296 // This could be a template parameter - we don't want it here
1297 continue;
1298
1299 common::model::relationship cp{parent_id,
1300 common::access_specifier_to_access_t(base.getAccessSpecifier()),
1301 base.isVirtual()};
1302
1303 LOG_DBG("Found base class {} [{}] for class {}",
1304 common::to_string(base.getType(), cls->getASTContext()),
1305 parent_id.value(), c.name());
1306
1307 c.add_relationship(std::move(cp));
1308 }
1309}
1310
1312 const clang::RecordDecl &cls, class_ &c)
1313{
1314 // Iterate over C struct fields
1315 for (const auto *field : cls.fields()) {
1316 if (field != nullptr)
1317 process_field(*field, c);
1318 }
1319
1320 c.complete(true);
1321}
1322
1324 const clang::CXXRecordDecl *cls, clanguml::class_diagram::model::class_ &c)
1325{
1326 assert(cls != nullptr);
1327
1328 // Iterate over class methods (both regular and static)
1329 for (const auto *method : cls->methods()) {
1330 if (method != nullptr) {
1331 process_method(*method, c);
1332 }
1333 }
1334
1335 // Iterate over class template methods
1336 if (const auto *cls_decl_context =
1337 clang::dyn_cast_or_null<clang::DeclContext>(cls);
1338 cls_decl_context != nullptr) {
1339 for (auto const *decl_iterator : cls_decl_context->decls()) {
1340 auto const *method_template =
1341 llvm::dyn_cast_or_null<clang::FunctionTemplateDecl>(
1342 decl_iterator);
1343 if (method_template == nullptr)
1344 continue;
1345
1346 process_template_method(*method_template, c);
1347 }
1348 }
1349
1350 // Iterate over regular class fields
1351 for (const auto *field : cls->fields()) {
1352 if (field != nullptr)
1353 process_field(*field, c);
1354 }
1355
1356 // First we have to collect any `typedef enum` declarations, which should
1357 // not be rendered as members (typedefs are visited here after enums)
1358 std::set<const clang::EnumDecl *> typedeffed_enums;
1359 for (const auto *decl : cls->decls()) {
1360 if (decl->getKind() == clang::Decl::Typedef) {
1361 const auto *typedeffed_enum = common::get_typedef_enum_decl(
1362 clang::dyn_cast<clang::TypedefDecl>(decl));
1363 if (typedeffed_enum != nullptr)
1364 typedeffed_enums.emplace(typedeffed_enum);
1365 }
1366 }
1367
1368 // Static fields have to be processed by iterating over variable
1369 // declarations
1370 for (const auto *decl : cls->decls()) {
1371 if (decl->getKind() == clang::Decl::Var) {
1372 const clang::VarDecl *variable_declaration{
1373 clang::dyn_cast_or_null<clang::VarDecl>(decl)};
1374 if ((variable_declaration != nullptr) &&
1375 variable_declaration->isStaticDataMember()) {
1376 process_static_field(*variable_declaration, c);
1377 }
1378 }
1379 else if (decl->getKind() == clang::Decl::Enum &&
1380 typedeffed_enums.count(
1381 clang::dyn_cast_or_null<clang::EnumDecl>(decl)) == 0) {
1382 const auto *enum_decl =
1383 clang::dyn_cast_or_null<clang::EnumDecl>(decl);
1384 if (enum_decl == nullptr)
1385 continue;
1386
1387 if (enum_decl->getNameAsString().empty()) {
1388 for (const auto *enum_const : enum_decl->enumerators()) {
1390 enum_decl->getAccess()),
1391 enum_const->getNameAsString(), "enum"};
1392 c.add_member(std::move(m));
1393 }
1394 }
1395 }
1396 }
1397
1398 if (cls->isCompleteDefinition())
1399 for (const auto *friend_declaration : cls->friends()) {
1400 if (friend_declaration != nullptr)
1401 process_friend(*friend_declaration, c);
1402 }
1403}
1404
1406 const clang::FriendDecl &f, class_ &c)
1407{
1408 if (const auto *friend_type_info = f.getFriendType()) {
1409 const auto friend_type = friend_type_info->getType();
1410 if (friend_type->getAs<clang::TemplateSpecializationType>() !=
1411 nullptr) {
1412 // TODO: handle template friend
1413 }
1414 else if (friend_type->getAs<clang::RecordType>() != nullptr) {
1415 if (should_include(friend_type->getAsRecordDecl())) {
1416 relationship r{relationship_t::kFriendship,
1417 common::to_id(*friend_type->getAsRecordDecl()),
1419 "<<friend>>"};
1420
1421 c.add_relationship(std::move(r));
1422 }
1423 }
1424 }
1425}
1426
1428 const clang::CXXMethodDecl &mf, class_ &c)
1429{
1430 // TODO: For now skip implicitly default methods
1431 // in the future, add config option to choose
1432 if (mf.isDefaulted() && !mf.isExplicitlyDefaulted())
1433 return;
1434
1435 auto method_return_type =
1436 common::to_string(mf.getReturnType(), mf.getASTContext());
1437
1438 common::ensure_lambda_type_is_relative(config(), method_return_type);
1439
1440 auto method_name = mf.getNameAsString();
1441 if (mf.isTemplated()) {
1442 // Sometimes in template specializations method names contain the
1443 // template parameters for some reason - drop them
1444 // Is there a better way to do this?
1445 method_name = method_name.substr(0, method_name.find('<'));
1446 }
1447
1449 util::trim(method_name),
1450 config().simplify_template_type(method_return_type)};
1451
1452 method.set_qualified_name(mf.getQualifiedNameAsString());
1453
1454 process_method_properties(mf, c, method_name, method);
1455
1456 process_comment(mf, method);
1457
1458 // Register the source location of the field declaration
1459 set_source_location(mf, method);
1460
1461 if (method.skip())
1462 return;
1463
1464 for (const auto *param : mf.parameters()) {
1465 if (param != nullptr)
1466 process_function_parameter(*param, method, c);
1467 }
1468
1469 // find relationship for return type
1470 found_relationships_t relationships;
1471
1472 // Move dereferencing to build() method of template_builder
1473 if (const auto *templ = mf.getReturnType()
1474 .getNonReferenceType()
1475 .getUnqualifiedType()
1476 ->getAs<clang::TemplateSpecializationType>();
1477 templ != nullptr) {
1478 const auto *unaliased_type = templ;
1479 if (unaliased_type->isTypeAlias())
1480 unaliased_type = unaliased_type->getAliasedType()
1481 ->getAs<clang::TemplateSpecializationType>();
1482
1483 if (unaliased_type != nullptr) {
1484 auto template_specialization_ptr =
1485 std::make_unique<class_>(config().using_namespace());
1487 *template_specialization_ptr,
1488 unaliased_type->getTemplateName().getAsTemplateDecl(),
1489 *unaliased_type, &c);
1490
1491 template_specialization_ptr->is_template();
1492
1493 if (diagram().should_include(*template_specialization_ptr)) {
1494 relationships.emplace_back(template_specialization_ptr->id(),
1495 relationship_t::kDependency, &mf);
1496
1497 add_class(std::move(template_specialization_ptr));
1498 }
1499 }
1500 }
1501
1503 &mf, mf.getReturnType(), relationships, relationship_t::kDependency);
1504
1505 for (const auto &[type_element_id, relationship_type, source_decl] :
1506 relationships) {
1507 if (type_element_id != c.id() &&
1508 (relationship_type != relationship_t::kNone)) {
1509 relationship r{relationship_t::kDependency, type_element_id};
1510
1511 if (source_decl != nullptr) {
1512 set_source_location(*source_decl, r);
1513 }
1514
1515 LOG_DBG("Adding method return type relationship from {}::{} to "
1516 "{}: {}",
1517 c, mf.getNameAsString(), r.type(), r.label());
1518
1519 c.add_relationship(std::move(r));
1520 }
1521 }
1522
1523 // Also consider the container itself if it is a template
1524 // instantiation it's arguments could count as reference to relevant
1525 // types
1526 auto underlying_type = mf.getReturnType();
1527 if (underlying_type->isReferenceType())
1528 underlying_type = underlying_type.getNonReferenceType();
1529 if (underlying_type->isPointerType())
1530 underlying_type = underlying_type->getPointeeType();
1531
1532 if (const auto *atsp = underlying_type->getAs<clang::AutoType>();
1533 atsp != nullptr) {
1535 }
1536
1537 method.update(config().using_namespace());
1538
1539 if (diagram().should_include(method)) {
1540 LOG_DBG("Adding method: {}", method.name());
1541
1542 c.add_method(std::move(method));
1543 }
1544}
1545
1547 const clang::ObjCMethodDecl &mf, objc_interface &c)
1548{
1549 auto method_return_type =
1550 common::to_string(mf.getReturnType(), mf.getASTContext());
1551
1553 util::trim(mf.getNameAsString()), method_return_type};
1554
1555 method.set_qualified_name(mf.getQualifiedNameAsString());
1556
1557 process_comment(mf, method);
1558
1559 // Register the source location of the field declaration
1560 set_source_location(mf, method);
1561
1562 if (method.skip())
1563 return;
1564
1565 method.is_static(mf.isClassMethod());
1566 method.is_optional(mf.isOptional());
1567
1568 for (const auto *param : mf.parameters()) {
1569 if (param != nullptr)
1570 process_objc_method_parameter(*param, method, c);
1571 }
1572
1573 // find relationship for return type
1574 found_relationships_t relationships;
1575
1577 &mf, mf.getReturnType(), relationships, relationship_t::kDependency);
1578
1579 for (const auto &[type_element_id, relationship_type, source_decl] :
1580 relationships) {
1581 if (type_element_id != c.id() &&
1582 (relationship_type != relationship_t::kNone)) {
1583 relationship r{relationship_t::kDependency, type_element_id};
1584
1585 if (source_decl != nullptr) {
1586 set_source_location(*source_decl, r);
1587 }
1588
1589 LOG_DBG("Adding method return type relationship from {}::{} to "
1590 "{}: {}",
1591 c, mf.getNameAsString(), r.type(), r.label());
1592
1593 c.add_relationship(std::move(r));
1594 }
1595 }
1596
1597 // TODO
1598 if (diagram().should_include(method)) {
1599 LOG_DBG("Adding ObjC method: {}", method.name());
1600
1601 c.add_method(std::move(method));
1602 }
1603}
1604
1606 const clang::CXXMethodDecl &mf, const class_ &c,
1607 const std::string &method_name, class_method &method) const
1608{
1609 const bool is_constructor = c.name() == method_name;
1610 const bool is_destructor = fmt::format("~{}", c.name()) == method_name;
1611
1612#if LLVM_VERSION_MAJOR > 17
1613 method.is_pure_virtual(mf.isPureVirtual());
1614#else
1615 method.is_pure_virtual(mf.isPure());
1616#endif
1617 method.is_virtual(mf.isVirtual());
1618 method.is_const(mf.isConst());
1619 method.is_defaulted(mf.isDefaulted());
1620 method.is_deleted(mf.isDeleted());
1621 method.is_static(mf.isStatic());
1622 method.is_operator(mf.isOverloadedOperator());
1623 method.is_constexpr(mf.isConstexprSpecified() && !is_constructor);
1624 method.is_consteval(mf.isConsteval());
1625 method.is_constructor(is_constructor);
1626 method.is_destructor(is_destructor);
1627 method.is_move_assignment(mf.isMoveAssignmentOperator());
1628 method.is_copy_assignment(mf.isCopyAssignmentOperator());
1629 method.is_noexcept(isNoexceptExceptionSpec(mf.getExceptionSpecType()));
1630 method.is_coroutine(common::is_coroutine(mf));
1631}
1632
1635 class_ &c, const clang::AutoType *atsp)
1636{
1637 auto desugared_atsp = atsp->getDeducedType();
1638
1639 if (atsp->isSugared()) {
1640 const auto *deduced_type =
1641 atsp->desugar()->getAs<clang::DeducedTemplateSpecializationType>();
1642
1643 if (deduced_type != nullptr)
1644 desugared_atsp = deduced_type->getDeducedType();
1645 }
1646
1647 if (desugared_atsp.isNull())
1648 return;
1649
1650 const auto *deduced_record_type = desugared_atsp->isRecordType()
1651 ? desugared_atsp->getAs<clang::RecordType>()
1652 : nullptr;
1653
1654 if (deduced_record_type != nullptr) {
1655 if (auto *deduced_auto_decl =
1656 llvm::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(
1657 deduced_record_type->getDecl());
1658 deduced_auto_decl != nullptr) {
1659
1660 const auto diagram_class_count_before_visit =
1661 diagram().classes().size();
1662
1663 VisitClassTemplateSpecializationDecl(deduced_auto_decl);
1664
1665 const bool visitor_added_new_template_specialization =
1666 (diagram().classes().size() -
1667 diagram_class_count_before_visit) > 0;
1668
1669 if (visitor_added_new_template_specialization) {
1670 const auto &template_specialization_model =
1671 diagram().classes().back();
1672
1673 if (should_include(deduced_auto_decl)) {
1674 relationship r{relationship_t::kDependency,
1675 template_specialization_model.get().id()};
1676
1677 c.add_relationship(std::move(r));
1678 }
1679 }
1680 }
1681 }
1682}
1683
1685 const clang::FunctionTemplateDecl &mf, class_ &c)
1686{
1687 // TODO: For now skip implicitly default methods
1688 // in the future, add config option to choose
1689 if (mf.getTemplatedDecl()->isDefaulted() &&
1690 !mf.getTemplatedDecl()->isExplicitlyDefaulted())
1691 return;
1692
1693 auto method_name = mf.getNameAsString();
1694 if (mf.isTemplated()) {
1695 // Sometimes in template specializations method names contain the
1696 // template parameters for some reason - drop them
1697 // Is there a better way to do this?
1698 method_name = util::trim(method_name.substr(0, method_name.find('<')));
1699 }
1700
1702 method_name, mf.getTemplatedDecl()->getReturnType().getAsString()};
1703
1705 clang::dyn_cast<clang::CXXMethodDecl>(mf.getTemplatedDecl()),
1706 [&](const auto *decl) {
1707 process_method_properties(*decl, c, method_name, method);
1708 });
1709
1711
1712 process_comment(mf, method);
1713
1714 if (method.skip())
1715 return;
1716
1717 for (const auto *param : mf.getTemplatedDecl()->parameters()) {
1718 if (param != nullptr)
1719 process_function_parameter(*param, method, c);
1720 }
1721
1722 method.update(config().using_namespace());
1723
1724 if (diagram().should_include(method)) {
1725 LOG_DBG("Adding method: {}", method.name());
1726
1727 c.add_method(std::move(method));
1728 }
1729}
1730
1732 const clang::QualType &type, found_relationships_t &relationships,
1734{
1735 bool result{false};
1736
1737 if (type->isPointerType()) {
1738 relationship_hint = relationship_t::kAssociation;
1740 decl, type->getPointeeType(), relationships, relationship_hint);
1741 }
1742 else if (type->isRValueReferenceType()) {
1743 relationship_hint = relationship_t::kAggregation;
1745 decl, type.getNonReferenceType(), relationships, relationship_hint);
1746 }
1747 else if (type->isLValueReferenceType()) {
1748 relationship_hint = relationship_t::kAssociation;
1750 decl, type.getNonReferenceType(), relationships, relationship_hint);
1751 }
1752 else if (type->isArrayType()) {
1753 find_relationships(decl, type->getAsArrayTypeUnsafe()->getElementType(),
1754 relationships, relationship_t::kAggregation);
1755 }
1756 else if (type->isEnumeralType()) {
1757 if (const auto *enum_type = type->getAs<clang::EnumType>();
1758 enum_type != nullptr) {
1759 // Use AST's local ID here for relationship target, as we can't
1760 // calculate here properly the ID for nested enums. It will be
1761 // resolved properly in finalize().
1762 relationships.emplace_back(
1763 enum_type->getDecl()->getID(), relationship_hint, decl);
1764 }
1765 }
1766 // TODO: Objc support
1767 else if (type->isRecordType()) {
1768 const auto *type_instantiation_type =
1769 type->getAs<clang::TemplateSpecializationType>();
1770
1771 if (type_instantiation_type != nullptr) {
1772 const auto *type_instantiation_template_decl =
1773 type_instantiation_type->getTemplateName().getAsTemplateDecl();
1774
1775 // If this template should be included in the diagram
1776 // add it - and then process recursively its arguments
1777 if (should_include(type_instantiation_template_decl)) {
1778 relationships.emplace_back(
1779 type_instantiation_type->getTemplateName()
1780 .getAsTemplateDecl()
1781 ->getID(),
1782 relationship_hint, decl);
1783 }
1784
1785 auto idx{0};
1786 for (const auto &template_argument :
1787 type_instantiation_type->template_arguments()) {
1788
1789 auto [overridden_relationship_hint, overridden] =
1790 override_relationship_hint(type_instantiation_template_decl
1791 ->getQualifiedNameAsString(),
1792 idx, relationship_hint);
1793
1794 const auto template_argument_kind = template_argument.getKind();
1795 if (template_argument_kind ==
1796 clang::TemplateArgument::ArgKind::Integral) {
1797 // pass
1798 }
1799 else if (template_argument_kind ==
1800 clang::TemplateArgument::ArgKind::Null) {
1801 // pass
1802 }
1803 else if (template_argument_kind ==
1804 clang::TemplateArgument::ArgKind::Expression) {
1805 // pass
1806 }
1807 else if (template_argument.getKind() ==
1808 clang::TemplateArgument::ArgKind::NullPtr) {
1809 // pass
1810 }
1811 else if (template_argument_kind ==
1812 clang::TemplateArgument::ArgKind::Template) {
1813 // pass
1814 }
1815 else if (template_argument_kind ==
1816 clang::TemplateArgument::ArgKind::TemplateExpansion) {
1817 // pass
1818 }
1819 else if (const auto *function_type =
1820 template_argument.getAsType()
1821 ->getAs<clang::FunctionProtoType>();
1822 function_type != nullptr) {
1823 for (const auto &param_type :
1824 function_type->param_types()) {
1825 result = find_relationships(decl, param_type,
1826 relationships, relationship_t::kDependency);
1827 }
1828 }
1829 else if (template_argument_kind ==
1830 clang::TemplateArgument::ArgKind::Type) {
1831 result =
1832 find_relationships(decl, template_argument.getAsType(),
1833 relationships, overridden_relationship_hint);
1834 }
1835 idx++;
1836 }
1837 }
1838 else if (type->getAsCXXRecordDecl() != nullptr) {
1839 relationships.emplace_back(
1840 type->getAsCXXRecordDecl()->getID(), relationship_hint, decl);
1841 result = true;
1842 }
1843 else {
1844 relationships.emplace_back(
1845 type->getAsRecordDecl()->getID(), relationship_hint, decl);
1846 result = true;
1847 }
1848 }
1849 else if (const auto *template_specialization_type =
1850 type->getAs<clang::TemplateSpecializationType>();
1851 template_specialization_type != nullptr) {
1852 const auto *type_instantiation_template_decl =
1853 template_specialization_type->getTemplateName().getAsTemplateDecl();
1854 if (should_include(template_specialization_type->getTemplateName()
1855 .getAsTemplateDecl())) {
1856 relationships.emplace_back(
1857 template_specialization_type->getTemplateName()
1858 .getAsTemplateDecl()
1859 ->getID(),
1860 relationship_hint, decl);
1861 }
1862 auto idx{0};
1863 for (const auto &template_argument :
1864 template_specialization_type->template_arguments()) {
1865
1866 auto [overridden_relationship_hint, overridden] =
1867 override_relationship_hint(type_instantiation_template_decl
1868 ->getQualifiedNameAsString(),
1869 idx, relationship_hint);
1870
1871 const auto template_argument_kind = template_argument.getKind();
1872 if (template_argument_kind ==
1873 clang::TemplateArgument::ArgKind::Integral) {
1874 // pass
1875 }
1876 else if (template_argument_kind ==
1877 clang::TemplateArgument::ArgKind::Null) {
1878 // pass
1879 }
1880 else if (template_argument_kind ==
1881 clang::TemplateArgument::ArgKind::Expression) {
1882 // pass
1883 }
1884 else if (template_argument.getKind() ==
1885 clang::TemplateArgument::ArgKind::NullPtr) {
1886 // pass
1887 }
1888 else if (template_argument_kind ==
1889 clang::TemplateArgument::ArgKind::Template) {
1890 // pass
1891 }
1892 else if (template_argument_kind ==
1893 clang::TemplateArgument::ArgKind::TemplateExpansion) {
1894 // pass
1895 }
1896 else if (const auto *function_type =
1897 template_argument.getAsType()
1898 ->getAs<clang::FunctionProtoType>();
1899 function_type != nullptr) {
1900 for (const auto &param_type : function_type->param_types()) {
1901 result = find_relationships(decl, param_type, relationships,
1902 relationship_t::kDependency);
1903 }
1904 }
1905 else if (template_argument_kind ==
1906 clang::TemplateArgument::ArgKind::Type) {
1907 result = find_relationships(decl, template_argument.getAsType(),
1908 relationships, overridden_relationship_hint);
1909 }
1910 idx++;
1911 }
1912 }
1913
1914 return result;
1915}
1916
1918 const clang::ParmVarDecl &param, objc_method &method, objc_interface &c)
1919{
1920 method_parameter parameter;
1921 parameter.set_name(param.getNameAsString());
1922
1923 process_comment(param, parameter);
1924
1925 if (parameter.skip())
1926 return;
1927
1928 auto parameter_type =
1929 common::to_string(param.getType(), param.getASTContext());
1930 parameter.set_type(parameter_type);
1931
1932 if (!parameter.skip_relationship()) {
1933 // find relationship for the type
1934 found_relationships_t relationships;
1935
1936 LOG_DBG("Looking for relationships in type: {}",
1937 common::to_string(param.getType(), param.getASTContext()));
1938
1939 find_relationships(&param, param.getType(), relationships,
1940 relationship_t::kDependency);
1941
1942 for (const auto &[type_element_id, relationship_type, source_decl] :
1943 relationships) {
1944 if (type_element_id != c.id() &&
1945 (relationship_type != relationship_t::kNone)) {
1946 relationship r{relationship_t::kDependency, type_element_id};
1947
1948 if (source_decl != nullptr) {
1949 set_source_location(*source_decl, r);
1950 }
1951
1952 LOG_DBG("Adding ObjC method parameter relationship from {} to "
1953 "{}: {}",
1954 c, r.type(), r.label());
1955
1956 c.add_relationship(std::move(r));
1957 }
1958 }
1959 }
1960
1961 method.add_parameter(std::move(parameter));
1962}
1963
1965 const clang::ParmVarDecl &p, class_method &method, class_ &c,
1966 const std::set<std::string> & /*template_parameter_names*/)
1967{
1968 method_parameter parameter;
1969 parameter.set_name(p.getNameAsString());
1970
1971 process_comment(p, parameter);
1972
1973 if (parameter.skip())
1974 return;
1975
1976 auto parameter_type = common::to_string(p.getType(), p.getASTContext());
1977
1978 // Is there no better way to determine that 'type' is a lambda?
1980
1981 parameter.set_type(parameter_type);
1982
1983 if (p.hasDefaultArg()) {
1984 const auto *default_arg = p.getDefaultArg();
1985 if (default_arg != nullptr) {
1986 auto default_arg_str = common::get_source_text(
1987 default_arg->getSourceRange(), source_manager());
1988 parameter.set_default_value(default_arg_str);
1989 }
1990 }
1991
1992 if (!parameter.skip_relationship()) {
1993 // find relationship for the type
1994 found_relationships_t relationships;
1995
1996 LOG_DBG("Looking for relationships in type: {}",
1997 common::to_string(p.getType(), p.getASTContext()));
1998
1999 if (const auto *templ =
2000 p.getType()
2001 .getNonReferenceType()
2002 .getUnqualifiedType()
2003 ->getAs<clang::TemplateSpecializationType>();
2004 templ != nullptr) {
2005 auto template_specialization_ptr =
2006 std::make_unique<class_>(config().using_namespace());
2008 *template_specialization_ptr,
2009 templ->getTemplateName().getAsTemplateDecl(), *templ, &c);
2010
2011 template_specialization_ptr->is_template(true);
2012
2013 if (diagram().should_include(*template_specialization_ptr)) {
2014 relationships.emplace_back(template_specialization_ptr->id(),
2015 relationship_t::kDependency, &p);
2016
2017 add_class(std::move(template_specialization_ptr));
2018 }
2019 }
2020
2022 &p, p.getType(), relationships, relationship_t::kDependency);
2023
2024 for (const auto &[type_element_id, relationship_type, source_decl] :
2025 relationships) {
2026 if (type_element_id != c.id() &&
2027 (relationship_type != relationship_t::kNone)) {
2028 relationship r{relationship_t::kDependency, type_element_id};
2029
2030 if (source_decl != nullptr) {
2031 set_source_location(*source_decl, r);
2032 }
2033
2034 LOG_DBG("Adding function parameter relationship from {} to "
2035 "{}: {}",
2036 c, r.type(), r.label());
2037
2038 c.add_relationship(std::move(r));
2039 }
2040 }
2041 }
2042
2043 method.add_parameter(std::move(parameter));
2044}
2045
2048 const found_relationships_t &relationships, bool break_on_first_aggregation)
2049{
2050 auto [decorator_rtype, decorator_rmult] = field.get_relationship();
2051
2052 for (const auto &[target, relationship_type, source_decl] : relationships) {
2053 if (relationship_type != relationship_t::kNone) {
2054 relationship r{relationship_type, target};
2055 r.set_label(field.name());
2056 r.set_access(field.access());
2057 if (source_decl != nullptr) {
2058 set_source_location(*source_decl, r);
2059 }
2060 bool mulitplicity_provided_in_comment{false};
2061 if (decorator_rtype != relationship_t::kNone) {
2062 r.set_type(decorator_rtype);
2063 auto mult = util::split(decorator_rmult, ":", false);
2064 if (mult.size() == 2) {
2065 mulitplicity_provided_in_comment = true;
2066 r.set_multiplicity_source(mult[0]);
2067 r.set_multiplicity_destination(mult[1]);
2068 }
2069 }
2070 if (!mulitplicity_provided_in_comment &&
2071 field.destination_multiplicity().has_value()) {
2072 r.set_multiplicity_destination(
2073 std::to_string(*field.destination_multiplicity()));
2074 }
2075
2076 r.set_style(field.style_spec());
2077
2078 LOG_DBG("Adding relationship from {} to {} with label {}", c,
2079 r.destination(), r.type(), r.label());
2080
2081 c.add_relationship(std::move(r));
2082
2083 if (break_on_first_aggregation &&
2084 relationship_type == relationship_t::kAggregation)
2085 break;
2086 }
2087 }
2088}
2089
2090std::pair<relationship_t, bool>
2092 const std::string &type_name, int index, relationship_t hint)
2093{
2094 bool overridden{false};
2095
2096 for (const auto &[pattern, rel_hint] : config().relationship_hints()) {
2097 if (type_name.find(pattern) == 0) {
2098 if (index == -1)
2099 hint = rel_hint.default_hint;
2100 else
2101 hint = rel_hint.get(index, hint);
2102
2103 overridden = true;
2104 break;
2105 }
2106 }
2107
2108 return {hint, overridden};
2109}
2110
2112 const clang::VarDecl &field_declaration, class_ &c)
2113{
2114 const auto field_type = field_declaration.getType();
2115 auto type_name =
2116 common::to_string(field_type, field_declaration.getASTContext());
2117 if (type_name.empty())
2118 type_name = "<<anonymous>>";
2119
2120 class_member field{
2121 common::access_specifier_to_access_t(field_declaration.getAccess()),
2122 field_declaration.getNameAsString(),
2123 config().simplify_template_type(type_name)};
2124
2125 field.set_qualified_name(field_declaration.getQualifiedNameAsString());
2126
2127 field.is_static(true);
2128
2129 process_comment(field_declaration, field);
2130 set_source_location(field_declaration, field);
2131
2132 if (field.skip() || !diagram().should_include(field))
2133 return;
2134
2135 if (!field.skip_relationship()) {
2136 found_relationships_t relationships;
2137
2138 // find relationship for the type
2139 find_relationships(&field_declaration, field_declaration.getType(),
2140 relationships, relationship_t::kAssociation);
2141
2142 add_relationships(c, field, relationships);
2143 }
2144
2145 c.add_member(std::move(field));
2146}
2147
2148std::unique_ptr<class_>
2150 clang::ClassTemplateSpecializationDecl *cls)
2151{
2152 LOG_DBG("Processing template specialization {} at {}",
2153 cls->getQualifiedNameAsString(),
2154 cls->getLocation().printToString(source_manager()));
2155
2156 auto c_ptr = std::make_unique<class_>(config().using_namespace());
2158
2159 auto &template_instantiation = *c_ptr;
2160 template_instantiation.is_template(true);
2161
2162 // TODO: refactor to method get_qualified_name()
2163 auto qualified_name = cls->getQualifiedNameAsString();
2164 util::replace_all(qualified_name, "(anonymous namespace)", "");
2165 util::replace_all(qualified_name, "::::", "::");
2166
2167 namespace_ ns{qualified_name};
2168 ns.pop_back();
2169 template_instantiation.set_name(cls->getNameAsString());
2170 template_instantiation.set_namespace(ns);
2171
2172 template_instantiation.is_struct(cls->isStruct());
2173
2174 process_record_parent(cls, template_instantiation, ns);
2175
2176 if (!template_instantiation.is_nested()) {
2177 template_instantiation.set_name(common::get_tag_name(*cls));
2178 template_instantiation.set_id(
2179 common::to_id(template_instantiation.full_name(false)));
2180 }
2181
2182 process_comment(*cls, template_instantiation);
2183 set_source_location(*cls, template_instantiation);
2184 set_owning_module(*cls, template_instantiation);
2185
2186 if (template_instantiation.skip())
2187 return {};
2188
2189 id_mapper().add(cls->getID(), template_instantiation.id());
2190
2191 return c_ptr;
2192}
2193
2195 const clang::FieldDecl &field_declaration, class_ &c)
2196{
2197 LOG_DBG(
2198 "== Visiting record member {}", field_declaration.getNameAsString());
2199
2200 // Default hint for relationship is aggregation
2201 auto relationship_hint = relationship_t::kAggregation;
2202 // If the first type of the template instantiation of this field type
2203 // has been added as aggregation relationship with class 'c', don't
2204 // add it's nested template types as aggregation
2205 [[maybe_unused]] bool template_instantiation_added_as_aggregation{false};
2206 // The actual field type
2207 auto field_type = field_declaration.getType();
2208
2209 // String representation of the field type
2210 auto type_name =
2211 common::to_string(field_type, field_declaration.getASTContext());
2212 // The field name
2213 const auto field_name = field_declaration.getNameAsString();
2214
2215 auto field_type_str =
2216 common::to_string(field_type, field_declaration.getASTContext(), false);
2217
2219
2220 class_member field{
2221 common::access_specifier_to_access_t(field_declaration.getAccess()),
2222 field_name, config().simplify_template_type(field_type_str)};
2223
2224 field.set_qualified_name(field_declaration.getQualifiedNameAsString());
2225
2226 // Parse the field comment
2227 process_comment(field_declaration, field);
2228 // Register the source location of the field declaration
2229 set_source_location(field_declaration, field);
2230
2231 // If the comment contains a skip directive, just return
2232 if (field.skip())
2233 return;
2234
2235 if (field_type->isPointerType()) {
2236 relationship_hint = relationship_t::kAssociation;
2237 field_type = field_type->getPointeeType();
2238 }
2239 else if (field_type->isLValueReferenceType()) {
2240 relationship_hint = relationship_t::kAssociation;
2241 field_type = field_type.getNonReferenceType();
2242 }
2243 else if (field_type->isArrayType()) {
2244 relationship_hint = relationship_t::kAggregation;
2245 while (field_type->isArrayType()) {
2246 auto current_multiplicity = field.destination_multiplicity();
2247 if (!current_multiplicity)
2248 field.set_destination_multiplicity(common::get_array_size(
2249 *field_type->getAsArrayTypeUnsafe()));
2250 else {
2251 auto maybe_array_size =
2252 common::get_array_size(*field_type->getAsArrayTypeUnsafe());
2253 if (maybe_array_size.has_value()) {
2254 field.set_destination_multiplicity(
2255 current_multiplicity.value() *
2256 maybe_array_size.value());
2257 }
2258 }
2259
2260 field_type = field_type->getAsArrayTypeUnsafe()->getElementType();
2261 }
2262 }
2263 else if (field_type->isRValueReferenceType()) {
2264 field_type = field_type.getNonReferenceType();
2265 }
2266
2267 auto [overridden_relationship_hint, overridden] =
2268 override_relationship_hint(type_name, -1, relationship_hint);
2269 if (overridden)
2270 relationship_hint = overridden_relationship_hint;
2271
2272 found_relationships_t relationships;
2273
2274 const auto *template_field_type =
2275 field_type->getAs<clang::TemplateSpecializationType>();
2276 // TODO: Refactor to an unalias_type() method
2277 if (template_field_type != nullptr)
2278 if (template_field_type->isTypeAlias())
2279 template_field_type =
2280 template_field_type->getAliasedType()
2281 ->getAs<clang::TemplateSpecializationType>();
2282
2283 bool field_type_is_template_template_parameter{false};
2284 if (template_field_type != nullptr) {
2285 // Skip types which are template template parameters of the parent
2286 // template
2287 for (const auto &class_template_param : c.template_params()) {
2288 if (class_template_param.name() ==
2289 template_field_type->getTemplateName()
2290 .getAsTemplateDecl()
2291 ->getNameAsString() +
2292 "<>") {
2293 field_type_is_template_template_parameter = true;
2294 }
2295 }
2296 }
2297
2298 // Process the type which is template instantiation of some sort
2299 if (template_field_type != nullptr &&
2300 !field_type_is_template_template_parameter) {
2301 // Build the template instantiation for the field type
2302 auto template_specialization_ptr =
2303 std::make_unique<class_>(config().using_namespace());
2305 *template_specialization_ptr,
2306 field_type->getAs<clang::TemplateSpecializationType>()
2307 ->getTemplateName()
2308 .getAsTemplateDecl(),
2309 *template_field_type, {&c});
2310 template_specialization_ptr->is_template(true);
2311
2312 if (!field.skip_relationship() && template_specialization_ptr) {
2313 const auto &template_specialization = *template_specialization_ptr;
2314
2315 // Check if this template instantiation should be added to the
2316 // current diagram. Even if the top level template type for
2317 // this instantiation should not be part of the diagram, e.g.
2318 // it's a std::vector<>, it's nested types might be added
2319 bool add_template_instantiation_to_diagram{false};
2320 if (diagram().should_include(template_specialization)) {
2321
2322 found_relationships_t::value_type r{
2323 template_specialization.id(), relationship_hint,
2324 &field_declaration};
2325
2326 add_template_instantiation_to_diagram = true;
2327
2328 // If the template instantiation for the build type has been
2329 // added as aggregation, skip its nested templates
2330 template_instantiation_added_as_aggregation =
2331 relationship_hint == relationship_t::kAggregation;
2332 relationships.emplace_back(std::move(r));
2333 }
2334
2335 // Try to find relationships to types nested in the template
2336 // instantiation
2337 found_relationships_t nested_relationships;
2338 auto idx{0};
2339 if (!template_instantiation_added_as_aggregation) {
2340 for (const auto &template_argument :
2341 template_specialization.template_params()) {
2342
2343 auto template_argument_str = template_argument.to_string(
2344 config().using_namespace(), false);
2345
2346 LOG_DBG("Looking for nested relationships from {}::{} in "
2347 "template argument {}",
2348 c, field_name, template_argument_str);
2349
2350 auto [overridden_relationship_hint_param,
2351 overridden_param] =
2353 template_specialization.full_name(false), idx,
2354 relationship_hint);
2355
2356 template_instantiation_added_as_aggregation =
2357 template_argument.find_nested_relationships(
2358 &field_declaration, nested_relationships,
2359 overridden_relationship_hint_param,
2360 !overridden_param,
2361 [&d = diagram()](const std::string &full_name) {
2362 if (full_name.empty())
2363 return false;
2364 auto [ns, name] = common::split_ns(full_name);
2365 return d.should_include(ns, name);
2366 });
2367 }
2368
2369 // Add any relationships to the class 'c' to the diagram,
2370 // unless the top level type has been added as aggregation
2371 add_relationships(c, field, nested_relationships,
2372 /* break on first aggregation */ false);
2373
2374 idx++;
2375 }
2376
2377 // Add the template instantiation object to the diagram if it
2378 // matches the include pattern
2379 if (add_template_instantiation_to_diagram)
2380 add_class(std::move(template_specialization_ptr));
2381 }
2382 }
2383
2384 if (!field.skip_relationship()) {
2385 // Find relationship for the type if the type has not been added
2386 // as aggregation
2387 if (!template_instantiation_added_as_aggregation) {
2388 if ((field_type->getAsRecordDecl() != nullptr) &&
2389 field_type->getAsRecordDecl()->getNameAsString().empty()) {
2390 // Relationships to fields whose type is an anonymous nested
2391 // struct have to be handled separately here
2392 anonymous_struct_relationships_[field_type->getAsRecordDecl()
2393 ->getID()] =
2394 std::make_tuple(field.name(), relationship_hint,
2395 field.access(), field.destination_multiplicity());
2396 }
2397 else
2398 find_relationships(&field_declaration, field_type,
2399 relationships, relationship_hint);
2400 }
2401
2402 add_relationships(c, field, relationships);
2403 }
2404
2405 // If this is an anonymous struct - replace the anonymous_XYZ part with
2406 // field name
2407 if ((field_type->getAsRecordDecl() != nullptr) &&
2408 field_type->getAsRecordDecl()->getNameAsString().empty()) {
2409 if (util::contains(field.type(), "(anonymous_")) {
2410 std::regex anonymous_re("anonymous_(\\d*)");
2411 field.set_type(
2412 std::regex_replace(field.type(), anonymous_re, field_name));
2413 }
2414 }
2415
2416 if (diagram().should_include(field)) {
2417 c.add_member(std::move(field));
2418 }
2419}
2420
2422 std::optional<eid_t> &parent_id_opt, namespace_ &parent_ns) const
2423{
2424 const auto *parent = decl->getParent();
2425
2426 if (parent != nullptr) {
2427 if (const auto *parent_record_decl =
2428 clang::dyn_cast<clang::RecordDecl>(parent);
2429 parent_record_decl != nullptr) {
2430 parent_ns = common::get_tag_namespace(*parent_record_decl);
2431
2432 eid_t local_id{parent_record_decl->getID()};
2433
2434 // First check if the parent has been added to the diagram as
2435 // regular class
2436 parent_id_opt = id_mapper().get_global_id(local_id);
2437
2438 // If not, check if the parent template declaration is in the
2439 // model
2440 if (!parent_id_opt) {
2441 if (parent_record_decl->getDescribedTemplate() != nullptr) {
2442 local_id =
2443 parent_record_decl->getDescribedTemplate()->getID();
2444 parent_id_opt = id_mapper().get_global_id(local_id);
2445 }
2446 }
2447 }
2448 }
2449
2450 if (parent_id_opt)
2451 return;
2452
2453 const auto *lexical_parent = decl->getLexicalParent();
2454 if (lexical_parent != nullptr) {
2455 if (const auto *parent_interface_decl =
2456 clang::dyn_cast<clang::ObjCInterfaceDecl>(lexical_parent);
2457 parent_interface_decl != nullptr) {
2458
2459 eid_t ast_id{parent_interface_decl->getID()};
2460
2461 // First check if the parent has been added to the diagram as
2462 // regular class
2463 parent_id_opt = id_mapper().get_global_id(ast_id);
2464 }
2465 }
2466}
2467
2469{
2470 for (auto &[id, c] : forward_declarations_.get<class_>()) {
2471 if (!diagram().find<class_>(id).has_value() &&
2472 diagram().should_include(c->get_namespace())) {
2473 add_class(std::move(c));
2474 }
2475 }
2476 forward_declarations_.get<class_>().clear();
2477
2478 for (auto &[id, e] : forward_declarations_.get<enum_>()) {
2479 if (!diagram().find<enum_>(id).has_value() &&
2480 diagram().should_include(e->get_namespace())) {
2481 add_enum(std::move(e));
2482 }
2483 }
2484 forward_declarations_.get<enum_>().clear();
2485}
2486
2488{
2489 diagram().for_all_elements([&](auto &element_view) {
2490 for (const auto &el : element_view) {
2491 for (auto &rel : el.get().relationships()) {
2492 if (!rel.destination().is_global()) {
2493 const auto maybe_id =
2494 id_mapper().get_global_id(rel.destination());
2495 if (maybe_id) {
2496 LOG_TRACE("= Resolved instantiation destination "
2497 "from local "
2498 "id {} to global id {}",
2499 rel.destination(), *maybe_id);
2500 rel.set_destination(*maybe_id);
2501 }
2502 }
2503 }
2504 el.get().remove_duplicate_relationships();
2505 }
2506 });
2507}
2508
2510{
2513 if (config().skip_redundant_dependencies()) {
2514 diagram().remove_redundant_dependencies();
2515 }
2516}
2517
2519 const clang::ConceptSpecializationExpr *concept_specialization,
2520 const clang::ConceptDecl *cpt,
2521 std::vector<std::string> &constrained_template_params,
2522 size_t argument_index, std::string &type_name) const
2523{
2524 const auto full_declaration_text = common::get_source_text_raw(
2525 concept_specialization->getSourceRange(), source_manager());
2526
2527 if (!full_declaration_text.empty()) {
2528 // Handle typename constraint in requires clause
2529 if (type_name.find("type-parameter-") == 0) {
2530 const auto concept_declaration_text = full_declaration_text.substr(
2531 full_declaration_text.find(cpt->getNameAsString()) +
2532 cpt->getNameAsString().size() + 1);
2533
2534 auto template_params = common::parse_unexposed_template_params(
2535 concept_declaration_text, [](const auto &t) { return t; });
2536
2537 if (template_params.size() > argument_index)
2538 type_name = template_params[argument_index].to_string(
2539 config().using_namespace(), false);
2540 }
2541 constrained_template_params.push_back(type_name);
2542 }
2543}
2544
2546 std::string qualified_name)
2547{
2548 processed_template_qualified_names_.emplace(std::move(qualified_name));
2549}
2550
2552 const std::string &qualified_name) const
2553{
2555}
2556
2558 std::unique_ptr<common::model::template_element> element)
2559{
2560 add_class(util::unique_pointer_cast<class_>(std::move(element)));
2561}
2562
2563void translation_unit_visitor::add_class(std::unique_ptr<class_> &&c)
2564{
2565 if ((config().generate_packages() &&
2566 config().package_type() == config::package_type_t::kDirectory)) {
2567 assert(!c->file().empty());
2568
2569 const auto file = config().make_path_relative(c->file());
2570
2573 p.pop_back();
2574
2575 diagram().add(p, std::move(c));
2576 }
2577 else if ((config().generate_packages() &&
2578 config().package_type() == config::package_type_t::kModule)) {
2579
2580 const auto module_path = config().make_module_relative(c->module());
2581
2583
2584 diagram().add(p, std::move(c));
2585 }
2586 else {
2587 diagram().add(c->path(), std::move(c));
2588 }
2589}
2590
2592 std::unique_ptr<objc_interface> &&c)
2593{
2594 if ((config().generate_packages() &&
2595 config().package_type() == config::package_type_t::kDirectory)) {
2596 assert(!c->file().empty());
2597
2598 const auto file = config().make_path_relative(c->file());
2599
2602 p.pop_back();
2603
2604 diagram().add(p, std::move(c));
2605 }
2606 else {
2607 diagram().add(c->path(), std::move(c));
2608 }
2609}
2610
2611void translation_unit_visitor::add_enum(std::unique_ptr<enum_> &&e)
2612{
2613 if ((config().generate_packages() &&
2614 config().package_type() == config::package_type_t::kDirectory)) {
2615 assert(!e->file().empty());
2616
2617 const auto file = config().make_path_relative(e->file());
2618
2621 p.pop_back();
2622
2623 diagram().add(p, std::move(e));
2624 }
2625 else if ((config().generate_packages() &&
2626 config().package_type() == config::package_type_t::kModule)) {
2627
2628 const auto module_path = config().make_module_relative(e->module());
2629
2631
2632 diagram().add(p, std::move(e));
2633 }
2634 else {
2635 diagram().add(e->path(), std::move(e));
2636 }
2637}
2638
2639void translation_unit_visitor::add_concept(std::unique_ptr<concept_> &&c)
2640{
2641 if ((config().generate_packages() &&
2642 config().package_type() == config::package_type_t::kDirectory)) {
2643 assert(!c->file().empty());
2644
2645 const auto file = config().make_path_relative(c->file());
2646
2649 p.pop_back();
2650
2651 diagram().add(p, std::move(c));
2652 }
2653 else if ((config().generate_packages() &&
2654 config().package_type() == config::package_type_t::kModule)) {
2655
2656 const auto module_path = config().make_module_relative(c->module());
2657
2659
2660 diagram().add(p, std::move(c));
2661 }
2662 else {
2663 diagram().add(c->path(), std::move(c));
2664 }
2665}
2666
2668 common::model::template_element &template_instantiation_base,
2669 const std::string &full_name, eid_t templated_decl_id)
2670{
2671 auto &template_instantiation = dynamic_cast<class_diagram::model::class_ &>(
2672 template_instantiation_base);
2673
2674 // First try to find the best match for this template in partially
2675 // specialized templates
2676 std::string destination{};
2677 std::string best_match_full_name{};
2678 auto full_template_name = template_instantiation.full_name(false);
2679 int best_match{};
2680 eid_t best_match_id{};
2681
2682 for (const auto templ : diagram().classes()) {
2683 if (templ.get() == template_instantiation)
2684 continue;
2685
2686 auto c_full_name = templ.get().full_name(false);
2687 auto match =
2688 template_instantiation.calculate_template_specialization_match(
2689 templ.get());
2690
2691 if (match > best_match) {
2692 best_match = match;
2693 best_match_full_name = c_full_name;
2694 best_match_id = templ.get().id();
2695 }
2696 }
2697
2698 auto templated_decl_global_id =
2699 id_mapper().get_global_id(templated_decl_id).value_or(eid_t{});
2700
2701 if (best_match_id.value() > 0) {
2702 destination = best_match_full_name;
2703 template_instantiation.add_relationship(
2705 template_instantiation.template_specialization_found(true);
2706 }
2707 // If we can't find optimal match for parent template specialization,
2708 // just use whatever clang suggests
2709 else if (diagram().has_element(templated_decl_global_id)) {
2710 template_instantiation.add_relationship(
2712 templated_decl_global_id});
2713 template_instantiation.template_specialization_found(true);
2714 }
2715 else if (id_mapper().get_global_id(templated_decl_id).has_value()) {
2716 template_instantiation.add_relationship(
2718 template_instantiation.template_specialization_found(true);
2719 }
2720 else if (diagram().should_include(common::model::namespace_{full_name})) {
2721 LOG_DBG("Skipping instantiation relationship from {} to {}",
2722 template_instantiation, templated_decl_global_id);
2723 }
2724 else {
2725 LOG_DBG("== Cannot determine global id for specialization template {} "
2726 "- delaying until the translation unit is complete ",
2727 templated_decl_global_id);
2728
2729 template_instantiation.add_relationship(
2731 }
2732}
2733
2734} // namespace clanguml::class_diagram::visitor