diff --git a/src/class_diagram/generators/json/class_diagram_generator.cc b/src/class_diagram/generators/json/class_diagram_generator.cc index f8cb28d5..69b00950 100644 --- a/src/class_diagram/generators/json/class_diagram_generator.cc +++ b/src/class_diagram/generators/json/class_diagram_generator.cc @@ -34,7 +34,7 @@ void set_module(nlohmann::json &j, const common::model::element &e) void to_json(nlohmann::json &j, const class_element &c) { j["name"] = c.name(); - j["type"] = c.type(); + j["type"] = common::generators::json::render_name(c.type()); if (c.access() != common::model::access_t::kNone) j["access"] = to_string(c.access()); if (!c.file().empty()) diff --git a/src/class_diagram/model/class_member.cc b/src/class_diagram/model/class_member.cc index 8d240cfb..bf331007 100644 --- a/src/class_diagram/model/class_member.cc +++ b/src/class_diagram/model/class_member.cc @@ -30,4 +30,14 @@ bool class_member::is_static() const { return is_static_; } void class_member::is_static(bool is_static) { is_static_ = is_static; } +void class_member::set_destination_multiplicity(std::optional m) +{ + destination_multiplicity_ = m; +} + +std::optional class_member::destination_multiplicity() const +{ + return destination_multiplicity_; +} + } // namespace clanguml::class_diagram::model diff --git a/src/class_diagram/model/class_member.h b/src/class_diagram/model/class_member.h index c6184b6d..9f034353 100644 --- a/src/class_diagram/model/class_member.h +++ b/src/class_diagram/model/class_member.h @@ -54,8 +54,23 @@ public: */ void is_static(bool is_static); + /** + * @brief Set members destination multiplicity. + * + * @param m Optional multiplicity value + */ + void set_destination_multiplicity(std::optional m); + + /** + * @brief Get members destination multiplicity. + * + * @return Optional multiplicity value + */ + std::optional destination_multiplicity() const; + private: bool is_static_{false}; + std::optional destination_multiplicity_{}; }; } // namespace clanguml::class_diagram::model diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 221fd7d8..cc9eed6b 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -906,14 +906,21 @@ void translation_unit_visitor::process_record_parent( if (cls_name.empty()) { // Nested structs can be anonymous if (anonymous_struct_relationships_.count(cls->getID()) > 0) { - const auto &[label, hint, access] = + const auto &[label, hint, access, destination_multiplicity] = anonymous_struct_relationships_[cls->getID()]; c.set_name(parent_class.value().name() + "##" + fmt::format("({})", label)); + std::string destination_multiplicity_str{}; + if (destination_multiplicity.has_value()) { + destination_multiplicity_str = + std::to_string(*destination_multiplicity); // NOLINT + } + parent_class.value().add_relationship( - {hint, common::to_id(c.full_name(false)), access, label}); + {hint, common::to_id(c.full_name(false)), access, label, "", + destination_multiplicity_str}); } else c.set_name(parent_class.value().name() + "##" + @@ -1649,14 +1656,22 @@ void translation_unit_visitor::add_relationships(class_ &c, relationship r{relationship_type, target}; r.set_label(field.name()); r.set_access(field.access()); + bool mulitplicity_provided_in_comment{false}; if (decorator_rtype != relationship_t::kNone) { r.set_type(decorator_rtype); auto mult = util::split(decorator_rmult, ":", false); if (mult.size() == 2) { + mulitplicity_provided_in_comment = true; r.set_multiplicity_source(mult[0]); r.set_multiplicity_destination(mult[1]); } } + if (!mulitplicity_provided_in_comment && + field.destination_multiplicity().has_value()) { + r.set_multiplicity_destination(std::to_string( + *field.destination_multiplicity())); // NOLINT + } + r.set_style(field.style_spec()); LOG_DBG("Adding relationship from {} to {} with label {}", @@ -1797,7 +1812,23 @@ void translation_unit_visitor::process_field( } else if (field_type->isArrayType()) { relationship_hint = relationship_t::kAggregation; - field_type = field_type->getAsArrayTypeUnsafe()->getElementType(); + while (field_type->isArrayType()) { + auto current_multiplicity = field.destination_multiplicity(); + if (!current_multiplicity) + field.set_destination_multiplicity(common::get_array_size( + *field_type->getAsArrayTypeUnsafe())); + else { + auto maybe_array_size = + common::get_array_size(*field_type->getAsArrayTypeUnsafe()); + if (maybe_array_size.has_value()) { + field.set_destination_multiplicity( + current_multiplicity.value() * + maybe_array_size.value()); + } + } + + field_type = field_type->getAsArrayTypeUnsafe()->getElementType(); + } } else if (field_type->isRValueReferenceType()) { field_type = field_type.getNonReferenceType(); @@ -1917,8 +1948,8 @@ void translation_unit_visitor::process_field( // struct have to be handled separately here anonymous_struct_relationships_[field_type->getAsRecordDecl() ->getID()] = - std::make_tuple( - field.name(), relationship_hint, field.access()); + std::make_tuple(field.name(), relationship_hint, + field.access(), field.destination_multiplicity()); } else find_relationships( @@ -1928,6 +1959,17 @@ void translation_unit_visitor::process_field( add_relationships(c, field, relationships); } + // If this is an anonymous struct - replace the anonymous_XYZ part with + // field name + if ((field_type->getAsRecordDecl() != nullptr) && + field_type->getAsRecordDecl()->getNameAsString().empty()) { + if (util::contains(field.type(), "(anonymous_")) { + std::regex anonymous_re("anonymous_(\\d*)"); + field.set_type( + std::regex_replace(field.type(), anonymous_re, field_name)); + } + } + c.add_member(std::move(field)); } diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index f7fbd525..d5d6e35b 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -438,7 +438,8 @@ private: std::map> + common::model::access_t, + std::optional /* destination_multiplicity */>> anonymous_struct_relationships_; /** diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index c928a158..c02ff514 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -113,13 +113,36 @@ std::string get_tag_name(const clang::TagDecl &declaration) return base_name; } +std::string to_string(const clang::ArrayType &array_type, + const clang::ASTContext &ctx, bool try_canonical, + std::vector &dimensions) +{ + auto maybe_size = get_array_size(array_type); + std::string array_size = + maybe_size.has_value() ? std::to_string(maybe_size.value()) : ""; + dimensions.emplace_back(std::move(array_size)); + + const auto underlying_type = array_type.getElementType(); + + if (underlying_type->isArrayType()) + return to_string(*underlying_type->getAsArrayTypeUnsafe(), ctx, + try_canonical, dimensions); + + std::string dimensions_str; + for (const auto &d : dimensions) { + dimensions_str += fmt::format("[{}]", d); + } + return fmt::format( + "{}{}", to_string(underlying_type, ctx, try_canonical), dimensions_str); +} + std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx, bool try_canonical) { if (type->isArrayType()) { - return fmt::format("{}[]", - to_string(type->getAsArrayTypeUnsafe()->getElementType(), ctx, - try_canonical)); + std::vector dimensions; + return to_string( + *type->getAsArrayTypeUnsafe(), ctx, try_canonical, dimensions); } clang::PrintingPolicy print_policy(ctx.getLangOpts()); @@ -962,4 +985,15 @@ bool has_attr(const clang::FunctionDecl *decl, clang::attr::Kind function_attr) [function_attr]( auto &&attr) { return attr->getKind() == function_attr; }); } + +std::optional get_array_size(const clang::ArrayType &type) +{ + if (const auto *constant_array = + clang::dyn_cast(&type); + constant_array != nullptr) { + return {constant_array->getSize().getZExtValue()}; + } + + return {}; +} } // namespace clanguml::common diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index e5bf04cf..25be8986 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -324,4 +324,12 @@ bool is_struct(const clang::NamedDecl *decl); */ bool has_attr(const clang::FunctionDecl *decl, clang::attr::Kind function_attr); +/** + * If `type` is a constant array, return it's number of elements. Otherwise + * nothing. + * + * @param type + * @return Number of elements in the array. + */ +std::optional get_array_size(const clang::ArrayType &type); } // namespace clanguml::common diff --git a/tests/t00006/test_case.h b/tests/t00006/test_case.h index e56e49ea..bdd4a56e 100644 --- a/tests/t00006/test_case.h +++ b/tests/t00006/test_case.h @@ -53,8 +53,8 @@ TEST_CASE("t00006") REQUIRE(IsAssociation(src, "R", "G", "g")); REQUIRE(IsAggregation(src, "R", "H", "h")); REQUIRE(IsAssociation(src, "R", "I", "i")); - REQUIRE(IsAggregation(src, "R", "J", "j")); - REQUIRE(IsAssociation(src, "R", "K", "k")); + REQUIRE(IsAggregation(src, "R", "J", "j", "", "10")); + REQUIRE(IsAssociation(src, "R", "K", "k", "", "20")); REQUIRE(IsAggregation(src, "R", "L", "lm")); REQUIRE(IsAggregation(src, "R", "M", "lm")); REQUIRE(IsAggregation(src, "R", "N", "ns")); diff --git a/tests/t00037/t00037.cc b/tests/t00037/t00037.cc index e9d73f9b..69333109 100644 --- a/tests/t00037/t00037.cc +++ b/tests/t00037/t00037.cc @@ -3,6 +3,11 @@ namespace t00037 { constexpr auto LENGTH{10ULL}; +struct S { + double x; + double y; +}; + class ST { public: struct { @@ -22,6 +27,8 @@ private: double c{1.0}; double h{1.0}; } units; + + S s[4][3][2]; }; struct A { diff --git a/tests/t00037/test_case.h b/tests/t00037/test_case.h index 2c18a940..16472dc6 100644 --- a/tests/t00037/test_case.h +++ b/tests/t00037/test_case.h @@ -35,6 +35,15 @@ TEST_CASE("t00037") REQUIRE( IsAggregation(src, "ST", "ST::(dimensions)", "dimensions")); REQUIRE(IsAggregation(src, "ST", "ST::(units)", "units")); - REQUIRE(IsAggregation(src, "ST", "ST::(bars)", "bars")); + REQUIRE( + IsAggregation(src, "ST", "ST::(bars)", "bars", "", "10")); + REQUIRE(IsAggregation( + src, "ST", "S", "s", "", std::to_string(4 * 3 * 2))); + + REQUIRE(IsField(src, "ST", "s", "S[4][3][2]")); + + REQUIRE(IsField(src, "ST", "bars", "ST::(bars)[10]")); + REQUIRE(IsField(src, "ST", "dimensions", "ST::(dimensions)")); + REQUIRE(IsField(src, "ST", "units", "ST::(units)")); }); } \ No newline at end of file