Skip to content

Commit

Permalink
Fixed handling of forward declarations in class diagrams (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkryza committed Feb 11, 2025
1 parent ac667d5 commit bcce036
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# CHANGELOG

* Fixed handling of forward declarations in class diagrams (#388)
* Added support for coroutines in sequence diagrams (#376)
* Fixed supported for compile_flags.txt (#381)
* Added separate return messages for each return branch in sequence diagrams
Expand Down
75 changes: 61 additions & 14 deletions src/class_diagram/visitor/translation_unit_visitor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
enm->getLocation().printToString(source_manager()));

auto e_ptr = create_enum_declaration(enm, nullptr);
if (!e_ptr)
return true;

auto id = e_ptr->id();

auto &enum_model = diagram().find<enum_>(id).has_value()
? *diagram().find<enum_>(id).get()
: *e_ptr;

if (enm->isCompleteDefinition() && !enum_model.complete())
process_enum_declaration(*enm, enum_model);

if (!enm->isCompleteDefinition()) {
enum_forward_declarations_.emplace(id, std::move(e_ptr));
return true;
}
enum_forward_declarations_.erase(id);

e_ptr->complete(true);

if (e_ptr && diagram().should_include(*e_ptr))
add_enum(std::move(e_ptr));
Expand Down Expand Up @@ -206,11 +225,15 @@ translation_unit_visitor::create_enum_declaration(

e.set_style(e.style_spec());

for (const auto &ev : enm->enumerators()) {
return e_ptr;
}

void translation_unit_visitor::process_enum_declaration(
const clang::EnumDecl &enm, clanguml::class_diagram::model::enum_ &e)
{
for (const auto &ev : enm.enumerators()) {
e.constants().push_back(ev->getNameAsString());
}

return e_ptr;
}

bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
Expand All @@ -236,15 +259,28 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
if (!template_specialization_ptr)
return true;

auto &template_specialization = *template_specialization_ptr;
auto id = template_specialization_ptr->id();

auto &template_specialization = diagram().find<class_>(id).has_value()
? *diagram().find<class_>(id).get()
: *template_specialization_ptr;

if (cls->hasDefinition()) {
// Process template specialization bases
process_class_bases(cls, template_specialization);

// Process class child entities
process_class_children(cls, template_specialization);

template_specialization.complete(true);
}

if (!cls->isCompleteDefinition()) {
forward_declarations_.emplace(
id, std::move(template_specialization_ptr));
return true;
}
forward_declarations_.erase(id);

if (!template_specialization.template_specialization_found()) {
// Only do this if we haven't found a better specialization during
Expand All @@ -258,7 +294,6 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(

if (diagram().should_include(template_specialization)) {
const auto full_name = template_specialization.full_name(false);
const auto id = template_specialization.id();

LOG_DBG("Adding class template specialization {} with id {}", full_name,
id);
Expand Down Expand Up @@ -347,11 +382,18 @@ bool translation_unit_visitor::VisitClassTemplateDecl(
find_relationships_in_constraint_expression(*c_ptr, expr);
}

auto &template_model = diagram().find<class_>(id).has_value()
? *diagram().find<class_>(id).get()
: *c_ptr;

if (cls->getTemplatedDecl()->hasDefinition()) {
process_class_declaration(*cls->getTemplatedDecl(), template_model);
}

if (!cls->getTemplatedDecl()->isCompleteDefinition()) {
forward_declarations_.emplace(id, std::move(c_ptr));
return true;
}
process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
forward_declarations_.erase(id);

if (diagram().should_include(*c_ptr)) {
Expand Down Expand Up @@ -844,6 +886,7 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
LOG_DBG("== isTemplateDecl() = {}", cls->isTemplateDecl());
LOG_DBG("== isTemplated() = {}", cls->isTemplated());
LOG_DBG("== getParent()->isRecord()() = {}", cls->getParent()->isRecord());
LOG_DBG("== isCompleteDefinition() = {}", cls->isCompleteDefinition());

if (const auto *parent_record =
clang::dyn_cast<clang::RecordDecl>(cls->getParent());
Expand Down Expand Up @@ -2473,6 +2516,13 @@ void translation_unit_visitor::add_incomplete_forward_declarations()
}
}
forward_declarations_.clear();

for (auto &[id, e] : enum_forward_declarations_) {
if (diagram().should_include(e->get_namespace())) {
add_enum(std::move(e));
}
}
enum_forward_declarations_.clear();
}

void translation_unit_visitor::resolve_local_to_global_ids()
Expand Down Expand Up @@ -2553,8 +2603,6 @@ void translation_unit_visitor::add_diagram_element(

void translation_unit_visitor::add_class(std::unique_ptr<class_> &&c)
{
c->complete(true);

if ((config().generate_packages() &&
config().package_type() == config::package_type_t::kDirectory)) {
assert(!c->file().empty());
Expand Down Expand Up @@ -2584,8 +2632,6 @@ void translation_unit_visitor::add_class(std::unique_ptr<class_> &&c)
void translation_unit_visitor::add_objc_interface(
std::unique_ptr<objc_interface> &&c)
{
c->complete(true);

if ((config().generate_packages() &&
config().package_type() == config::package_type_t::kDirectory)) {
assert(!c->file().empty());
Expand All @@ -2605,8 +2651,6 @@ void translation_unit_visitor::add_objc_interface(

void translation_unit_visitor::add_enum(std::unique_ptr<enum_> &&e)
{
e->complete(true);

if ((config().generate_packages() &&
config().package_type() == config::package_type_t::kDirectory)) {
assert(!e->file().empty());
Expand Down Expand Up @@ -2635,8 +2679,6 @@ void translation_unit_visitor::add_enum(std::unique_ptr<enum_> &&e)

void translation_unit_visitor::add_concept(std::unique_ptr<concept_> &&c)
{
c->complete(true);

if ((config().generate_packages() &&
config().package_type() == config::package_type_t::kDirectory)) {
assert(!c->file().empty());
Expand Down Expand Up @@ -2711,6 +2753,11 @@ void translation_unit_visitor::find_instantiation_relationships(
templated_decl_global_id});
template_instantiation.template_specialization_found(true);
}
else if (id_mapper().get_global_id(templated_decl_id).has_value()) {
template_instantiation.add_relationship(
{common::model::relationship_t::kInstantiation, templated_decl_id});
template_instantiation.template_specialization_found(true);
}
else if (diagram().should_include(common::model::namespace_{full_name})) {
LOG_DBG("Skipping instantiation relationship from {} to {}",
template_instantiation, templated_decl_global_id);
Expand Down
11 changes: 11 additions & 0 deletions src/class_diagram/visitor/translation_unit_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,15 @@ class translation_unit_visitor
void process_class_declaration(const clang::CXXRecordDecl &cls,
clanguml::class_diagram::model::class_ &c);

/**
* @brief Process enum declaration
*
* @param enm Enum declaration
* @param e Enum diagram element returned from `create_enum_declaration`
*/
void process_enum_declaration(
const clang::EnumDecl &enm, clanguml::class_diagram::model::enum_ &e);

/**
* @brief Process Objective-C category declaration
*
Expand Down Expand Up @@ -561,6 +570,8 @@ class translation_unit_visitor

std::map<eid_t, std::unique_ptr<clanguml::class_diagram::model::class_>>
forward_declarations_;
std::map<eid_t, std::unique_ptr<clanguml::class_diagram::model::enum_>>
enum_forward_declarations_;

std::map<int64_t /* local anonymous struct id */,
std::tuple<std::string /* field name */, common::model::relationship_t,
Expand Down
6 changes: 3 additions & 3 deletions src/common/model/diagram_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ class diagram_element
void nested(bool nested);

/**
* Returns the diagrams completion status.
* Returns the diagram element building completion status.
*
* @return Whether the diagram is complete.
* @return Whether the diagram element is complete.
*/
bool complete() const;

/**
* Set the diagrams completion status.
* Set the diagram element building completion status.
*
* @param completed
*/
Expand Down
10 changes: 10 additions & 0 deletions tests/t00091/.clang-uml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
diagrams:
t00091_class:
type: class
glob:
- t00091_a.cc
- t00091_b.cc
include:
namespaces:
- clanguml::t00091
using_namespace: clanguml::t00091
17 changes: 17 additions & 0 deletions tests/t00091/t00091_a.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <string>

namespace clanguml::t00091 {
struct B;

template <class T> struct C;

template <> struct C<std::string>;

enum class D;

struct R {
B *b;
C<int> *c;
D d;
};
} // namespace clanguml::t00091
17 changes: 17 additions & 0 deletions tests/t00091/t00091_b.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <string>

namespace clanguml::t00091 {
struct B {
int value;
};

template <class T> struct C {
T value;
};

template <> struct C<std::string> {
std::string value;
};

enum class D { one, two, three };
} // namespace clanguml::t00091
45 changes: 45 additions & 0 deletions tests/t00091/test_case.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* tests/t00091/test_case.h
*
* Copyright (c) 2021-2025 Bartek Kryza <[email protected]>
*
* 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("t00091")
{
using namespace clanguml::test;
using namespace std::string_literals;

auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00091", "t00091_class");

CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(IsClass(src, "B"));
REQUIRE(IsClassTemplate(src, "C<T>"));
REQUIRE(IsClassTemplate(src, "C<int>"));
REQUIRE(IsClassTemplate(src, "C<std::string>"));
REQUIRE(IsEnum(src, "D"));

REQUIRE(IsField<Public>(src, "R", "b", "B *"));
REQUIRE(IsField<Public>(src, "R", "c", "C<int> *"));
REQUIRE(IsField<Public>(src, "R", "d", "D"));

REQUIRE(IsField<Public>(src, "B", "value", "int"));
REQUIRE(IsField<Public>(src, "C<T>", "value", "T"));
REQUIRE(IsField<Public>(src, "C<std::string>", "value", "std::string"));

REQUIRE(IsInstantiation(src, "C<T>", "C<int>"));
REQUIRE(IsInstantiation(src, "C<T>", "C<std::string>"));
});
}
3 changes: 3 additions & 0 deletions tests/test_cases.cc
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
#if defined(ENABLE_CXX_STD_20_TEST_CASES)
#include "t00090/test_case.h"
#endif

#include "t00091/test_case.h"

///
/// Sequence diagram tests
///
Expand Down
3 changes: 3 additions & 0 deletions tests/test_cases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ test_cases:
- name: t00090
title: Metaprogramming test case with recursive type list
description:
- name: t00091
title: Declaration forwarding test case
description:
Sequence diagrams:
- name: t20001
title: Basic sequence diagram test case
Expand Down

0 comments on commit bcce036

Please sign in to comment.