From 0fbf491dfea920bab6d6880c683c91f82bb34b53 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 14 May 2023 16:34:59 +0200 Subject: [PATCH] Fixed handling of decltype cxxmember type aliases with dependend parameters --- src/class_diagram/visitor/template_builder.cc | 155 +++++++++-------- src/class_diagram/visitor/template_builder.h | 12 ++ .../visitor/translation_unit_visitor.cc | 158 +++++++++++------- .../visitor/translation_unit_visitor.h | 5 - src/common/clang_utils.cc | 112 +++++++++++++ src/common/clang_utils.h | 3 + src/common/model/template_parameter.cc | 5 +- src/common/model/template_parameter.h | 3 +- tests/t00064/t00064.cc | 35 +++- tests/t00064/test_case.h | 30 +++- 10 files changed, 374 insertions(+), 144 deletions(-) diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index 8fb6fc4d..23217861 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -452,10 +452,11 @@ void template_builder::argument_process_dispatch( case clang::TemplateArgument::Template: argument.push_back(process_template_argument(arg)); break; - case clang::TemplateArgument::Type: + case clang::TemplateArgument::Type: { argument.push_back(process_type_argument(parent, cls, template_decl, arg.getAsType(), template_instantiation, argument_index)); break; + } case clang::TemplateArgument::Declaration: break; case clang::TemplateArgument::NullPtr: @@ -509,79 +510,11 @@ template_parameter template_builder::process_template_expansion( clang::QualType template_builder::consume_context( clang::QualType type, template_parameter &tp) const { - while (true) { - bool try_again{false}; - common::model::context ctx; + auto [unqualified_type, context] = common::consume_type_context(type); - if (type->isPointerType() || type->isReferenceType()) { - if (type.isConstQualified() || type.isVolatileQualified()) { - ctx.is_ref_const = type.isConstQualified(); - ctx.is_ref_volatile = type.isVolatileQualified(); + tp.deduced_context(std::move(context)); - try_again = true; - } - } - - if (type->isLValueReferenceType()) { - ctx.pr = common::model::rpqualifier::kLValueReference; - try_again = true; - } - else if (type->isRValueReferenceType()) { - ctx.pr = common::model::rpqualifier::kRValueReference; - try_again = true; - } - else if (type->isMemberFunctionPointerType() && - type->getPointeeType()->getAs() != - nullptr) { - const auto ref_qualifier = - type->getPointeeType() // NOLINT - ->getAs() // NOLINT - ->getRefQualifier(); - - if (ref_qualifier == clang::RefQualifierKind::RQ_RValue) { - ctx.pr = common::model::rpqualifier::kRValueReference; - try_again = true; - } - else if (ref_qualifier == clang::RefQualifierKind::RQ_LValue) { - ctx.pr = common::model::rpqualifier::kLValueReference; - try_again = true; - } - } - else if (type->isPointerType()) { - ctx.pr = common::model::rpqualifier::kPointer; - try_again = true; - } - - if (try_again) { - if (type->isPointerType()) { - if (type->getPointeeType().isConstQualified()) - ctx.is_const = true; - if (type->getPointeeType().isVolatileQualified()) - ctx.is_volatile = true; - - type = type->getPointeeType().getUnqualifiedType(); - } - else if (type->isReferenceType()) { - if (type.getNonReferenceType().isConstQualified()) - ctx.is_const = true; - if (type.getNonReferenceType().isVolatileQualified()) - ctx.is_volatile = true; - - type = type.getNonReferenceType().getUnqualifiedType(); - } - else if (type.isConstQualified() || type.isVolatileQualified()) { - ctx.is_const = type.isConstQualified(); - ctx.is_volatile = type.isVolatileQualified(); - } - - tp.push_context(ctx); - - if (type->isMemberFunctionPointerType()) - return type; - } - else - return type; - } + return unqualified_type; } template_parameter template_builder::process_type_argument( @@ -626,6 +559,16 @@ template_parameter template_builder::process_type_argument( if (argument) return *argument; + argument = try_as_decl_type(parent, cls, template_decl, type, + template_instantiation, argument_index); + if (argument) + return *argument; + + argument = try_as_typedef_type(parent, cls, template_decl, type, + template_instantiation, argument_index); + if (argument) + return *argument; + argument = try_as_lambda(cls, template_decl, type); if (argument) return *argument; @@ -940,6 +883,8 @@ std::optional template_builder::try_as_function_prototype( if (function_type == nullptr) return {}; + LOG_DBG("Template argument is a function prototype"); + auto argument = template_parameter::make_template_type(""); type = consume_context(type, argument); @@ -969,6 +914,57 @@ std::optional template_builder::try_as_function_prototype( return argument; } +std::optional template_builder::try_as_decl_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *decl_type = + common::dereference(type)->getAs(); + if (decl_type == nullptr) { + return {}; + } + + LOG_DBG("Template argument is a decltype()"); + + // TODO + return {}; +} + +std::optional template_builder::try_as_typedef_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *typedef_type = + common::dereference(type)->getAs(); + if (typedef_type == nullptr) { + return {}; + } + + LOG_DBG("Template argument is a typedef/using"); + + // If this is a typedef/using alias to a decltype - we're not able + // to figure out anything out of it probably + if (typedef_type->getAs() != nullptr) { + // Here we need to figure out the parent context of this alias, + // it can be a: + // - class/struct + if (typedef_type->getDecl()->isCXXClassMember() && parent) { + return template_parameter::make_argument( + fmt::format("{}::{}", parent.value()->full_name(false), + typedef_type->getDecl()->getNameAsString())); + } + // - namespace + return template_parameter::make_argument( + typedef_type->getDecl()->getQualifiedNameAsString()); + } + + return {}; +} + std::optional template_builder::try_as_template_specialization_type( std::optional &parent, @@ -978,8 +974,11 @@ template_builder::try_as_template_specialization_type( { const auto *nested_template_type = common::dereference(type)->getAs(); - if (nested_template_type == nullptr) + if (nested_template_type == nullptr) { return {}; + } + + LOG_DBG("Template argument is a template specialization type"); auto argument = template_parameter::make_argument(""); type = consume_context(type, argument); @@ -1074,6 +1073,8 @@ std::optional template_builder::try_as_template_parm_type( if (type_parameter == nullptr) return {}; + LOG_DBG("Template argument is a template parameter type"); + auto argument = template_parameter::make_template_type(""); type = consume_context(type, argument); @@ -1100,6 +1101,8 @@ std::optional template_builder::try_as_lambda( if (type_name.find("(lambda at ") != 0) return {}; + LOG_DBG("Template argument is a lambda"); + auto argument = template_parameter::make_argument(""); type = consume_context(type, argument); @@ -1120,6 +1123,8 @@ std::optional template_builder::try_as_record_type( if (record_type == nullptr) return {}; + LOG_DBG("Template argument is a c++ record"); + auto argument = template_parameter::make_argument({}); type = consume_context(type, argument); @@ -1176,6 +1181,8 @@ std::optional template_builder::try_as_enum_type( if (enum_type == nullptr) return {}; + LOG_DBG("Template argument is a an enum"); + auto argument = template_parameter::make_argument({}); type = consume_context(type, argument); @@ -1200,6 +1207,8 @@ std::optional template_builder::try_as_builtin_type( if (builtin_type == nullptr) return {}; + LOG_DBG("Template argument is a builtin type"); + auto type_name = common::to_string(type, template_decl->getASTContext()); auto argument = template_parameter::make_argument(type_name); diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 9f34f699..428d4b68 100644 --- a/src/class_diagram/visitor/template_builder.h +++ b/src/class_diagram/visitor/template_builder.h @@ -157,6 +157,18 @@ public: clang::QualType &type, class_ &template_instantiation, size_t argument_index); + std::optional try_as_decl_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + + std::optional try_as_typedef_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index); + clang::QualType consume_context( clang::QualType type, template_parameter &tp) const; diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index 9d5e8377..a6169bfe 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -1291,6 +1291,30 @@ void translation_unit_visitor::process_method( // find relationship for return type found_relationships_t relationships; + // Move dereferencing to build() method of template_builder + if (const auto *templ = mf.getReturnType() + .getNonReferenceType() + .getUnqualifiedType() + ->getAs(); + templ != nullptr) { + auto *unaliased_type = templ; + if (unaliased_type->isTypeAlias()) + unaliased_type = unaliased_type->getAliasedType() + ->getAs(); + + auto template_specialization_ptr = tbuilder().build( + unaliased_type->getTemplateName().getAsTemplateDecl(), + *unaliased_type, &c); + + if (diagram().should_include( + template_specialization_ptr->full_name(false))) { + relationships.emplace_back( + template_specialization_ptr->id(), relationship_t::kDependency); + + diagram().add_class(std::move(template_specialization_ptr)); + } + } + find_relationships( mf.getReturnType(), relationships, relationship_t::kDependency); @@ -1317,13 +1341,8 @@ void translation_unit_visitor::process_method( if (underlying_type->isPointerType()) underlying_type = underlying_type->getPointeeType(); - if (const auto *tsp = - underlying_type->getAs(); - tsp != nullptr) { - process_function_parameter_find_relationships_in_template(c, {}, *tsp); - } - else if (const auto *atsp = underlying_type->getAs(); - atsp != nullptr) { + if (const auto *atsp = underlying_type->getAs(); + atsp != nullptr) { process_function_parameter_find_relationships_in_autotype(c, atsp); } @@ -1521,6 +1540,60 @@ bool translation_unit_visitor::find_relationships(const clang::QualType &type, result = true; } } + else if (const auto *template_specialization_type = + type->getAs(); + template_specialization_type != nullptr) { + if (should_include(template_specialization_type->getTemplateName() + .getAsTemplateDecl())) { + relationships.emplace_back( + template_specialization_type->getTemplateName() + .getAsTemplateDecl() + ->getID(), + relationship_hint); + } + for (const auto &template_argument : + template_specialization_type->template_arguments()) { + const auto template_argument_kind = template_argument.getKind(); + if (template_argument_kind == + clang::TemplateArgument::ArgKind::Integral) { + // pass + } + else if (template_argument_kind == + clang::TemplateArgument::ArgKind::Null) { + // pass + } + else if (template_argument_kind == + clang::TemplateArgument::ArgKind::Expression) { + // pass + } + else if (template_argument.getKind() == + clang::TemplateArgument::ArgKind::NullPtr) { + // pass + } + else if (template_argument_kind == + clang::TemplateArgument::ArgKind::Template) { + // pass + } + else if (template_argument_kind == + clang::TemplateArgument::ArgKind::TemplateExpansion) { + // pass + } + else if (const auto *function_type = + template_argument.getAsType() + ->getAs(); + function_type != nullptr) { + for (const auto ¶m_type : function_type->param_types()) { + result = find_relationships( + param_type, relationships, relationship_t::kDependency); + } + } + else if (template_argument_kind == + clang::TemplateArgument::ArgKind::Type) { + result = find_relationships(template_argument.getAsType(), + relationships, relationship_hint); + } + } + } return result; } @@ -1557,6 +1630,27 @@ void translation_unit_visitor::process_function_parameter( // find relationship for the type found_relationships_t relationships; + LOG_DBG("Looking for relationships in type: {}", + common::to_string(p.getType(), p.getASTContext())); + + if (const auto *templ = + p.getType() + .getNonReferenceType() + .getUnqualifiedType() + ->getAs(); + templ != nullptr) { + auto template_specialization_ptr = tbuilder().build( + templ->getTemplateName().getAsTemplateDecl(), *templ, &c); + + if (diagram().should_include( + template_specialization_ptr->full_name(false))) { + relationships.emplace_back(template_specialization_ptr->id(), + relationship_t::kDependency); + + diagram().add_class(std::move(template_specialization_ptr)); + } + } + find_relationships( p.getType(), relationships, relationship_t::kDependency); @@ -1573,22 +1667,6 @@ void translation_unit_visitor::process_function_parameter( c.add_relationship(std::move(r)); } } - - // Also consider the container itself if it is a template - // instantiation it's arguments could count as reference to relevant - // types - auto underlying_type = p.getType(); - if (underlying_type->isReferenceType()) - underlying_type = underlying_type.getNonReferenceType(); - if (underlying_type->isPointerType()) - underlying_type = underlying_type->getPointeeType(); - - if (const auto *tsp = - underlying_type->getAs(); - tsp != nullptr) { - process_function_parameter_find_relationships_in_template( - c, template_parameter_names, *tsp); - } } method.add_parameter(std::move(parameter)); @@ -1630,40 +1708,6 @@ void translation_unit_visitor::ensure_lambda_type_is_relative( } } -void translation_unit_visitor:: - process_function_parameter_find_relationships_in_template(class_ &c, - const std::set & /*template_parameter_names*/, - const clang::TemplateSpecializationType &template_instantiation_type) -{ - if (!should_include( - template_instantiation_type.getTemplateName().getAsTemplateDecl())) - return; - - auto template_specialization_ptr = tbuilder().build( - template_instantiation_type.getTemplateName().getAsTemplateDecl(), - template_instantiation_type, &c); - - if (template_instantiation_type.isDependentType()) { - if (template_specialization_ptr) { - relationship r{ - relationship_t::kDependency, template_specialization_ptr->id()}; - - c.add_relationship(std::move(r)); - } - } - else { - if (template_specialization_ptr) { - relationship r{ - relationship_t::kDependency, template_specialization_ptr->id()}; - - if (!diagram().has_element(template_specialization_ptr->id())) - diagram().add_class(std::move(template_specialization_ptr)); - - c.add_relationship(std::move(r)); - } - } -} - void translation_unit_visitor::add_relationships(class_ &c, const class_member &field, const found_relationships_t &relationships, bool break_on_first_aggregation) diff --git a/src/class_diagram/visitor/translation_unit_visitor.h b/src/class_diagram/visitor/translation_unit_visitor.h index 01b98387..ae12cf50 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.h +++ b/src/class_diagram/visitor/translation_unit_visitor.h @@ -190,11 +190,6 @@ private: void process_function_parameter_find_relationships_in_autotype( model::class_ &c, const clang::AutoType *atsp); - void process_function_parameter_find_relationships_in_template( - clanguml::class_diagram::model::class_ &c, - const std::set &template_parameter_names, - const clang::TemplateSpecializationType &template_instantiation_type); - void find_relationships_in_constraint_expression( clanguml::common::model::element &c, const clang::Expr *expr); diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 8c6ab799..885fc2fc 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -161,6 +161,27 @@ std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx, clanguml::util::replace_all(result, ", ", ","); clanguml::util::replace_all(result, "> >", ">>"); + // Get rid of 'type-parameter-X-Y' ugliness + if (result.find("type-parameter-") != std::string::npos) { + util::apply_if_not_null( + common::dereference(type)->getAs(), + [&result, &type](auto *p) { + auto [unqualified_type, context] = + common::consume_type_context(type); + result = p->getDecl()->getNameAsString(); + if (!context.empty()) { + std::vector deduced_contexts; + + for (const auto &c : context) { + deduced_contexts.push_back(c.to_string()); + } + + result = fmt::format( + "{} {}", result, fmt::join(deduced_contexts, " ")); + } + }); + } + return result; } @@ -541,6 +562,97 @@ clang::QualType dereference(clang::QualType type) return res; } +std::pair> +consume_type_context(clang::QualType type) +{ + std::deque res; + + while (true) { + bool try_again{false}; + common::model::context ctx; + + if (type.isConstQualified()) { + ctx.is_const = true; + try_again = true; + } + + if (type.isVolatileQualified()) { + ctx.is_volatile = true; + try_again = true; + } + + if (type->isPointerType() || type->isReferenceType()) { + if (type.isConstQualified() || type.isVolatileQualified()) { + ctx.is_ref_const = type.isConstQualified(); + ctx.is_ref_volatile = type.isVolatileQualified(); + + try_again = true; + } + } + + if (type->isLValueReferenceType()) { + ctx.pr = common::model::rpqualifier::kLValueReference; + try_again = true; + } + else if (type->isRValueReferenceType()) { + ctx.pr = common::model::rpqualifier::kRValueReference; + try_again = true; + } + else if (type->isMemberFunctionPointerType() && + type->getPointeeType()->getAs() != + nullptr) { + const auto ref_qualifier = + type->getPointeeType() // NOLINT + ->getAs() // NOLINT + ->getRefQualifier(); + + if (ref_qualifier == clang::RefQualifierKind::RQ_RValue) { + ctx.pr = common::model::rpqualifier::kRValueReference; + try_again = true; + } + else if (ref_qualifier == clang::RefQualifierKind::RQ_LValue) { + ctx.pr = common::model::rpqualifier::kLValueReference; + try_again = true; + } + } + else if (type->isPointerType()) { + ctx.pr = common::model::rpqualifier::kPointer; + try_again = true; + } + + if (try_again) { + if (type->isPointerType()) { + if (type->getPointeeType().isConstQualified()) + ctx.is_const = true; + if (type->getPointeeType().isVolatileQualified()) + ctx.is_volatile = true; + + type = type->getPointeeType().getUnqualifiedType(); + } + else if (type->isReferenceType()) { + if (type.getNonReferenceType().isConstQualified()) + ctx.is_const = true; + if (type.getNonReferenceType().isVolatileQualified()) + ctx.is_volatile = true; + + type = type.getNonReferenceType().getUnqualifiedType(); + } + else if (type.isConstQualified() || type.isVolatileQualified()) { + ctx.is_const = type.isConstQualified(); + ctx.is_volatile = type.isVolatileQualified(); + type = type.getUnqualifiedType(); + } + + res.push_front(ctx); + + if (type->isMemberFunctionPointerType()) + return std::make_pair(type, res); + } + else + return std::make_pair(type, res); + } +} + std::vector tokenize_unexposed_template_parameter( const std::string &t) { diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index e8cede7e..1aa906a4 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -186,4 +186,7 @@ bool is_type_token(const std::string &t); clang::QualType dereference(clang::QualType type); +std::pair> +consume_type_context(clang::QualType type); + } // namespace clanguml::common diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index 3218cf30..9cf86f95 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -634,14 +634,15 @@ void template_parameter::push_context(const context &q) { context_.push_front(q); } + const std::deque &template_parameter::deduced_context() const { return context_; } -void template_parameter::deduced_context(const std::deque &c) +void template_parameter::deduced_context(std::deque c) { - context_ = c; + context_ = std::move(c); } void template_parameter::is_ellipsis(bool e) { is_ellipsis_ = e; } diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index 7c6d0790..15d29d51 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -166,8 +166,9 @@ public: bool is_array() const; void push_context(const context &q); + const std::deque &deduced_context() const; - void deduced_context(const std::deque &c); + void deduced_context(std::deque c); void is_ellipsis(bool e); bool is_ellipsis() const; diff --git a/tests/t00064/t00064.cc b/tests/t00064/t00064.cc index 4c862724..3d454d19 100644 --- a/tests/t00064/t00064.cc +++ b/tests/t00064/t00064.cc @@ -1,28 +1,53 @@ #include +#include namespace clanguml { namespace t00064 { +// Loosely based on template struct type_list { }; template -struct type_list { }; +struct type_list { }; -template struct head; +template struct type_list { }; + +template struct head; template struct head> { using type = Head; }; -template using head_t = typename head::type; +template using first_t = type_list; + +template using second_t = type_list; template class type_group_pair; template -class type_group_pair, type_list> { - template +class type_group_pair, second_t> { static constexpr size_t size = sizeof...(First) + sizeof...(Second); }; +template struct optional_ref { }; + +template class type_group_pair_it; +template +class type_group_pair_it, second_t> { +public: + using value_type = + decltype(std::tuple_cat(std::make_tuple(*std::declval()), + std::declval().get_as_tuple({})..., + std::declval().get_as_tuple({})...)); + + using ref_t = optional_ref; + + ref_t get(unsigned i) { return {}; } + + const value_type *getp(unsigned i) { return nullptr; } + + constexpr unsigned find(value_type const &v) { return 0; } +}; + struct A { }; struct B { }; struct C { }; diff --git a/tests/t00064/test_case.h b/tests/t00064/test_case.h index ae406a93..e7655c98 100644 --- a/tests/t00064/test_case.h +++ b/tests/t00064/test_case.h @@ -37,9 +37,37 @@ TEST_CASE("t00064", "[test-case][class]") REQUIRE_THAT(puml, !Contains("type-parameter-")); - // Check if all classes exist + REQUIRE_THAT(puml, IsClass(_A("A"))); + REQUIRE_THAT(puml, IsClass(_A("B"))); + REQUIRE_THAT(puml, IsClass(_A("C"))); + REQUIRE_THAT(puml, IsClass(_A("R"))); + + REQUIRE_THAT(puml, IsClassTemplate("type_list", "Ts...")); + REQUIRE_THAT(puml, IsClassTemplate("type_list", "Ret(Arg &&),Ts...")); + REQUIRE_THAT(puml, IsClassTemplate("type_list", "T const,Ts...")); + + REQUIRE_THAT(puml, IsClassTemplate("head", "typename")); + REQUIRE_THAT(puml, IsClassTemplate("head", "type_list")); REQUIRE_THAT( puml, IsClassTemplate("type_group_pair", "typename,typename")); + REQUIRE_THAT(puml, + IsClassTemplate( + "type_group_pair", "type_list,type_list")); + REQUIRE_THAT(puml, + IsClassTemplate( + "type_group_pair", "type_list,type_list")); + + REQUIRE_THAT(puml, IsClassTemplate("optional_ref", "T")); + + REQUIRE_THAT(puml, + IsClassTemplate("type_group_pair_it", + "It,type_list,type_list")); + REQUIRE_THAT( + puml, (IsMethod("get", "ref_t", "unsigned int i"))); + REQUIRE_THAT(puml, + (IsMethod("getp", "value_type const*", "unsigned int i"))); + REQUIRE_THAT(puml, + (IsMethod("find", "unsigned int", "value_type const& v"))); save_puml( config.output_directory() + "/" + diagram->name + ".puml", puml);