diff --git a/docs/mrdocs.schema.json b/docs/mrdocs.schema.json index dde6f3af09..281d139942 100644 --- a/docs/mrdocs.schema.json +++ b/docs/mrdocs.schema.json @@ -219,7 +219,7 @@ }, "sfinae": { "default": true, - "description": "When set to true, MrDocs detects SFINAE expressions in the source code and extracts them as part of the documentation. Expressions such as `std::enable_if<...>` are detected, removed, and documented as a requirement.", + "description": "When set to true, MrDocs detects SFINAE expressions in the source code and extracts them as part of the documentation. Expressions such as `std::enable_if<...>` are detected, removed, and documented as a requirement. MrDocs uses an algorithm that extracts SFINAE infomation from types by identifying inspecting the primary template and specializations to detect the result type and the controlling expressions in a specialization.", "title": "Detect and reduce SFINAE expressions", "type": "boolean" }, diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/field.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/field.hbs index cd4c317b88..adb83e6528 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/signature/field.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/field.hbs @@ -1,4 +1,4 @@ -{{#if attributes}}[{{join ", " attributes}}] +{{#if attributes}}[[{{join ", " attributes}}]] {{/if}} {{#if isMutable}}mutable {{/if~}} diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs index ceb4a159f3..412346c13e 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs @@ -2,7 +2,7 @@ {{/if~}} {{#if isFriend}}friend {{/if~}} -{{#if attributes}}{{#unless isFriend}}[{{join ", " attributes}}] +{{#if attributes}}{{#unless isFriend}}[[{{join ", " attributes}}]] {{/unless}}{{/if~}} {{#if constexprKind}}{{constexprKind}} {{/if~}} diff --git a/src/lib/AST/ASTVisitor.cpp b/src/lib/AST/ASTVisitor.cpp index 54e5b6fbef..8aecf5e4ac 100644 --- a/src/lib/AST/ASTVisitor.cpp +++ b/src/lib/AST/ASTVisitor.cpp @@ -675,7 +675,8 @@ populateInfoBases(InfoTy& I, bool const isNew, DeclTy* D) { // Try an exact match here auto qualifiedName = this->qualifiedName(D); - if (checkSymbolFiltersImpl(std::string_view(qualifiedName.str()))) + if (checkSymbolFiltersImpl(std::string_view(qualifiedName.str())) && + checkFileFilters(D)) { I.Extraction = ExtractionMode::Regular; // default mode also becomes regular for its @@ -1066,7 +1067,7 @@ populate( populate(I.Explicit, D->getExplicitSpecifier()); } - ArrayRef params = D->parameters(); + ArrayRef const params = D->parameters(); I.Params.resize(params.size()); for (std::size_t i = 0; i < params.size(); ++i) { @@ -1100,11 +1101,11 @@ populate( I.Class = toFunctionClass(D->getDeclKind()); - QualType const RT = D->getReturnType(); - // extract the return type in direct dependency mode // if it contains a placeholder type which is // deduceded as a local class type + QualType const RT = D->getReturnType(); + MRDOCS_SYMBOL_TRACE(RT, context_); I.ReturnType = toTypeInfo(RT); if (auto* TRC = D->getTrailingRequiresClause()) @@ -2083,138 +2084,175 @@ getSourceCode(SourceRange const& R) const context_.getLangOpts()).str(); } -std::optional>> +std::optional ASTVisitor:: -isSFINAEType(QualType const T) +extractSFINAEInfo(QualType const T) { - if (!config_->sfinae) - { - return std::nullopt; - } - - auto sfinae_info = getSFINAETemplate(T, true); - if (!sfinae_info) - { - return std::nullopt; - } - - auto sfinae_result = isSFINAETemplate( - sfinae_info->Template, sfinae_info->Member); + MRDOCS_SYMBOL_TRACE(T, context_); + MRDOCS_CHECK_OR(config_->sfinae, std::nullopt); - if (!sfinae_result) - { - return std::nullopt; - } + // Get the primary template information of the type + auto const templateInfo = getSFINAETemplateInfo(T, true); + MRDOCS_CHECK_OR(templateInfo, std::nullopt); - auto [template_params, controlling_params, param_idx] = *sfinae_result; + // Find the control parameters for SFINAE + auto SFINAEControl = getSFINAEControlParams(*templateInfo); + MRDOCS_CHECK_OR(SFINAEControl, std::nullopt); - auto const Args = sfinae_info->Arguments; + // Find the parameter that represents the SFINAE result + auto const Args = templateInfo->Arguments; + MRDOCS_SYMBOL_TRACE(Args, context_); auto const param_arg = tryGetTemplateArgument( - template_params, Args, param_idx); - if (!param_arg) - { - return std::nullopt; - } + SFINAEControl->Parameters, Args, SFINAEControl->ParamIdx); + MRDOCS_CHECK_OR(param_arg, std::nullopt); + MRDOCS_SYMBOL_TRACE(*param_arg, context_); + // Create a vector of template arguments that represent the + // controlling parameters of the SFINAE template std::vector ControllingArgs; for (std::size_t I = 0; I < Args.size(); ++I) { - if (controlling_params[I]) + if (SFINAEControl->ControllingParams[I]) { + MRDOCS_SYMBOL_TRACE(Args[I], context_); ControllingArgs.emplace_back(Args[I]); } } - return std::make_pair(param_arg->getAsType(), std::move(ControllingArgs)); + // Return the main type and controlling types + return SFINAEInfo{param_arg->getAsType(), std::move(ControllingArgs)}; } -std::optional> +std::optional ASTVisitor:: -isSFINAETemplate( +getSFINAEControlParams( TemplateDecl* TD, - const IdentifierInfo* Member) + IdentifierInfo const* Member) { - if (!TD) - { - return std::nullopt; - } + MRDOCS_SYMBOL_TRACE(TD, context_); + MRDOCS_SYMBOL_TRACE(Member, context_); + MRDOCS_CHECK_OR(TD, std::nullopt); + // The `FindParam` lambda function is used to find the index of a + // template argument in a list of template arguments. It is used + // to find the index of the controlling parameter in the list of + // template arguments of the template declaration. auto FindParam = [this]( ArrayRef Arguments, - const TemplateArgument& Arg) -> unsigned + const TemplateArgument& Arg) -> std::size_t { if (Arg.getKind() != TemplateArgument::Type) { return -1; } - auto Found = std::ranges::find_if(Arguments, [&](const TemplateArgument& Other) - { - if (Other.getKind() != TemplateArgument::Type) + auto const It = std::ranges::find_if( + Arguments, + [&](const TemplateArgument& Other) { - return false; - } - return context_.hasSameType(Other.getAsType(), Arg.getAsType()); - }); - return Found != Arguments.end() ? Found - Arguments.data() : -1; + if (Other.getKind() != TemplateArgument::Type) + { + return false; + } + return context_.hasSameType(Other.getAsType(), Arg.getAsType()); + }); + bool const found = It != Arguments.end(); + return found ? It - Arguments.data() : static_cast(-1); }; if(auto* ATD = dyn_cast(TD)) { + // If the alias template is an alias template specialization, + // we need to do the process for the underlying type + MRDOCS_SYMBOL_TRACE(ATD, context_); auto Underlying = ATD->getTemplatedDecl()->getUnderlyingType(); - auto sfinae_info = getSFINAETemplate(Underlying, !Member); - if (!sfinae_info) - { - return std::nullopt; - } + MRDOCS_SYMBOL_TRACE(Underlying, context_); + auto underlyingTemplateInfo = getSFINAETemplateInfo(Underlying, Member == nullptr); + MRDOCS_CHECK_OR(underlyingTemplateInfo, std::nullopt); if (Member) { - sfinae_info->Member = Member; - } - auto sfinae_result = isSFINAETemplate( - sfinae_info->Template, sfinae_info->Member); - if (!sfinae_result) - { - return std::nullopt; + // Get the member specified in the alias type from + // the underlying type. If `Member` is `nullptr`, + // `getSFINAETemplateInfo` was already allowed to populate + // the `Member` field. + underlyingTemplateInfo->Member = Member; } - auto [template_params, controlling_params, param_idx] = *sfinae_result; + auto sfinaeControl = getSFINAEControlParams(*underlyingTemplateInfo); + MRDOCS_CHECK_OR(sfinaeControl, std::nullopt); + + // Find the index of the parameter that represents the SFINAE result + // in the underlying template arguments auto param_arg = tryGetTemplateArgument( - template_params, sfinae_info->Arguments, param_idx); - if (!param_arg) - { - return std::nullopt; - } + sfinaeControl->Parameters, + underlyingTemplateInfo->Arguments, + sfinaeControl->ParamIdx); + MRDOCS_CHECK_OR(param_arg, std::nullopt); + MRDOCS_SYMBOL_TRACE(*param_arg, context_); + + // Find the index of the parameter that represents the SFINAE result + // in the primary template arguments unsigned ParamIdx = FindParam(ATD->getInjectedTemplateArgs(context_), *param_arg); - return std::make_tuple(ATD->getTemplateParameters(), std::move(controlling_params), ParamIdx); + + // Return the controlling parameters with values corresponding to + // the primary template arguments + TemplateParameterList* primaryTemplParams = ATD->getTemplateParameters(); + MRDOCS_SYMBOL_TRACE(primaryTemplParams, context_); + return SFINAEControlParams( + primaryTemplParams, + std::move(sfinaeControl->ControllingParams), + ParamIdx); } + // Ensure this is a ClassTemplateDecl auto* CTD = dyn_cast(TD); - if (!CTD) - { - return std::nullopt; - } + MRDOCS_SYMBOL_TRACE(CTD, context_); + MRDOCS_CHECK_OR(CTD, std::nullopt); + // Get the template arguments of the primary template auto PrimaryArgs = CTD->getInjectedTemplateArgs(context_); - llvm::SmallBitVector ControllingParams(PrimaryArgs.size()); + MRDOCS_SYMBOL_TRACE(PrimaryArgs, context_); + // Type of the member that represents the SFINAE result. QualType MemberType; + + // Index of the parameter that represents the the SFINAE result. + // For instance, in the specialization `std::enable_if::type`, + // `type` is `T`, which corresponds to the second template parameter + // `T`, so `ParamIdx` is `1` to represent the second parameter. unsigned ParamIdx = -1; + + // The `IsMismatch` function checks if there's a mismatch between the + // CXXRecordDecl of the ClassTemplateDecl and the specified template + // arguments. If there's a mismatch and `IsMismatch` returns `true`, + // the caller returns `std::nullopt` to indicate that the template + // is not a SFINAE template. If there are no mismatches, the caller + // continues to check the controlling parameters of the template. + // This function also updates the `MemberType` and `ParamIdx` variables + // so that they can be used to check the controlling parameters. auto IsMismatch = [&](CXXRecordDecl* RD, ArrayRef Args) { + MRDOCS_SYMBOL_TRACE(RD, context_); + MRDOCS_SYMBOL_TRACE(Args, context_); if (!RD->hasDefinition()) { return false; } + // Look for member in the record, such + // as the member `::type` in `std::enable_if` auto MemberLookup = RD->lookup(Member); + MRDOCS_SYMBOL_TRACE(MemberLookup, context_); QualType CurrentType; if(MemberLookup.empty()) { if (!RD->getNumBases()) { + // Didn't find a definition for the specified member and + // there can't be a base class that defines the + // specified member: no mismatch return false; } for(auto& Base : RD->bases()) { - auto sfinae_info = getSFINAETemplate(Base.getType(), false); + auto sfinae_info = getSFINAETemplateInfo(Base.getType(), false); if(! sfinae_info) { // if the base is an opaque dependent type, we can't determine @@ -2232,7 +2270,7 @@ isSFINAETemplate( return true; } - auto sfinae_result = isSFINAETemplate( + auto sfinae_result = getSFINAEControlParams( sfinae_info->Template, Member); if (!sfinae_result) { @@ -2263,15 +2301,26 @@ isSFINAETemplate( } else { - // ambiguous lookup + // MemberLookup is not empty. if (!MemberLookup.isSingleResult()) { + // Ambiguous lookup: If there's more than one result, + // we can't determine if the template is a SFINAE template + // and return `true` to indicate that the template is not a + // SFINAE template. return true; } if (auto* TND = dyn_cast(MemberLookup.front())) { + // Update the current type to the underlying type of the + // typedef declaration. + // For instance, if the member is `::type` in the record + // `std::enable_if`, then the current type is `T`. + // The next checks will occur for this underlying type. CurrentType = TND->getUnderlyingType(); - } else + MRDOCS_SYMBOL_TRACE(CurrentType, context_); + } + else { // the specialization has a member with the right name, // but it isn't an alias declaration/typedef declaration... @@ -2279,56 +2328,93 @@ isSFINAETemplate( } } + // If the current type depends on a template parameter, we need to + // find the corresponding template argument in the template arguments + // of the primary template. If the template argument is not found, + // we can't determine if the template is a SFINAE template and return + // `true` to indicate a mismatch. if(CurrentType->isDependentType()) { - auto FoundIdx = FindParam(Args, TemplateArgument(CurrentType)); - if (FoundIdx == static_cast(-1) - || FoundIdx >= PrimaryArgs.size()) + TemplateArgument asTemplateArg(CurrentType); + auto FoundIdx = FindParam(Args, asTemplateArg); + if (FoundIdx == static_cast(-1) || + FoundIdx >= PrimaryArgs.size()) { return true; } + // Set the controlling parameter index to the index of the + // template argument that controls the SFINAE. For instance, + // in the specialization `std::enable_if::type`, + // `type` is `T`, which corresponds to the second template + // parameter `T`, so `ParamIdx` is `1` to represent the + // second parameter. ParamIdx = FoundIdx; + // Get this primary template argument as a template + // argument of the current type. TemplateArgument MappedPrimary = PrimaryArgs[FoundIdx]; - assert(MappedPrimary.getKind() == TemplateArgument::Type); + MRDOCS_SYMBOL_TRACE(MappedPrimary, context_); + // The primary argument in SFINAE should be a type + MRDOCS_ASSERT(MappedPrimary.getKind() == TemplateArgument::Type); + // Update the current type to the type of the primary argument CurrentType = MappedPrimary.getAsType(); + MRDOCS_SYMBOL_TRACE(CurrentType, context_); } + // Update the type of the member that represents the SFINAE result + // to the current type if it is not already set. if (MemberType.isNull()) { MemberType = CurrentType; } - return ! context_.hasSameType(MemberType, CurrentType); + // As a last check, the current type should be the same as the + // type of the member that represents the SFINAE result so that + // we can extract SFINAE information from the template. + return !context_.hasSameType(MemberType, CurrentType); }; - if (IsMismatch(CTD->getTemplatedDecl(), PrimaryArgs)) - { - return std::nullopt; - } + // Check if there's a mismatch between the primary record and the arguments + CXXRecordDecl* PrimaryRD = CTD->getTemplatedDecl(); + MRDOCS_SYMBOL_TRACE(PrimaryRD, context_); + MRDOCS_CHECK_OR(!IsMismatch(PrimaryRD, PrimaryArgs), std::nullopt); + // Check if there's a mismatch between any explicit specialization and the arguments for(auto* CTSD : CTD->specializations()) { - if (CTSD->isExplicitSpecialization() - && IsMismatch(CTSD, CTSD->getTemplateArgs().asArray())) + MRDOCS_SYMBOL_TRACE(CTSD, context_); + if (!CTSD->isExplicitSpecialization()) { - return std::nullopt; + continue; } + ArrayRef SpecArgs = CTSD->getTemplateArgs().asArray(); + MRDOCS_CHECK_OR(!IsMismatch(CTSD, SpecArgs), std::nullopt); } + // Check if there's a mismatch between any partial specialization and the arguments SmallVector PartialSpecs; CTD->getPartialSpecializations(PartialSpecs); - for(auto* CTPSD : PartialSpecs) { + MRDOCS_SYMBOL_TRACE(CTPSD, context_); auto PartialArgs = CTPSD->getTemplateArgs().asArray(); - if (IsMismatch(CTPSD, PartialArgs)) - { - return std::nullopt; - } - for(std::size_t I = 0; I < PartialArgs.size(); ++I) + MRDOCS_SYMBOL_TRACE(PartialArgs, context_); + MRDOCS_CHECK_OR(!IsMismatch(CTPSD, PartialArgs), std::nullopt); + } + + // Find the controlling parameters of the template, that is, the + // template parameters that control the SFINAE result. The controlling + // parameters are expressions that cannot be converted to + // non-type template parameters. + llvm::SmallBitVector ControllingParams(PrimaryArgs.size()); + for(auto* CTPSD : PartialSpecs) { + MRDOCS_SYMBOL_TRACE(CTPSD, context_); + auto PartialArgs = CTPSD->getTemplateArgs().asArray(); + MRDOCS_SYMBOL_TRACE(PartialArgs, context_); + for(std::size_t i = 0; i < PartialArgs.size(); ++i) { - TemplateArgument Arg = PartialArgs[I]; - switch(Arg.getKind()) + TemplateArgument Arg = PartialArgs[i]; + MRDOCS_SYMBOL_TRACE(Arg, context_); + switch (Arg.getKind()) { case TemplateArgument::Integral: case TemplateArgument::Declaration: @@ -2344,38 +2430,50 @@ isSFINAETemplate( default: continue; } - ControllingParams.set(I); - //.getAsExpr() + ControllingParams.set(i); } } - return std::make_tuple(CTD->getTemplateParameters(), std::move(ControllingParams), ParamIdx); + return SFINAEControlParams(CTD->getTemplateParameters(), std::move(ControllingParams), ParamIdx); } -std::optional -ASTVisitor:: -getSFINAETemplate(QualType T, bool const AllowDependentNames) +std::optional +ASTVisitor::getSFINAETemplateInfo(QualType T, bool const AllowDependentNames) const { - assert(!T.isNull()); - SFINAEInfo SFINAE; + MRDOCS_SYMBOL_TRACE(T, context_); + MRDOCS_ASSERT(!T.isNull()); + + // If the type is an elaborated type, get the named type if (auto* ET = T->getAs()) { T = ET->getNamedType(); } - if(auto* DNT = T->getAsAdjusted(); + // If the type is a dependent name type and dependent names are allowed, + // extract the identifier and the qualifier's type + SFINAETemplateInfo SFINAE; + if (auto* DNT = T->getAsAdjusted(); DNT && AllowDependentNames) { SFINAE.Member = DNT->getIdentifier(); + MRDOCS_SYMBOL_TRACE(SFINAE.Member, context_); T = QualType(DNT->getQualifier()->getAsType(), 0); + MRDOCS_SYMBOL_TRACE(T, context_); } - if(auto* TST = T->getAsAdjusted()) + // If the type is a template specialization type, extract the template name + // and the template arguments + if (auto* TST = T->getAsAdjusted()) { + MRDOCS_SYMBOL_TRACE(TST, context_); SFINAE.Template = TST->getTemplateName().getAsTemplateDecl(); + MRDOCS_SYMBOL_TRACE(SFINAE.Template, context_); SFINAE.Arguments = TST->template_arguments(); + MRDOCS_SYMBOL_TRACE(SFINAE.Arguments, context_); return SFINAE; } + + // Return nullopt if the type does not match the expected patterns return std::nullopt; } @@ -2383,30 +2481,36 @@ std::optional ASTVisitor:: tryGetTemplateArgument( TemplateParameterList* Parameters, - ArrayRef Arguments, - unsigned const Index) + ArrayRef const Arguments, + std::size_t const Index) { - if (Index == static_cast(-1)) - { - return std::nullopt; - } + MRDOCS_SYMBOL_TRACE(Parameters, context_); + MRDOCS_SYMBOL_TRACE(Arguments, context_); + MRDOCS_CHECK_OR(Index != static_cast(-1), std::nullopt); + + // If the index is within the range of the template arguments, return the argument if (Index < Arguments.size()) { return Arguments[Index]; } - if(Parameters && Index < Parameters->size()) + + MRDOCS_CHECK_OR(Parameters, std::nullopt); + MRDOCS_CHECK_OR(Index < Parameters->size(), std::nullopt); + + // Attempt to get the default argument of the template parameter + NamedDecl* ND = Parameters->getParam(Index); + MRDOCS_SYMBOL_TRACE(ND, context_); + if(auto* TTPD = dyn_cast(ND); + TTPD && TTPD->hasDefaultArgument()) + { + MRDOCS_SYMBOL_TRACE(TTPD, context_); + return TTPD->getDefaultArgument().getArgument(); + } + if(auto* NTTPD = dyn_cast(ND); + NTTPD && NTTPD->hasDefaultArgument()) { - NamedDecl* ND = Parameters->getParam(Index); - if(auto* TTPD = dyn_cast(ND); - TTPD && TTPD->hasDefaultArgument()) - { - return TTPD->getDefaultArgument().getArgument(); - } - if(auto* NTTPD = dyn_cast(ND); - NTTPD && NTTPD->hasDefaultArgument()) - { - return NTTPD->getDefaultArgument().getArgument(); - } + MRDOCS_SYMBOL_TRACE(NTTPD, context_); + return NTTPD->getDefaultArgument().getArgument(); } return std::nullopt; } diff --git a/src/lib/AST/ASTVisitor.hpp b/src/lib/AST/ASTVisitor.hpp index 41ac5a40be..18810c99cd 100644 --- a/src/lib/AST/ASTVisitor.hpp +++ b/src/lib/AST/ASTVisitor.hpp @@ -148,18 +148,6 @@ class ASTVisitor */ ExtractionMode mode_ = ExtractionMode::Regular; - struct SFINAEInfo - { - TemplateDecl* Template = nullptr; - const IdentifierInfo* Member = nullptr; - ArrayRef Arguments; - }; - - template - struct upsertResult { - InfoTy& I; - bool isNew; - }; public: /** Constructor for ASTVisitor. @@ -636,30 +624,152 @@ class ASTVisitor std::string getSourceCode(SourceRange const& R) const; - // Determine if a type is a SFINAE type - std::optional>> - isSFINAEType(QualType T); + /* Struct to hold the underlying type result of a SFINAE type. - // Determine if a type is a SFINAE type - std::optional>> - isSFINAEType(Type const* T) + This struct is used to store the underlying type and the template + arguments of a type used in a SFINAE context. + + For instance, for the type `std::enable_if_t, T>`, + the `Type` would be `T`, and the `Arguments` would be + `{std::is_integral_v, T}`. + */ + struct SFINAEInfo { - return isSFINAEType(QualType(T, 0)); + // The underlying type of the SFINAE type. + QualType Type; + + // The template arguments used in the SFINAE context. + std::vector Arguments; + }; + + /* Get the underlying type result of a SFINAE type + + This function will return the underlying type of a + type used in a SFINAE context. + + For instance, it returns the type `T` for the + type `std::enable_if_t, T>`. + + @param T The type to check. + + @return `std::nullopt` if the type is not a SFINAE type, + and the underlying type with the template arguments + otherwise. + */ + std::optional + extractSFINAEInfo(QualType T); + + // @copydoc extractSFINAEInfo(QualType) + std::optional + extractSFINAEInfo(Type const* T) + { + return extractSFINAEInfo(QualType(T, 0)); } - std::optional< - std::tuple> - isSFINAETemplate(TemplateDecl* TD, IdentifierInfo const* Member); + /* Struct to hold SFINAE information. - static std::optional - getSFINAETemplate(QualType T, bool AllowDependentNames); + This struct is used to store information about a template that is + involved in a SFINAE context. It contains the template declaration, + the member identifier, and the template arguments. - static + For instance, for the type `std::enabled_if_t, T>`, + the struct would contain the `Template` declaration for `std::enable_if`, + and the `Arguments` `{std::is_integral_v, T}`. + */ + struct SFINAETemplateInfo + { + /// The template declaration involved in SFINAE + TemplateDecl* Template = nullptr; + + /// The identifier of the member being checked. + IdentifierInfo const* Member = nullptr; + + /// The template arguments used in the SFINAE context. + ArrayRef Arguments; + }; + + /* Get the template declaration and member identifier + + This function is a helper used by `extractSFINAEInfo` to + extract the template declaration and member identifier + from a type used in a SFINAE context. + + For instance, for the type `std::enabled_if_t, T>`, + the struct would contain the `Template` declaration for `std::enable_if`, + and the `Arguments` `{std::is_integral_v, T}`. + + If `AllowDependentNames` is set to `true`, the function will + also populate the `Member` field with the member identifier + if the type is a dependent name type. For instance, + for the type `typename std::enable_if::type`, the `Member` + field would be `type`, and the other fields would be populated + respective to `std::enable_if`: `Template` would be `std::enable_if`, + and `Arguments` would be `{B,T}`. + */ + std::optional + getSFINAETemplateInfo(QualType T, bool AllowDependentNames) const; + + /* The controlling parameters of a SFINAE template + + This struct is used to store information about the controlling + parameters of a template that is involved in a SFINAE context. + + It contains the template parameters, the controlling parameters, + and the index of the parameter that represents the result. + + For instance, for the template `template typename + std::enable_if::type`, the struct would contain the template + parameters `{B, T}`, the index of the controlling parameters `{B}`, + and the index `1` to represent the second result parameter `T`. + */ + struct SFINAEControlParams + { + // The template parameters of the template declaration + TemplateParameterList* Parameters = nullptr; + + // The controlling parameters of the template declaration + llvm::SmallBitVector ControllingParams; + + // The index of the parameter that represents the SFINAE result + std::size_t ParamIdx = static_cast(-1); + }; + + /* Determine if a template is SFINAE and returns constraints + + This function is used by `isSFINAETemplate` to determine if a + template is involved in a SFINAE context. + + If the template is involved in a SFINAE context, the function + will return the template parameters, the controlling parameters, + and the index of the parameter that controls the SFINAE context. + + If the template is an alias (such as `std::enable_if_t`), the + template information of the underlying type + (such as `typename enable_if::type`) will be extract instead. + */ + std::optional + getSFINAEControlParams(TemplateDecl* TD, IdentifierInfo const* Member); + + std::optional + getSFINAEControlParams(SFINAETemplateInfo const& SFINAE) { + return getSFINAEControlParams(SFINAE.Template, SFINAE.Member); + } + + /* Get the template argument with specified index. + + If the index is a valid index in the template arguments, + the function will return the template argument at the + specified index. + + If the index is outside the bounds of the + arguments, the function will attempt to get the + argument from the default template arguments. + */ std::optional tryGetTemplateArgument( TemplateParameterList* Parameters, ArrayRef Arguments, - unsigned Index); + std::size_t Index); // ================================================= // Filters @@ -806,6 +916,20 @@ class ASTVisitor FileInfo* findFileInfo(clang::SourceLocation loc); + /* Result of an upsert operation + + This struct is used to return the result of an + upsert operation. The struct contains a reference + to the Info object that was created or found, and + a boolean flag indicating whether the Info object + was newly created or not. + */ + template + struct upsertResult { + InfoTy& I; + bool isNew; + }; + /* Get or construct an empty Info with a specified id. If an Info object for the declaration has already diff --git a/src/lib/AST/ClangHelpers.hpp b/src/lib/AST/ClangHelpers.hpp index 12a4f040d8..a180545940 100644 --- a/src/lib/AST/ClangHelpers.hpp +++ b/src/lib/AST/ClangHelpers.hpp @@ -731,6 +731,26 @@ MRDOCS_DECL QualType getDeclaratorType(const DeclaratorDecl* DD); +/** Get the NonTypeTemplateParm of an expression + + This function will return the NonTypeTemplateParmDecl + corresponding to the expression `E` if it is a + NonTypeTemplateParmDecl. If the expression is not + a NonTypeTemplateParmDecl, the function will return + nullptr. + + For instance, given the expression `x` in the following + code snippet: + + @code + template + void f() {} + @endcode + + the function will return the NonTypeTemplateParmDecl + corresponding to `x`, which is the template parameter + of the function `f`. + */ MRDOCS_DECL NonTypeTemplateParmDecl const* getNTTPFromExpr(const Expr* E, unsigned Depth); @@ -779,11 +799,6 @@ isAllImplicit(Decl const* D); #ifdef NDEBUG #define MRDOCS_SYMBOL_TRACE(D, C) #else - -# define MRDOCS_SYMBOL_TRACE_MERGE_(a, b) a##b -# define MRDOCS_SYMBOL_TRACE_LABEL_(a) MRDOCS_SYMBOL_TRACE_MERGE_(symbol_name_, a) -# define MRDOCS_SYMBOL_TRACE_UNIQUE_NAME MRDOCS_SYMBOL_TRACE_LABEL_(__LINE__) - namespace detail { // concept to check if ID->printQualifiedName( // std::declval(), @@ -801,6 +816,35 @@ namespace detail { D.print(OS, PP); }; + template + concept HasPrintWithPolicyFirst = requires(T const& D, llvm::raw_svector_ostream& OS, PrintingPolicy PP) + { + D.print(PP, OS, true); + }; + + template + concept HasDump = requires(T const& D, llvm::raw_svector_ostream& OS, ASTContext const& C) + { + D.dump(OS, C); + }; + + template + concept ConvertibleToUnqualifiedQualType = requires(T const& D) + { + QualType(&D, 0); + }; + + template + concept HasGetName = requires(T const& D) + { + D.getName(); + }; + + template + requires (!std::is_pointer_v) + void + printTraceName(T const& D, ASTContext const& C, SmallString<256>& symbol_name); + template void printTraceName(T const* D, ASTContext const& C, SmallString<256>& symbol_name) @@ -829,6 +873,38 @@ namespace detail { { D->print(os, C.getPrintingPolicy()); } + else if constexpr (HasPrintWithPolicyFirst) + { + D->print(C.getPrintingPolicy(), os, true); + } + else if constexpr (ConvertibleToUnqualifiedQualType) + { + QualType const QT(D, 0); + QT.print(os, C.getPrintingPolicy()); + } + else if constexpr (HasDump) + { + D->dump(os, C); + } + else if constexpr (HasGetName) + { + os << D->getName(); + } + else if constexpr (std::ranges::range) + { + bool first = true; + os << "{"; + for (auto it = D->begin(); it != D->end(); ++it) + { + if (!first) + { + os << ", "; + } + first = false; + printTraceName(*it, C, symbol_name); + } + os << "}"; + } } template @@ -840,6 +916,9 @@ namespace detail { } } // namespace detail +# define MRDOCS_SYMBOL_TRACE_MERGE_(a, b) a##b +# define MRDOCS_SYMBOL_TRACE_LABEL_(a) MRDOCS_SYMBOL_TRACE_MERGE_(symbol_name_, a) +# define MRDOCS_SYMBOL_TRACE_UNIQUE_NAME MRDOCS_SYMBOL_TRACE_LABEL_(__LINE__) #define MRDOCS_SYMBOL_TRACE(D, C) \ SmallString<256> MRDOCS_SYMBOL_TRACE_UNIQUE_NAME; \ detail::printTraceName(D, C, MRDOCS_SYMBOL_TRACE_UNIQUE_NAME); \ diff --git a/src/lib/AST/TerminalTypeVisitor.hpp b/src/lib/AST/TerminalTypeVisitor.hpp index 11910eea9b..d131ef05d6 100644 --- a/src/lib/AST/TerminalTypeVisitor.hpp +++ b/src/lib/AST/TerminalTypeVisitor.hpp @@ -85,7 +85,7 @@ class TerminalTypeVisitor bool IsPack_ = false; // The optional NestedNameSpecifier. - const NestedNameSpecifier* NNS_; + const NestedNameSpecifier* NNS_ = nullptr; public: /** Constructor for TerminalTypeVisitor. @@ -117,12 +117,18 @@ class TerminalTypeVisitor This function stores the local qualifiers of the given Qualified Type and calls the corresponding `VisitXXXType` function for the associated `Type`. + + Example: + - Wrapped type: `const int` + - Unwrapped type: `int` */ bool Visit(QualType const QT) { + MRDOCS_SYMBOL_TRACE(QT, Visitor_.context_); Quals_ |= QT.getLocalFastQualifiers(); Type const* T = QT.getTypePtrOrNull(); + MRDOCS_SYMBOL_TRACE(T, Visitor_.context_); return Visit(T); } @@ -284,23 +290,45 @@ class TerminalTypeVisitor return static_cast(*this); } - // A type with parentheses, e.g., `(int)`. + /** Visit a type with parentheses, e.g., `(int)`. + + This function unwraps the inner type from the parentheses. + + Example: + - Wrapped type: `(int)` + - Unwrapped type: `int` + */ bool - VisitParenType( - const ParenType* T) + VisitParenType(const ParenType* T) { QualType I = T->getInnerType(); return Visit(I); } + /** Visit a macro qualified type. + + This function unwraps the underlying type from the macro qualifier. + + Example: + - Wrapped type: `MACRO_QUALIFIED(int)` + - Unwrapped type: `int` + */ bool VisitMacroQualified( - const MacroQualifiedType* T) + MacroQualifiedType const* T) { QualType UT = T->getUnderlyingType(); return Visit(UT); } + /** Visit an attributed type. + + This function unwraps the modified type from the attribute. + + Example: + - Wrapped type: `[[attribute]] int` + - Unwrapped type: `int` + */ bool VisitAttributedType( const AttributedType* T) @@ -309,22 +337,44 @@ class TerminalTypeVisitor return Visit(MT); } + /** Visit an adjusted type. + + This function unwraps the original type from the adjusted type. + + Example: + - Wrapped type: adjusted/decayed `int*` + - Unwrapped type: original `int[4]` + */ bool - VisitAdjustedType( - const AdjustedType* T) + VisitAdjustedType(AdjustedType const* T) { QualType OT = T->getOriginalType(); return Visit(OT); } + /** Visit a using type. + + This function unwraps the underlying type from the using type. + + Example: + - Wrapped type: `using TypeAlias = int` + - Unwrapped type: `int` + */ bool - VisitUsingType( - const UsingType* T) + VisitUsingType(UsingType const* T) { QualType UT = T->getUnderlyingType(); return Visit(UT); } + /** Visit a substituted template type parameter type. + + This function unwraps the replacement type from the substituted template type parameter. + + Example: + - Wrapped type: `T` + - Unwrapped type: `int` (if `T` is substituted with `int`) + */ bool VisitSubstTemplateTypeParmType( const SubstTemplateTypeParmType* T) @@ -335,17 +385,38 @@ class TerminalTypeVisitor // ---------------------------------------------------------------- - // A type that was referred to using an elaborated type keyword, - // e.g., `struct S`, or via a qualified name, e.g., `N::M::type`. + + + /** Visit an elaborated type. + + This function unwraps the named type from the elaborated type: + a type that was referred to using an elaborated type keyword, + e.g., `struct S`, or via a qualified name, e.g., `N::M::type`. + + Example: + - Wrapped type: `struct S` + - Unwrapped type: `S` + */ bool VisitElaboratedType( const ElaboratedType* T) { + MRDOCS_SYMBOL_TRACE(T, Visitor_.context_); NNS_ = T->getQualifier(); + MRDOCS_SYMBOL_TRACE(NNS_, Visitor_.context_); QualType NT = T->getNamedType(); + MRDOCS_SYMBOL_TRACE(NT, Visitor_.context_); return Visit(NT); } + /** Visit a pack expansion type. + + This function unwraps the pattern type from the pack expansion. + + Example: + - Wrapped type: `int...` + - Unwrapped type: `int` + */ bool VisitPackExpansionType( const PackExpansionType* T) @@ -357,6 +428,14 @@ class TerminalTypeVisitor // ---------------------------------------------------------------- + /** Visit a pointer type. + + This function unwraps the pointee type from the pointer type. + + Example: + - Wrapped type: `int*` + - Unwrapped type: `int` + */ bool VisitPointerType( const PointerType* T) @@ -366,6 +445,14 @@ class TerminalTypeVisitor return Visit(PT); } + /** Visit an lvalue reference type. + + This function unwraps the pointee type from the lvalue reference type. + + Example: + - Wrapped type: `int&` + - Unwrapped type: `int` + */ bool VisitLValueReferenceType( const LValueReferenceType* T) @@ -376,6 +463,14 @@ class TerminalTypeVisitor return Visit(PT); } + /** Visit an rvalue reference type. + + This function unwraps the pointee type from the rvalue reference type. + + Example: + - Wrapped type: `int&&` + - Unwrapped type: `int` + */ bool VisitRValueReferenceType( const RValueReferenceType* T) @@ -386,6 +481,14 @@ class TerminalTypeVisitor return Visit(PT); } + /** Visit a member pointer type. + + This function unwraps the pointee type from the member pointer type. + + Example: + - Wrapped type: `int Class::*` + - Unwrapped type: `int` + */ bool VisitMemberPointerType( const MemberPointerType* T) @@ -404,6 +507,14 @@ class TerminalTypeVisitor return Visit(RT); } + /** Visit an array type. + + This function unwraps the element type from the array type. + + Example: + - Wrapped type: `int[10]` + - Unwrapped type: `int` + */ bool VisitArrayType( const ArrayType* T) @@ -457,9 +568,10 @@ class TerminalTypeVisitor VisitDependentNameType( const DependentNameType* T) { - if (auto SFINAE = getASTVisitor().isSFINAEType(T); SFINAE.has_value()) + if (auto SFINAE = getASTVisitor().extractSFINAEInfo(T)) { - return getDerived().Visit(SFINAE->first); + NNS_ = nullptr; + return getDerived().Visit(SFINAE->Type); } if (auto const* NNS = T->getQualifier()) @@ -475,6 +587,7 @@ class TerminalTypeVisitor VisitDependentTemplateSpecializationType( const DependentTemplateSpecializationType* T) { + MRDOCS_SYMBOL_TRACE(T, Visitor_.context_); if (auto const* NNS = T->getQualifier()) { NNS_ = NNS; @@ -489,9 +602,11 @@ class TerminalTypeVisitor VisitTemplateSpecializationType( TemplateSpecializationType const* T) { - if (auto SFINAE = getASTVisitor().isSFINAEType(T); SFINAE.has_value()) + MRDOCS_SYMBOL_TRACE(T, Visitor_.context_); + if (auto SFINAE = getASTVisitor().extractSFINAEInfo(T)) { - return getDerived().Visit(SFINAE->first); + NNS_ = nullptr; + return getDerived().Visit(SFINAE->Type); } // In most cases, a template name is simply a reference @@ -574,9 +689,11 @@ class TerminalTypeVisitor VisitTemplateTypeParmType( const TemplateTypeParmType* T) { + MRDOCS_SYMBOL_TRACE(T, Visitor_.context_); const IdentifierInfo* II = nullptr; if (TemplateTypeParmDecl const* D = T->getDecl()) { + MRDOCS_SYMBOL_TRACE(D, Visitor_.context_); if(D->isImplicit()) { // special case for implicit template parameters diff --git a/src/lib/Gen/xml/XMLWriter.cpp b/src/lib/Gen/xml/XMLWriter.cpp index 5b99a34ea3..fe0671228d 100644 --- a/src/lib/Gen/xml/XMLWriter.cpp +++ b/src/lib/Gen/xml/XMLWriter.cpp @@ -195,6 +195,13 @@ XMLWriter:: operator()( T const& I) { + Info const& base = I; + if (base.Extraction == ExtractionMode::Dependency) + { + return; + } + + #define INFO(Type) if constexpr(T::is##Type()) write##Type(I); #include } diff --git a/src/lib/Lib/ConfigOptions.json b/src/lib/Lib/ConfigOptions.json index 4842722b77..dee5e87e5b 100644 --- a/src/lib/Lib/ConfigOptions.json +++ b/src/lib/Lib/ConfigOptions.json @@ -167,7 +167,7 @@ { "name": "sfinae", "brief": "Detect and reduce SFINAE expressions", - "details": "When set to true, MrDocs detects SFINAE expressions in the source code and extracts them as part of the documentation. Expressions such as `std::enable_if<...>` are detected, removed, and documented as a requirement.", + "details": "When set to true, MrDocs detects SFINAE expressions in the source code and extracts them as part of the documentation. Expressions such as `std::enable_if<...>` are detected, removed, and documented as a requirement. MrDocs uses an algorithm that extracts SFINAE infomation from types by identifying inspecting the primary template and specializations to detect the result type and the controlling expressions in a specialization.", "type": "bool", "default": true }, diff --git a/test-files/golden-tests/core/libcxx.adoc b/test-files/golden-tests/core/libcxx.adoc index 4f98fa9603..29e6e9d4d2 100644 --- a/test-files/golden-tests/core/libcxx.adoc +++ b/test-files/golden-tests/core/libcxx.adoc @@ -5,14 +5,6 @@ == Global namespace -=== Namespaces - -[cols=1] -|=== -| Name - -| <> -|=== === Functions [cols=2] @@ -26,11 +18,6 @@ |=== -[#std] -== std - - - [#sqrt] == sqrt @@ -47,7 +34,7 @@ Declared in `<libcxx.cpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- template<typename T> -<>::T +T sqrt(T value); ---- diff --git a/test-files/golden-tests/core/libcxx.html b/test-files/golden-tests/core/libcxx.html index df50908d0c..c9e8446a3d 100644 --- a/test-files/golden-tests/core/libcxx.html +++ b/test-files/golden-tests/core/libcxx.html @@ -9,18 +9,6 @@

Reference

Global namespace

-

Namespaces

- - - - - - - - - - -
Name
std

Functions

@@ -38,11 +26,6 @@

Functions

-

std

-
-
-
-

sqrt

Computes the square root of an integral value.

@@ -57,7 +40,7 @@

Synopsis

 
 template<typename T>
-std::T
+T
 sqrt(T value);
 
 
diff --git a/test-files/golden-tests/core/libcxx.xml b/test-files/golden-tests/core/libcxx.xml index c9ace632b4..fd73826747 100644 --- a/test-files/golden-tests/core/libcxx.xml +++ b/test-files/golden-tests/core/libcxx.xml @@ -2,14 +2,12 @@ - -