Fixed template instantiation matching

This commit is contained in:
Bartek Kryza
2022-05-21 13:41:58 +02:00
parent 073b3d157d
commit dc26d1354d
8 changed files with 215 additions and 51 deletions

View File

@@ -105,7 +105,7 @@ std::string class_::full_name_no_ns() const
ostr << name();
render_template_params(ostr);
render_template_params(ostr, false);
return ostr.str();
}
@@ -118,7 +118,7 @@ std::string class_::full_name(bool relative) const
std::ostringstream ostr;
ostr << name_and_ns();
render_template_params(ostr);
render_template_params(ostr, relative);
std::string res;
@@ -134,7 +134,7 @@ std::string class_::full_name(bool relative) const
}
std::ostringstream &class_::render_template_params(
std::ostringstream &ostr) const
std::ostringstream &ostr, bool relative) const
{
using clanguml::common::model::namespace_;
@@ -144,8 +144,8 @@ std::ostringstream &class_::render_template_params(
std::transform(templates_.cbegin(), templates_.cend(),
std::back_inserter(tnames),
[ns = using_namespace()](
const auto &tmplt) { return tmplt.to_string(ns); });
[ns = using_namespace(), relative](
const auto &tmplt) { return tmplt.to_string(ns, relative); });
ostr << fmt::format("<{}>", fmt::join(tnames, ","));
}
@@ -160,4 +160,5 @@ bool class_::is_abstract() const
return std::any_of(methods_.begin(), methods_.end(),
[](const auto &method) { return method.is_pure_virtual(); });
}
}

View File

@@ -79,8 +79,13 @@ public:
void is_alias(bool alias) { is_alias_ = alias; }
void find_relationships(
std::vector<std::pair<std::string, common::model::relationship_t>>
&nested_relationships);
private:
std::ostringstream &render_template_params(std::ostringstream &ostr) const;
std::ostringstream &render_template_params(
std::ostringstream &ostr, bool relative) const;
bool is_struct_{false};
bool is_template_{false};

View File

@@ -17,6 +17,7 @@
*/
#include "template_parameter.h"
#include "common/model/enums.h"
#include <common/model/namespace.h>
#include <fmt/format.h>
@@ -81,6 +82,11 @@ 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
{
@@ -113,28 +119,37 @@ bool operator!=(const template_parameter &l, const template_parameter &r)
}
std::string template_parameter::to_string(
const clanguml::common::model::namespace_ &using_namespace) const
const clanguml::common::model::namespace_ &using_namespace,
bool relative) const
{
using clanguml::common::model::namespace_;
std::string res;
if (!type().empty())
res += namespace_{type()}.relative_to(using_namespace).to_string();
// Render nested template params
if (!template_params_.empty()) {
std::vector<std::string> params;
for (const auto &template_param : template_params_) {
params.push_back(template_param.to_string(using_namespace));
}
res += fmt::format("<{}>", fmt::join(params, ","));
if (!type().empty()) {
if (!relative)
res += namespace_{type()}.to_string();
else
res += namespace_{type()}.relative_to(using_namespace).to_string();
}
if (!name().empty()) {
if (!type().empty())
res += " ";
res += namespace_{name()}.relative_to(using_namespace).to_string();
if (!relative)
res += namespace_{name()}.to_string();
else
res += namespace_{name()}.relative_to(using_namespace).to_string();
}
// Render nested template params
if (!template_params_.empty()) {
std::vector<std::string> params;
for (const auto &template_param : template_params_) {
params.push_back(
template_param.to_string(using_namespace, relative));
}
res += fmt::format("<{}>", fmt::join(params, ","));
}
if (!default_value().empty()) {
@@ -142,7 +157,38 @@ std::string template_parameter::to_string(
res += default_value();
}
// TODO: Refactor this to external configurable class
util::replace_all(res, "std::basic_string<char>", "std::string");
util::replace_all(res, "std::basic_string<wchar_t>", "std::wstring");
return res;
}
void template_parameter::find_nested_relationships(
std::vector<std::pair<std::string, common::model::relationship_t>>
&nested_relationships,
common::model::relationship_t hint,
std::function<bool(const std::string &full_name)> condition) const
{
// If this type argument should be included in the relationship
// just add it and skip recursion (e.g. this is a user defined type)
if (condition(name())) {
nested_relationships.push_back({to_string({}, false), hint});
}
// 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()) {
if (condition(template_argument.name())) {
nested_relationships.push_back(
{template_argument.to_string({}, false), hint});
}
else {
template_argument.find_nested_relationships(
nested_relationships, hint, condition);
}
}
}
}
}

View File

