diff --git a/README.md b/README.md index 56fb0b2e..1bf02b33 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Full documentation can be found [here](./docs/README.md). Main features supported so far include: * **Class diagram generation** - * Basic class properties and methods including visibility + * Basic class properties and methods including access * Class relationships including associations, aggregations, dependencies and friendship * Template instantiation relationships * Relationship inference from C++ containers and smart pointers diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index 62cca1d3..dd30ff48 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -583,41 +583,39 @@ template_parameter template_builder::process_type_argument( argument = try_as_function_prototype(parent, cls, template_decl, type, template_instantiation, argument_index); - if (argument) return *argument; argument = try_as_member_pointer(parent, cls, template_decl, type, template_instantiation, argument_index); + if (argument) + return *argument; + argument = try_as_array(parent, cls, template_decl, type, + template_instantiation, argument_index); if (argument) return *argument; argument = try_as_template_specialization_type(parent, cls, template_decl, type, template_instantiation, argument_index); - if (argument) return *argument; argument = try_as_template_parm_type(cls, template_decl, type); - if (argument) return *argument; argument = try_as_lambda(cls, template_decl, type); - if (argument) return *argument; argument = try_as_record_type(parent, cls, template_decl, type, template_instantiation, argument_index); - if (argument) return *argument; argument = try_as_enum_type( parent, cls, template_decl, type, template_instantiation); - if (argument) return *argument; @@ -894,6 +892,7 @@ std::optional template_builder::try_as_array( ->getSize() .getLimitedValue()))); } + // TODO: Handle variable sized arrays return argument; @@ -922,9 +921,17 @@ std::optional template_builder::try_as_function_prototype( argument.add_template_param(return_arg); // Set function template argument types - for (const auto ¶m_type : function_type->param_types()) { - argument.add_template_param(process_type_argument(parent, cls, - template_decl, param_type, template_instantiation, argument_index)); + if (function_type->isVariadic() && function_type->param_types().empty()) { + auto fallback_arg = template_parameter::make_argument({}); + fallback_arg.is_ellipsis(true); + argument.add_template_param(std::move(fallback_arg)); + } + else { + for (const auto ¶m_type : function_type->param_types()) { + argument.add_template_param( + process_type_argument(parent, cls, template_decl, param_type, + template_instantiation, argument_index)); + } } return argument; diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index bd210681..faa66a56 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -138,6 +138,21 @@ template_parameter template_parameter::make_unexposed_argument( return p; } +bool template_parameter::is_specialization() const +{ + return is_function_template() || is_array() || is_data_pointer() || + is_member_pointer() || !deduced_context().empty(); +} + +bool template_parameter::is_same_specialization( + const template_parameter &other) const +{ + return is_array() == other.is_array() && + is_function_template() == other.is_function_template() && + is_data_pointer() == other.is_data_pointer() && + is_member_pointer() == other.is_member_pointer(); +} + void template_parameter::set_type(const std::string &type) { assert(kind_ != template_parameter_kind_t::template_type); @@ -212,10 +227,17 @@ int template_parameter::calculate_specialization_match( // If the potential base template has a deduction context (e.g. const&), // the specialization must have the same and possibly more - if (!base_template_parameter.deduced_context().empty() && - !util::starts_with( - deduced_context(), base_template_parameter.deduced_context())) - return 0; + if (base_template_parameter.is_specialization()) { + if (!deduced_context().empty() && + (base_template_parameter.deduced_context().empty() || + !util::starts_with(deduced_context(), + base_template_parameter.deduced_context()))) + return 0; + + if (!base_template_parameter.deduced_context().empty() && + deduced_context().empty()) + return 0; + } if (is_template_parameter() && base_template_parameter.is_template_parameter() && @@ -245,9 +267,6 @@ int template_parameter::calculate_specialization_match( if (base_template_parameter.is_array() && !is_array()) return 0; - if (base_template_parameter.is_array() && is_array()) - res++; - if (base_template_parameter.is_function_template() && !is_function_template()) return 0; @@ -259,7 +278,8 @@ int template_parameter::calculate_specialization_match( return 0; if (!base_template_parameter.template_params().empty() && - !template_params().empty()) { + !template_params().empty() && + is_same_specialization(base_template_parameter)) { auto params_match = calculate_template_params_specialization_match( template_params(), base_template_parameter.template_params()); @@ -270,8 +290,21 @@ int template_parameter::calculate_specialization_match( } else if ((base_template_parameter.is_template_parameter() || base_template_parameter.is_template_template_parameter()) && - !is_template_parameter()) + !is_template_parameter()) { return 1; + } + else if (base_template_parameter.is_template_parameter() && + base_template_parameter.template_params().empty()) { + // If the base is a regular template param, only possible with deduced + // context (deduced context already matches if exists) + res++; + + if (!deduced_context().empty() && + !base_template_parameter.deduced_context().empty() && + util::starts_with( + deduced_context(), base_template_parameter.deduced_context())) + res += base_template_parameter.deduced_context().size(); + } return res; } @@ -335,13 +368,26 @@ std::string template_parameter::to_string( const clanguml::common::model::namespace_ &using_namespace, bool relative, bool skip_qualifiers) const { - if (is_elipssis()) + if (is_ellipsis()) return "..."; using clanguml::common::model::namespace_; assert(!(type().has_value() && concept_constraint().has_value())); + if (is_array()) { + auto it = template_params_.begin(); + auto element_type = it->to_string(using_namespace, relative); + std::advance(it, 1); + + std::vector dimension_args; + for (; it != template_params_.end(); it++) + dimension_args.push_back(it->to_string(using_namespace, relative)); + + return fmt::format( + "{}[{}]", element_type, fmt::join(dimension_args, "][")); + } + if (is_function_template()) { auto it = template_params_.begin(); auto return_type = it->to_string(using_namespace, relative); @@ -589,13 +635,19 @@ const std::deque &template_parameter::deduced_context() const { return context_; } + void template_parameter::deduced_context(const std::deque &c) { context_ = c; } -void template_parameter::is_elipssis(bool e) { is_elipssis_ = e; } -bool template_parameter::is_elipssis() const { return is_elipssis_; } +void template_parameter::is_ellipsis(bool e) { is_ellipsis_ = e; } + +bool template_parameter::is_ellipsis() const { return is_ellipsis_; } + +void template_parameter::is_noexcept(bool e) { is_noexcept_ = e; } + +bool template_parameter::is_noexcept() const { return is_noexcept_; } int calculate_template_params_specialization_match( const std::vector &specialization_params, @@ -620,6 +672,7 @@ int calculate_template_params_specialization_match( template_params.at(template_index)); if (match == 0) { + // If any of the matches is 0 - the entire match fails return 0; } diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index b209aa11..50e94479 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -131,6 +131,10 @@ public: bool is_association() const; + bool is_specialization() const; + + bool is_same_specialization(const template_parameter &other) const; + bool find_nested_relationships( std::vector> &nested_relationships, @@ -147,7 +151,6 @@ public: void set_kind(template_parameter_kind_t kind); bool is_unexposed() const; - void set_unexposed(bool unexposed); void is_function_template(bool ft); @@ -166,8 +169,11 @@ public: const std::deque &deduced_context() const; void deduced_context(const std::deque &c); - void is_elipssis(bool e); - bool is_elipssis() const; + void is_ellipsis(bool e); + bool is_ellipsis() const; + + void is_noexcept(bool e); + bool is_noexcept() const; private: template_parameter() = default; @@ -194,7 +200,9 @@ private: /// Can only be true when is_template_parameter_ is true bool is_template_template_parameter_{false}; - bool is_elipssis_{false}; + bool is_ellipsis_{false}; + + bool is_noexcept_{false}; /// Whether the template parameter is variadic bool is_variadic_{false}; diff --git a/tests/t00062/t00062.cc b/tests/t00062/t00062.cc index 4d17c4f6..35928618 100644 --- a/tests/t00062/t00062.cc +++ b/tests/t00062/t00062.cc @@ -17,6 +17,10 @@ template struct A &> { template <> struct A> &> { }; +template struct A { + U **u; +}; + template struct A { U ***u; }; @@ -75,6 +79,13 @@ template struct A { template <> struct A { std::vector n; }; + +template struct A { char klm[K][L][M]; }; + +template struct A { + bool u; +}; + // // template struct eval; // diff --git a/tests/t00062/test_case.h b/tests/t00062/test_case.h index dfa0d53c..e7d5796c 100644 --- a/tests/t00062/test_case.h +++ b/tests/t00062/test_case.h @@ -51,36 +51,22 @@ TEST_CASE("t00062", "[test-case][class]") REQUIRE_THAT(puml, IsClassTemplate("A", "char[N]")); REQUIRE_THAT(puml, IsClassTemplate("A", "char[1000]")); - // Check if class templates exist - // REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); + REQUIRE_THAT(puml, IsClassTemplate("A", "U(...)")); - // Check concepts - // REQUIRE_THAT(puml, IsConcept(_A("AConcept"))); - // REQUIRE_THAT(puml, - // IsConceptRequirement( - // _A("AConcept"), "sizeof (T) > sizeof (P)")); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); - // Check if all enums exist - // REQUIRE_THAT(puml, IsEnum(_A("Lights"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); + REQUIRE_THAT(puml, + IsInstantiation(_A("A"), _A("A"))); - // Check if all inner classes exist - // REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); + REQUIRE_THAT( + puml, IsInstantiation(_A("A"), _A("A"))); - // Check if all inheritance relationships exist - // REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child"))); - - // Check if all methods exist - // REQUIRE_THAT(puml, (IsMethod("foo"))); - - // Check if all fields exist - // REQUIRE_THAT(puml, (IsField("private_member", "int"))); - - // Check if all relationships exist - // REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); - // REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); - // REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag")); - // REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac")); - // REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); + REQUIRE_THAT(puml, IsInstantiation(_A("A"), _A("A"))); save_puml( config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/test_cases.h b/tests/test_cases.h index 4b70a55f..93073628 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -236,6 +236,8 @@ struct AliasMatcher { util::replace_all(name, ")", "\\)"); util::replace_all(name, " ", "\\s"); util::replace_all(name, "*", "\\*"); + util::replace_all(name, "[", "\\["); + util::replace_all(name, "]", "\\]"); patterns.push_back( std::regex{"class\\s\"" + name + "\"\\sas\\s" + alias_regex}); diff --git a/tests/test_model.cc b/tests/test_model.cc index 27f329d4..ac4d75b4 100644 --- a/tests/test_model.cc +++ b/tests/test_model.cc @@ -318,4 +318,92 @@ TEST_CASE( CHECK(tp2.calculate_specialization_match(tp1)); } + + { + using clanguml::common::model::context; + using clanguml::common::model::rpqualifier; + + auto tp1 = template_parameter::make_template_type({}); + tp1.is_data_pointer(true); + tp1.add_template_param(template_parameter::make_template_type("T")); + tp1.add_template_param(template_parameter::make_template_type("C")); + + CHECK(tp1.is_specialization()); + + auto tp2 = template_parameter::make_template_type({}); + tp2.is_data_pointer(true); + tp2.add_template_param(template_parameter::make_argument("T")); + tp2.add_template_param(template_parameter::make_template_type("C")); + tp2.push_context(context{.pr = rpqualifier::kRValueReference}); + + CHECK(tp2.is_specialization()); + + CHECK(tp2.calculate_specialization_match(tp1) == 0); + CHECK(tp1.calculate_specialization_match(tp2) == 0); + } + + { + using clanguml::common::model::context; + using clanguml::common::model::rpqualifier; + + auto tp1 = template_parameter::make_template_type("T"); + tp1.push_context(context{.pr = rpqualifier::kRValueReference}); + CHECK(tp1.is_specialization()); + + auto tp2 = template_parameter::make_template_type({}); + tp2.is_data_pointer(true); + tp2.add_template_param(template_parameter::make_template_type("T")); + tp2.add_template_param(template_parameter::make_template_type("C")); + tp2.push_context(context{.pr = rpqualifier::kRValueReference}); + + CHECK(tp2.is_specialization()); + + CHECK(tp2.calculate_specialization_match(tp1) > 1); + CHECK(tp1.calculate_specialization_match(tp2) == 0); + } + + { + using clanguml::common::model::context; + using clanguml::common::model::rpqualifier; + + auto tp1 = template_parameter::make_template_type({}); + tp1.is_array(true); + tp1.add_template_param(template_parameter::make_template_type("int")); + tp1.add_template_param(template_parameter::make_template_type("N")); + + CHECK(tp1.to_string({}, false) == "int[N]"); + CHECK(tp1.is_specialization()); + + auto tp2 = template_parameter::make_argument({}); + tp2.is_array(true); + tp2.add_template_param(template_parameter::make_argument("int")); + tp2.add_template_param(template_parameter::make_argument("1000")); + + CHECK(tp2.to_string({}, false) == "int[1000]"); + CHECK(tp2.is_specialization()); + + CHECK(tp2.calculate_specialization_match(tp1) > 1); + CHECK(tp1.calculate_specialization_match(tp2) == 0); + } + + { + using clanguml::common::model::context; + using clanguml::common::model::rpqualifier; + + auto tp1 = template_parameter::make_template_type("T"); + tp1.push_context(context{.pr = rpqualifier::kLValueReference}); + CHECK(tp1.is_specialization()); + CHECK(tp1.to_string({}, false) == "T &"); + + auto tp2 = template_parameter::make_template_type({}); + tp2.is_array(true); + tp2.add_template_param(template_parameter::make_template_type("int")); + tp2.add_template_param(template_parameter::make_template_type("N")); + + CHECK(tp2.is_specialization()); + CHECK(tp2.to_string({}, false) == "int[N]"); + + CHECK(tp2.calculate_specialization_match(tp1) == 0); + CHECK(tp1.calculate_specialization_match(tp2) == 0); + } }