diff --git a/src/class_diagram/visitor/template_builder.cc b/src/class_diagram/visitor/template_builder.cc index e93f40a2..e66efe0b 100644 --- a/src/class_diagram/visitor/template_builder.cc +++ b/src/class_diagram/visitor/template_builder.cc @@ -436,7 +436,7 @@ void template_builder::argument_process_dispatch( break; case clang::TemplateArgument::Type: argument.push_back(process_type_argument(parent, cls, template_decl, - arg, template_instantiation, argument_index)); + arg.getAsType(), template_instantiation, argument_index)); break; case clang::TemplateArgument::Declaration: break; @@ -450,6 +450,7 @@ void template_builder::argument_process_dispatch( argument.push_back(process_template_argument(arg)); break; case clang::TemplateArgument::TemplateExpansion: + argument.push_back(process_template_expansion(arg)); break; case clang::TemplateArgument::Expression: argument.push_back(process_expression_argument(arg)); @@ -471,284 +472,124 @@ template_parameter template_builder::process_template_argument( auto arg_name = arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); + return template_parameter::make_template_type(arg_name); } +template_parameter template_builder::process_template_expansion( + const clang::TemplateArgument &arg) +{ + LOG_DBG( + "Processing template expansion argument: {}", common::to_string(arg)); + + auto arg_name = + arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); + + auto param = template_parameter::make_template_type(arg_name); + param.is_variadic(true); + + return param; +} + +clang::QualType template_builder::consume_context( + clang::QualType type, template_parameter &tp) const +{ + while (true) { + bool try_again{false}; + common::model::context ctx; + + ctx.is_const = type.isConstQualified(); + ctx.is_volatile = type.isVolatileQualified(); + + 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->isPointerType()) { + ctx.pr = common::model::rpqualifier::kPointer; + try_again = true; + } + + if (type.isConstQualified() || type.isVolatileQualified()) { + ctx.is_const = type.isConstQualified(); + ctx.is_volatile = type.isVolatileQualified(); + + try_again = true; + } + + if (try_again) { + type = type.getNonReferenceType().getUnqualifiedType(); + if (type->isPointerType()) + type = type->getPointeeType(); + + tp.push_context(std::move(ctx)); + } + else + return type; + } +} + template_parameter template_builder::process_type_argument( std::optional &parent, const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, class_ &template_instantiation, - size_t argument_index) + // const clang::TemplateArgument &arg, + clang::QualType type, class_ &template_instantiation, size_t argument_index) { - assert(arg.getKind() == clang::TemplateArgument::Type); + auto type_name = common::to_string(type, &cls->getASTContext()); - auto type_name = common::to_string(arg, &cls->getASTContext()); - - auto argument = template_parameter::make_argument({}); - - auto type = arg.getAsType().getNonReferenceType().getUnqualifiedType(); - if (type->isPointerType()) - type = type->getPointeeType(); -// if (type->isMemberPointerType()) -// type = type->getPointeeType(); + std::optional argument; LOG_DBG("Processing template {} type argument: {}, {}, {}", template_decl->getQualifiedNameAsString(), type_name, - type->getTypeClassName(), common::to_string(type, cls->getASTContext())); + type->getTypeClassName(), + common::to_string(type, cls->getASTContext())); - if (const auto *function_type = type->getAs(); - function_type != nullptr) { + argument = try_as_function_prototype(parent, cls, template_decl, type, + template_instantiation, argument_index); - argument.set_function_template(true); + if (argument) + return *argument; - // Set function template return type - const auto return_type_name = - function_type->getReturnType().getAsString(); + argument = try_as_member_pointer(parent, cls, template_decl, type, + template_instantiation, argument_index); - // Try to match the return type to template parameter in case - // the type name is in the form 'type-parameter-X-Y' - auto maybe_return_arg = - get_template_argument_from_type_parameter_string( - cls, return_type_name); + if (argument) + return *argument; - if (maybe_return_arg) - argument.add_template_param(*maybe_return_arg); - else { - argument.add_template_param( - template_parameter::make_argument(return_type_name)); - } + argument = try_as_template_specialization_type(parent, cls, template_decl, + type, template_instantiation, argument_index); - // Set function template argument types - for (const auto ¶m_type : function_type->param_types()) { - auto maybe_arg = get_template_argument_from_type_parameter_string( - cls, param_type.getAsString()); + if (argument) + return *argument; - if (maybe_arg) { - argument.add_template_param(*maybe_arg); - continue; - } + argument = try_as_template_parm_type(cls, template_decl, type); - if (param_type->isBuiltinType()) { - argument.add_template_param(template_parameter::make_argument( - param_type.getAsString())); - continue; - } + if (argument) + return *argument; - const auto *param_record_type = - param_type->getAs(); - if (param_record_type == nullptr) - continue; + argument = try_as_lamda(cls, template_decl, type); - auto *classTemplateSpecialization = - llvm::dyn_cast( - param_type->getAsRecordDecl()); + if (argument) + return *argument; - if (classTemplateSpecialization != nullptr) { - // Read arg info as needed. - auto nested_template_instantiation = - build_from_class_template_specialization( - *classTemplateSpecialization, // *param_record_type, - diagram().should_include( - template_decl->getQualifiedNameAsString()) - ? std::make_optional(&template_instantiation) - : parent); + argument = try_as_record_type(parent, cls, template_decl, type, + template_instantiation, argument_index); - const auto nested_template_name = - classTemplateSpecialization->getQualifiedNameAsString(); + if (argument) + return *argument; - if (nested_template_instantiation) { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } + argument = try_as_enum_type( + parent, cls, template_decl, type, template_instantiation); - auto nested_template_instantiation_full_name = - nested_template_instantiation->full_name(false); - if (diagram().should_include( - nested_template_instantiation_full_name)) { - diagram().add_class( - std::move(nested_template_instantiation)); - } - } - } - } - else if (const auto *nested_template_type = - type->getAs(); - nested_template_type != nullptr) { + if (argument) + return *argument; - const auto nested_type_name = nested_template_type->getTemplateName() - .getAsTemplateDecl() - ->getQualifiedNameAsString(); - - argument.set_type(nested_type_name); - - auto nested_template_instantiation = build(cls, *nested_template_type, - diagram().should_include(template_decl->getQualifiedNameAsString()) - ? std::make_optional(&template_instantiation) - : parent); - - argument.set_id(nested_template_instantiation->id()); - - for (const auto &t : nested_template_instantiation->template_params()) - argument.add_template_param(t); - - // Check if this template should be simplified (e.g. system - // template aliases such as 'std:basic_string' should - // be simply 'std::string') - simplify_system_template( - argument, argument.to_string(using_namespace(), false)); - - const auto nested_template_instantiation_full_name = - nested_template_instantiation->full_name(false); - - if (nested_template_instantiation && - diagram().should_include(nested_template_instantiation_full_name)) { - if (diagram().should_include( - template_decl->getQualifiedNameAsString())) { - template_instantiation.add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - else { - if (parent.has_value()) - parent.value()->add_relationship( - {relationship_t::kDependency, - nested_template_instantiation->id()}); - } - } - - if (diagram().should_include(nested_template_instantiation_full_name)) { - diagram().add_class(std::move(nested_template_instantiation)); - } - } - else if (type->getAs() != nullptr) { - argument = template_parameter::make_template_type({}); - - auto parameter_name = common::to_string(type, cls->getASTContext()); - - ensure_lambda_type_is_relative(parameter_name); - - // clang does not provide declared template parameter/argument - // names in template specializations - so we have to extract - // them from raw source code... - if (parameter_name.find("type-parameter-") == 0) { - auto maybe_arg = get_template_argument_from_type_parameter_string( - cls, parameter_name); - - if (maybe_arg) { - argument = *maybe_arg; - } - } - else { - argument.set_name(parameter_name); - } - } - /* - // Case for unexposed template - else if ((type_name.find('<') != std::string::npos) && - (type_name.find("type-parameter-") == 0)) { - - ensure_lambda_type_is_relative(type_name); - if (type_name.find('<') != std::string::npos) { - argument = template_parameter::make_argument({}); - - // Sometimes template instantiation is reported as - // RecordType in the AST and getAs to - // TemplateSpecializationType returns null pointer so we - // have to at least make sure it's properly formatted - // (e.g. std:integral_constant, or any template - // specialization which contains it - see t00038) - process_unexposed_template_specialization_parameters( - type_name.substr(type_name.find('<') + 1, - type_name.size() - (type_name.find('<') + 2)), - argument, template_instantiation); - - auto unexposed_type_name = type_name.substr(0, type_name.find('<')); - ensure_lambda_type_is_relative(unexposed_type_name); - - argument.set_type(unexposed_type_name); - } - else if (type_name.find("type-parameter-") == 0) { - auto maybe_arg = get_template_argument_from_type_parameter_string( - cls, type_name); - - if (maybe_arg) { - return *maybe_arg; - } - - // Otherwise just set the name for the template argument to - // whatever clang says - return template_parameter::make_template_type(type_name); - } - } - */ - else if (type_name.find("type-parameter-") != std::string::npos) { - // This is some sort of template parameter with unexposed type - // parameters in the form 'type-parameter-X-Y' - if (const auto maybe_arg = - get_template_argument_from_type_parameter_string( - cls, arg.getAsType().getAsString()); - maybe_arg) { - // The type is only in the form 'type-parameter-X-Y' so we have - // to match it to a template parameter name in the 'cls' template - argument = *maybe_arg; - } - else { - // fallback - just put whatever clang returns - argument.is_template_parameter(false); - argument.set_type( - common::to_string(type, template_decl->getASTContext())); - } - } - else if (type_name.find("(lambda at ") == 0) { - // This is just a lambda reference - ensure_lambda_type_is_relative(type_name); - argument.set_type(type_name); - } - else { - // This is just a regular record type - process_tag_argument( - template_instantiation, template_decl, type, argument); - } - - if (arg.getAsType()->isLValueReferenceType()) { - argument.is_lvalue_reference(true); - } - if (arg.getAsType()->isRValueReferenceType()) { - argument.is_rvalue_reference(true); - } - if (arg.getAsType()->isPointerType()) { - argument.is_pointer(true); - } - if (arg.getAsType()->isMemberPointerType()) { - argument.set_method_template(true); - } - if (arg.getAsType().isConstQualified()) { - argument.set_qualifier(template_parameter::cvqualifier::kConst); - } - if (arg.getAsType().isVolatileQualified()) { - argument.set_qualifier(template_parameter::cvqualifier::kVolatile); - } - - return argument; -} - -void template_builder::process_unexposed_template_specialization_parameters( - const std::string &type_name, template_parameter &tp, class_ &c) -{ - auto template_params = common::parse_unexposed_template_params( - type_name, [](const std::string &t) { return t; }); - - found_relationships_t relationships; - for (auto ¶m : template_params) { - find_relationships_in_unexposed_template_params(param, relationships); - tp.add_template_param(param); - } - - for (auto &r : relationships) { - c.add_relationship({std::get<1>(r), std::get<0>(r)}); - } + // fallback + return template_parameter::make_argument(type_name); } bool template_builder::find_relationships_in_unexposed_template_params( @@ -787,7 +628,7 @@ bool template_builder::find_relationships_in_unexposed_template_params( return found; } -using token_it = std::vector::const_iterator; +// using token_it = std::vector::const_iterator; namespace detail { @@ -827,171 +668,28 @@ std::string map_type_parameter_to_template_parameter( } -template_parameter map_type_parameter_to_template_parameter( - const clang::Decl *decl, const std::string &tp) +std::string map_type_parameter_to_template_parameter_name( + const clang::Decl *decl, const std::string &type_parameter) { + if (type_parameter.find("type-parameter-") != 0) + return type_parameter; + if (const auto *template_decl = llvm::dyn_cast(decl); - template_decl != nullptr && tp.find("type-parameter-") == 0) { - return template_parameter::make_template_type( - detail::map_type_parameter_to_template_parameter( - template_decl, tp)); + template_decl != nullptr) { + return detail::map_type_parameter_to_template_parameter( + template_decl, type_parameter); } if (const auto *alias_decl = llvm::dyn_cast(decl); - alias_decl != nullptr && - (tp.find("type-parameter-") != std::string::npos)) { - return template_parameter::make_template_type( - detail::map_type_parameter_to_template_parameter(alias_decl, tp)); + alias_decl != nullptr) { + return detail::map_type_parameter_to_template_parameter( + alias_decl, type_parameter); } - std::string arg = tp; - if (arg == "_Bool") - arg = "bool"; - - return template_parameter::make_argument(arg); -} - -std::optional build_template_parameter( - const clang::Decl *decl, token_it begin, token_it end) -{ - if (decl == nullptr) - return {}; - - auto res = template_parameter::make_template_type({}); - - auto it = begin; - auto it_next = it; - it_next++; - - if (*it == "const") { - res.set_qualifier(template_parameter::cvqualifier::kConst); - it++; - it_next++; - } - - if (it == end) - return {}; - - // simple template param without qualifiers - if (common::is_type_token(*it) && (it_next == end || *it_next == ")")) { - res = map_type_parameter_to_template_parameter(decl, *it); - return res; - } - // template parameter with qualifier at the end - else if (common::is_type_token(*it) && common::is_qualifier(*it_next)) { - res = map_type_parameter_to_template_parameter(decl, *it); - // param_qualifier += *it_next; - // res.set_qualifier(param_qualifier); - return res; - } - // method template parameter - else if (common::is_type_token(*it) && common::is_type_token(*it_next)) { - res.add_template_param( - map_type_parameter_to_template_parameter(decl, *it)); - res.add_template_param( - map_type_parameter_to_template_parameter(decl, *it_next)); - it = it_next; - it++; - if (it != end && *it == "::") { - res.set_method_template(true); - it++; - it++; - } - - if (it != end && common::is_qualifier(*it)) { - // param_qualifier += *it; - // res.set_qualifier(param_qualifier); - } - - return res; - } - else if (common::is_type_token(*it) && *it_next == "...") { - // Variadic template parameter - auto parm = map_type_parameter_to_template_parameter(decl, *it); - parm.is_variadic(true); - return parm; - } - else if (common::is_type_token(*it) && *it_next == "(") { - res.add_template_param( - map_type_parameter_to_template_parameter(decl, *it)); - it_next++; // skip '(' - res.add_template_param( - map_type_parameter_to_template_parameter(decl, *it_next)); - - it = it_next; - it++; - it_next = it; - it_next++; - - if (*it == "::" && *it_next == "*") { - res.set_method_template(true); - std::advance(it, 3); - } - - if (it != end) { - // handle args - if (*it == "(") { - it++; - - if(it != end && *it == "...") { - // handle elipssis arg - res.is_elipssis(true); - return res; - } - - while (true) { - // This will break on more complex args - auto arg_separator = std::find(it, end, ","); - if (arg_separator == end) { - // just one arg - auto args_end = std::find(it, end, ")"); - auto arg = build_template_parameter(decl, it, args_end); - if (arg) - res.add_template_param(*arg); - it++; - break; - } - else { - auto arg = - build_template_parameter(decl, it, arg_separator); - if (arg) - res.add_template_param(*arg); - it = arg_separator; - it++; - } - } - - //if(it!=end) - //assert(*it == ")"); - - it++; - - //if (it != end && common::is_qualifier(*it)) { - // param_qualifier += *it; - // res.set_qualifier(param_qualifier); - //} - } - - return res; - } - else - return res; - } - - return {}; -} - -std::optional -template_builder::get_template_argument_from_type_parameter_string( - const clang::Decl *decl, std::string type_name) const -{ - type_name = util::trim(type_name); - - auto toks = common::tokenize_unexposed_template_parameter(type_name); - - return build_template_parameter(decl, toks.begin(), toks.end()); + // Fallback + return type_parameter; } template_parameter template_builder::process_integral_argument( @@ -1035,7 +733,7 @@ template_parameter template_builder::process_expression_argument( std::vector template_builder::process_pack_argument( std::optional &parent, const clang::NamedDecl *cls, class_ &template_instantiation, - const clang::TemplateDecl *template_decl, + const clang::TemplateDecl *base_template_decl, const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument) { @@ -1047,79 +745,360 @@ std::vector template_builder::process_pack_argument( for (const auto &a : arg.getPackAsArray()) { argument_process_dispatch(parent, cls, template_instantiation, - template_decl, a, pack_argument_index++, res); + base_template_decl, a, pack_argument_index++, res); } return res; } -void template_builder::process_tag_argument(class_ &template_instantiation, - const clang::TemplateDecl *template_decl, const clang::QualType type, - template_parameter &argument) +std::optional template_builder::try_as_member_pointer( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) { - [[maybe_unused]] auto current_instantiation_name = - template_instantiation.full_name(false); + const auto *mp_type = + common::dereference(type)->getAs(); + if (mp_type == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + type = consume_context(type, argument); + + // Handle a pointer to a data member of a class + if (mp_type->isMemberDataPointer()) { + argument.is_member_pointer(false); + argument.is_data_pointer(true); + + auto pointee_arg = process_type_argument(parent, cls, template_decl, + mp_type->getPointeeType(), template_instantiation, argument_index); + + argument.add_template_param(std::move(pointee_arg)); + + const auto *member_class_type = mp_type->getClass(); + + if (member_class_type == nullptr) + return {}; + + auto class_type_arg = process_type_argument(parent, cls, template_decl, + mp_type->getClass()->getCanonicalTypeUnqualified(), + template_instantiation, argument_index); + + argument.add_template_param(std::move(class_type_arg)); + } + // Handle pointer to class method member + else { + argument.is_member_pointer(true); + argument.is_data_pointer(false); + + const auto *function_type = + mp_type->getPointeeType()->getAs(); + + assert(function_type != nullptr); + + auto return_type_arg = process_type_argument(parent, cls, template_decl, + function_type->getReturnType(), template_instantiation, + argument_index); + + // Add return type argument + argument.add_template_param(std::move(return_type_arg)); + + const auto *member_class_type = mp_type->getClass(); + + if (member_class_type == nullptr) + return {}; + + auto class_type_arg = process_type_argument(parent, cls, template_decl, + mp_type->getClass()->getCanonicalTypeUnqualified(), + template_instantiation, argument_index); + + // Add class type argument + argument.add_template_param(std::move(class_type_arg)); + + // Add 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)); + } + } + + return argument; +} + +std::optional template_builder::try_as_array( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *array_type = common::dereference(type)->getAsArrayTypeUnsafe(); + if (array_type == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + + type = consume_context(type, argument); + + argument.is_array(true); + + // Set function template return type + auto element_type = process_type_argument(parent, cls, template_decl, + array_type->getElementType(), template_instantiation, argument_index); + + argument.add_template_param(element_type); + + if (array_type->isDependentSizedArrayType() && + array_type->getDependence() == + clang::TypeDependence::DependentInstantiation) { + argument.add_template_param(template_parameter::make_template_type( + common::to_string(((clang::DependentSizedArrayType *)array_type) + ->getSizeExpr()))); + } + else if (array_type->isConstantArrayType()) { + argument.add_template_param(template_parameter::make_argument( + std::to_string(((clang::ConstantArrayType *)array_type) + ->getSize() + .getLimitedValue()))); + } + // TODO: Handle variable sized arrays + + return argument; +} + +std::optional template_builder::try_as_function_prototype( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation, + size_t argument_index) +{ + const auto *function_type = type->getAs(); + if (function_type == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + + type = consume_context(type, argument); + + argument.is_function_template(true); + + // Set function template return type + auto return_arg = process_type_argument(parent, cls, template_decl, + function_type->getReturnType(), template_instantiation, argument_index); + + 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)); + } + + return argument; +} + +std::optional +template_builder::try_as_template_specialization_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 *nested_template_type = + common::dereference(type)->getAs(); + if (nested_template_type == nullptr) + return {}; + + auto argument = template_parameter::make_argument(""); + type = consume_context(type, argument); + + const auto nested_type_name = nested_template_type->getTemplateName() + .getAsTemplateDecl() + ->getQualifiedNameAsString(); + + argument.set_type(nested_type_name); + + auto nested_template_instantiation = build(cls, *nested_template_type, + diagram().should_include(template_decl->getQualifiedNameAsString()) + ? std::make_optional(&template_instantiation) + : parent); + + argument.set_id(nested_template_instantiation->id()); + + for (const auto &t : nested_template_instantiation->template_params()) + argument.add_template_param(t); + + // Check if this template should be simplified (e.g. system + // template aliases such as 'std:basic_string' should + // be simply 'std::string') + simplify_system_template( + argument, argument.to_string(using_namespace(), false)); + + const auto nested_template_instantiation_full_name = + nested_template_instantiation->full_name(false); + + if (nested_template_instantiation && + diagram().should_include(nested_template_instantiation_full_name)) { + if (diagram().should_include( + template_decl->getQualifiedNameAsString())) { + template_instantiation.add_relationship( + {relationship_t::kDependency, + nested_template_instantiation->id()}); + } + else { + if (parent.has_value()) + parent.value()->add_relationship({relationship_t::kDependency, + nested_template_instantiation->id()}); + } + } + + if (diagram().should_include(nested_template_instantiation_full_name)) { + diagram().add_class(std::move(nested_template_instantiation)); + } + + return argument; +} + +std::optional template_builder::try_as_template_parm_type( + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type) +{ + auto is_variadic{false}; + + auto type_parameter = + common::dereference(type)->getAs(); + + if (type_parameter == nullptr) { + if (common::dereference(type)->getAs()) { + is_variadic = true; + type_parameter = common::dereference(type) + ->getAs() + ->getPattern() + ->getAs(); + } + } + + if (type_parameter == nullptr) + return {}; + + auto argument = template_parameter::make_template_type(""); + type = consume_context(type, argument); + + argument.is_variadic(is_variadic); + + auto type_name = common::to_string(type, &cls->getASTContext()); + + auto type_parameter_name = common::to_string(type, cls->getASTContext()); + + ensure_lambda_type_is_relative(type_parameter_name); + + argument.set_name(map_type_parameter_to_template_parameter_name( + cls, type_parameter_name)); + + return argument; +} + +std::optional template_builder::try_as_lamda( + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type) +{ + auto type_name = common::to_string(type, &cls->getASTContext()); + + if (type_name.find("(lambda at ") != 0) + return {}; + + auto argument = template_parameter::make_argument(""); + type = consume_context(type, argument); + + ensure_lambda_type_is_relative(type_name); + argument.set_type(type_name); + + return argument; +} + +std::optional template_builder::try_as_record_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 *record_type = + common::dereference(type)->getAs(); + if (record_type == nullptr) + return {}; + + auto argument = template_parameter::make_argument({}); + type = consume_context(type, argument); - argument.is_template_parameter(false); auto type_name = common::to_string(type, template_decl->getASTContext()); argument.set_type(type_name); const auto type_id = common::to_id(type_name); argument.set_id(type_id); - if (const auto *tsp = type->getAs(); - tsp != nullptr) { - if (const auto *record_type_decl = tsp->getAsRecordDecl(); - record_type_decl != nullptr) { + const auto *class_template_specialization = + clang::dyn_cast( + record_type->getAsRecordDecl()); - if (diagram().should_include( - template_decl->getQualifiedNameAsString())) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, type_id}); + if (class_template_specialization != nullptr) { + auto tag_argument = build_from_class_template_specialization( + *class_template_specialization); + + if (tag_argument) { + argument.set_type(tag_argument->name_and_ns()); + for (const auto &p : tag_argument->template_params()) + argument.add_template_param(p); + for (auto &r : tag_argument->relationships()) { + template_instantiation.add_relationship(std::move(r)); + } + + if (diagram().should_include(tag_argument->full_name(false))) { + if (parent.has_value()) + parent.value()->add_relationship( + {relationship_t::kDependency, tag_argument->id()}); + + diagram().add_class(std::move(tag_argument)); } } } - else if (const auto *record_type = type->getAs(); - record_type != nullptr) { - - const auto *class_template_specialization = - clang::dyn_cast( - record_type->getAsRecordDecl()); - - if (class_template_specialization != nullptr) { - auto tag_argument = build_from_class_template_specialization( - *class_template_specialization); - - if (tag_argument) { - argument.set_type(tag_argument->name_and_ns()); - for (const auto &p : tag_argument->template_params()) - argument.add_template_param(p); - for (auto &r : tag_argument->relationships()) { - template_instantiation.add_relationship(std::move(r)); - } - } - } - else if (const auto *record_type_decl = record_type->getAsRecordDecl(); - record_type_decl != nullptr) { + else if (const auto *record_type_decl = record_type->getAsRecordDecl(); + record_type_decl != nullptr) { #if LLVM_VERSION_MAJOR >= 16 - argument.set_type(record_type_decl->getQualifiedNameAsString()); + argument.set_type(record_type_decl->getQualifiedNameAsString()); #endif - if (diagram().should_include(type_name)) { - // Add dependency relationship to the parent - // template - template_instantiation.add_relationship( - {relationship_t::kDependency, type_id}); - } - } - } - else if (const auto *enum_type = type->getAs(); - enum_type != nullptr) { - if (enum_type->getAsTagDecl() != nullptr) { + if (diagram().should_include(type_name)) { + // Add dependency relationship to the parent + // template template_instantiation.add_relationship( {relationship_t::kDependency, type_id}); } } + + return argument; +} + +std::optional template_builder::try_as_enum_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation) +{ + const auto *enum_type = type->getAs(); + if (enum_type == nullptr) + return {}; + + auto argument = template_parameter::make_argument({}); + type = consume_context(type, argument); + + auto type_name = common::to_string(type, template_decl->getASTContext()); + argument.set_type(type_name); + const auto type_id = common::to_id(type_name); + argument.set_id(type_id); + + if (enum_type->getAsTagDecl() != nullptr) { + template_instantiation.add_relationship( + {relationship_t::kDependency, type_id}); + } + + return argument; } bool template_builder::add_base_classes(class_ &tinst, diff --git a/src/class_diagram/visitor/template_builder.h b/src/class_diagram/visitor/template_builder.h index 0c8b14ca..999a024a 100644 --- a/src/class_diagram/visitor/template_builder.h +++ b/src/class_diagram/visitor/template_builder.h @@ -80,11 +80,6 @@ public: const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument); - void process_tag_argument(model::class_ &template_instantiation, - const clang::TemplateDecl *template_decl, - const clang::QualType type, - common::model::template_parameter &argument); - template_parameter process_expression_argument( const clang::TemplateArgument &arg); @@ -100,30 +95,73 @@ public: std::vector process_pack_argument( std::optional &parent, const clang::NamedDecl *cls, class_ &template_instantiation, - const clang::TemplateDecl *template_decl, + const clang::TemplateDecl *base_template_decl, const clang::TemplateArgument &arg, size_t argument_index, std::vector &argument); template_parameter process_type_argument( std::optional &parent, - const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, - const clang::TemplateArgument &arg, - model::class_ &template_instantiation, size_t argument_index); + const clang::NamedDecl *cls, + const clang::TemplateDecl *base_template_decl, + // const clang::TemplateArgument &arg, + clang::QualType type, model::class_ &template_instantiation, + size_t argument_index); common::model::template_parameter process_template_argument( const clang::TemplateArgument &arg); - void process_unexposed_template_specialization_parameters( - const std::string &type_name, template_parameter &tp, class_ &c); + common::model::template_parameter process_template_expansion( + const clang::TemplateArgument &arg); + + std::optional try_as_function_prototype( + 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_array( + 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_template_specialization_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_template_parm_type( + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type); + + std::optional try_as_lamda(const clang::NamedDecl *cls, + const clang::TemplateDecl *template_decl, clang::QualType &type); + + std::optional try_as_record_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_enum_type( + std::optional &parent, + const clang::NamedDecl *cls, const clang::TemplateDecl *template_decl, + clang::QualType &type, class_ &template_instantiation); + + std::optional try_as_member_pointer( + 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; bool find_relationships_in_unexposed_template_params( const template_parameter &ct, class_diagram::visitor::found_relationships_t &relationships); - std::optional - get_template_argument_from_type_parameter_string( - const clang::Decl *decl, std::string type_name) const; - common::visitor::ast_id_mapper &id_mapper(); clang::SourceManager &source_manager() const; diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index bb03f840..89b5869e 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -306,7 +306,8 @@ template <> id_t to_id(const std::string &full_name) return static_cast(std::hash{}(full_name) >> 3U); } -id_t to_id(const clang::QualType &type, const clang::ASTContext &ctx) { +id_t to_id(const clang::QualType &type, const clang::ASTContext &ctx) +{ return to_id(common::to_string(type, ctx)); } @@ -520,6 +521,22 @@ bool is_type_token(const std::string &t) (is_identifier(t) && !is_qualifier(t) && !is_bracket(t)); } +clang::QualType dereference(clang::QualType type) +{ + auto res = type; + + while (true) { + if (res->isReferenceType()) + res = res.getNonReferenceType(); + else if (res->isPointerType()) + res = res->getPointeeType(); + else + break; + } + + return res; +} + std::vector tokenize_unexposed_template_parameter( const std::string &t) { @@ -537,7 +554,8 @@ std::vector tokenize_unexposed_template_parameter( std::string tok; for (const char c : word) { - if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' || c == '>') { + if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' || + c == '>') { if (!tok.empty()) result.push_back(tok); result.push_back(std::string{c}); diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index 32218304..e8cede7e 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -184,4 +184,6 @@ bool is_qualified_identifier(const std::string &t); bool is_type_token(const std::string &t); +clang::QualType dereference(clang::QualType type); + } // namespace clanguml::common diff --git a/src/common/model/template_parameter.cc b/src/common/model/template_parameter.cc index 52457da0..386858f3 100644 --- a/src/common/model/template_parameter.cc +++ b/src/common/model/template_parameter.cc @@ -69,8 +69,9 @@ void template_parameter::set_name(const std::string &name) { assert(kind_ != template_parameter_kind_t::argument); - if (name.empty()) + if (name.empty()) { return; + } if (util::ends_with(name, std::string{"..."})) { name_ = name.substr(0, name.size() - 3); @@ -113,10 +114,11 @@ int template_parameter::calculate_specialization_match( { int res{0}; - // If the potential base template has a qualifier, the current template - // must match it - if (!base_template_parameter.qualifiers().empty() && - (qualifiers() != base_template_parameter.qualifiers())) + // 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 (is_template_parameter() && @@ -126,7 +128,7 @@ int template_parameter::calculate_specialization_match( is_variadic() == is_variadic() && is_function_template() == base_template_parameter.is_function_template() && - is_method_template() == base_template_parameter.is_method_template()) { + is_member_pointer() == base_template_parameter.is_member_pointer()) { return 1; } @@ -137,7 +139,6 @@ int template_parameter::calculate_specialization_match( maybe_template_parameter_type.has_value() && !base_template_parameter.is_template_parameter() && !is_template_parameter()) { - if (maybe_base_template_parameter_type.value() != maybe_template_parameter_type.value()) return 0; @@ -145,11 +146,20 @@ int template_parameter::calculate_specialization_match( res++; } + 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; - if (base_template_parameter.is_method_template() && !is_method_template()) + if (base_template_parameter.is_member_pointer() && !is_member_pointer()) + return 0; + + if (base_template_parameter.is_data_pointer() && !is_data_pointer()) return 0; if (!base_template_parameter.template_params().empty() && @@ -214,31 +224,22 @@ bool operator!=(const template_parameter &l, const template_parameter &r) return !(l == r); } -std::string template_parameter::qualifiers_str() const +std::string template_parameter::deduced_context_str() const { - std::string res; - if (qualifiers_.count(cvqualifier::kConst) == 1) - res += "const"; + std::vector deduced_contexts; - if (is_rvalue_reference()) - res += "&&"; - else if (is_lvalue_reference()) - res += "&"; + for (const auto &c : deduced_context()) { + deduced_contexts.push_back(c.to_string()); + } - if (is_pointer()) - res += "*"; - - if(res.empty()) - return res; - - return " " + res; + return fmt::format("{}", fmt::join(deduced_contexts, " ")); } std::string template_parameter::to_string( const clanguml::common::model::namespace_ &using_namespace, bool relative, bool skip_qualifiers) const { - if(is_elipssis()) + if (is_elipssis()) return "..."; using clanguml::common::model::namespace_; @@ -258,35 +259,41 @@ std::string template_parameter::to_string( "{}({})", return_type, fmt::join(function_args, ",")); } - if (is_method_template()) { - assert(template_params().size() > 1); + if (is_data_pointer()) { + assert(template_params().size() == 2); - std::string unqualified; - if (template_params().size() == 2) { - unqualified = fmt::format("{} {}::*", - template_params().at(0).to_string(using_namespace, relative), - template_params().at(1).to_string(using_namespace, relative)); - } - else { - auto it = template_params().begin(); - auto return_type = it->to_string(using_namespace, relative); - it++; - auto class_type = it->to_string(using_namespace, relative); - it++; - std::vector args; - - for (; it != template_params().end(); it++) { - args.push_back(it->to_string(using_namespace, relative)); - } - - unqualified = fmt::format("{} ({}::*)({})", return_type, class_type, - fmt::join(args, ",")); - } + std::string unqualified = fmt::format("{} {}::*", + template_params().at(0).to_string(using_namespace, relative), + template_params().at(1).to_string(using_namespace, relative)); if (skip_qualifiers) return unqualified; - else - return unqualified + qualifiers_str(); + else { + return util::join(" ", unqualified, deduced_context_str()); + } + } + + if (is_member_pointer()) { + assert(template_params().size() > 1); + + auto it = template_params().begin(); + auto return_type = it->to_string(using_namespace, relative); + it++; + auto class_type = it->to_string(using_namespace, relative); + it++; + std::vector args; + + for (; it != template_params().end(); it++) { + args.push_back(it->to_string(using_namespace, relative)); + } + + std::string unqualified = fmt::format( + "{} ({}::*)({})", return_type, class_type, fmt::join(args, ",")); + if (skip_qualifiers) + return unqualified; + else { + return util::join(" ", unqualified, deduced_context_str()); + } } std::string res; @@ -339,7 +346,7 @@ std::string template_parameter::to_string( } if (!skip_qualifiers) - res += qualifiers_str(); + res = util::join(" ", res, deduced_context_str()); const auto &maybe_default_value = default_value(); if (maybe_default_value) { @@ -362,8 +369,9 @@ bool template_parameter::find_nested_relationships( // If this type argument should be included in the relationship // just add it and skip recursion (e.g. this is a user defined type) const auto maybe_type = type(); + if (maybe_type && should_include(maybe_type.value())) { - if (is_pointer() || is_lvalue_reference() || is_rvalue_reference()) + if (is_association()) hint = common::model::relationship_t::kAssociation; const auto maybe_id = id(); @@ -383,9 +391,7 @@ bool template_parameter::find_nested_relationships( if (maybe_id && maybe_arg_type && should_include(*maybe_arg_type)) { - if (template_argument.is_pointer() || - template_argument.is_lvalue_reference() || - template_argument.is_rvalue_reference()) + if (template_argument.is_association()) hint = common::model::relationship_t::kAssociation; nested_relationships.emplace_back(maybe_id.value(), hint); diff --git a/src/common/model/template_parameter.h b/src/common/model/template_parameter.h index cf17a2e1..5729f9ff 100644 --- a/src/common/model/template_parameter.h +++ b/src/common/model/template_parameter.h @@ -20,6 +20,7 @@ #include "common/model/enums.h" #include "common/model/namespace.h" +#include #include #include #include @@ -31,10 +32,49 @@ enum class template_parameter_kind_t { template_type, template_template_type, non_type_template, - argument, // a.k.a. type parameter specialization + argument, concept_constraint }; +// TODO: rename to include the pointer and reference +enum class cvqualifier { kConst, kVolatile }; + +enum class rpqualifier { kLValueReference, kRValueReference, kPointer, kNone }; + +struct context { + bool is_const; + bool is_volatile; + rpqualifier pr{rpqualifier::kNone}; + + std::string to_string() const + { + std::vector cv_qualifiers; + if (is_const) + cv_qualifiers.push_back("const"); + if (is_volatile) + cv_qualifiers.push_back("volatile"); + + auto res = fmt::format("{}", fmt::join(cv_qualifiers, " ")); + + if (pr == rpqualifier::kPointer) + res += "*"; + else if (pr == rpqualifier::kLValueReference) + res += "&"; + else if (pr == rpqualifier::kRValueReference) + res += "&&"; + + return res; + } + + bool operator==(const context &rhs) const + { + return is_const == rhs.is_const && is_volatile == rhs.is_volatile && + pr == rhs.pr; + } + + bool operator!=(const context &rhs) const { return !(rhs == *this); } +}; + std::string to_string(template_parameter_kind_t k); /// @brief Represents template parameter, template arguments or concept @@ -45,13 +85,6 @@ std::string to_string(template_parameter_kind_t k); /// nested templates class template_parameter { public: - enum class cvqualifier { - kConst, - kVolatile, - kLValueReference, - kRValueReference - }; - static template_parameter make_template_type(const std::string &name, const std::optional &default_value = {}, bool is_variadic = false) @@ -167,6 +200,15 @@ public: void clear_params() { template_params_.clear(); } + bool is_association() const + { + return std::any_of(deduced_context().begin(), deduced_context().end(), + [](const auto &c) { + return c.pr == rpqualifier::kPointer || + c.pr == rpqualifier::kLValueReference; + }); + } + bool find_nested_relationships( std::vector> &nested_relationships, @@ -186,33 +228,29 @@ public: void set_unexposed(bool unexposed) { is_unexposed_ = unexposed; } - void set_function_template(bool ft) { is_function_template_ = ft; } - + void is_function_template(bool ft) { is_function_template_ = ft; } bool is_function_template() const { return is_function_template_; } - void set_method_template(bool mt) { is_method_template_ = mt; } + void is_member_pointer(bool m) { is_member_pointer_ = m; } + bool is_member_pointer() const { return is_member_pointer_; } - bool is_method_template() const { return is_method_template_; } + void is_data_pointer(bool m) { is_data_pointer_ = m; } + bool is_data_pointer() const { return is_data_pointer_; } - void set_qualifier(const cvqualifier q) { qualifiers_.emplace(q); } + void is_array(bool a) { is_array_ = a; } + bool is_array() const { return is_array_; } - const std::set &qualifiers() const { return qualifiers_; } - - void is_pointer(bool p) { is_pointer_ = p; } - bool is_pointer() const { return is_pointer_; } - - void is_lvalue_reference(bool p) { is_lvalue_reference_ = p; } - bool is_lvalue_reference() const { return is_lvalue_reference_; } - - void is_rvalue_reference(bool p) { is_rvalue_reference_ = p; } - bool is_rvalue_reference() const { return is_rvalue_reference_; } + void push_context(const context q) { context_.push_front(q); } + const std::deque &deduced_context() const { return context_; } + void deduced_context(const std::deque &c) { context_ = c; } void is_elipssis(bool e) { is_elipssis_ = e; } bool is_elipssis() const { return is_elipssis_; } + private: template_parameter() = default; - std::string qualifiers_str() const; + std::string deduced_context_str() const; template_parameter_kind_t kind_{template_parameter_kind_t::template_type}; @@ -239,15 +277,16 @@ private: /// Whether the template parameter is variadic bool is_variadic_{false}; - bool is_pointer_{false}; - bool is_lvalue_reference_{false}; - bool is_rvalue_reference_{false}; - bool is_function_template_{false}; - bool is_method_template_{false}; + bool is_data_pointer_{false}; - std::set qualifiers_; + bool is_member_pointer_{false}; + + bool is_array_{false}; + + /// Stores the template parameter/argument deduction context e.g. const& + std::deque context_; /// Stores optional fully qualified name of constraint for this template /// parameter diff --git a/src/util/util.h b/src/util/util.h index e992ef95..19892657 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -92,9 +92,26 @@ std::string get_git_toplevel_dir(); std::vector split( std::string str, std::string_view delimiter, bool skip_empty = true); +template void erase_if(std::vector &v, F &&f) +{ + v.erase(std::remove_if(v.begin(), v.end(), std::forward(f)), v.end()); +} + std::string join( const std::vector &toks, std::string_view delimiter); +template +std::string join(std::string_view delimiter, Args... args) +{ + std::vector coll{args...}; + + erase_if(coll, [](const auto &s) { + return s.find_first_not_of(" \t") == std::string::npos; + }); + + return fmt::format("{}", fmt::join(coll, delimiter)); +} + /** * @brief Abbreviate string to max_length, and replace last 3 characters * with ellipsis. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9491e375..4c17b96e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,7 +40,6 @@ set(TEST_CASES test_config test_cli_handler test_filters - test_template_parser test_thread_pool_executor) foreach(TEST_NAME ${TEST_CASES}) diff --git a/tests/t00051/test_case.h b/tests/t00051/test_case.h index a19d6067..14269b36 100644 --- a/tests/t00051/test_case.h +++ b/tests/t00051/test_case.h @@ -59,9 +59,9 @@ TEST_CASE("t00051", "[test-case][class]") REQUIRE_THAT(puml, (IsMethod("ff", "void"))); REQUIRE_THAT(puml, - IsClassTemplate("B", - "(lambda at ../../tests/t00051/t00051.cc:43:18)")); - //,(lambda at ../../tests/t00051/t00051.cc:43:27)")); + IsClassTemplate( + "B", "(lambda at ../../tests/t00051/t00051.cc:43:18)")); + //,(lambda at ../../tests/t00051/t00051.cc:43:27)")); REQUIRE_THAT(puml, IsInstantiation(_A("B"), diff --git a/tests/t00062/t00062.cc b/tests/t00062/t00062.cc index 7858d7f2..d1f81e85 100644 --- a/tests/t00062/t00062.cc +++ b/tests/t00062/t00062.cc @@ -17,7 +17,7 @@ template struct A &> { template <> struct A> &> { }; -template struct A { +template struct A { U ***u; }; @@ -71,6 +71,14 @@ template struct A { template <> struct A { std::vector n; }; - +// +// template struct eval; +// +// template