0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
template_parameter.cc
Go to the documentation of this file.
1/**
2 * @file src/common/model/template_parameter.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
19#include "template_parameter.h"
20#include "common/model/enums.h"
22
23#include <utility>
24
26
27std::string context::to_string() const
28{
29 std::vector<std::string> cv_qualifiers;
30 if (is_const)
31 cv_qualifiers.emplace_back("const");
32 if (is_volatile)
33 cv_qualifiers.emplace_back("volatile");
34
35 auto res = fmt::format("{}", fmt::join(cv_qualifiers, " "));
36
38 res += "*";
40 res += "&";
42 res += "&&";
43
44 if (is_ref_const)
45 res += " const";
47 res += " volatile";
48
49 return res;
50}
51
52bool context::operator==(const context &rhs) const
53{
54 return is_const == rhs.is_const && is_volatile == rhs.is_volatile &&
56 is_ref_volatile == rhs.is_ref_volatile && pr == rhs.pr;
57}
58
59bool context::operator!=(const context &rhs) const { return !(rhs == *this); }
60
62{
63 switch (k) {
65 return "template_type";
67 return "template_template_type";
69 return "non_type_template";
71 return "argument";
73 return "concept_constraint";
75 return "empty";
76 default:
77 assert(false);
78 return "";
79 }
80}
81
83{
86 return p;
87}
88
90 const std::string &name, const std::optional<std::string> &default_value,
91 bool is_variadic)
92{
95 p.set_name(name);
98 if (default_value)
100 return p;
101}
102
104 const std::string &name, const std::optional<std::string> &default_value,
105 bool is_variadic)
106{
109 p.set_name(name + "<>");
110 if (default_value)
113 return p;
114}
115
117 const std::string &type, const std::optional<std::string> &name,
118 const std::optional<std::string> &default_value, bool is_variadic)
119{
122 p.set_type(type);
123 if (name)
124 p.set_name(name.value());
125 if (default_value)
128 return p;
129}
130
132 const std::string &type, const std::optional<std::string> &default_value)
133{
136 p.set_type(type);
137 if (default_value)
139 return p;
140}
141
143 const std::string &type, const std::optional<std::string> &default_value)
144{
146 p.set_unexposed(true);
147 return p;
148}
149
151{
152 return is_function_template() || is_array() || is_data_pointer() ||
153 is_member_pointer() || !deduced_context().empty();
154}
155
157 const template_parameter &other) const
158{
159 return is_array() == other.is_array() &&
161 is_data_pointer() == other.is_data_pointer() &&
163}
164
165void template_parameter::set_type(const std::string &type)
166{
168
169 if (util::ends_with(type, std::string{"..."})) {
170 type_ = type.substr(0, type.size() - 3);
171 is_variadic_ = true;
172 }
173 else
174 type_ = type;
175}
176
177std::optional<std::string> template_parameter::type() const
178{
179 if (!type_)
180 return {};
181
182 if (is_variadic_)
183 return type_.value() + "...";
184
185 return type_;
186}
187
188void template_parameter::set_name(const std::string &name)
189{
191
192 if (name.empty()) {
193 return;
194 }
195
196 if (util::ends_with(name, std::string{"..."})) {
197 name_ = name.substr(0, name.size() - 3);
198 is_variadic_ = true;
199 }
200 else
201 name_ = name;
202}
203
204std::optional<std::string> template_parameter::name() const
205{
206 if (!name_)
207 return {};
208
210 name_.has_value() && name_.value().empty())
211 return "typename";
212
214 return name_.value() + "...";
215
216 return name_;
217}
218
219void template_parameter::set_default_value(const std::string &value)
220{
222
223 default_value_ = value;
224}
225
226const std::optional<std::string> &template_parameter::default_value() const
227{
228 return default_value_;
229}
230
231void template_parameter::is_variadic(bool is_variadic) noexcept
232{
233 is_variadic_ = is_variadic;
234}
235
236bool template_parameter::is_variadic() const noexcept { return is_variadic_; }
237
239 const template_parameter &base_template_parameter) const
240{
241 int res{0};
242
243 // If the potential base template has a deduction context (e.g. const&),
244 // the specialization must have the same and possibly more
245 if (base_template_parameter.is_specialization()) {
246 if (!deduced_context().empty() &&
247 (base_template_parameter.deduced_context().empty() ||
249 base_template_parameter.deduced_context())))
250 return 0;
251
252 if (!base_template_parameter.deduced_context().empty() &&
254 return 0;
255 }
256
257 if (is_template_parameter() &&
258 base_template_parameter.is_template_parameter() &&
260 base_template_parameter.template_params().empty() &&
261 is_variadic() == is_variadic() &&
263 base_template_parameter.is_function_template() &&
264 is_member_pointer() == base_template_parameter.is_member_pointer()) {
265 return 1;
266 }
267
268 auto maybe_base_template_parameter_type = base_template_parameter.type();
269 auto maybe_template_parameter_type = type();
270
271 if (maybe_base_template_parameter_type.has_value() &&
272 maybe_template_parameter_type.has_value() &&
273 !base_template_parameter.is_template_parameter() &&
275 if (maybe_base_template_parameter_type.value() !=
276 maybe_template_parameter_type.value())
277 return 0;
278
279 res++;
280 }
281
282 if (base_template_parameter.is_array() && !is_array())
283 return 0;
284
285 if (base_template_parameter.is_function_template() &&
287 return 0;
288
289 if (base_template_parameter.is_member_pointer() && !is_member_pointer())
290 return 0;
291
292 if (base_template_parameter.is_data_pointer() && !is_data_pointer())
293 return 0;
294
295 if (!base_template_parameter.template_params().empty() &&
296 !template_params().empty() &&
297 is_same_specialization(base_template_parameter)) {
299 template_params(), base_template_parameter.template_params());
300
301 if (params_match == 0)
302 return 0;
303
304 res += params_match;
305 }
306 else if ((base_template_parameter.is_template_parameter() ||
307 base_template_parameter.is_template_template_parameter()) &&
309 return 1;
310 }
311 else if (base_template_parameter.is_template_parameter() &&
312 base_template_parameter.template_params().empty()) {
313 // If the base is a regular template param, only possible with deduced
314 // context (deduced context already matches if exists)
315 res++;
316
317 if (!deduced_context().empty() &&
318 !base_template_parameter.deduced_context().empty() &&
320 deduced_context(), base_template_parameter.deduced_context()))
321 res += static_cast<int>(
322 base_template_parameter.deduced_context().size());
323 }
324
325 return res;
326}
327
329{
330 template_params_.emplace_back(std::move(ct));
331}
332
334{
335 template_params_.push_back(ct);
336}
337
338const std::vector<template_parameter> &
340{
341 return template_params_;
342}
343
345{
346 std::vector<std::string> deduced_contexts;
347
348 for (const auto &c : deduced_context()) {
349 deduced_contexts.push_back(c.to_string());
350 }
351
352 return fmt::format("{}", fmt::join(deduced_contexts, " "));
353}
354
356 const clanguml::common::model::namespace_ &using_namespace, bool relative,
357 bool skip_qualifiers) const
358{
360 return "";
361 }
362
363 if (is_ellipsis())
364 return "...";
365
367
368 assert(!(type().has_value() && concept_constraint().has_value()));
369
370 if (is_array()) {
371 auto it = template_params_.begin();
372 auto element_type = it->to_string(using_namespace, relative);
373 std::advance(it, 1);
374
375 std::vector<std::string> dimension_args;
376 for (; it != template_params_.end(); it++)
377 dimension_args.push_back(it->to_string(using_namespace, relative));
378
379 return fmt::format(
380 "{}[{}]", element_type, fmt::join(dimension_args, "]["));
381 }
382
383 if (is_function_template()) {
384 auto it = template_params_.begin();
385 auto return_type = it->to_string(using_namespace, relative);
386 std::advance(it, 1);
387
388 std::vector<std::string> function_args;
389 for (; it != template_params_.end(); it++)
390 function_args.push_back(it->to_string(using_namespace, relative));
391
392 return fmt::format(
393 "{}({})", return_type, fmt::join(function_args, ","));
394 }
395
396 if (is_data_pointer()) {
397 assert(template_params().size() == 2);
398
399 std::string unqualified = fmt::format("{} {}::*",
400 template_params().at(0).to_string(using_namespace, relative),
401 template_params().at(1).to_string(using_namespace, relative));
402
403 if (skip_qualifiers)
404 return unqualified;
405
406 return util::join(" ", unqualified, deduced_context_str());
407 }
408
409 if (is_member_pointer()) {
410 assert(template_params().size() > 1);
411
412 auto it = template_params().begin();
413 auto return_type = it->to_string(using_namespace, relative);
414 it++;
415 auto class_type = it->to_string(using_namespace, relative);
416 it++;
417 std::vector<std::string> args;
418
419 for (; it != template_params().end(); it++) {
420 args.push_back(it->to_string(using_namespace, relative));
421 }
422
423 std::string unqualified = fmt::format(
424 "{} ({}::*)({})", return_type, class_type, fmt::join(args, ","));
425 if (skip_qualifiers)
426 return unqualified;
427
428 return util::join(" ", unqualified, deduced_context_str());
429 }
430
431 std::string res;
432 const auto maybe_type = type();
433 if (maybe_type) {
434 if (!relative)
435 res += namespace_{*maybe_type}.to_string();
436 else
437 res += namespace_{*maybe_type}
438 .relative_to(using_namespace)
439 .to_string();
440 }
441
442 const auto &maybe_concept_constraint = concept_constraint();
443
444 if (maybe_concept_constraint) {
445 if (!relative)
446 res += namespace_{maybe_concept_constraint.value()}.to_string();
447 else
448 res += namespace_{maybe_concept_constraint.value()}
449 .relative_to(using_namespace)
450 .to_string();
451 }
452
453 const auto maybe_name = name();
454
455 if (maybe_name) {
456 if ((maybe_type && !maybe_type.value().empty()) ||
457 maybe_concept_constraint)
458 res += " ";
459
460 if (!relative)
461 res += namespace_{*maybe_name}.to_string();
462 else
463 res += namespace_{*maybe_name}
464 .relative_to(using_namespace)
465 .to_string();
466 }
467
468 // Render nested template params
469 if (!template_params_.empty()) {
470 std::vector<std::string> params;
471 params.reserve(template_params_.size());
472 for (const auto &template_param : template_params_) {
473 params.push_back(
474 template_param.to_string(using_namespace, relative));
475 }
476
477 res += fmt::format("<{}>", fmt::join(params, ","));
478 }
479
480 if (!skip_qualifiers)
481 res = util::join(" ", res, deduced_context_str());
482
483 const auto &maybe_default_value = default_value();
484 if (maybe_default_value) {
485 res += "=";
486 res += maybe_default_value.value();
487 }
488
489 return res;
490}
491
493 std::vector<std::tuple<eid_t, common::model::relationship_t,
494 const clang::Decl *>> &nested_relationships,
496 const std::function<bool(const std::string &full_name)> &should_include)
497 const
498{
499 bool added_aggregation_relationship{false};
500
501 // If this type argument should be included in the relationship
502 // just add it and skip recursion (e.g. this is a user defined type)
503 const auto maybe_type = type();
504
507
508 if (maybe_type && should_include(maybe_type.value())) {
509 if (is_association())
511
512 const auto maybe_id = id();
513 if (maybe_id) {
514 nested_relationships.emplace_back(maybe_id.value(), hint, decl);
515 added_aggregation_relationship =
517 }
518 }
519 // Otherwise (e.g. this is a std::shared_ptr) and we're actually
520 // interested what is stored inside it
521 else {
522 for (const auto &template_argument : template_params()) {
523
524 const auto maybe_id = template_argument.id();
525 const auto maybe_arg_type = template_argument.type();
526
527 if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) {
528
529 if (template_argument.is_association() &&
532
533 nested_relationships.emplace_back(maybe_id.value(), hint, decl);
534
535 added_aggregation_relationship =
537 }
538 else {
539 if (template_argument.is_function_template())
541
542 added_aggregation_relationship =
543 template_argument.find_nested_relationships(
544 decl, nested_relationships, hint, should_include);
545 }
546 }
547 }
548
549 return added_aggregation_relationship;
550}
551
553{
555}
556
557void template_parameter::is_template_parameter(bool is_template_parameter)
558{
560}
561
563{
565}
566
568 bool is_template_template_parameter)
569{
571}
572
574{
575 concept_constraint_ = std::move(constraint);
576}
577
578const std::optional<std::string> &template_parameter::concept_constraint() const
579{
580 return concept_constraint_;
581}
582
584{
585 return std::any_of(
586 deduced_context().begin(), deduced_context().end(), [](const auto &c) {
587 return c.pr == rpqualifier::kPointer ||
589 });
590}
591
593
595{
596 kind_ = kind;
597}
598
600
602{
603 is_unexposed_ = unexposed;
604}
605
607{
609}
611{
613}
614
617{
618 return is_member_pointer_;
619}
620
623
626
628{
629 context_.push_front(q);
630}
631
632const std::deque<context> &template_parameter::deduced_context() const
633{
634 return context_;
635}
636
637void template_parameter::deduced_context(std::deque<context> c)
638{
639 context_ = std::move(c);
640}
641
643
645
647 const std::vector<template_parameter> &specialization_params,
648 const std::vector<template_parameter> &template_params)
649{
650 int res{0};
651
652 if (specialization_params.size() != template_params.size() &&
653 !std::any_of(template_params.begin(), template_params.end(),
654 [](const auto &t) { return t.is_variadic(); })) {
655 return 0;
656 }
657
658 if (!specialization_params.empty() && !template_params.empty()) {
659 auto template_index{0U};
660 auto arg_index{0U};
661
662 while (arg_index < specialization_params.size() &&
663 template_index < template_params.size()) {
664 auto match = specialization_params.at(arg_index)
665 .calculate_specialization_match(
666 template_params.at(template_index));
667
668 if (match == 0) {
669 // If any of the matches is 0 - the entire match fails
670 return 0;
671 }
672
673 // Add 1 point if the current specialization param is an argument
674 // as it's a more specific match than 2 template params
675 if (!specialization_params.at(arg_index).is_template_parameter())
676 res++;
677
678 // Add 1 point if the current template param is an argument
679 // as it's a more specific match than 2 template params
680 if (!template_params.at(template_index).is_template_parameter())
681 res++;
682
683 if (!template_params.at(template_index).is_variadic())
684 template_index++;
685
686 res += match;
687
688 arg_index++;
689 }
690
691 if (arg_index == specialization_params.size()) {
692 // Check also backwards to make sure that trailing non-variadic
693 // params match after a variadic parameter
694 template_index = template_params.size() - 1;
695 arg_index = specialization_params.size() - 1;
696
697 while (true) {
698 auto match = specialization_params.at(arg_index)
699 .calculate_specialization_match(
700 template_params.at(template_index));
701 if (match == 0) {
702 return 0;
703 }
704
705 if (arg_index == 0 || template_index == 0)
706 break;
707
708 arg_index--;
709
710 if (!template_params.at(template_index).is_variadic())
711 template_index--;
712 else
713 break;
714 }
715
716 return res;
717 }
718
719 return 0;
720 }
721
722 return 0;
723}
724
725} // namespace clanguml::common::model