@@ -17,6 +17,7 @@
*/
#pragma once
#include "common/model/enums.h"
#include "common/model/namespace.h"
#include <string>
@@ -24,10 +25,10 @@
namespace clanguml::class_diagram::model {
/// @brief Represents template parameter or template parameter instantiation
/// @brief Represents template parameter or template argument
///
/// This class can represent both template parameter and template parameter
/// instantiation, including variadic parameters and instantiations with
/// This class can represent both template parameter and template arguments,
/// including variadic parameters and instantiations with
/// nested templates
class template_parameter {
public:
@@ -35,6 +36,8 @@ public:
const std::string &name = "", const std::string &default_value = "",
bool is_variadic = false);
template_parameter(const template_parameter &right) = default;
void set_type(const std::string &type);
std::string type() const;
@@ -72,12 +75,21 @@ public:
}
std::string to_string(
const clanguml::common::model::namespace_ &using_namespace) const;
const clanguml::common::model::namespace_ &using_namespace,
bool relative) const;
void add_template_param(template_parameter &&ct);
void add_template_param(const template_parameter &ct);
const std::vector<template_parameter> &template_params() const;
void find_nested_relationships(
std::vector<std::pair<std::string, common::model::relationship_t>>
&nested_relationships,
common::model::relationship_t hint,
std::function<bool(const std::string &full_name)> condition) const;
private:
/// Represents the type of non-type template parameters
/// e.g. 'int'

View File

