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