Files
clang-uml/src/common/model/template_parameter.cc
2024-01-02 23:19:46 +01:00

741 lines
22 KiB
C++

/**
* @file src/common/model/template_parameter.cc
*
* Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "template_parameter.h"
#include "common/model/enums.h"
#include <common/model/namespace.h>
#include <utility>
namespace clanguml::common::model {
std::string context::to_string() const
{
std::vector<std::string> cv_qualifiers;
if (is_const)
cv_qualifiers.emplace_back("const");
if (is_volatile)
cv_qualifiers.emplace_back("volatile");
auto res = fmt::format("{}", fmt::join(cv_qualifiers, " "));
if (pr == rpqualifier::kPointer)
res += "*";
else if (pr == rpqualifier::kLValueReference)
res += "&";
else if (pr == rpqualifier::kRValueReference)
res += "&&";
if (is_ref_const)
res += " const";
if (is_ref_volatile)
res += " volatile";
return res;
}
bool context::operator==(const context &rhs) const
{
return is_const == rhs.is_const && is_volatile == rhs.is_volatile &&
is_ref_const == rhs.is_ref_const &&
is_ref_volatile == rhs.is_ref_volatile && pr == rhs.pr;
}
bool context::operator!=(const context &rhs) const { return !(rhs == *this); }
std::string to_string(template_parameter_kind_t k)
{
switch (k) {
case template_parameter_kind_t::template_type:
return "template_type";
case template_parameter_kind_t::template_template_type:
return "template_template_type";
case template_parameter_kind_t::non_type_template:
return "non_type_template";
case template_parameter_kind_t::argument:
return "argument";
case template_parameter_kind_t::concept_constraint:
return "concept_constraint";
default:
assert(false);
return "";
}
}
template_parameter template_parameter::make_template_type(
const std::string &name, const std::optional<std::string> &default_value,
bool is_variadic)
{
template_parameter p;
p.set_kind(template_parameter_kind_t::template_type);
p.set_name(name);
p.is_variadic(is_variadic);
p.is_template_parameter(true);
if (default_value)
p.set_default_value(default_value.value());
return p;
}
template_parameter template_parameter::make_template_template_type(
const std::string &name, const std::optional<std::string> &default_value,
bool is_variadic)
{
template_parameter p;
p.set_kind(template_parameter_kind_t::template_template_type);
p.set_name(name + "<>");
if (default_value)
p.set_default_value(default_value.value());
p.is_variadic(is_variadic);
return p;
}
template_parameter template_parameter::make_non_type_template(
const std::string &type, const std::optional<std::string> &name,
const std::optional<std::string> &default_value, bool is_variadic)
{
template_parameter p;
p.set_kind(template_parameter_kind_t::non_type_template);
p.set_type(type);
if (name)
p.set_name(name.value());
if (default_value)
p.set_default_value(default_value.value());
p.is_variadic(is_variadic);
return p;
}
template_parameter template_parameter::make_argument(
const std::string &type, const std::optional<std::string> &default_value)
{
template_parameter p;
p.set_kind(template_parameter_kind_t::argument);
p.set_type(type);
if (default_value)
p.set_default_value(default_value.value());
return p;
}
template_parameter template_parameter::make_unexposed_argument(
const std::string &type, const std::optional<std::string> &default_value)
{
template_parameter p = make_argument(type, default_value);
p.set_unexposed(true);
return p;
}
bool template_parameter::is_specialization() const
{
return is_function_template() || is_array() || is_data_pointer() ||
is_member_pointer() || !deduced_context().empty();
}
bool template_parameter::is_same_specialization(
const template_parameter &other) const
{
return is_array() == other.is_array() &&
is_function_template() == other.is_function_template() &&
is_data_pointer() == other.is_data_pointer() &&
is_member_pointer() == other.is_member_pointer();
}
void template_parameter::set_type(const std::string &type)
{
assert(kind_ != template_parameter_kind_t::template_type);
if (util::ends_with(type, std::string{"..."})) {
type_ = type.substr(0, type.size() - 3);
is_variadic_ = true;
}
else
type_ = type;
}
std::optional<std::string> template_parameter::type() const
{
if (!type_)
return {};
if (is_variadic_)
return type_.value() + "...";
return type_;
}
void template_parameter::set_name(const std::string &name)
{
assert(kind_ != template_parameter_kind_t::argument);
if (name.empty()) {
return;
}
if (util::ends_with(name, std::string{"..."})) {
name_ = name.substr(0, name.size() - 3);
is_variadic_ = true;
}
else
name_ = name;
}
std::optional<std::string> template_parameter::name() const
{
if (!name_)
return {};
if (kind_ == template_parameter_kind_t::template_type &&
name_.has_value() && name_.value().empty())
return "typename";
if (is_variadic_ && (kind_ != template_parameter_kind_t::non_type_template))
return name_.value() + "...";
return name_;
}
void template_parameter::set_default_value(const std::string &value)
{
assert(kind_ != template_parameter_kind_t::argument);
default_value_ = value;
}
const std::optional<std::string> &template_parameter::default_value() const
{
return default_value_;
}
void template_parameter::is_variadic(bool is_variadic) noexcept
{
is_variadic_ = is_variadic;
}
bool template_parameter::is_variadic() const noexcept { return is_variadic_; }
int template_parameter::calculate_specialization_match(
const template_parameter &base_template_parameter) const
{
int res{0};
// If the potential base template has a deduction context (e.g. const&),
// the specialization must have the same and possibly more
if (base_template_parameter.is_specialization()) {
if (!deduced_context().empty() &&
(base_template_parameter.deduced_context().empty() ||
!util::starts_with(deduced_context(),
base_template_parameter.deduced_context())))
return 0;
if (!base_template_parameter.deduced_context().empty() &&
deduced_context().empty())
return 0;
}
if (is_template_parameter() &&
base_template_parameter.is_template_parameter() &&
template_params().empty() &&
base_template_parameter.template_params().empty() &&
is_variadic() == is_variadic() &&
is_function_template() ==
base_template_parameter.is_function_template() &&
is_member_pointer() == base_template_parameter.is_member_pointer()) {
return 1;
}
auto maybe_base_template_parameter_type = base_template_parameter.type();
auto maybe_template_parameter_type = type();
if (maybe_base_template_parameter_type.has_value() &&
maybe_template_parameter_type.has_value() &&
!base_template_parameter.is_template_parameter() &&
!is_template_parameter()) {
if (maybe_base_template_parameter_type.value() !=
maybe_template_parameter_type.value())
return 0;
res++;
}
if (base_template_parameter.is_array() && !is_array())
return 0;
if (base_template_parameter.is_function_template() &&
!is_function_template())
return 0;
if (base_template_parameter.is_member_pointer() && !is_member_pointer())
return 0;
if (base_template_parameter.is_data_pointer() && !is_data_pointer())
return 0;
if (!base_template_parameter.template_params().empty() &&
!template_params().empty() &&
is_same_specialization(base_template_parameter)) {
auto params_match = calculate_template_params_specialization_match(
template_params(), base_template_parameter.template_params());
if (params_match == 0)
return 0;
res += params_match;
}
else if ((base_template_parameter.is_template_parameter() ||
base_template_parameter.is_template_template_parameter()) &&
!is_template_parameter()) {
return 1;
}
else if (base_template_parameter.is_template_parameter() &&
base_template_parameter.template_params().empty()) {
// If the base is a regular template param, only possible with deduced
// context (deduced context already matches if exists)
res++;
if (!deduced_context().empty() &&
!base_template_parameter.deduced_context().empty() &&
util::starts_with(
deduced_context(), base_template_parameter.deduced_context()))
res += static_cast<int>(
base_template_parameter.deduced_context().size());
}
return res;
}
void template_parameter::add_template_param(template_parameter &&ct)
{
template_params_.emplace_back(std::move(ct));
}
void template_parameter::add_template_param(const template_parameter &ct)
{
template_params_.push_back(ct);
}
const std::vector<template_parameter> &
template_parameter::template_params() const
{
return template_params_;
}
bool operator==(const template_parameter &l, const template_parameter &r)
{
bool res{false};
if (l.is_template_parameter() != r.is_template_parameter())
return res;
if (l.is_function_template() != r.is_function_template())
return res;
if (l.is_template_parameter()) {
// If this is a template parameter (e.g. 'typename T' or 'typename U'
// we don't actually care what it is called
res = (l.is_variadic() == r.is_variadic()) &&
(l.default_value() == r.default_value());
}
else
res = (l.name() == r.name()) && (l.type() == r.type()) &&
(l.default_value() == r.default_value());
return res && (l.template_params_ == r.template_params_);
}
bool operator!=(const template_parameter &l, const template_parameter &r)
{
return !(l == r);
}
std::string template_parameter::deduced_context_str() const
{
std::vector<std::string> deduced_contexts;
for (const auto &c : deduced_context()) {
deduced_contexts.push_back(c.to_string());
}
return fmt::format("{}", fmt::join(deduced_contexts, " "));
}
std::string template_parameter::to_string(
const clanguml::common::model::namespace_ &using_namespace, bool relative,
bool skip_qualifiers) const
{
if (is_ellipsis())
return "...";
using clanguml::common::model::namespace_;
assert(!(type().has_value() && concept_constraint().has_value()));
if (is_array()) {
auto it = template_params_.begin();
auto element_type = it->to_string(using_namespace, relative);
std::advance(it, 1);
std::vector<std::string> dimension_args;
for (; it != template_params_.end(); it++)
dimension_args.push_back(it->to_string(using_namespace, relative));
return fmt::format(
"{}[{}]", element_type, fmt::join(dimension_args, "]["));
}
if (is_function_template()) {
auto it = template_params_.begin();
auto return_type = it->to_string(using_namespace, relative);
std::advance(it, 1);
std::vector<std::string> function_args;
for (; it != template_params_.end(); it++)
function_args.push_back(it->to_string(using_namespace, relative));
return fmt::format(
"{}({})", return_type, fmt::join(function_args, ","));
}
if (is_data_pointer()) {
assert(template_params().size() == 2);
std::string unqualified = fmt::format("{} {}::*",
template_params().at(0).to_string(using_namespace, relative),
template_params().at(1).to_string(using_namespace, relative));
if (skip_qualifiers)
return unqualified;
return util::join(" ", unqualified, deduced_context_str());
}
if (is_member_pointer()) {
assert(template_params().size() > 1);
auto it = template_params().begin();
auto return_type = it->to_string(using_namespace, relative);
it++;
auto class_type = it->to_string(using_namespace, relative);
it++;
std::vector<std::string> args;
for (; it != template_params().end(); it++) {
args.push_back(it->to_string(using_namespace, relative));
}
std::string unqualified = fmt::format(
"{} ({}::*)({})", return_type, class_type, fmt::join(args, ","));
if (skip_qualifiers)
return unqualified;
return util::join(" ", unqualified, deduced_context_str());
}
std::string res;
const auto maybe_type = type();
if (maybe_type) {
if (!relative)
res += namespace_{*maybe_type}.to_string();
else
res += namespace_{*maybe_type}
.relative_to(using_namespace)
.to_string();
}
const auto &maybe_concept_constraint = concept_constraint();
if (maybe_concept_constraint) {
if (!relative)
res += namespace_{maybe_concept_constraint.value()}.to_string();
else
res += namespace_{maybe_concept_constraint.value()}
.relative_to(using_namespace)
.to_string();
}
const auto maybe_name = name();
if (maybe_name) {
if ((maybe_type && !maybe_type.value().empty()) ||
maybe_concept_constraint)
res += " ";
if (!relative)
res += namespace_{*maybe_name}.to_string();
else
res += namespace_{*maybe_name}
.relative_to(using_namespace)
.to_string();
}
// Render nested template params
if (!template_params_.empty()) {
std::vector<std::string> params;
params.reserve(template_params_.size());
for (const auto &template_param : template_params_) {
params.push_back(
template_param.to_string(using_namespace, relative));
}
res += fmt::format("<{}>", fmt::join(params, ","));
}
if (!skip_qualifiers)
res = util::join(" ", res, deduced_context_str());
const auto &maybe_default_value = default_value();
if (maybe_default_value) {
res += "=";
res += maybe_default_value.value();
}
return res;
}
bool template_parameter::find_nested_relationships(
std::vector<std::pair<int64_t, common::model::relationship_t>>
&nested_relationships,
common::model::relationship_t hint,
const std::function<bool(const std::string &full_name)> &should_include)
const
{
bool added_aggregation_relationship{false};
// If this type argument should be included in the relationship
// just add it and skip recursion (e.g. this is a user defined type)
const auto maybe_type = type();
if (is_function_template())
hint = common::model::relationship_t::kDependency;
if (maybe_type && should_include(maybe_type.value())) {
if (is_association())
hint = common::model::relationship_t::kAssociation;
const auto maybe_id = id();
if (maybe_id) {
nested_relationships.emplace_back(maybe_id.value(), hint);
added_aggregation_relationship =
(hint == common::model::relationship_t::kAggregation);
}
}
// Otherwise (e.g. this is a std::shared_ptr) and we're actually
// interested what is stored inside it
else {
for (const auto &template_argument : template_params()) {
const auto maybe_id = template_argument.id();
const auto maybe_arg_type = template_argument.type();
if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) {
if (template_argument.is_association() &&
hint == common::model::relationship_t::kAggregation)
hint = common::model::relationship_t::kAssociation;
nested_relationships.emplace_back(maybe_id.value(), hint);
added_aggregation_relationship =
(hint == common::model::relationship_t::kAggregation);
}
else {
if (template_argument.is_function_template())
hint = common::model::relationship_t::kDependency;
added_aggregation_relationship =
template_argument.find_nested_relationships(
nested_relationships, hint, should_include);
}
}
}
return added_aggregation_relationship;
}
bool template_parameter::is_template_parameter() const
{
return is_template_parameter_;
}
void template_parameter::is_template_parameter(bool is_template_parameter)
{
is_template_parameter_ = is_template_parameter;
}
bool template_parameter::is_template_template_parameter() const
{
return is_template_template_parameter_;
}
void template_parameter::is_template_template_parameter(
bool is_template_template_parameter)
{
is_template_template_parameter_ = is_template_template_parameter;
}
void template_parameter::set_concept_constraint(std::string constraint)
{
concept_constraint_ = std::move(constraint);
}
const std::optional<std::string> &template_parameter::concept_constraint() const
{
return concept_constraint_;
}
bool template_parameter::is_association() const
{
return std::any_of(
deduced_context().begin(), deduced_context().end(), [](const auto &c) {
return c.pr == rpqualifier::kPointer ||
c.pr == rpqualifier::kLValueReference;
});
}
template_parameter_kind_t template_parameter::kind() const { return kind_; }
void template_parameter::set_kind(template_parameter_kind_t kind)
{
kind_ = kind;
}
bool template_parameter::is_unexposed() const { return is_unexposed_; }
void template_parameter::set_unexposed(bool unexposed)
{
is_unexposed_ = unexposed;
}
void template_parameter::is_function_template(bool ft)
{
is_function_template_ = ft;
}
bool template_parameter::is_function_template() const
{
return is_function_template_;
}
void template_parameter::is_member_pointer(bool m) { is_member_pointer_ = m; }
bool template_parameter::is_member_pointer() const
{
return is_member_pointer_;
}
void template_parameter::is_data_pointer(bool m) { is_data_pointer_ = m; }
bool template_parameter::is_data_pointer() const { return is_data_pointer_; }
void template_parameter::is_array(bool a) { is_array_ = a; }
bool template_parameter::is_array() const { return is_array_; }
void template_parameter::push_context(const context &q)
{
context_.push_front(q);
}
const std::deque<context> &template_parameter::deduced_context() const
{
return context_;
}
void template_parameter::deduced_context(std::deque<context> c)
{
context_ = std::move(c);
}
void template_parameter::is_ellipsis(bool e) { is_ellipsis_ = e; }
bool template_parameter::is_ellipsis() const { return is_ellipsis_; }
int calculate_template_params_specialization_match(
const std::vector<template_parameter> &specialization_params,
const std::vector<template_parameter> &template_params)
{
int res{0};
if (specialization_params.size() != template_params.size() &&
!std::any_of(template_params.begin(), template_params.end(),
[](const auto &t) { return t.is_variadic(); })) {
return 0;
}
if (!specialization_params.empty() && !template_params.empty()) {
auto template_index{0U};
auto arg_index{0U};
while (arg_index < specialization_params.size() &&
template_index < template_params.size()) {
auto match = specialization_params.at(arg_index)
.calculate_specialization_match(
template_params.at(template_index));
if (match == 0) {
// If any of the matches is 0 - the entire match fails
return 0;
}
// Add 1 point if the current specialization param is an argument
// as it's a more specific match than 2 template params
if (!specialization_params.at(arg_index).is_template_parameter())
res++;
// Add 1 point if the current template param is an argument
// as it's a more specific match than 2 template params
if (!template_params.at(template_index).is_template_parameter())
res++;
if (!template_params.at(template_index).is_variadic())
template_index++;
res += match;
arg_index++;
}
if (arg_index == specialization_params.size()) {
// Check also backwards to make sure that trailing non-variadic
// params match after a variadic parameter
template_index = template_params.size() - 1;
arg_index = specialization_params.size() - 1;
while (true) {
auto match = specialization_params.at(arg_index)
.calculate_specialization_match(
template_params.at(template_index));
if (match == 0) {
return 0;
}
if (arg_index == 0 || template_index == 0)
break;
arg_index--;
if (!template_params.at(template_index).is_variadic())
template_index--;
else
break;
}
return res;
}
return 0;
}
return 0;
}
} // namespace clanguml::common::model