Fixed handling of relationship_hints option (#394)

This commit is contained in:
Bartek Kryza 2025-03-02 23:56:31 +01:00
parent 1cc3cb2874
commit 6b95e203c4
No known key found for this signature in database
GPG Key ID: 241B25F44E85B4D7
11 changed files with 196 additions and 31 deletions

View File

@ -1740,22 +1740,32 @@ bool translation_unit_visitor::find_relationships(const clang::Decl *decl,
}
// TODO: Objc support
else if (type->isRecordType()) {
const auto *type_instantiation_decl =
const auto *type_instantiation_type =
type->getAs<clang::TemplateSpecializationType>();
if (type_instantiation_decl != nullptr) {
if (type_instantiation_type != nullptr) {
const auto *type_instantiation_template_decl =
type_instantiation_type->getTemplateName().getAsTemplateDecl();
// If this template should be included in the diagram
// add it - and then process recursively its arguments
if (should_include(type_instantiation_decl->getTemplateName()
.getAsTemplateDecl())) {
if (should_include(type_instantiation_template_decl)) {
relationships.emplace_back(
type_instantiation_decl->getTemplateName()
type_instantiation_type->getTemplateName()
.getAsTemplateDecl()
->getID(),
relationship_hint, decl);
}
auto idx{0U};
for (const auto &template_argument :
type_instantiation_decl->template_arguments()) {
type_instantiation_type->template_arguments()) {
auto [overridden_relationship_hint, overridden] =
override_relationship_hint(type_instantiation_template_decl
->getQualifiedNameAsString(),
idx, relationship_hint);
const auto template_argument_kind = template_argument.getKind();
if (template_argument_kind ==
clang::TemplateArgument::ArgKind::Integral) {
@ -1795,8 +1805,9 @@ bool translation_unit_visitor::find_relationships(const clang::Decl *decl,
clang::TemplateArgument::ArgKind::Type) {
result =
find_relationships(decl, template_argument.getAsType(),
relationships, relationship_hint);
relationships, overridden_relationship_hint);
}
idx++;
}
}
else if (type->getAsCXXRecordDecl() != nullptr) {
@ -1813,6 +1824,8 @@ bool translation_unit_visitor::find_relationships(const clang::Decl *decl,
else if (const auto *template_specialization_type =
type->getAs<clang::TemplateSpecializationType>();
template_specialization_type != nullptr) {
const auto *type_instantiation_template_decl =
template_specialization_type->getTemplateName().getAsTemplateDecl();
if (should_include(template_specialization_type->getTemplateName()
.getAsTemplateDecl())) {
relationships.emplace_back(
@ -1821,8 +1834,15 @@ bool translation_unit_visitor::find_relationships(const clang::Decl *decl,
->getID(),
relationship_hint, decl);
}
auto idx{0U};
for (const auto &template_argument :
template_specialization_type->template_arguments()) {
auto [overridden_relationship_hint, overridden] =
override_relationship_hint(type_instantiation_template_decl
->getQualifiedNameAsString(),
idx, relationship_hint);
const auto template_argument_kind = template_argument.getKind();
if (template_argument_kind ==
clang::TemplateArgument::ArgKind::Integral) {
@ -1860,8 +1880,9 @@ bool translation_unit_visitor::find_relationships(const clang::Decl *decl,
else if (template_argument_kind ==
clang::TemplateArgument::ArgKind::Type) {
result = find_relationships(decl, template_argument.getAsType(),
relationships, relationship_hint);
relationships, overridden_relationship_hint);
}
idx++;
}
}
@ -2041,6 +2062,27 @@ void translation_unit_visitor::add_relationships(
}
}
std::pair<relationship_t, bool>
translation_unit_visitor::override_relationship_hint(
const std::string &type_name, int index, relationship_t hint)
{
bool overridden{false};
for (const auto &[pattern, rel_hint] : config().relationship_hints()) {
if (type_name.find(pattern) == 0) {
if (index == -1)
hint = rel_hint.default_hint;
else
hint = rel_hint.get(index, hint);
overridden = true;
break;
}
}
return {hint, overridden};
}
void translation_unit_visitor::process_static_field(
const clang::VarDecl &field_declaration, class_ &c)
{
@ -2197,10 +2239,10 @@ void translation_unit_visitor::process_field(
field_type = field_type.getNonReferenceType();
}
if (type_name.find("std::shared_ptr") == 0)
relationship_hint = relationship_t::kAssociation;
if (type_name.find("std::weak_ptr") == 0)
relationship_hint = relationship_t::kAssociation;
auto [overridden_relationship_hint, overridden] =
override_relationship_hint(type_name, -1, relationship_hint);
if (overridden)
relationship_hint = overridden_relationship_hint;
found_relationships_t relationships;
@ -2268,20 +2310,29 @@ void translation_unit_visitor::process_field(
// Try to find relationships to types nested in the template
// instantiation
found_relationships_t nested_relationships;
auto idx{0U};
if (!template_instantiation_added_as_aggregation) {
for (const auto &template_argument :
template_specialization.template_params()) {
auto template_argument_str = template_argument.to_string(
config().using_namespace(), false);
LOG_DBG("Looking for nested relationships from {}::{} in "
"template argument {}",
c, field_name,
template_argument.to_string(
config().using_namespace(), false));
c, field_name, template_argument_str);
auto [overridden_relationship_hint_param,
overridden_param] =
override_relationship_hint(
template_specialization.full_name(false), idx,
relationship_hint);
template_instantiation_added_as_aggregation =
template_argument.find_nested_relationships(
&field_declaration, nested_relationships,
relationship_hint,
overridden_relationship_hint_param,
!overridden_param,
[&d = diagram()](const std::string &full_name) {
if (full_name.empty())
return false;
@ -2294,6 +2345,8 @@ void translation_unit_visitor::process_field(
// unless the top level type has been added as aggregation
add_relationships(c, field, nested_relationships,
/* break on first aggregation */ false);
idx++;
}
// Add the template instantiation object to the diagram if it
@ -2357,7 +2410,8 @@ void translation_unit_visitor::find_record_parent_id(const clang::TagDecl *decl,
// regular class
parent_id_opt = id_mapper().get_global_id(local_id);
// If not, check if the parent template declaration is in the model
// If not, check if the parent template declaration is in the
// model
if (!parent_id_opt) {
if (parent_record_decl->getDescribedTemplate() != nullptr) {
local_id =
@ -2414,9 +2468,9 @@ void translation_unit_visitor::resolve_local_to_global_ids()
const auto maybe_id =
id_mapper().get_global_id(rel.destination());
if (maybe_id) {
LOG_TRACE(
"= Resolved instantiation destination from local "
"id {} to global id {}",
LOG_TRACE("= Resolved instantiation destination "
"from local "
"id {} to global id {}",
rel.destination(), *maybe_id);
rel.set_destination(*maybe_id);
}

View File

@ -487,6 +487,16 @@ private:
const found_relationships_t &relationships,
bool break_on_first_aggregation = false);
/**
* @brief Try to override relationship hint using configuration file
*
* @param type_name
* @param hint
* @return Maybe overridden relationship type hint
*/
std::pair<relationship_t, bool> override_relationship_hint(
const std::string &type_name, int index, relationship_t hint);
/**
* @brief Process record parent element (e.g. for nested classes)
*

View File

@ -492,7 +492,7 @@ std::string template_parameter::to_string(
bool template_parameter::find_nested_relationships(const clang::Decl *decl,
std::vector<std::tuple<eid_t, common::model::relationship_t,
const clang::Decl *>> &nested_relationships,
common::model::relationship_t hint,
common::model::relationship_t hint, bool allow_hint_override,
const std::function<bool(const std::string &full_name)> &should_include)
const
{
@ -502,11 +502,11 @@ bool template_parameter::find_nested_relationships(const clang::Decl *decl,
// just add it and skip recursion (e.g. this is a user defined type)
const auto maybe_type = type();
if (is_function_template())
if (allow_hint_override && is_function_template())
hint = common::model::relationship_t::kDependency;
if (maybe_type && should_include(maybe_type.value())) {
if (is_association())
if (allow_hint_override && is_association())
hint = common::model::relationship_t::kAssociation;
const auto maybe_id = id();
@ -525,8 +525,7 @@ bool template_parameter::find_nested_relationships(const clang::Decl *decl,
const auto maybe_arg_type = template_argument.type();
if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) {
if (template_argument.is_association() &&
if (allow_hint_override && template_argument.is_association() &&
hint == common::model::relationship_t::kAggregation)
hint = common::model::relationship_t::kAssociation;
@ -536,12 +535,14 @@ bool template_parameter::find_nested_relationships(const clang::Decl *decl,
(hint == common::model::relationship_t::kAggregation);
}
else {
if (template_argument.is_function_template())
if (allow_hint_override &&
template_argument.is_function_template())
hint = common::model::relationship_t::kDependency;
added_aggregation_relationship =
template_argument.find_nested_relationships(
decl, nested_relationships, hint, should_include);
template_argument.find_nested_relationships(decl,
nested_relationships, hint, allow_hint_override,
should_include);
}
}
}

View File

@ -405,7 +405,7 @@ public:
bool find_nested_relationships(const clang::Decl *decl,
std::vector<std::tuple<eid_t, common::model::relationship_t,
const clang::Decl *>> &nested_relationships,
common::model::relationship_t hint,
common::model::relationship_t hint, bool allow_hint_override,
const std::function<bool(const std::string &full_name)> &should_include)
const;

View File

@ -550,12 +550,13 @@ struct relationship_hint_t {
{
}
common::model::relationship_t get(unsigned int argument_index) const
common::model::relationship_t get(unsigned int argument_index,
std::optional<common::model::relationship_t> def = {}) const
{
if (argument_hints.count(argument_index) > 0)
return argument_hints.at(argument_index);
return default_hint;
return def.has_value() ? *def : default_hint;
}
};

View File

@ -418,6 +418,7 @@ root:
type_aliases: !optional map_t<string;string>
filter_mode: !optional filter_mode_t
include_system_headers: !optional bool
relationship_hints: !optional map_t<string;any>
)";
} // namespace clanguml::config

21
tests/t00092/.clang-uml Normal file
View File

@ -0,0 +1,21 @@
diagrams:
t00092_class:
type: class
glob:
- t00092.cc
include:
namespaces:
- clanguml::t00092
exclude:
elements:
- r: "clanguml::t00092::OwningPtr.*"
- r: "clanguml::t00092::WeakPtr.*"
- r: "clanguml::t00092::Map.*"
relationship_hints:
clanguml::t00092::OwningPtr: aggregation
clanguml::t00092::WeakPtr: association
clanguml::t00092::Map:
default: association
0: dependency
1: aggregation
using_namespace: clanguml::t00092

34
tests/t00092/t00092.cc Normal file
View File

@ -0,0 +1,34 @@
#include <map>
namespace clanguml::t00092 {
template <typename T> struct OwningPtr {
T *ptr;
};
template <typename T> struct WeakPtr {
T *ptr;
};
struct String {
char *data;
size_t length;
};
template <typename K, typename V> struct Map {
std::map<K, V> map;
};
class A { };
class B { };
class C { };
class R {
public:
OwningPtr<A> a;
WeakPtr<B> b;
Map<String, C> m;
};
} // namespace clanguml::t00092

39
tests/t00092/test_case.h Normal file
View File

@ -0,0 +1,39 @@
/**
* tests/t00092/test_case.h
*
* Copyright (c) 2021-2025 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.
*/
TEST_CASE("t00092")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00092", "t00092_class");
CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(IsClass(src, "A"));
REQUIRE(IsClass(src, "B"));
REQUIRE(IsClass(src, "C"));
REQUIRE(IsClass(src, "R"));
REQUIRE(IsClass(src, "String"));
REQUIRE(IsDependency(src, "R", "String"));
REQUIRE(IsAggregation<Public>(src, "R", "A", "a"));
REQUIRE(IsAssociation<Public>(src, "R", "B", "b"));
REQUIRE(IsAggregation<Public>(src, "R", "C", "m"));
});
}

View File

@ -596,6 +596,7 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
#endif
#include "t00091/test_case.h"
#include "t00092/test_case.h"
///
/// Sequence diagram tests

View File

@ -270,6 +270,9 @@ test_cases:
- name: t00091
title: Declaration forwarding test case
description:
- name: t00092
title: Test case for relationship_hints config option
description:
Sequence diagrams:
- name: t20001
title: Basic sequence diagram test case