//===--- AST.cpp - Utility AST functions -----------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "AST.h" #include "SourceCode.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Stmt.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Index/USRGeneration.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include namespace clang { namespace clangd { namespace { std::optional> getTemplateSpecializationArgLocs(const NamedDecl &ND) { if (auto *Func = llvm::dyn_cast(&ND)) { if (const ASTTemplateArgumentListInfo *Args = Func->getTemplateSpecializationArgsAsWritten()) return Args->arguments(); } else if (auto *Cls = llvm::dyn_cast(&ND)) { if (auto *Args = Cls->getTemplateArgsAsWritten()) return Args->arguments(); } else if (auto *Var = llvm::dyn_cast(&ND)) { if (auto *Args = Var->getTemplateArgsAsWritten()) return Args->arguments(); } // We return std::nullopt for ClassTemplateSpecializationDecls because it does // not contain TemplateArgumentLoc information. return std::nullopt; } template bool isTemplateSpecializationKind(const NamedDecl *D, TemplateSpecializationKind Kind) { if (const auto *TD = dyn_cast(D)) return TD->getTemplateSpecializationKind() == Kind; return false; } bool isTemplateSpecializationKind(const NamedDecl *D, TemplateSpecializationKind Kind) { return isTemplateSpecializationKind(D, Kind) || isTemplateSpecializationKind(D, Kind) || isTemplateSpecializationKind(D, Kind); } // Store all UsingDirectiveDecls in parent contexts of DestContext, that were // introduced before InsertionPoint. llvm::DenseSet getUsingNamespaceDirectives(const DeclContext *DestContext, SourceLocation Until) { const auto &SM = DestContext->getParentASTContext().getSourceManager(); llvm::DenseSet VisibleNamespaceDecls; for (const auto *DC = DestContext; DC; DC = DC->getLookupParent()) { for (const auto *D : DC->decls()) { if (!SM.isWrittenInSameFile(D->getLocation(), Until) || !SM.isBeforeInTranslationUnit(D->getLocation(), Until)) continue; if (auto *UDD = llvm::dyn_cast(D)) VisibleNamespaceDecls.insert( UDD->getNominatedNamespace()->getCanonicalDecl()); } } return VisibleNamespaceDecls; } // Goes over all parents of SourceContext until we find a common ancestor for // DestContext and SourceContext. Any qualifier including and above common // ancestor is redundant, therefore we stop at lowest common ancestor. // In addition to that stops early whenever IsVisible returns true. This can be // used to implement support for "using namespace" decls. std::string getQualification(ASTContext &Context, const DeclContext *DestContext, const DeclContext *SourceContext, llvm::function_ref IsVisible) { std::vector Parents; bool ReachedNS = false; for (const DeclContext *CurContext = SourceContext; CurContext; CurContext = CurContext->getLookupParent()) { // Stop once we reach a common ancestor. if (CurContext->Encloses(DestContext)) break; NestedNameSpecifier *NNS = nullptr; if (auto *TD = llvm::dyn_cast(CurContext)) { // There can't be any more tag parents after hitting a namespace. assert(!ReachedNS); (void)ReachedNS; NNS = NestedNameSpecifier::Create(Context, nullptr, TD->getTypeForDecl()); } else if (auto *NSD = llvm::dyn_cast(CurContext)) { ReachedNS = true; NNS = NestedNameSpecifier::Create(Context, nullptr, NSD); // Anonymous and inline namespace names are not spelled while qualifying // a name, so skip those. if (NSD->isAnonymousNamespace() || NSD->isInlineNamespace()) continue; } else { // Other types of contexts cannot be spelled in code, just skip over // them. continue; } // Stop if this namespace is already visible at DestContext. if (IsVisible(NNS)) break; Parents.push_back(NNS); } // Go over name-specifiers in reverse order to create necessary qualification, // since we stored inner-most parent first. std::string Result; llvm::raw_string_ostream OS(Result); for (const auto *Parent : llvm::reverse(Parents)) { if (Parent != *Parents.rbegin() && Parent->isDependent() && Parent->getAsRecordDecl() && Parent->getAsRecordDecl()->getDescribedClassTemplate()) OS << "template "; Parent->print(OS, Context.getPrintingPolicy()); } return OS.str(); } } // namespace bool isImplicitTemplateInstantiation(const NamedDecl *D) { return isTemplateSpecializationKind(D, TSK_ImplicitInstantiation); } bool isExplicitTemplateSpecialization(const NamedDecl *D) { return isTemplateSpecializationKind(D, TSK_ExplicitSpecialization); } bool isImplementationDetail(const Decl *D) { return !isSpelledInSource(D->getLocation(), D->getASTContext().getSourceManager()); } SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM) { auto L = D.getLocation(); // For `- (void)foo` we want `foo` not the `-`. if (const auto *MD = dyn_cast(&D)) L = MD->getSelectorStartLoc(); if (isSpelledInSource(L, SM)) return SM.getSpellingLoc(L); return SM.getExpansionLoc(L); } std::string printQualifiedName(const NamedDecl &ND) { std::string QName; llvm::raw_string_ostream OS(QName); PrintingPolicy Policy(ND.getASTContext().getLangOpts()); // Note that inline namespaces are treated as transparent scopes. This // reflects the way they're most commonly used for lookup. Ideally we'd // include them, but at query time it's hard to find all the inline // namespaces to query: the preamble doesn't have a dedicated list. Policy.SuppressUnwrittenScope = true; // (unnamed struct), not (unnamed struct at /path/to/foo.cc:42:1). // In clangd, context is usually available and paths are mostly noise. Policy.AnonymousTagLocations = false; ND.printQualifiedName(OS, Policy); assert(!StringRef(QName).starts_with("::")); return QName; } static bool isAnonymous(const DeclarationName &N) { return N.isIdentifier() && !N.getAsIdentifierInfo(); } NestedNameSpecifierLoc getQualifierLoc(const NamedDecl &ND) { if (auto *V = llvm::dyn_cast(&ND)) return V->getQualifierLoc(); if (auto *T = llvm::dyn_cast(&ND)) return T->getQualifierLoc(); return NestedNameSpecifierLoc(); } std::string printUsingNamespaceName(const ASTContext &Ctx, const UsingDirectiveDecl &D) { PrintingPolicy PP(Ctx.getLangOpts()); std::string Name; llvm::raw_string_ostream Out(Name); if (auto *Qual = D.getQualifier()) Qual->print(Out, PP); D.getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { std::string Name; llvm::raw_string_ostream Out(Name); PrintingPolicy PP(Ctx.getLangOpts()); // We don't consider a class template's args part of the constructor name. PP.SuppressTemplateArgsInCXXConstructors = true; // Handle 'using namespace'. They all have the same name - . if (auto *UD = llvm::dyn_cast(&ND)) { Out << "using namespace "; if (auto *Qual = UD->getQualifier()) Qual->print(Out, PP); UD->getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } if (isAnonymous(ND.getDeclName())) { // Come up with a presentation for an anonymous entity. if (isa(ND)) return "(anonymous namespace)"; if (auto *Cls = llvm::dyn_cast(&ND)) { if (Cls->isLambda()) return "(lambda)"; return ("(anonymous " + Cls->getKindName() + ")").str(); } if (isa(ND)) return "(anonymous enum)"; return "(anonymous)"; } // Print nested name qualifier if it was written in the source code. if (auto *Qualifier = getQualifierLoc(ND).getNestedNameSpecifier()) Qualifier->print(Out, PP); // Print the name itself. ND.getDeclName().print(Out, PP); // Print template arguments. Out << printTemplateSpecializationArgs(ND); return Out.str(); } std::string printTemplateSpecializationArgs(const NamedDecl &ND) { std::string TemplateArgs; llvm::raw_string_ostream OS(TemplateArgs); PrintingPolicy Policy(ND.getASTContext().getLangOpts()); if (std::optional> Args = getTemplateSpecializationArgLocs(ND)) { printTemplateArgumentList(OS, *Args, Policy); } else if (auto *Cls = llvm::dyn_cast(&ND)) { // FIXME: Fix cases when getTypeAsWritten returns null inside clang AST, // e.g. friend decls. Currently we fallback to Template Arguments without // location information. printTemplateArgumentList(OS, Cls->getTemplateArgs().asArray(), Policy); } return TemplateArgs; } std::string printNamespaceScope(const DeclContext &DC) { for (const auto *Ctx = &DC; Ctx != nullptr; Ctx = Ctx->getParent()) if (const auto *NS = dyn_cast(Ctx)) if (!NS->isAnonymousNamespace() && !NS->isInlineNamespace()) return printQualifiedName(*NS) + "::"; return ""; } static llvm::StringRef getNameOrErrForObjCInterface(const ObjCInterfaceDecl *ID) { return ID ? ID->getName() : "<>"; } std::string printObjCMethod(const ObjCMethodDecl &Method) { std::string Name; llvm::raw_string_ostream OS(Name); OS << (Method.isInstanceMethod() ? '-' : '+') << '['; // Should always be true. if (const ObjCContainerDecl *C = dyn_cast(Method.getDeclContext())) OS << printObjCContainer(*C); Method.getSelector().print(OS << ' '); if (Method.isVariadic()) OS << ", ..."; OS << ']'; return Name; } std::string printObjCContainer(const ObjCContainerDecl &C) { if (const ObjCCategoryDecl *Category = dyn_cast(&C)) { std::string Name; llvm::raw_string_ostream OS(Name); const ObjCInterfaceDecl *Class = Category->getClassInterface(); OS << getNameOrErrForObjCInterface(Class) << '(' << Category->getName() << ')'; return Name; } if (const ObjCCategoryImplDecl *CID = dyn_cast(&C)) { std::string Name; llvm::raw_string_ostream OS(Name); const ObjCInterfaceDecl *Class = CID->getClassInterface(); OS << getNameOrErrForObjCInterface(Class) << '(' << CID->getName() << ')'; return Name; } return C.getNameAsString(); } SymbolID getSymbolID(const Decl *D) { llvm::SmallString<128> USR; if (index::generateUSRForDecl(D, USR)) return {}; return SymbolID(USR); } SymbolID getSymbolID(const llvm::StringRef MacroName, const MacroInfo *MI, const SourceManager &SM) { if (MI == nullptr) return {}; llvm::SmallString<128> USR; if (index::generateUSRForMacro(MacroName, MI->getDefinitionLoc(), SM, USR)) return {}; return SymbolID(USR); } const ObjCImplDecl *getCorrespondingObjCImpl(const ObjCContainerDecl *D) { if (const auto *ID = dyn_cast(D)) return ID->getImplementation(); if (const auto *CD = dyn_cast(D)) { if (CD->IsClassExtension()) { if (const auto *ID = CD->getClassInterface()) return ID->getImplementation(); return nullptr; } return CD->getImplementation(); } return nullptr; } Symbol::IncludeDirective preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts, ArrayRef MainFileIncludes, ArrayRef TopLevelDecls) { // Always prefer #include for non-ObjC code. if (!LangOpts.ObjC) return Symbol::IncludeDirective::Include; // If this is not a header file and has ObjC set as the language, prefer // #import. if (!isHeaderFile(FileName, LangOpts)) return Symbol::IncludeDirective::Import; // Headers lack proper compile flags most of the time, so we might treat a // header as ObjC accidentally. Perform some extra checks to make sure this // works. // Any file with a #import, should keep #import-ing. for (auto &Inc : MainFileIncludes) if (Inc.Directive == tok::pp_import) return Symbol::IncludeDirective::Import; // Any file declaring an ObjC decl should also be #import-ing. // No need to look over the references, as the file doesn't have any #imports, // it must be declaring interesting ObjC-like decls. for (const Decl *D : TopLevelDecls) if (isa( D)) return Symbol::IncludeDirective::Import; return Symbol::IncludeDirective::Include; } std::string printType(const QualType QT, const DeclContext &CurContext, const llvm::StringRef Placeholder) { std::string Result; llvm::raw_string_ostream OS(Result); PrintingPolicy PP(CurContext.getParentASTContext().getPrintingPolicy()); PP.SuppressTagKeyword = true; PP.SuppressUnwrittenScope = true; class PrintCB : public PrintingCallbacks { public: PrintCB(const DeclContext *CurContext) : CurContext(CurContext) {} virtual ~PrintCB() {} bool isScopeVisible(const DeclContext *DC) const override { return DC->Encloses(CurContext); } private: const DeclContext *CurContext; }; PrintCB PCB(&CurContext); PP.Callbacks = &PCB; QT.print(OS, PP, Placeholder); return OS.str(); } bool hasReservedName(const Decl &D) { if (const auto *ND = llvm::dyn_cast(&D)) if (const auto *II = ND->getIdentifier()) return isReservedName(II->getName()); return false; } bool hasReservedScope(const DeclContext &DC) { for (const DeclContext *D = &DC; D; D = D->getParent()) { if (D->isTransparentContext() || D->isInlineNamespace()) continue; if (const auto *ND = llvm::dyn_cast(D)) if (hasReservedName(*ND)) return true; } return false; } QualType declaredType(const TypeDecl *D) { ASTContext &Context = D->getASTContext(); if (const auto *CTSD = llvm::dyn_cast(D)) if (const auto *Args = CTSD->getTemplateArgsAsWritten()) return Context.getTemplateSpecializationType( TemplateName(CTSD->getSpecializedTemplate()), Args->arguments(), /*CanonicalArgs=*/std::nullopt); return Context.getTypeDeclType(D); } namespace { /// Computes the deduced type at a given location by visiting the relevant /// nodes. We use this to display the actual type when hovering over an "auto" /// keyword or "decltype()" expression. /// FIXME: This could have been a lot simpler by visiting AutoTypeLocs but it /// seems that the AutoTypeLocs that can be visited along with their AutoType do /// not have the deduced type set. Instead, we have to go to the appropriate /// DeclaratorDecl/FunctionDecl and work our back to the AutoType that does have /// a deduced type set. The AST should be improved to simplify this scenario. class DeducedTypeVisitor : public RecursiveASTVisitor { SourceLocation SearchedLocation; public: DeducedTypeVisitor(SourceLocation SearchedLocation) : SearchedLocation(SearchedLocation) {} // Handle auto initializers: //- auto i = 1; //- decltype(auto) i = 1; //- auto& i = 1; //- auto* i = &a; bool VisitDeclaratorDecl(DeclaratorDecl *D) { if (!D->getTypeSourceInfo() || !D->getTypeSourceInfo()->getTypeLoc().getContainedAutoTypeLoc() || D->getTypeSourceInfo() ->getTypeLoc() .getContainedAutoTypeLoc() .getNameLoc() != SearchedLocation) return true; if (auto *AT = D->getType()->getContainedAutoType()) { DeducedType = AT->desugar(); } return true; } // Handle auto return types: //- auto foo() {} //- auto& foo() {} //- auto foo() -> int {} //- auto foo() -> decltype(1+1) {} //- operator auto() const { return 10; } bool VisitFunctionDecl(FunctionDecl *D) { if (!D->getTypeSourceInfo()) return true; // Loc of auto in return type (c++14). auto CurLoc = D->getReturnTypeSourceRange().getBegin(); // Loc of "auto" in operator auto() if (CurLoc.isInvalid() && isa(D)) CurLoc = D->getTypeSourceInfo()->getTypeLoc().getBeginLoc(); // Loc of "auto" in function with trailing return type (c++11). if (CurLoc.isInvalid()) CurLoc = D->getSourceRange().getBegin(); if (CurLoc != SearchedLocation) return true; const AutoType *AT = D->getReturnType()->getContainedAutoType(); if (AT && !AT->getDeducedType().isNull()) { DeducedType = AT->getDeducedType(); } else if (auto *DT = dyn_cast(D->getReturnType())) { // auto in a trailing return type just points to a DecltypeType and // getContainedAutoType does not unwrap it. if (!DT->getUnderlyingType().isNull()) DeducedType = DT->getUnderlyingType(); } else if (!D->getReturnType().isNull()) { DeducedType = D->getReturnType(); } return true; } // Handle non-auto decltype, e.g.: // - auto foo() -> decltype(expr) {} // - decltype(expr); bool VisitDecltypeTypeLoc(DecltypeTypeLoc TL) { if (TL.getBeginLoc() != SearchedLocation) return true; // A DecltypeType's underlying type can be another DecltypeType! E.g. // int I = 0; // decltype(I) J = I; // decltype(J) K = J; const DecltypeType *DT = dyn_cast(TL.getTypePtr()); while (DT && !DT->getUnderlyingType().isNull()) { DeducedType = DT->getUnderlyingType(); DT = dyn_cast(DeducedType.getTypePtr()); } return true; } // Handle functions/lambdas with `auto` typed parameters. // We deduce the type if there's exactly one instantiation visible. bool VisitParmVarDecl(ParmVarDecl *PVD) { if (!PVD->getType()->isDependentType()) return true; // 'auto' here does not name an AutoType, but an implicit template param. TemplateTypeParmTypeLoc Auto = getContainedAutoParamType(PVD->getTypeSourceInfo()->getTypeLoc()); if (Auto.isNull() || Auto.getNameLoc() != SearchedLocation) return true; // We expect the TTP to be attached to this function template. // Find the template and the param index. auto *Templated = llvm::dyn_cast(PVD->getDeclContext()); if (!Templated) return true; auto *FTD = Templated->getDescribedFunctionTemplate(); if (!FTD) return true; int ParamIndex = paramIndex(*FTD, *Auto.getDecl()); if (ParamIndex < 0) { assert(false && "auto TTP is not from enclosing function?"); return true; } // Now find the instantiation and the deduced template type arg. auto *Instantiation = llvm::dyn_cast_or_null(getOnlyInstantiation(Templated)); if (!Instantiation) return true; const auto *Args = Instantiation->getTemplateSpecializationArgs(); if (Args->size() != FTD->getTemplateParameters()->size()) return true; // no weird variadic stuff DeducedType = Args->get(ParamIndex).getAsType(); return true; } static int paramIndex(const TemplateDecl &TD, NamedDecl &Param) { unsigned I = 0; for (auto *ND : *TD.getTemplateParameters()) { if (&Param == ND) return I; ++I; } return -1; } QualType DeducedType; }; } // namespace std::optional getDeducedType(ASTContext &ASTCtx, SourceLocation Loc) { if (!Loc.isValid()) return {}; DeducedTypeVisitor V(Loc); V.TraverseAST(ASTCtx); if (V.DeducedType.isNull()) return std::nullopt; return V.DeducedType; } TemplateTypeParmTypeLoc getContainedAutoParamType(TypeLoc TL) { if (auto QTL = TL.getAs()) return getContainedAutoParamType(QTL.getUnqualifiedLoc()); if (llvm::isa(TL.getTypePtr())) return getContainedAutoParamType(TL.getNextTypeLoc()); if (auto FTL = TL.getAs()) return getContainedAutoParamType(FTL.getReturnLoc()); if (auto TTPTL = TL.getAs()) { if (TTPTL.getTypePtr()->getDecl()->isImplicit()) return TTPTL; } return {}; } template static NamedDecl *getOnlyInstantiationImpl(TemplateDeclTy *TD) { NamedDecl *Only = nullptr; for (auto *Spec : TD->specializations()) { if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) continue; if (Only != nullptr) return nullptr; Only = Spec; } return Only; } NamedDecl *getOnlyInstantiation(NamedDecl *TemplatedDecl) { if (TemplateDecl *TD = TemplatedDecl->getDescribedTemplate()) { if (auto *CTD = llvm::dyn_cast(TD)) return getOnlyInstantiationImpl(CTD); if (auto *FTD = llvm::dyn_cast(TD)) return getOnlyInstantiationImpl(FTD); if (auto *VTD = llvm::dyn_cast(TD)) return getOnlyInstantiationImpl(VTD); } return nullptr; } std::vector getAttributes(const DynTypedNode &N) { std::vector Result; if (const auto *TL = N.get()) { for (AttributedTypeLoc ATL = TL->getAs(); !ATL.isNull(); ATL = ATL.getModifiedLoc().getAs()) { if (const Attr *A = ATL.getAttr()) Result.push_back(A); assert(!ATL.getModifiedLoc().isNull()); } } if (const auto *S = N.get()) { for (; S != nullptr; S = dyn_cast(S->getSubStmt())) for (const Attr *A : S->getAttrs()) if (A) Result.push_back(A); } if (const auto *D = N.get()) { for (const Attr *A : D->attrs()) if (A) Result.push_back(A); } return Result; } std::string getQualification(ASTContext &Context, const DeclContext *DestContext, SourceLocation InsertionPoint, const NamedDecl *ND) { auto VisibleNamespaceDecls = getUsingNamespaceDirectives(DestContext, InsertionPoint); return getQualification( Context, DestContext, ND->getDeclContext(), [&](NestedNameSpecifier *NNS) { if (NNS->getKind() != NestedNameSpecifier::Namespace) return false; const auto *CanonNSD = NNS->getAsNamespace()->getCanonicalDecl(); return llvm::any_of(VisibleNamespaceDecls, [CanonNSD](const NamespaceDecl *NSD) { return NSD->getCanonicalDecl() == CanonNSD; }); }); } std::string getQualification(ASTContext &Context, const DeclContext *DestContext, const NamedDecl *ND, llvm::ArrayRef VisibleNamespaces) { for (llvm::StringRef NS : VisibleNamespaces) { assert(NS.ends_with("::")); (void)NS; } return getQualification( Context, DestContext, ND->getDeclContext(), [&](NestedNameSpecifier *NNS) { return llvm::any_of(VisibleNamespaces, [&](llvm::StringRef Namespace) { std::string NS; llvm::raw_string_ostream OS(NS); NNS->print(OS, Context.getPrintingPolicy()); return OS.str() == Namespace; }); }); } bool hasUnstableLinkage(const Decl *D) { // Linkage of a ValueDecl depends on the type. // If that's not deduced yet, deducing it may change the linkage. auto *VD = llvm::dyn_cast_or_null(D); return VD && !VD->getType().isNull() && VD->getType()->isUndeducedType(); } bool isDeeplyNested(const Decl *D, unsigned MaxDepth) { size_t ContextDepth = 0; for (auto *Ctx = D->getDeclContext(); Ctx && !Ctx->isTranslationUnit(); Ctx = Ctx->getParent()) { if (++ContextDepth == MaxDepth) return true; } return false; } namespace { // returns true for `X` in `template void foo()` bool isTemplateTypeParameterPack(NamedDecl *D) { if (const auto *TTPD = dyn_cast(D)) { return TTPD->isParameterPack(); } return false; } // Returns the template parameter pack type from an instantiated function // template, if it exists, nullptr otherwise. const TemplateTypeParmType *getFunctionPackType(const FunctionDecl *Callee) { if (const auto *TemplateDecl = Callee->getPrimaryTemplate()) { auto TemplateParams = TemplateDecl->getTemplateParameters()->asArray(); // find the template parameter pack from the back const auto It = std::find_if(TemplateParams.rbegin(), TemplateParams.rend(), isTemplateTypeParameterPack); if (It != TemplateParams.rend()) { const auto *TTPD = dyn_cast(*It); return TTPD->getTypeForDecl()->castAs(); } } return nullptr; } // Returns the template parameter pack type that this parameter was expanded // from (if in the Args... or Args&... or Args&&... form), if this is the case, // nullptr otherwise. const TemplateTypeParmType *getUnderlyingPackType(const ParmVarDecl *Param) { const auto *PlainType = Param->getType().getTypePtr(); if (auto *RT = dyn_cast(PlainType)) PlainType = RT->getPointeeTypeAsWritten().getTypePtr(); if (const auto *SubstType = dyn_cast(PlainType)) { const auto *ReplacedParameter = SubstType->getReplacedParameter(); if (ReplacedParameter->isParameterPack()) { return ReplacedParameter->getTypeForDecl() ->castAs(); } } return nullptr; } // This visitor walks over the body of an instantiated function template. // The template accepts a parameter pack and the visitor records whether // the pack parameters were forwarded to another call. For example, given: // // template // auto make_unique(Args... args) { // return unique_ptr(new T(args...)); // } // // When called as `make_unique(2, 'x')` this yields a function // `make_unique` with two parameters. // The visitor records that those two parameters are forwarded to the // `constructor std::string(int, char);`. // // This information is recorded in the `ForwardingInfo` split into fully // resolved parameters (passed as argument to a parameter that is not an // expanded template type parameter pack) and forwarding parameters (passed to a // parameter that is an expanded template type parameter pack). class ForwardingCallVisitor : public RecursiveASTVisitor { public: ForwardingCallVisitor(ArrayRef Parameters) : Parameters{Parameters}, PackType{getUnderlyingPackType(Parameters.front())} {} bool VisitCallExpr(CallExpr *E) { auto *Callee = getCalleeDeclOrUniqueOverload(E); if (Callee) { handleCall(Callee, E->arguments()); } return !Info.has_value(); } bool VisitCXXConstructExpr(CXXConstructExpr *E) { auto *Callee = E->getConstructor(); if (Callee) { handleCall(Callee, E->arguments()); } return !Info.has_value(); } // The expanded parameter pack to be resolved ArrayRef Parameters; // The type of the parameter pack const TemplateTypeParmType *PackType; struct ForwardingInfo { // If the parameters were resolved to another FunctionDecl, these are its // first non-variadic parameters (i.e. the first entries of the parameter // pack that are passed as arguments bound to a non-pack parameter.) ArrayRef Head; // If the parameters were resolved to another FunctionDecl, these are its // variadic parameters (i.e. the entries of the parameter pack that are // passed as arguments bound to a pack parameter.) ArrayRef Pack; // If the parameters were resolved to another FunctionDecl, these are its // last non-variadic parameters (i.e. the last entries of the parameter pack // that are passed as arguments bound to a non-pack parameter.) ArrayRef Tail; // If the parameters were resolved to another forwarding FunctionDecl, this // is it. std::optional PackTarget; }; // The output of this visitor std::optional Info; private: // inspects the given callee with the given args to check whether it // contains Parameters, and sets Info accordingly. void handleCall(FunctionDecl *Callee, typename CallExpr::arg_range Args) { // Skip functions with less parameters, they can't be the target. if (Callee->parameters().size() < Parameters.size()) return; if (llvm::any_of(Args, [](const Expr *E) { return isa(E); })) { return; } auto PackLocation = findPack(Args); if (!PackLocation) return; ArrayRef MatchingParams = Callee->parameters().slice(*PackLocation, Parameters.size()); // Check whether the function has a parameter pack as the last template // parameter if (const auto *TTPT = getFunctionPackType(Callee)) { // In this case: Separate the parameters into head, pack and tail auto IsExpandedPack = [&](const ParmVarDecl *P) { return getUnderlyingPackType(P) == TTPT; }; ForwardingInfo FI; FI.Head = MatchingParams.take_until(IsExpandedPack); FI.Pack = MatchingParams.drop_front(FI.Head.size()).take_while(IsExpandedPack); FI.Tail = MatchingParams.drop_front(FI.Head.size() + FI.Pack.size()); FI.PackTarget = Callee; Info = FI; return; } // Default case: assume all parameters were fully resolved ForwardingInfo FI; FI.Head = MatchingParams; Info = FI; } // Returns the beginning of the expanded pack represented by Parameters // in the given arguments, if it is there. std::optional findPack(typename CallExpr::arg_range Args) { // find the argument directly referring to the first parameter assert(Parameters.size() <= static_cast(llvm::size(Args))); for (auto Begin = Args.begin(), End = Args.end() - Parameters.size() + 1; Begin != End; ++Begin) { if (const auto *RefArg = unwrapForward(*Begin)) { if (Parameters.front() != RefArg->getDecl()) continue; // Check that this expands all the way until the last parameter. // It's enough to look at the last parameter, because it isn't possible // to expand without expanding all of them. auto ParamEnd = Begin + Parameters.size() - 1; RefArg = unwrapForward(*ParamEnd); if (!RefArg || Parameters.back() != RefArg->getDecl()) continue; return std::distance(Args.begin(), Begin); } } return std::nullopt; } static FunctionDecl *getCalleeDeclOrUniqueOverload(CallExpr *E) { Decl *CalleeDecl = E->getCalleeDecl(); auto *Callee = dyn_cast_or_null(CalleeDecl); if (!Callee) { if (auto *Lookup = dyn_cast(E->getCallee())) { Callee = resolveOverload(Lookup, E); } } // Ignore the callee if the number of arguments is wrong (deal with va_args) if (Callee && Callee->getNumParams() == E->getNumArgs()) return Callee; return nullptr; } static FunctionDecl *resolveOverload(UnresolvedLookupExpr *Lookup, CallExpr *E) { FunctionDecl *MatchingDecl = nullptr; if (!Lookup->requiresADL()) { // Check whether there is a single overload with this number of // parameters for (auto *Candidate : Lookup->decls()) { if (auto *FuncCandidate = dyn_cast_or_null(Candidate)) { if (FuncCandidate->getNumParams() == E->getNumArgs()) { if (MatchingDecl) { // there are multiple candidates - abort return nullptr; } MatchingDecl = FuncCandidate; } } } } return MatchingDecl; } // Tries to get to the underlying argument by unwrapping implicit nodes and // std::forward. static const DeclRefExpr *unwrapForward(const Expr *E) { E = E->IgnoreImplicitAsWritten(); // There might be an implicit copy/move constructor call on top of the // forwarded arg. // FIXME: Maybe mark implicit calls in the AST to properly filter here. if (const auto *Const = dyn_cast(E)) if (Const->getConstructor()->isCopyOrMoveConstructor()) E = Const->getArg(0)->IgnoreImplicitAsWritten(); if (const auto *Call = dyn_cast(E)) { const auto Callee = Call->getBuiltinCallee(); if (Callee == Builtin::BIforward) { return dyn_cast( Call->getArg(0)->IgnoreImplicitAsWritten()); } } return dyn_cast(E); } }; } // namespace SmallVector resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) { auto Parameters = D->parameters(); // If the function has a template parameter pack if (const auto *TTPT = getFunctionPackType(D)) { // Split the parameters into head, pack and tail auto IsExpandedPack = [TTPT](const ParmVarDecl *P) { return getUnderlyingPackType(P) == TTPT; }; ArrayRef Head = Parameters.take_until(IsExpandedPack); ArrayRef Pack = Parameters.drop_front(Head.size()).take_while(IsExpandedPack); ArrayRef Tail = Parameters.drop_front(Head.size() + Pack.size()); SmallVector Result(Parameters.size()); // Fill in non-pack parameters auto *HeadIt = std::copy(Head.begin(), Head.end(), Result.begin()); auto TailIt = std::copy(Tail.rbegin(), Tail.rend(), Result.rbegin()); // Recurse on pack parameters size_t Depth = 0; const FunctionDecl *CurrentFunction = D; llvm::SmallSet SeenTemplates; if (const auto *Template = D->getPrimaryTemplate()) { SeenTemplates.insert(Template); } while (!Pack.empty() && CurrentFunction && Depth < MaxDepth) { // Find call expressions involving the pack ForwardingCallVisitor V{Pack}; V.TraverseStmt(CurrentFunction->getBody()); if (!V.Info) { break; } // If we found something: Fill in non-pack parameters auto Info = *V.Info; HeadIt = std::copy(Info.Head.begin(), Info.Head.end(), HeadIt); TailIt = std::copy(Info.Tail.rbegin(), Info.Tail.rend(), TailIt); // Prepare next recursion level Pack = Info.Pack; CurrentFunction = Info.PackTarget.value_or(nullptr); Depth++; // If we are recursing into a previously encountered function: Abort if (CurrentFunction) { if (const auto *Template = CurrentFunction->getPrimaryTemplate()) { bool NewFunction = SeenTemplates.insert(Template).second; if (!NewFunction) { return {Parameters.begin(), Parameters.end()}; } } } } // Fill in the remaining unresolved pack parameters HeadIt = std::copy(Pack.begin(), Pack.end(), HeadIt); assert(TailIt.base() == HeadIt); return Result; } return {Parameters.begin(), Parameters.end()}; } bool isExpandedFromParameterPack(const ParmVarDecl *D) { return getUnderlyingPackType(D) != nullptr; } } // namespace clangd } // namespace clang