@@ -746,7 +746,7 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
mv.type().kind() == cppast::cpp_type_kind::reference_t)
relationship_type = relationship_t::kAssociation;
else
relationship_type = relationship_t::kAggregation;
relationship_type = nested_relationship_hint;
relationship rr{relationship_type, tinst.full_name(),
detail::cpp_access_specifier_to_access(as), mv.name()};
@@ -780,8 +780,20 @@ bool translation_unit_visitor::process_field_with_template_instantiation(
ctx.diagram().add_class(std::move(tinst_ptr));
}
else if (!nested_relationships.empty()) {
for (const auto &rel : nested_relationships) {
found_relationships_t nested_relationships2;
if (!ctx.diagram().should_include(tinst.get_namespace(), tinst.name()))
for (const auto &template_argument : tinst.templates()) {
template_argument.find_nested_relationships(nested_relationships2,
relationship_type,
[&d = ctx.diagram()](const std::string &full_name) {
auto [ns, name] = cx::util::split_ns(full_name);
return d.should_include(ns, name);
});
}
if (!nested_relationships2.empty()) {
for (const auto &rel : nested_relationships2) {
relationship nested_relationship{std::get<1>(rel), std::get<0>(rel),
detail::cpp_access_specifier_to_access(as), mv.name()};
nested_relationship.set_style(m.style_spec());
@@ -1581,7 +1593,7 @@ bool translation_unit_visitor::find_relationships_in_unexposed_template_params(
{
bool found{false};
LOG_DBG("Finding relationships in user defined type: {}",
ct.to_string(ctx.config().using_namespace()));
ct.to_string(ctx.config().using_namespace(), false));
auto type_with_namespace = ctx.get_name_with_namespace(ct.type());
@@ -1610,14 +1622,18 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
relationship_t nested_relationship_hint,
std::optional<clanguml::class_diagram::model::class_ *> parent)
{
//
// Create class_ instance to hold the template instantiation
//
auto tinst_ptr = std::make_unique<class_>(ctx.config().using_namespace());
auto &tinst = *tinst_ptr;
std::string full_template_name;
auto tr_declaration = cppast::to_string(t);
//
// If this is an alias - resolve the alias target
//
const auto &unaliased =
static_cast<const cppast::cpp_template_instantiation_type &>(
resolve_alias(t));
@@ -1625,16 +1641,20 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
bool t_is_alias = t_unaliased_declaration != tr_declaration;
//
// Here we'll hold the template base params to replace with the instantiated
// values
//
std::deque<std::tuple<std::string, int, bool>> template_base_params{};
tinst.set_namespace(ctx.get_namespace());
auto tinst_full_name = cppast::to_string(t);
//
// Typically, every template instantiation should have a primary_template()
// which should also be generated here if it doesn't exist yet in the model
//
if (t.primary_template().get(ctx.entity_index()).size()) {
auto size = t.primary_template().get(ctx.entity_index()).size();
(void)size;
@@ -1650,7 +1670,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
LOG_DBG("Building template instantiation for {}", full_template_name);
//
// Extract namespace from base template name
//
const auto [ns, name] = cx::util::split_ns(tinst_full_name);
tinst.set_name(name);
if (ns.is_empty())
@@ -1661,6 +1683,9 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
tinst.is_template_instantiation(true);
tinst.is_alias(t_is_alias);
//
// Process exposed template arguments - if any
//
if (t.arguments_exposed()) {
auto arg_index = 0U;
// We can figure this only when we encounter variadic param in
@@ -1690,7 +1715,10 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
ct);
}
LOG_DBG("Adding template argument '{}'", ct.name());
LOG_DBG("Adding template argument '{}'",
ct.to_string(ctx.config().using_namespace(), false));
// assert(!ct.name().empty() || !ct.type().empty());
tinst.add_template(std::move(ct));
}
@@ -1702,6 +1730,7 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
auto fn = cx::util::full_name(tt, ctx.entity_index(), false);
fn = util::split(fn, "<")[0];
// TODO: Refactor template instantiation search to a separate method
std::string destination;
if (ctx.has_type_alias(fn)) {
// If this is a template alias - set the instantiation
@@ -1715,9 +1744,12 @@ std::unique_ptr<class_> translation_unit_visitor::build_template_instantiation(
// First try to find the best match for this template in partially
// specialized templates
for (const auto c : ctx.diagram().classes()) {
if (c->name_and_ns() == full_template_name &&
std::string left = c->name_and_ns();
auto left_count = c->templates().size();
auto right_count = tinst.templates().size();
if (c != tinst && left == full_template_name &&
// TODO: handle variadic templates
c->templates().size() == tinst.templates().size()) {
left_count == right_count) {
int tmp_match = 0;
for (int i = 0; i < tinst.templates().size(); i++) {
const auto &tinst_i = tinst.templates().at(i);
@@ -1771,11 +1803,12 @@ bool translation_unit_visitor::build_template_instantiation_add_base_classes(
}
if (add_template_argument_as_base_class) {
LOG_DBG("Adding template argument as base class '{}'", ct.name());
LOG_DBG("Adding template argument as base class '{}'",
ct.to_string({}, false));
class_parent cp;
cp.set_access(access_t::kPublic);
cp.set_name(ct.name());
cp.set_name(ct.to_string({}, false));
tinst.add_parent(std::move(cp));
}
@@ -1806,31 +1839,42 @@ void translation_unit_visitor::
template_parameter &ct, found_relationships_t &nested_relationships,
common::model::relationship_t relationship_hint)
{
ct.set_name(cppast::to_string(targ.type().value()));
LOG_DBG("Template argument is a type {}", ct.name());
auto fn = cx::util::full_name(
auto full_name = cx::util::full_name(
cppast::remove_cv(cx::util::unreferenced(targ.type().value())),
ctx.entity_index(), false);
auto [fn_ns, fn_name] = cx::util::split_ns(fn);
auto [fn_ns, fn_name] = cx::util::split_ns(full_name);
auto template_argument_kind = targ.type().value().kind();
if (template_argument_kind == cppast::cpp_type_kind::unexposed_t) {
// Here we're on our own - just make a best guess
if (!fn.empty() && !util::contains(fn, "<") &&
!util::contains(fn, ":") && std::isupper(fn.at(0)))
if (!full_name.empty() && !util::contains(full_name, "<") &&
!util::contains(full_name, ":") && std::isupper(full_name.at(0)))
ct.is_template_parameter(true);
else
ct.is_template_parameter(false);
ct.set_name(full_name);
}
else if (template_argument_kind ==
cppast::cpp_type_kind::template_parameter_t) {
ct.is_template_parameter(true);
ct.set_name(full_name);
}
else if (template_argument_kind == cppast::cpp_type_kind::builtin_t) {
ct.is_template_parameter(false);
ct.set_type(full_name);
}
else if (template_argument_kind ==
cppast::cpp_type_kind::template_instantiation_t) {
// Check if this template should be simplified (e.g. system
// template aliases such as std:basic_string<char> should be simply
// std::string
if (simplify_system_template(ct, full_name)) {
return;
}
const auto &nested_template_parameter =
static_cast<const cppast::cpp_template_instantiation_type &>(
targ.type().value());
@@ -1842,14 +1886,23 @@ void translation_unit_visitor::
auto [tinst_ns, tinst_name] =
cx::util::split_ns(tinst.full_name(false));
auto nested_tinst = build_template_instantiation(
nested_template_parameter, nested_relationships, relationship_hint,
ctx.diagram().should_include(tinst_ns, tinst_name)
? std::make_optional(&tinst)
: parent);
ct.set_name(full_name.substr(0, full_name.find('<')));
assert(!ct.name().empty());
found_relationships_t sub_nested_relationships;
auto nested_tinst =
build_template_instantiation(nested_template_parameter,
sub_nested_relationships, relationship_hint,
ctx.diagram().should_include(tinst_ns, tinst_name)
? std::make_optional(&tinst)
: parent);
assert(nested_tinst);
for (const auto &t : nested_tinst->templates())
ct.add_template_param(t);
relationship tinst_dependency{
relationship_t::kDependency, nested_tinst->full_name()};
@@ -1863,6 +1916,10 @@ void translation_unit_visitor::
{nested_tinst->full_name(false), relationship_hint});
ctx.diagram().add_class(std::move(nested_tinst));
}
else {
if (!sub_nested_relationships.empty())
nested_relationships.push_back(sub_nested_relationships[0]);
}
if (ctx.diagram().should_include(tinst_ns, tinst_name)
// TODO: check why this breaks t00033:
@@ -1871,7 +1928,7 @@ void translation_unit_visitor::
) {
LOG_DBG("Creating nested template dependency to template "
"instantiation {}, {} -> {}",
fn, tinst.full_name(), tinst_dependency.destination());
full_name, tinst.full_name(), tinst_dependency.destination());
tinst.add_relationship(std::move(tinst_dependency));
}
@@ -1879,14 +1936,15 @@ void translation_unit_visitor::
LOG_DBG("Creating nested template dependency to parent "
"template "
"instantiation {}, {} -> {}",
fn, (*parent)->full_name(), tinst_dependency.destination());
full_name, (*parent)->full_name(),
tinst_dependency.destination());
(*parent)->add_relationship(std::move(tinst_dependency));
}
else {
LOG_DBG("No nested template dependency to template "
"instantiation: {}, {} -> {}",
fn, tinst.full_name(), tinst_dependency.destination());
full_name, tinst.full_name(), tinst_dependency.destination());
}
}
else if (template_argument_kind == cppast::cpp_type_kind::user_defined_t) {
@@ -1899,6 +1957,8 @@ void translation_unit_visitor::
"type {} -> {}",
tinst.full_name(), tinst_dependency.destination());
ct.set_name(full_name);
if (ctx.diagram().should_include(fn_ns, fn_name)) {
tinst.add_relationship(std::move(tinst_dependency));
nested_relationships.push_back(
@@ -2034,4 +2094,27 @@ const cppast::cpp_type &translation_unit_visitor::resolve_alias_template(
return type;
}
bool translation_unit_visitor::simplify_system_template(
template_parameter &ct, const std::string &full_name)
{
if (full_name == "std::basic_string<char>") {
ct.set_name("std::string");
return true;
}
else if (full_name == "std::basic_string<wchar_t>") {
ct.set_name("std::wstring");
return true;
}
else if (full_name == "std::basic_string<char16_t>") {
ct.set_name("std::u16string");
return true;
}
else if (full_name == "std::basic_string<char32_t>") {
ct.set_name("std::u32string");
return true;
}
else
return false;
}
}

View File

@@ -49,6 +49,11 @@ namespace clanguml::class_diagram::visitor {
using found_relationships_t =
std::vector<std::pair<std::string, common::model::relationship_t>>;
// class nested_template_relationships {
//
// std::vector<std::unique_ptr<nested_template_relationships>> children;
//};
class translation_unit_visitor {
public:
translation_unit_visitor(cppast::cpp_entity_index &idx,
@@ -232,5 +237,7 @@ private:
// ctx allows to track current visitor context, e.g. current namespace
translation_unit_context ctx;
bool simplify_system_template(
model::template_parameter &parameter, const std::string &basicString);
};
}

View File

@@ -1,11 +1,11 @@
#include <algorithm>
//#include <algorithm>
#include <functional>
#include <ios>
//#include <ios>
#include <map>
#include <memory>
#include <numeric>
//#include <numeric>
#include <string>
#include <type_traits>
//#include <type_traits>
#include <variant>
#include <vector>

View File

@@ -44,6 +44,16 @@ TEST_CASE("t00019", "[test-case][class]")
IsBaseClass(
_A("Layer2<Layer3<Base>>"), _A("Layer1<Layer2<Layer3<Base>>>")));
REQUIRE_THAT(puml,
IsAggregation(_A("A"), _A("Layer1<Layer2<Layer3<Base>>>"), "+layers"));
REQUIRE_THAT(
puml, !IsAggregation(_A("A"), _A("Layer2<Layer3<Base>>"), "+layers"));
REQUIRE_THAT(puml, !IsAggregation(_A("A"), _A("Layer3<Base>"), "+layers"));
REQUIRE_THAT(puml, !IsAggregation(_A("A"), _A("Base"), "+layers"));
save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
}