0.5.4
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-2024 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";
74 default:
75 assert(false);
76 return "";
77 }
78}
79
81 const std::string &name, const std::optional<std::string> &default_value,
82 bool is_variadic)
83{
86 p.set_name(name);
89 if (default_value)
91 return p;
92}
93
95 const std::string &name, const std::optional<std::string> &default_value,
96 bool is_variadic)
97{
100 p.set_name(name + "<>");
101 if (default_value)
104 return p;
105}
106
108 const std::string &type, const std::optional<std::string> &name,
109 const std::optional<std::string> &default_value, bool is_variadic)
110{
113 p.set_type(type);
114 if (name)
115 p.set_name(name.value());
116 if (default_value)
119 return p;
120}
121
123 const std::string &type, const std::optional<std::string> &default_value)
124{
127 p.set_type(type);
128 if (default_value)
130 return p;
131}
132
134 const std::string &type, const std::optional<std::string> &default_value)
135{
137 p.set_unexposed(true);
138 return p;
139}
140
142{
143 return is_function_template() || is_array() || is_data_pointer() ||
144 is_member_pointer() || !deduced_context().empty();
145}
146
148 const template_parameter &other) const
149{
150 return is_array() == other.is_array() &&
152 is_data_pointer() == other.is_data_pointer() &&
154}
155
156void template_parameter::set_type(const std::string &type)
157{
159
160 if (util::ends_with(type, std::string{"..."})) {
161 type_ = type.substr(0, type.size() - 3);
162 is_variadic_ = true;
163 }
164 else
165 type_ = type;
166}
167
168std::optional<std::string> template_parameter::type() const
169{
170 if (!type_)
171 return {};
172
173 if (is_variadic_)
174 return type_.value() + "...";
175
176 return type_;
177}
178
179void template_parameter::set_name(const std::string &name)
180{
182
183 if (name.empty()) {
184 return;
185 }
186
187 if (util::ends_with(name, std::string{"..."})) {
188 name_ = name.substr(0, name.size() - 3);
189 is_variadic_ = true;
190 }
191 else
192 name_ = name;
193}
194
195std::optional<std::string> template_parameter::name() const
196{
197 if (!name_)
198 return {};
199
201 name_.has_value() && name_.value().empty())
202 return "typename";
203
205 return name_.value() + "...";
206
207 return name_;
208}
209
210void template_parameter::set_default_value(const std::string &value)
211{
213
214 default_value_ = value;
215}
216
217const std::optional<std::string> &template_parameter::default_value() const
218{
219 return default_value_;
220}
221
222void template_parameter::is_variadic(bool is_variadic) noexcept
223{
224 is_variadic_ = is_variadic;
225}
226
227bool template_parameter::is_variadic() const noexcept { return is_variadic_; }
228
230 const template_parameter &base_template_parameter) const
231{
232 int res{0};
233
234 // If the potential base template has a deduction context (e.g. const&),
235 // the specialization must have the same and possibly more
236 if (base_template_parameter.is_specialization()) {
237 if (!deduced_context().empty() &&
238 (base_template_parameter.deduced_context().empty() ||
240 base_template_parameter.deduced_context())))
241 return 0;
242
243 if (!base_template_parameter.deduced_context().empty() &&
244 deduced_context().empty())
245 return 0;
246 }
247
248 if (is_template_parameter() &&
249 base_template_parameter.is_template_parameter() &&
250 template_params().empty() &&
251 base_template_parameter.template_params().empty() &&
252 is_variadic() == is_variadic() &&
254 base_template_parameter.is_function_template() &&
255 is_member_pointer() == base_template_parameter.is_member_pointer()) {
256 return 1;
257 }
258
259 auto maybe_base_template_parameter_type = base_template_parameter.type();
260 auto maybe_template_parameter_type = type();
261
262 if (maybe_base_template_parameter_type.has_value() &&
263 maybe_template_parameter_type.has_value() &&
264 !base_template_parameter.is_template_parameter() &&
266 if (maybe_base_template_parameter_type.value() !=
267 maybe_template_parameter_type.value())
268 return 0;
269
270 res++;
271 }
272
273 if (base_template_parameter.is_array() && !is_array())
274 return 0;
275
276 if (base_template_parameter.is_function_template() &&
278 return 0;
279
280 if (base_template_parameter.is_member_pointer() && !is_member_pointer())
281 return 0;
282
283 if (base_template_parameter.is_data_pointer() && !is_data_pointer())
284 return 0;
285
286 if (!base_template_parameter.template_params().empty() &&
287 !template_params().empty() &&
288 is_same_specialization(base_template_parameter)) {
290 template_params(), base_template_parameter.template_params());
291
292 if (params_match == 0)
293 return 0;
294
295 res += params_match;
296 }
297 else if ((base_template_parameter.is_template_parameter() ||
298 base_template_parameter.is_template_template_parameter()) &&
300 return 1;
301 }
302 else if (base_template_parameter.is_template_parameter() &&
303 base_template_parameter.template_params().empty()) {
304 // If the base is a regular template param, only possible with deduced
305 // context (deduced context already matches if exists)
306 res++;
307
308 if (!deduced_context().empty() &&
309 !base_template_parameter.deduced_context().empty() &&
311 deduced_context(), base_template_parameter.deduced_context()))
312 res += static_cast<int>(
313 base_template_parameter.deduced_context().size());
314 }
315
316 return res;
317}
318
320{
321 template_params_.emplace_back(std::move(ct));
322}
323
325{
326 template_params_.push_back(ct);
327}
328
329const std::vector<template_parameter> &
331{
332 return template_params_;
333}
334
336{
337 std::vector<std::string> deduced_contexts;
338
339 for (const auto &c : deduced_context()) {
340 deduced_contexts.push_back(c.to_string());
341 }
342
343 return fmt::format("{}", fmt::join(deduced_contexts, " "));
344}
345
347 const clanguml::common::model::namespace_ &using_namespace, bool relative,
348 bool skip_qualifiers) const
349{
350 if (is_ellipsis())
351 return "...";
352
354
355 assert(!(type().has_value() && concept_constraint().has_value()));
356
357 if (is_array()) {
358 auto it = template_params_.begin();
359 auto element_type = it->to_string(using_namespace, relative);
360 std::advance(it, 1);
361
362 std::vector<std::string> dimension_args;
363 for (; it != template_params_.end(); it++)
364 dimension_args.push_back(it->to_string(using_namespace, relative));
365
366 return fmt::format(
367 "{}[{}]", element_type, fmt::join(dimension_args, "]["));
368 }
369
370 if (is_function_template()) {
371 auto it = template_params_.begin();
372 auto return_type = it->to_string(using_namespace, relative);
373 std::advance(it, 1);
374
375 std::vector<std::string> function_args;
376 for (; it != template_params_.end(); it++)
377 function_args.push_back(it->to_string(using_namespace, relative));
378
379 return fmt::format(
380 "{}({})", return_type, fmt::join(function_args, ","));
381 }
382
383 if (is_data_pointer()) {
384 assert(template_params().size() == 2);
385
386 std::string unqualified = fmt::format("{} {}::*",
387 template_params().at(0).to_string(using_namespace, relative),
388 template_params().at(1).to_string(using_namespace, relative));
389
390 if (skip_qualifiers)
391 return unqualified;
392
393 return util::join(" ", unqualified, deduced_context_str());
394 }
395
396 if (is_member_pointer()) {
397 assert(template_params().size() > 1);
398
399 auto it = template_params().begin();
400 auto return_type = it->to_string(using_namespace, relative);
401 it++;
402 auto class_type = it->to_string(using_namespace, relative);
403 it++;
404 std::vector<std::string> args;
405
406 for (; it != template_params().end(); it++) {
407 args.push_back(it->to_string(using_namespace, relative));
408 }
409
410 std::string unqualified = fmt::format(
411 "{} ({}::*)({})", return_type, class_type, fmt::join(args, ","));
412 if (skip_qualifiers)
413 return unqualified;
414
415 return util::join(" ", unqualified, deduced_context_str());
416 }
417
418 std::string res;
419 const auto maybe_type = type();
420 if (maybe_type) {
421 if (!relative)
422 res += namespace_{*maybe_type}.to_string();
423 else
424 res += namespace_{*maybe_type}
425 .relative_to(using_namespace)
426 .to_string();
427 }
428
429 const auto &maybe_concept_constraint = concept_constraint();
430
431 if (maybe_concept_constraint) {
432 if (!relative)
433 res += namespace_{maybe_concept_constraint.value()}.to_string();
434 else
435 res += namespace_{maybe_concept_constraint.value()}
436 .relative_to(using_namespace)
437 .to_string();
438 }
439
440 const auto maybe_name = name();
441
442 if (maybe_name) {
443 if ((maybe_type && !maybe_type.value().empty()) ||
444 maybe_concept_constraint)
445 res += " ";
446
447 if (!relative)
448 res += namespace_{*maybe_name}.to_string();
449 else
450 res += namespace_{*maybe_name}
451 .relative_to(using_namespace)
452 .to_string();
453 }
454
455 // Render nested template params
456 if (!template_params_.empty()) {
457 std::vector<std::string> params;
458 params.reserve(template_params_.size());
459 for (const auto &template_param : template_params_) {
460 params.push_back(
461 template_param.to_string(using_namespace, relative));
462 }
463
464 res += fmt::format("<{}>", fmt::join(params, ","));
465 }
466
467 if (!skip_qualifiers)
468 res = util::join(" ", res, deduced_context_str());
469
470 const auto &maybe_default_value = default_value();
471 if (maybe_default_value) {
472 res += "=";
473 res += maybe_default_value.value();
474 }
475
476 return res;
477}
478
480 std::vector<std::pair<eid_t, common::model::relationship_t>>
481 &nested_relationships,
483 const std::function<bool(const std::string &full_name)> &should_include)
484 const
485{
486 bool added_aggregation_relationship{false};
487
488 // If this type argument should be included in the relationship
489 // just add it and skip recursion (e.g. this is a user defined type)
490 const auto maybe_type = type();
491
494
495 if (maybe_type && should_include(maybe_type.value())) {
496 if (is_association())
498
499 const auto maybe_id = id();
500 if (maybe_id) {
501 nested_relationships.emplace_back(maybe_id.value(), hint);
502 added_aggregation_relationship =
504 }
505 }
506 // Otherwise (e.g. this is a std::shared_ptr) and we're actually
507 // interested what is stored inside it
508 else {
509 for (const auto &template_argument : template_params()) {
510
511 const auto maybe_id = template_argument.id();
512 const auto maybe_arg_type = template_argument.type();
513
514 if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) {
515
516 if (template_argument.is_association() &&
519
520 nested_relationships.emplace_back(maybe_id.value(), hint);
521
522 added_aggregation_relationship =
524 }
525 else {
526 if (template_argument.is_function_template())
528
529 added_aggregation_relationship =
530 template_argument.find_nested_relationships(
531 nested_relationships, hint, should_include);
532 }
533 }
534 }
535
536 return added_aggregation_relationship;
537}
538
540{
542}
543
544void template_parameter::is_template_parameter(bool is_template_parameter)
545{
547}
548
550{
552}
553
555 bool is_template_template_parameter)
556{
558}
559
561{
562 concept_constraint_ = std::move(constraint);
563}
564
565const std::optional<std::string> &template_parameter::concept_constraint() const
566{
567 return concept_constraint_;
568}
569
571{
572 return std::any_of(
573 deduced_context().begin(), deduced_context().end(), [](const auto &c) {
574 return c.pr == rpqualifier::kPointer ||
576 });
577}
578
580
582{
583 kind_ = kind;
584}
585
587
589{
590 is_unexposed_ = unexposed;
591}
592
594{
596}
598{
600}
601
604{
605 return is_member_pointer_;
606}
607
610
613
615{
616 context_.push_front(q);
617}
618
619const std::deque<context> &template_parameter::deduced_context() const
620{
621 return context_;
622}
623
624void template_parameter::deduced_context(std::deque<context> c)
625{
626 context_ = std::move(c);
627}
628
630
632
634 const std::vector<template_parameter> &specialization_params,
635 const std::vector<template_parameter> &template_params)
636{
637 int res{0};
638
639 if (specialization_params.size() != template_params.size() &&
640 !std::any_of(template_params.begin(), template_params.end(),
641 [](const auto &t) { return t.is_variadic(); })) {
642 return 0;
643 }
644
645 if (!specialization_params.empty() && !template_params.empty()) {
646 auto template_index{0U};
647 auto arg_index{0U};
648
649 while (arg_index < specialization_params.size() &&
650 template_index < template_params.size()) {
651 auto match = specialization_params.at(arg_index)
652 .calculate_specialization_match(
653 template_params.at(template_index));
654
655 if (match == 0) {
656 // If any of the matches is 0 - the entire match fails
657 return 0;
658 }
659
660 // Add 1 point if the current specialization param is an argument
661 // as it's a more specific match than 2 template params
662 if (!specialization_params.at(arg_index).is_template_parameter())
663 res++;
664
665 // Add 1 point if the current template param is an argument
666 // as it's a more specific match than 2 template params
667 if (!template_params.at(template_index).is_template_parameter())
668 res++;
669
670 if (!template_params.at(template_index).is_variadic())
671 template_index++;
672
673 res += match;
674
675 arg_index++;
676 }
677
678 if (arg_index == specialization_params.size()) {
679 // Check also backwards to make sure that trailing non-variadic
680 // params match after a variadic parameter
681 template_index = template_params.size() - 1;
682 arg_index = specialization_params.size() - 1;
683
684 while (true) {
685 auto match = specialization_params.at(arg_index)
686 .calculate_specialization_match(
687 template_params.at(template_index));
688 if (match == 0) {
689 return 0;
690 }
691
692 if (arg_index == 0 || template_index == 0)
693 break;
694
695 arg_index--;
696
697 if (!template_params.at(template_index).is_variadic())
698 template_index--;
699 else
700 break;
701 }
702
703 return res;
704 }
705
706 return 0;
707 }
708
709 return 0;
710}
711
712} // namespace clanguml::common::model