Fixed template_builder handling of nested template specializations

This commit is contained in:
Bartek Kryza
2023-04-18 00:13:29 +02:00
parent 758c1418a6
commit 6323ce8a92
11 changed files with 356 additions and 475 deletions

View File

@@ -22,6 +22,8 @@ diagrams:
include!: uml/sequence_diagram_visitor_sequence_diagram.yml
class_diagram_generator_sequence:
include!: uml/class_diagram_generator_sequence_diagram.yml
template_builder_sequence:
include!: uml/template_builder_sequence_diagram.yml
package_model_class:
include!: uml/package_model_class_diagram.yml
include_graph:

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,10 @@ using common::model::namespace_;
using common::model::relationship_t;
using common::model::template_parameter;
using found_relationships_t =
std::vector<std::pair<clanguml::common::model::diagram_element::id_t,
common::model::relationship_t>>;
class template_builder {
public:
template_builder(class_diagram::model::diagram &d,
@@ -47,14 +51,13 @@ public:
template_parameter &ct, const std::string &full_name) const;
std::unique_ptr<clanguml::class_diagram::model::class_> build(
const clang::Decl *cls,
const clang::NamedDecl *cls,
const clang::TemplateSpecializationType &template_type_decl,
std::optional<clanguml::class_diagram::model::class_ *> parent = {});
std::unique_ptr<clanguml::class_diagram::model::class_>
build_from_class_template_specialization(
const clang::ClassTemplateSpecializationDecl &template_specialization,
const clang::RecordType &record_type,
std::optional<clanguml::class_diagram::model::class_ *> parent = {});
bool add_base_classes(clanguml::class_diagram::model::class_ &tinst,
@@ -64,23 +67,20 @@ public:
void process_template_arguments(
std::optional<clanguml::class_diagram::model::class_ *> &parent,
const clang::Decl *cls,
const clang::NamedDecl *cls,
std::deque<std::tuple<std::string, int, bool>> &template_base_params,
const clang::ArrayRef<clang::TemplateArgument> &template_args,
model::class_ &template_instantiation,
const std::string &full_template_specialization_name,
const clang::TemplateDecl *template_decl);
void argument_process_dispatch(
std::optional<clanguml::class_diagram::model::class_ *> &parent,
const clang::Decl *cls, class_ &template_instantiation,
const std::string &full_template_specialization_name,
const clang::NamedDecl *cls, class_ &template_instantiation,
const clang::TemplateDecl *template_decl,
const clang::TemplateArgument &arg,
const clang::TemplateArgument &arg, size_t argument_index,
std::vector<template_parameter> &argument);
void process_tag_argument(model::class_ &template_instantiation,
const std::string &full_template_specialization_name,
const clang::TemplateDecl *template_decl,
const clang::TemplateArgument &arg,
common::model::template_parameter &argument);
@@ -99,23 +99,27 @@ public:
std::vector<template_parameter> process_pack_argument(
std::optional<clanguml::class_diagram::model::class_ *> &parent,
const clang::Decl *cls, class_ &template_instantiation,
const std::string &full_template_specialization_name,
const clang::NamedDecl *cls, class_ &template_instantiation,
const clang::TemplateDecl *template_decl,
const clang::TemplateArgument &arg,
const clang::TemplateArgument &arg, size_t argument_index,
std::vector<template_parameter> &argument);
template_parameter process_type_argument(
std::optional<clanguml::class_diagram::model::class_ *> &parent,
const clang::Decl *cls,
const std::string &full_template_specialization_name,
const clang::TemplateDecl *template_decl,
const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl,
const clang::TemplateArgument &arg,
model::class_ &template_instantiation);
model::class_ &template_instantiation, size_t argument_index);
common::model::template_parameter process_template_argument(
const clang::TemplateArgument &arg);
void process_unexposed_template_specialization_parameters(
const std::string &type_name, template_parameter &tp, class_ &c);
bool find_relationships_in_unexposed_template_params(
const template_parameter &ct,
class_diagram::visitor::found_relationships_t &relationships);
std::optional<template_parameter>
get_template_argument_from_type_parameter_string(
const clang::Decl *decl, const std::string &return_type_name) const;
@@ -125,6 +129,8 @@ public:
clang::SourceManager &source_manager() const;
private:
void ensure_lambda_type_is_relative(std::string &parameter_type) const;
// Reference to the output diagram model
clanguml::class_diagram::model::diagram &diagram_;

View File

@@ -1715,7 +1715,8 @@ std::unique_ptr<class_>
translation_unit_visitor::process_template_specialization(
clang::ClassTemplateSpecializationDecl *cls)
{
auto c_ptr{std::make_unique<class_>(config_.using_namespace())};
auto c_ptr = tbuilder().build_from_class_template_specialization(*cls);
auto &template_instantiation = *c_ptr;
template_instantiation.is_template(true);
@@ -1745,374 +1746,11 @@ translation_unit_visitor::process_template_specialization(
if (template_instantiation.skip())
return {};
const auto template_args_count = cls->getTemplateArgs().size();
for (auto arg_it = 0U; arg_it < template_args_count; arg_it++) {
const auto arg = cls->getTemplateArgs().get(arg_it);
process_template_specialization_argument(
cls, template_instantiation, arg, arg_it);
}
template_instantiation.set_id(
common::to_id(template_instantiation.full_name(false)));
id_mapper().add(cls->getID(), template_instantiation.id());
return c_ptr;
}
void translation_unit_visitor::process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
class_ &template_instantiation, const clang::TemplateArgument &arg,
size_t argument_index, bool /*in_parameter_pack*/)
{
const auto argument_kind = arg.getKind();
if (argument_kind == clang::TemplateArgument::Type) {
std::optional<template_parameter> argument;
// If this is a nested template type - add nested templates as
// template arguments
if (const auto *function_type =
arg.getAsType()->getAs<clang::FunctionProtoType>();
function_type != nullptr) {
auto a = template_parameter::make_template_type({});
a.set_function_template(true);
// Set function template return type
const auto return_type_name =
function_type->getReturnType().getAsString();
// Try to match the return type to template parameter in case
// the type name is in the form 'type-parameter-X-Y'
auto maybe_return_arg =
tbuilder().get_template_argument_from_type_parameter_string(
cls, return_type_name);
if (maybe_return_arg)
a.add_template_param(*maybe_return_arg);
else {
a.add_template_param(
template_parameter::make_argument(return_type_name));
}
// Set function template argument types
for (const auto &param_type : function_type->param_types()) {
auto maybe_arg =
tbuilder().get_template_argument_from_type_parameter_string(
cls, param_type.getAsString());
if (maybe_arg) {
a.add_template_param(*maybe_arg);
continue;
}
if (param_type->isBuiltinType()) {
a.add_template_param(template_parameter::make_argument(
param_type.getAsString()));
continue;
}
const auto *param_record_type =
param_type->getAs<clang::RecordType>();
if (param_record_type == nullptr)
continue;
}
argument = a;
}
else if (const auto *nested_template_type =
arg.getAsType()
->getAs<clang::TemplateSpecializationType>();
nested_template_type != nullptr) {
argument = template_parameter::make_argument({});
const auto nested_template_name =
nested_template_type->getTemplateName()
.getAsTemplateDecl()
->getQualifiedNameAsString();
argument->set_type(nested_template_name);
auto nested_template_instantiation = tbuilder().build(
cls, *nested_template_type, {&template_instantiation});
argument->set_id(nested_template_instantiation->id());
for (const auto &t :
nested_template_instantiation->template_params())
argument->add_template_param(t);
}
else if (arg.getAsType()->getAs<clang::TemplateTypeParmType>() !=
nullptr) {
argument = template_parameter::make_template_type({});
auto parameter_name =
common::to_string(arg.getAsType(), cls->getASTContext());
// clang does not provide declared template parameter/argument
// names in template specializations - so we have to extract
// them from raw source code...
if (parameter_name.find("type-parameter-") == 0) {
auto declaration_text = common::get_source_text_raw(
cls->getSourceRange(), source_manager());
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
cls->getNameAsString().size() + 1);
auto template_params = common::parse_unexposed_template_params(
declaration_text, [](const auto &t) { return t; });
if (template_params.size() > argument_index)
parameter_name = template_params[argument_index].to_string(
config().using_namespace(), false);
else {
LOG_DBG("Failed to find type specialization for argument "
"{} at index {} in declaration \n===\n{}\n===\n",
parameter_name, argument_index, declaration_text);
}
}
argument->set_name(parameter_name);
}
else {
auto type_name =
common::to_string(arg.getAsType(), cls->getASTContext());
ensure_lambda_type_is_relative(type_name);
if (type_name.find('<') != std::string::npos) {
argument = template_parameter::make_argument({});
// Sometimes template instantiation is reported as
// RecordType in the AST and getAs to
// TemplateSpecializationType returns null pointer so we
// have to at least make sure it's properly formatted
// (e.g. std:integral_constant, or any template
// specialization which contains it - see t00038)
process_unexposed_template_specialization_parameters(
type_name.substr(type_name.find('<') + 1,
type_name.size() - (type_name.find('<') + 2)),
*argument, template_instantiation);
auto unexposed_type_name =
type_name.substr(0, type_name.find('<'));
ensure_lambda_type_is_relative(unexposed_type_name);
argument->set_type(unexposed_type_name);
}
else if (type_name.find("type-parameter-") == 0) {
argument = template_parameter::make_template_type({});
auto declaration_text = common::get_source_text_raw(
cls->getSourceRange(), source_manager());
declaration_text = declaration_text.substr(
declaration_text.find(cls->getNameAsString()) +
cls->getNameAsString().size() + 1);
auto template_params = common::parse_unexposed_template_params(
declaration_text, [](const auto &t) { return t; });
if (template_params.size() > argument_index)
type_name = template_params[argument_index].to_string(
config().using_namespace(), false);
else {
LOG_DBG("Failed to find type specialization for argument "
"{} at index {} in declaration \n===\n{}\n===\n",
type_name, argument_index, declaration_text);
}
// Otherwise just set the name for the template argument to
// whatever clang says
argument->set_name(type_name);
}
else {
argument = template_parameter::make_argument({});
argument->set_type(type_name);
}
}
if (!argument)
return;
LOG_DBG("Adding template instantiation argument {}",
argument.value().to_string(config().using_namespace(), false));
tbuilder().simplify_system_template(*argument,
argument.value().to_string(config().using_namespace(), false));
template_instantiation.add_template(std::move(argument.value()));
}
else if (argument_kind == clang::TemplateArgument::Integral) {
auto argument = template_parameter::make_argument(
std::to_string(arg.getAsIntegral().getExtValue()));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::Expression) {
auto argument =
template_parameter::make_argument(common::get_source_text(
arg.getAsExpr()->getSourceRange(), source_manager()));
template_instantiation.add_template(std::move(argument));
}
else if (argument_kind == clang::TemplateArgument::TemplateExpansion) {
// TODO
}
else if (argument_kind == clang::TemplateArgument::Pack) {
// This will only work for now if pack is at the end
size_t argument_pack_index{argument_index};
for (const auto &template_argument : arg.getPackAsArray()) {
process_template_specialization_argument(cls,
template_instantiation, template_argument,
argument_pack_index++, true);
}
}
else {
LOG_ERROR("Unsupported template argument kind {} [{}]", arg.getKind(),
cls->getLocation().printToString(source_manager()));
}
}
void translation_unit_visitor::
process_unexposed_template_specialization_parameters(
const std::string &type_name, template_parameter &tp, class_ &c)
{
auto template_params = common::parse_unexposed_template_params(
type_name, [](const std::string &t) { return t; });
found_relationships_t relationships;
for (auto &param : template_params) {
find_relationships_in_unexposed_template_params(param, relationships);
tp.add_template_param(param);
}
for (auto &r : relationships) {
c.add_relationship({std::get<1>(r), std::get<0>(r)});
}
}
bool translation_unit_visitor::find_relationships_in_unexposed_template_params(
const template_parameter &ct, found_relationships_t &relationships)
{
const auto &type = ct.type();
if (!type)
return false;
bool found{false};
LOG_DBG("Finding relationships in user defined type: {}",
ct.to_string(config().using_namespace(), false));
auto type_with_namespace =
std::make_optional<common::model::namespace_>(type.value());
if (!type_with_namespace.has_value()) {
// Couldn't find declaration of this type
type_with_namespace = common::model::namespace_{type.value()};
}
auto element_opt = diagram().get(type_with_namespace.value().to_string());
if (element_opt) {
relationships.emplace_back(
element_opt.value().id(), relationship_t::kDependency);
found = true;
}
for (const auto &nested_template_params : ct.template_params()) {
found = find_relationships_in_unexposed_template_params(
nested_template_params, relationships) ||
found;
}
return found;
}
std::unique_ptr<class_>
template_builder::build_from_class_template_specialization(
const clang::ClassTemplateSpecializationDecl &template_specialization,
const clang::RecordType &record_type,
std::optional<clanguml::class_diagram::model::class_ *> parent)
{
auto template_instantiation_ptr =
std::make_unique<class_>(config_.using_namespace());
//
// Here we'll hold the template base params to replace with the
// instantiated values
//
std::deque<std::tuple</*parameter name*/ std::string, /* position */ int,
/*is variadic */ bool>>
template_base_params{};
auto &template_instantiation = *template_instantiation_ptr;
std::string full_template_specialization_name =
common::to_string(record_type, template_specialization.getASTContext());
const auto *template_decl =
template_specialization.getSpecializedTemplate();
auto qualified_name = template_decl->getQualifiedNameAsString();
namespace_ ns{qualified_name};
ns.pop_back();
template_instantiation.set_name(template_decl->getNameAsString());
template_instantiation.set_namespace(ns);
template_instantiation.set_id(template_decl->getID() +
static_cast<common::id_t>(
std::hash<std::string>{}(full_template_specialization_name) >> 4U));
process_template_arguments(parent, &template_specialization,
template_base_params,
template_specialization.getTemplateArgs().asArray(),
template_instantiation, full_template_specialization_name,
template_decl);
// First try to find the best match for this template in partially
// specialized templates
std::string destination{};
std::string best_match_full_name{};
auto full_template_name = template_instantiation.full_name(false);
int best_match{};
common::model::diagram_element::id_t best_match_id{0};
for (const auto templ : diagram().classes()) {
if (templ.get() == template_instantiation)
continue;
auto c_full_name = templ.get().full_name(false);
auto match =
template_instantiation.calculate_template_specialization_match(
templ.get());
if (match > best_match) {
best_match = match;
best_match_full_name = c_full_name;
best_match_id = templ.get().id();
}
}
auto templated_decl_id = template_specialization.getID();
auto templated_decl_local_id =
id_mapper().get_global_id(templated_decl_id).value_or(0);
if (best_match_id > 0) {
destination = best_match_full_name;
template_instantiation.add_relationship(
{relationship_t::kInstantiation, best_match_id});
}
// If we can't find optimal match for parent template specialization,
// just use whatever clang suggests
else if (diagram().has_element(templated_decl_local_id)) {
template_instantiation.add_relationship(
{relationship_t::kInstantiation, templated_decl_local_id});
}
else if (diagram().should_include(qualified_name)) {
LOG_DBG("Skipping instantiation relationship from {} to {}",
template_instantiation_ptr->full_name(false), templated_decl_id);
}
return template_instantiation_ptr;
}
void translation_unit_visitor::process_field(
const clang::FieldDecl &field_declaration, class_ &c)
{
@@ -2333,25 +1971,6 @@ void translation_unit_visitor::finalize()
resolve_local_to_global_ids();
}
//
// void translation_unit_visitor::id_mapper().add(
// int64_t local_id, common::model::diagram_element::id_t global_id)
//{
// LOG_DBG("== Setting local element mapping {} --> {}", local_id,
// global_id);
//
// local_ast_id_map_[local_id] = global_id;
//}
//
// std::optional<common::model::diagram_element::id_t>
// translation_unit_visitor::id_mapper().get_global_id(int64_t local_id) const
//{
// if (local_ast_id_map_.find(local_id) == local_ast_id_map_.end())
// return {};
//
// return local_ast_id_map_.at(local_id);
//}
void translation_unit_visitor::extract_constrained_template_param_name(
const clang::ConceptSpecializationExpr *concept_specialization,
const clang::ConceptDecl *cpt,

View File

@@ -20,6 +20,7 @@
#include "class_diagram/model/class.h"
#include "class_diagram/model/concept.h"
#include "class_diagram/model/diagram.h"
#include "class_diagram/visitor/template_builder.h"
#include "common/model/enums.h"
#include "common/model/template_trait.h"
#include "common/visitor/ast_id_mapper.h"
@@ -52,10 +53,6 @@ using clanguml::common::model::relationship_t;
using clanguml::common::model::template_parameter;
using clanguml::common::model::template_trait;
using found_relationships_t =
std::vector<std::pair<clanguml::common::model::diagram_element::id_t,
common::model::relationship_t>>;
/**
* @brief Class diagram translation unit visitor
*
@@ -152,12 +149,6 @@ private:
clanguml::common::model::template_trait &t,
common::optional_ref<common::model::element> templated_element = {});
void process_template_specialization_argument(
const clang::ClassTemplateSpecializationDecl *cls,
model::class_ &template_instantiation,
const clang::TemplateArgument &arg, size_t argument_index,
bool in_parameter_pack = false);
void process_method(const clang::CXXMethodDecl &mf,
clanguml::class_diagram::model::class_ &c);
@@ -203,22 +194,10 @@ private:
void find_relationships_in_constraint_expression(
clanguml::common::model::element &c, const clang::Expr *expr);
void process_unexposed_template_specialization_parameters(
const std::string &tspec,
clanguml::common::model::template_parameter &tp,
clanguml::class_diagram::model::class_ &c);
bool find_relationships_in_unexposed_template_params(
const clanguml::common::model::template_parameter &ct,
found_relationships_t &relationships);
void add_incomplete_forward_declarations();
void resolve_local_to_global_ids();
bool simplify_system_template(common::model::template_parameter &ct,
const std::string &full_name) const;
void process_constraint_requirements(const clang::ConceptDecl *cpt,
const clang::Expr *expr, model::concept_ &concept_model) const;

View File

@@ -169,13 +169,14 @@ std::string to_string(const clang::RecordType &type,
return to_string(type.desugar(), ctx, try_canonical);
}
std::string to_string(const clang::TemplateArgument &arg)
std::string to_string(
const clang::TemplateArgument &arg, const clang::ASTContext *ctx)
{
switch (arg.getKind()) {
case clang::TemplateArgument::Expression:
return to_string(arg.getAsExpr());
case clang::TemplateArgument::Type:
return to_string(arg.getAsType());
return to_string(arg.getAsType(), *ctx, false);
case clang::TemplateArgument::Null:
return "";
case clang::TemplateArgument::NullPtr:
@@ -428,7 +429,7 @@ std::vector<common::model::template_parameter> parse_unexposed_template_params(
}
if (complete_class_template_argument) {
auto t = template_parameter::make_unexposed_argument(
ns_resolve(clanguml::util::trim(type)));
ns_resolve(clanguml::util::trim_typename(type)));
type = "";
for (auto &&param : nested_params)
t.add_template_param(std::move(param));
@@ -441,7 +442,7 @@ std::vector<common::model::template_parameter> parse_unexposed_template_params(
if (!type.empty()) {
auto t = template_parameter::make_unexposed_argument(
ns_resolve(clanguml::util::trim(type)));
ns_resolve(clanguml::util::trim_typename(type)));
type = "";
for (auto &&param : nested_params)
t.add_template_param(std::move(param));

View File

@@ -81,7 +81,8 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
std::string to_string(const clang::RecordType &type,
const clang::ASTContext &ctx, bool try_canonical = true);
std::string to_string(const clang::TemplateArgument &arg);
std::string to_string(
const clang::TemplateArgument &arg, const clang::ASTContext *ctx = nullptr);
std::string to_string(const clang::Expr *expr);

View File

@@ -136,6 +136,15 @@ std::string rtrim(const std::string &s)
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
std::string trim_typename(const std::string &s)
{
auto res = trim(s);
if (res.find("typename ") == 0)
return res.substr(strlen("typename "));
return res;
}
std::string trim(const std::string &s) { return rtrim(ltrim(s)); }
std::vector<std::string> split(

View File

@@ -57,6 +57,7 @@ namespace clanguml::util {
std::string ltrim(const std::string &s);
std::string rtrim(const std::string &s);
std::string trim(const std::string &s);
std::string trim_typename(const std::string &s);
#define FILENAME_ \
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

View File

@@ -1,5 +1,4 @@
// Inspired by skypjack/entt signal handlers
// This test case checks that at least clang-uml does not crash on this code
namespace clanguml::t00044 {
template <typename T> class sink;

View File

@@ -0,0 +1,21 @@
type: sequence
combine_free_functions_into_file_participants: true
generate_method_arguments: none
glob:
- src/class_diagram/visitor/template_builder.cc
include:
namespaces:
- clanguml
paths:
- src/class_diagram/visitor/template_builder.h
- src/class_diagram/visitor/template_builder.cc
exclude:
paths:
- src/common/model/source_location.h
using_namespace:
- clanguml
plantuml:
before:
- 'title clang-uml class_diagram::visitor::template_builder::build sequence diagram'
start_from:
- function: "clanguml::class_diagram::visitor::template_builder::build(const clang::NamedDecl *,const clang::TemplateSpecializationType &,std::optional<class_diagram::model::class_ *>)"