brintos

brintos / llvm-project-archived public Read only

0
0
Text · 45.1 KiB · 23bd023 Raw
1194 lines · cpp
1//===--- InlayHints.cpp ------------------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "InlayHints.h"9#include "../clang-tidy/utils/DesignatedInitializers.h"10#include "AST.h"11#include "Config.h"12#include "ParsedAST.h"13#include "Protocol.h"14#include "SourceCode.h"15#include "clang/AST/ASTDiagnostic.h"16#include "clang/AST/Decl.h"17#include "clang/AST/DeclBase.h"18#include "clang/AST/DeclarationName.h"19#include "clang/AST/Expr.h"20#include "clang/AST/ExprCXX.h"21#include "clang/AST/RecursiveASTVisitor.h"22#include "clang/AST/Stmt.h"23#include "clang/AST/StmtVisitor.h"24#include "clang/AST/Type.h"25#include "clang/Basic/Builtins.h"26#include "clang/Basic/OperatorKinds.h"27#include "clang/Basic/SourceLocation.h"28#include "clang/Basic/SourceManager.h"29#include "clang/Sema/HeuristicResolver.h"30#include "llvm/ADT/DenseSet.h"31#include "llvm/ADT/STLExtras.h"32#include "llvm/ADT/SmallVector.h"33#include "llvm/ADT/StringExtras.h"34#include "llvm/ADT/StringRef.h"35#include "llvm/ADT/Twine.h"36#include "llvm/Support/Casting.h"37#include "llvm/Support/ErrorHandling.h"38#include "llvm/Support/FormatVariadic.h"39#include "llvm/Support/SaveAndRestore.h"40#include "llvm/Support/ScopedPrinter.h"41#include "llvm/Support/raw_ostream.h"42#include <algorithm>43#include <iterator>44#include <optional>45#include <string>46 47namespace clang {48namespace clangd {49namespace {50 51// For now, inlay hints are always anchored at the left or right of their range.52enum class HintSide { Left, Right };53 54void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim('_'); }55 56// getDeclForType() returns the decl responsible for Type's spelling.57// This is the inverse of ASTContext::getTypeDeclType().58const NamedDecl *getDeclForType(const Type *T) {59  switch (T->getTypeClass()) {60  case Type::Enum:61  case Type::Record:62  case Type::InjectedClassName:63    return cast<TagType>(T)->getDecl();64  case Type::TemplateSpecialization:65    return cast<TemplateSpecializationType>(T)66        ->getTemplateName()67        .getAsTemplateDecl(/*IgnoreDeduced=*/true);68  case Type::Typedef:69    return cast<TypedefType>(T)->getDecl();70  case Type::UnresolvedUsing:71    return cast<UnresolvedUsingType>(T)->getDecl();72  case Type::Using:73    return cast<UsingType>(T)->getDecl();74  default:75    return nullptr;76  }77  llvm_unreachable("Unknown TypeClass enum");78}79 80// getSimpleName() returns the plain identifier for an entity, if any.81llvm::StringRef getSimpleName(const DeclarationName &DN) {82  if (IdentifierInfo *Ident = DN.getAsIdentifierInfo())83    return Ident->getName();84  return "";85}86llvm::StringRef getSimpleName(const NamedDecl &D) {87  return getSimpleName(D.getDeclName());88}89llvm::StringRef getSimpleName(QualType T) {90  if (const auto *BT = llvm::dyn_cast<BuiltinType>(T)) {91    PrintingPolicy PP(LangOptions{});92    PP.adjustForCPlusPlus();93    return BT->getName(PP);94  }95  if (const auto *D = getDeclForType(T.getTypePtr()))96    return getSimpleName(D->getDeclName());97  return "";98}99 100// Returns a very abbreviated form of an expression, or "" if it's too complex.101// For example: `foo->bar()` would produce "bar".102// This is used to summarize e.g. the condition of a while loop.103std::string summarizeExpr(const Expr *E) {104  struct Namer : ConstStmtVisitor<Namer, std::string> {105    std::string Visit(const Expr *E) {106      if (E == nullptr)107        return "";108      return ConstStmtVisitor::Visit(E->IgnoreImplicit());109    }110 111    // Any sort of decl reference, we just use the unqualified name.112    std::string VisitMemberExpr(const MemberExpr *E) {113      return getSimpleName(*E->getMemberDecl()).str();114    }115    std::string VisitDeclRefExpr(const DeclRefExpr *E) {116      return getSimpleName(*E->getFoundDecl()).str();117    }118    std::string VisitCallExpr(const CallExpr *E) {119      std::string Result = Visit(E->getCallee());120      Result += E->getNumArgs() == 0 ? "()" : "(...)";121      return Result;122    }123    std::string124    VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *E) {125      return getSimpleName(E->getMember()).str();126    }127    std::string128    VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr *E) {129      return getSimpleName(E->getDeclName()).str();130    }131    std::string VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *E) {132      return getSimpleName(E->getType()).str();133    }134    std::string VisitCXXTemporaryObjectExpr(const CXXTemporaryObjectExpr *E) {135      return getSimpleName(E->getType()).str();136    }137 138    // Step through implicit nodes that clang doesn't classify as such.139    std::string VisitCXXMemberCallExpr(const CXXMemberCallExpr *E) {140      // Call to operator bool() inside if (X): dispatch to X.141      if (E->getNumArgs() == 0 && E->getMethodDecl() &&142          E->getMethodDecl()->getDeclName().getNameKind() ==143              DeclarationName::CXXConversionFunctionName &&144          E->getSourceRange() ==145              E->getImplicitObjectArgument()->getSourceRange())146        return Visit(E->getImplicitObjectArgument());147      return ConstStmtVisitor::VisitCXXMemberCallExpr(E);148    }149    std::string VisitCXXConstructExpr(const CXXConstructExpr *E) {150      if (E->getNumArgs() == 1)151        return Visit(E->getArg(0));152      return "";153    }154 155    // Literals are just printed156    std::string VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *E) {157      return "nullptr";158    }159    std::string VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E) {160      return E->getValue() ? "true" : "false";161    }162    std::string VisitIntegerLiteral(const IntegerLiteral *E) {163      return llvm::to_string(E->getValue());164    }165    std::string VisitFloatingLiteral(const FloatingLiteral *E) {166      std::string Result;167      llvm::raw_string_ostream OS(Result);168      E->getValue().print(OS);169      // Printer adds newlines?!170      Result.resize(llvm::StringRef(Result).rtrim().size());171      return Result;172    }173    std::string VisitStringLiteral(const StringLiteral *E) {174      std::string Result = "\"";175      if (E->containsNonAscii()) {176        Result += "...";177      } else {178        llvm::raw_string_ostream OS(Result);179        if (E->getLength() > 10) {180          llvm::printEscapedString(E->getString().take_front(7), OS);181          Result += "...";182        } else {183          llvm::printEscapedString(E->getString(), OS);184        }185      }186      Result.push_back('"');187      return Result;188    }189 190    // Simple operators. Motivating cases are `!x` and `I < Length`.191    std::string printUnary(llvm::StringRef Spelling, const Expr *Operand,192                           bool Prefix) {193      std::string Sub = Visit(Operand);194      if (Sub.empty())195        return "";196      if (Prefix)197        return (Spelling + Sub).str();198      Sub += Spelling;199      return Sub;200    }201    bool InsideBinary = false; // No recursing into binary expressions.202    std::string printBinary(llvm::StringRef Spelling, const Expr *LHSOp,203                            const Expr *RHSOp) {204      if (InsideBinary)205        return "";206      llvm::SaveAndRestore InBinary(InsideBinary, true);207 208      std::string LHS = Visit(LHSOp);209      std::string RHS = Visit(RHSOp);210      if (LHS.empty() && RHS.empty())211        return "";212 213      if (LHS.empty())214        LHS = "...";215      LHS.push_back(' ');216      LHS += Spelling;217      LHS.push_back(' ');218      if (RHS.empty())219        LHS += "...";220      else221        LHS += RHS;222      return LHS;223    }224    std::string VisitUnaryOperator(const UnaryOperator *E) {225      return printUnary(E->getOpcodeStr(E->getOpcode()), E->getSubExpr(),226                        !E->isPostfix());227    }228    std::string VisitBinaryOperator(const BinaryOperator *E) {229      return printBinary(E->getOpcodeStr(E->getOpcode()), E->getLHS(),230                         E->getRHS());231    }232    std::string VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *E) {233      const char *Spelling = getOperatorSpelling(E->getOperator());234      // Handle weird unary-that-look-like-binary postfix operators.235      if ((E->getOperator() == OO_PlusPlus ||236           E->getOperator() == OO_MinusMinus) &&237          E->getNumArgs() == 2)238        return printUnary(Spelling, E->getArg(0), false);239      if (E->isInfixBinaryOp())240        return printBinary(Spelling, E->getArg(0), E->getArg(1));241      if (E->getNumArgs() == 1) {242        switch (E->getOperator()) {243        case OO_Plus:244        case OO_Minus:245        case OO_Star:246        case OO_Amp:247        case OO_Tilde:248        case OO_Exclaim:249        case OO_PlusPlus:250        case OO_MinusMinus:251          return printUnary(Spelling, E->getArg(0), true);252        default:253          break;254        }255      }256      return "";257    }258  };259  return Namer{}.Visit(E);260}261 262// Determines if any intermediate type in desugaring QualType QT is of263// substituted template parameter type. Ignore pointer or reference wrappers.264bool isSugaredTemplateParameter(QualType QT) {265  static auto PeelWrapper = [](QualType QT) {266    // Neither `PointerType` nor `ReferenceType` is considered as sugared267    // type. Peel it.268    QualType Peeled = QT->getPointeeType();269    return Peeled.isNull() ? QT : Peeled;270  };271 272  // This is a bit tricky: we traverse the type structure and find whether or273  // not a type in the desugaring process is of SubstTemplateTypeParmType.274  // During the process, we may encounter pointer or reference types that are275  // not marked as sugared; therefore, the desugar function won't apply. To276  // move forward the traversal, we retrieve the pointees using277  // QualType::getPointeeType().278  //279  // However, getPointeeType could leap over our interests: The QT::getAs<T>()280  // invoked would implicitly desugar the type. Consequently, if the281  // SubstTemplateTypeParmType is encompassed within a TypedefType, we may lose282  // the chance to visit it.283  // For example, given a QT that represents `std::vector<int *>::value_type`:284  //  `-ElaboratedType 'value_type' sugar285  //    `-TypedefType 'vector<int *>::value_type' sugar286  //      |-Typedef 'value_type'287  //      `-SubstTemplateTypeParmType 'int *' sugar class depth 0 index 0 T288  //        |-ClassTemplateSpecialization 'vector'289  //        `-PointerType 'int *'290  //          `-BuiltinType 'int'291  // Applying `getPointeeType` to QT results in 'int', a child of our target292  // node SubstTemplateTypeParmType.293  //294  // As such, we always prefer the desugared over the pointee for next type295  // in the iteration. It could avoid the getPointeeType's implicit desugaring.296  while (true) {297    if (QT->getAs<SubstTemplateTypeParmType>())298      return true;299    QualType Desugared = QT->getLocallyUnqualifiedSingleStepDesugaredType();300    if (Desugared != QT)301      QT = Desugared;302    else if (auto Peeled = PeelWrapper(Desugared); Peeled != QT)303      QT = Peeled;304    else305      break;306  }307  return false;308}309 310// A simple wrapper for `clang::desugarForDiagnostic` that provides optional311// semantic.312std::optional<QualType> desugar(ASTContext &AST, QualType QT) {313  bool ShouldAKA = false;314  auto Desugared = clang::desugarForDiagnostic(AST, QT, ShouldAKA);315  if (!ShouldAKA)316    return std::nullopt;317  return Desugared;318}319 320// Apply a series of heuristic methods to determine whether or not a QualType QT321// is suitable for desugaring (e.g. getting the real name behind the using-alias322// name). If so, return the desugared type. Otherwise, return the unchanged323// parameter QT.324//325// This could be refined further. See326// https://github.com/clangd/clangd/issues/1298.327QualType maybeDesugar(ASTContext &AST, QualType QT) {328  // Prefer desugared type for name that aliases the template parameters.329  // This can prevent things like printing opaque `: type` when accessing std330  // containers.331  if (isSugaredTemplateParameter(QT))332    return desugar(AST, QT).value_or(QT);333 334  // Prefer desugared type for `decltype(expr)` specifiers.335  if (QT->isDecltypeType())336    return QT.getCanonicalType();337  if (const AutoType *AT = QT->getContainedAutoType())338    if (!AT->getDeducedType().isNull() &&339        AT->getDeducedType()->isDecltypeType())340      return QT.getCanonicalType();341 342  return QT;343}344 345ArrayRef<const ParmVarDecl *>346maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {347  if (!Params.empty() && Params.front()->isExplicitObjectParameter())348    Params = Params.drop_front(1);349  return Params;350}351 352template <typename R>353std::string joinAndTruncate(const R &Range, size_t MaxLength) {354  std::string Out;355  llvm::raw_string_ostream OS(Out);356  llvm::ListSeparator Sep(", ");357  for (auto &&Element : Range) {358    OS << Sep;359    if (Out.size() + Element.size() >= MaxLength) {360      OS << "...";361      break;362    }363    OS << Element;364  }365  OS.flush();366  return Out;367}368 369struct Callee {370  // Only one of Decl or Loc is set.371  // Loc is for calls through function pointers.372  const FunctionDecl *Decl = nullptr;373  FunctionProtoTypeLoc Loc;374};375 376class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {377public:378  InlayHintVisitor(std::vector<InlayHint> &Results, ParsedAST &AST,379                   const Config &Cfg, std::optional<Range> RestrictRange,380                   InlayHintOptions HintOptions)381      : Results(Results), AST(AST.getASTContext()), Tokens(AST.getTokens()),382        Cfg(Cfg), RestrictRange(std::move(RestrictRange)),383        MainFileID(AST.getSourceManager().getMainFileID()),384        Resolver(AST.getHeuristicResolver()),385        TypeHintPolicy(this->AST.getPrintingPolicy()),386        HintOptions(HintOptions) {387    bool Invalid = false;388    llvm::StringRef Buf =389        AST.getSourceManager().getBufferData(MainFileID, &Invalid);390    MainFileBuf = Invalid ? StringRef{} : Buf;391 392    TypeHintPolicy.SuppressScope = true; // keep type names short393    TypeHintPolicy.AnonymousTagLocations =394        false; // do not print lambda locations395 396    // Not setting PrintCanonicalTypes for "auto" allows397    // SuppressDefaultTemplateArgs (set by default) to have an effect.398  }399 400  bool VisitTypeLoc(TypeLoc TL) {401    if (const auto *DT = llvm::dyn_cast<DecltypeType>(TL.getType()))402      if (QualType UT = DT->getUnderlyingType(); !UT->isDependentType())403        addTypeHint(TL.getSourceRange(), UT, ": ");404    return true;405  }406 407  bool VisitCXXConstructExpr(CXXConstructExpr *E) {408    // Weed out constructor calls that don't look like a function call with409    // an argument list, by checking the validity of getParenOrBraceRange().410    // Also weed out std::initializer_list constructors as there are no names411    // for the individual arguments.412    if (!E->getParenOrBraceRange().isValid() ||413        E->isStdInitListInitialization()) {414      return true;415    }416 417    Callee Callee;418    Callee.Decl = E->getConstructor();419    if (!Callee.Decl)420      return true;421    processCall(Callee, E->getParenOrBraceRange().getEnd(),422                {E->getArgs(), E->getNumArgs()});423    return true;424  }425 426  // Carefully recurse into PseudoObjectExprs, which typically incorporate427  // a syntactic expression and several semantic expressions.428  bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {429    Expr *SyntacticExpr = E->getSyntacticForm();430    if (isa<CallExpr>(SyntacticExpr))431      // Since the counterpart semantics usually get the identical source432      // locations as the syntactic one, visiting those would end up presenting433      // confusing hints e.g., __builtin_dump_struct.434      // Thus, only traverse the syntactic forms if this is written as a435      // CallExpr. This leaves the door open in case the arguments in the436      // syntactic form could possibly get parameter names.437      return RecursiveASTVisitor<InlayHintVisitor>::TraverseStmt(SyntacticExpr);438    // We don't want the hints for some of the MS property extensions.439    // e.g.440    // struct S {441    //   __declspec(property(get=GetX, put=PutX)) int x[];442    //   void PutX(int y);443    //   void Work(int y) { x = y; } // Bad: `x = y: y`.444    // };445    if (isa<BinaryOperator>(SyntacticExpr))446      return true;447    // FIXME: Handle other forms of a pseudo object expression.448    return RecursiveASTVisitor<InlayHintVisitor>::TraversePseudoObjectExpr(E);449  }450 451  bool VisitCallExpr(CallExpr *E) {452    if (!Cfg.InlayHints.Parameters)453      return true;454 455    bool IsFunctor = isFunctionObjectCallExpr(E);456    // Do not show parameter hints for user-defined literals or457    // operator calls except for operator(). (Among other reasons, the resulting458    // hints can look awkward, e.g. the expression can itself be a function459    // argument and then we'd get two hints side by side).460    if ((isa<CXXOperatorCallExpr>(E) && !IsFunctor) ||461        isa<UserDefinedLiteral>(E))462      return true;463 464    auto CalleeDecls = Resolver->resolveCalleeOfCallExpr(E);465    if (CalleeDecls.size() != 1)466      return true;467 468    Callee Callee;469    if (const auto *FD = dyn_cast<FunctionDecl>(CalleeDecls[0]))470      Callee.Decl = FD;471    else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CalleeDecls[0]))472      Callee.Decl = FTD->getTemplatedDecl();473    else if (FunctionProtoTypeLoc Loc =474                 Resolver->getFunctionProtoTypeLoc(E->getCallee()))475      Callee.Loc = Loc;476    else477      return true;478 479    // N4868 [over.call.object]p3 says,480    // The argument list submitted to overload resolution consists of the481    // argument expressions present in the function call syntax preceded by the482    // implied object argument (E).483    //484    // As well as the provision from P0847R7 Deducing This [expr.call]p7:485    // ...If the function is an explicit object member function and there is an486    // implied object argument ([over.call.func]), the list of provided487    // arguments is preceded by the implied object argument for the purposes of488    // this correspondence...489    llvm::ArrayRef<const Expr *> Args = {E->getArgs(), E->getNumArgs()};490    // We don't have the implied object argument through a function pointer491    // either.492    if (const CXXMethodDecl *Method =493            dyn_cast_or_null<CXXMethodDecl>(Callee.Decl))494      if (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter())495        Args = Args.drop_front(1);496    processCall(Callee, E->getRParenLoc(), Args);497    return true;498  }499 500  bool VisitFunctionDecl(FunctionDecl *D) {501    if (auto *FPT =502            llvm::dyn_cast<FunctionProtoType>(D->getType().getTypePtr())) {503      if (!FPT->hasTrailingReturn()) {504        if (auto FTL = D->getFunctionTypeLoc())505          addReturnTypeHint(D, FTL.getRParenLoc());506      }507    }508    if (Cfg.InlayHints.BlockEnd && D->isThisDeclarationADefinition()) {509      // We use `printName` here to properly print name of ctor/dtor/operator510      // overload.511      if (const Stmt *Body = D->getBody())512        addBlockEndHint(Body->getSourceRange(), "", printName(AST, *D), "");513    }514    return true;515  }516 517  bool VisitForStmt(ForStmt *S) {518    if (Cfg.InlayHints.BlockEnd) {519      std::string Name;520      // Common case: for (int I = 0; I < N; I++). Use "I" as the name.521      if (auto *DS = llvm::dyn_cast_or_null<DeclStmt>(S->getInit());522          DS && DS->isSingleDecl())523        Name = getSimpleName(llvm::cast<NamedDecl>(*DS->getSingleDecl()));524      else525        Name = summarizeExpr(S->getCond());526      markBlockEnd(S->getBody(), "for", Name);527    }528    return true;529  }530 531  bool VisitCXXForRangeStmt(CXXForRangeStmt *S) {532    if (Cfg.InlayHints.BlockEnd)533      markBlockEnd(S->getBody(), "for", getSimpleName(*S->getLoopVariable()));534    return true;535  }536 537  bool VisitWhileStmt(WhileStmt *S) {538    if (Cfg.InlayHints.BlockEnd)539      markBlockEnd(S->getBody(), "while", summarizeExpr(S->getCond()));540    return true;541  }542 543  bool VisitSwitchStmt(SwitchStmt *S) {544    if (Cfg.InlayHints.BlockEnd)545      markBlockEnd(S->getBody(), "switch", summarizeExpr(S->getCond()));546    return true;547  }548 549  // If/else chains are tricky.550  //   if (cond1) {551  //   } else if (cond2) {552  //   } // mark as "cond1" or "cond2"?553  // For now, the answer is neither, just mark as "if".554  // The ElseIf is a different IfStmt that doesn't know about the outer one.555  llvm::DenseSet<const IfStmt *> ElseIfs; // not eligible for names556  bool VisitIfStmt(IfStmt *S) {557    if (Cfg.InlayHints.BlockEnd) {558      if (const auto *ElseIf = llvm::dyn_cast_or_null<IfStmt>(S->getElse()))559        ElseIfs.insert(ElseIf);560      // Don't use markBlockEnd: the relevant range is [then.begin, else.end].561      if (const auto *EndCS = llvm::dyn_cast<CompoundStmt>(562              S->getElse() ? S->getElse() : S->getThen())) {563        addBlockEndHint(564            {S->getThen()->getBeginLoc(), EndCS->getRBracLoc()}, "if",565            ElseIfs.contains(S) ? "" : summarizeExpr(S->getCond()), "");566      }567    }568    return true;569  }570 571  void markBlockEnd(const Stmt *Body, llvm::StringRef Label,572                    llvm::StringRef Name = "") {573    if (const auto *CS = llvm::dyn_cast_or_null<CompoundStmt>(Body))574      addBlockEndHint(CS->getSourceRange(), Label, Name, "");575  }576 577  bool VisitTagDecl(TagDecl *D) {578    if (Cfg.InlayHints.BlockEnd && D->isThisDeclarationADefinition()) {579      std::string DeclPrefix = D->getKindName().str();580      if (const auto *ED = dyn_cast<EnumDecl>(D)) {581        if (ED->isScoped())582          DeclPrefix += ED->isScopedUsingClassTag() ? " class" : " struct";583      };584      addBlockEndHint(D->getBraceRange(), DeclPrefix, getSimpleName(*D), ";");585    }586    return true;587  }588 589  bool VisitNamespaceDecl(NamespaceDecl *D) {590    if (Cfg.InlayHints.BlockEnd) {591      // For namespace, the range actually starts at the namespace keyword. But592      // it should be fine since it's usually very short.593      addBlockEndHint(D->getSourceRange(), "namespace", getSimpleName(*D), "");594    }595    return true;596  }597 598  bool VisitLambdaExpr(LambdaExpr *E) {599    FunctionDecl *D = E->getCallOperator();600    if (!E->hasExplicitResultType()) {601      SourceLocation TypeHintLoc;602      if (!E->hasExplicitParameters())603        TypeHintLoc = E->getIntroducerRange().getEnd();604      else if (auto FTL = D->getFunctionTypeLoc())605        TypeHintLoc = FTL.getRParenLoc();606      if (TypeHintLoc.isValid())607        addReturnTypeHint(D, TypeHintLoc);608    }609    return true;610  }611 612  void addReturnTypeHint(FunctionDecl *D, SourceRange Range) {613    auto *AT = D->getReturnType()->getContainedAutoType();614    if (!AT || AT->getDeducedType().isNull())615      return;616    addTypeHint(Range, D->getReturnType(), /*Prefix=*/"-> ");617  }618 619  bool VisitVarDecl(VarDecl *D) {620    // Do not show hints for the aggregate in a structured binding,621    // but show hints for the individual bindings.622    if (auto *DD = dyn_cast<DecompositionDecl>(D)) {623      for (auto *Binding : DD->bindings()) {624        // For structured bindings, print canonical types. This is important625        // because for bindings that use the tuple_element protocol, the626        // non-canonical types would be "tuple_element<I, A>::type".627        if (auto Type = Binding->getType();628            !Type.isNull() && !Type->isDependentType())629          addTypeHint(Binding->getLocation(), Type.getCanonicalType(),630                      /*Prefix=*/": ");631      }632      return true;633    }634 635    if (auto *AT = D->getType()->getContainedAutoType()) {636      if (AT->isDeduced()) {637        QualType T;638        // If the type is dependent, HeuristicResolver *may* be able to639        // resolve it to something that's useful to print. In other640        // cases, it can't, and the resultng type would just be printed641        // as "<dependent type>", in which case don't hint it at all.642        if (D->getType()->isDependentType()) {643          if (D->hasInit()) {644            QualType Resolved = Resolver->resolveExprToType(D->getInit());645            if (Resolved != AST.DependentTy) {646              T = Resolved;647            }648          }649        } else {650          T = D->getType();651        }652        if (!T.isNull()) {653          // Our current approach is to place the hint on the variable654          // and accordingly print the full type655          // (e.g. for `const auto& x = 42`, print `const int&`).656          // Alternatively, we could place the hint on the `auto`657          // (and then just print the type deduced for the `auto`).658          addTypeHint(D->getLocation(), T, /*Prefix=*/": ");659        }660      }661    }662 663    // Handle templates like `int foo(auto x)` with exactly one instantiation.664    if (auto *PVD = llvm::dyn_cast<ParmVarDecl>(D)) {665      if (D->getIdentifier() && PVD->getType()->isDependentType() &&666          !getContainedAutoParamType(D->getTypeSourceInfo()->getTypeLoc())667               .isNull()) {668        if (auto *IPVD = getOnlyParamInstantiation(PVD))669          addTypeHint(D->getLocation(), IPVD->getType(), /*Prefix=*/": ");670      }671    }672 673    return true;674  }675 676  ParmVarDecl *getOnlyParamInstantiation(ParmVarDecl *D) {677    auto *TemplateFunction = llvm::dyn_cast<FunctionDecl>(D->getDeclContext());678    if (!TemplateFunction)679      return nullptr;680    auto *InstantiatedFunction = llvm::dyn_cast_or_null<FunctionDecl>(681        getOnlyInstantiation(TemplateFunction));682    if (!InstantiatedFunction)683      return nullptr;684 685    unsigned ParamIdx = 0;686    for (auto *Param : TemplateFunction->parameters()) {687      // Can't reason about param indexes in the presence of preceding packs.688      // And if this param is a pack, it may expand to multiple params.689      if (Param->isParameterPack())690        return nullptr;691      if (Param == D)692        break;693      ++ParamIdx;694    }695    assert(ParamIdx < TemplateFunction->getNumParams() &&696           "Couldn't find param in list?");697    assert(ParamIdx < InstantiatedFunction->getNumParams() &&698           "Instantiated function has fewer (non-pack) parameters?");699    return InstantiatedFunction->getParamDecl(ParamIdx);700  }701 702  bool VisitInitListExpr(InitListExpr *Syn) {703    // We receive the syntactic form here (shouldVisitImplicitCode() is false).704    // This is the one we will ultimately attach designators to.705    // It may have subobject initializers inlined without braces. The *semantic*706    // form of the init-list has nested init-lists for these.707    // getUnwrittenDesignators will look at the semantic form to determine the708    // labels.709    assert(Syn->isSyntacticForm() && "RAV should not visit implicit code!");710    if (!Cfg.InlayHints.Designators)711      return true;712    if (Syn->isIdiomaticZeroInitializer(AST.getLangOpts()))713      return true;714    llvm::DenseMap<SourceLocation, std::string> Designators =715        tidy::utils::getUnwrittenDesignators(Syn);716    for (const Expr *Init : Syn->inits()) {717      if (llvm::isa<DesignatedInitExpr>(Init))718        continue;719      auto It = Designators.find(Init->getBeginLoc());720      if (It != Designators.end() &&721          !isPrecededByParamNameComment(Init, It->second))722        addDesignatorHint(Init->getSourceRange(), It->second);723    }724    return true;725  }726 727  // FIXME: Handle RecoveryExpr to try to hint some invalid calls.728 729private:730  using NameVec = SmallVector<StringRef, 8>;731 732  void processCall(Callee Callee, SourceLocation RParenOrBraceLoc,733                   llvm::ArrayRef<const Expr *> Args) {734    assert(Callee.Decl || Callee.Loc);735 736    if ((!Cfg.InlayHints.Parameters && !Cfg.InlayHints.DefaultArguments) ||737        Args.size() == 0)738      return;739 740    // The parameter name of a move or copy constructor is not very interesting.741    if (Callee.Decl)742      if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee.Decl))743        if (Ctor->isCopyOrMoveConstructor())744          return;745 746    SmallVector<std::string> FormattedDefaultArgs;747    bool HasNonDefaultArgs = false;748 749    ArrayRef<const ParmVarDecl *> Params, ForwardedParams;750    // Resolve parameter packs to their forwarded parameter751    SmallVector<const ParmVarDecl *> ForwardedParamsStorage;752    if (Callee.Decl) {753      Params = maybeDropCxxExplicitObjectParameters(Callee.Decl->parameters());754      ForwardedParamsStorage = resolveForwardingParameters(Callee.Decl);755      ForwardedParams =756          maybeDropCxxExplicitObjectParameters(ForwardedParamsStorage);757    } else {758      Params = maybeDropCxxExplicitObjectParameters(Callee.Loc.getParams());759      ForwardedParams = {Params.begin(), Params.end()};760    }761 762    NameVec ParameterNames = chooseParameterNames(ForwardedParams);763 764    // Exclude setters (i.e. functions with one argument whose name begins with765    // "set"), and builtins like std::move/forward/... as their parameter name766    // is also not likely to be interesting.767    if (Callee.Decl &&768        (isSetter(Callee.Decl, ParameterNames) || isSimpleBuiltin(Callee.Decl)))769      return;770 771    for (size_t I = 0; I < ParameterNames.size() && I < Args.size(); ++I) {772      // Pack expansion expressions cause the 1:1 mapping between arguments and773      // parameters to break down, so we don't add further inlay hints if we774      // encounter one.775      if (isa<PackExpansionExpr>(Args[I])) {776        break;777      }778 779      StringRef Name = ParameterNames[I];780      const bool NameHint =781          shouldHintName(Args[I], Name) && Cfg.InlayHints.Parameters;782      const bool ReferenceHint =783          shouldHintReference(Params[I], ForwardedParams[I]) &&784          Cfg.InlayHints.Parameters;785 786      const bool IsDefault = isa<CXXDefaultArgExpr>(Args[I]);787      HasNonDefaultArgs |= !IsDefault;788      if (IsDefault) {789        if (Cfg.InlayHints.DefaultArguments) {790          const auto SourceText = Lexer::getSourceText(791              CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()),792              AST.getSourceManager(), AST.getLangOpts());793          const auto Abbrev =794              (SourceText.size() > Cfg.InlayHints.TypeNameLimit ||795               SourceText.contains("\n"))796                  ? "..."797                  : SourceText;798          if (NameHint)799            FormattedDefaultArgs.emplace_back(800                llvm::formatv("{0}: {1}", Name, Abbrev));801          else802            FormattedDefaultArgs.emplace_back(llvm::formatv("{0}", Abbrev));803        }804      } else if (NameHint || ReferenceHint) {805        addInlayHint(Args[I]->getSourceRange(), HintSide::Left,806                     InlayHintKind::Parameter, ReferenceHint ? "&" : "",807                     NameHint ? Name : "", ": ");808      }809    }810 811    if (!FormattedDefaultArgs.empty()) {812      std::string Hint =813          joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit);814      addInlayHint(SourceRange{RParenOrBraceLoc}, HintSide::Left,815                   InlayHintKind::DefaultArgument,816                   HasNonDefaultArgs ? ", " : "", Hint, "");817    }818  }819 820  static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) {821    if (ParamNames.size() != 1)822      return false;823 824    StringRef Name = getSimpleName(*Callee);825    if (!Name.starts_with_insensitive("set"))826      return false;827 828    // In addition to checking that the function has one parameter and its829    // name starts with "set", also check that the part after "set" matches830    // the name of the parameter (ignoring case). The idea here is that if831    // the parameter name differs, it may contain extra information that832    // may be useful to show in a hint, as in:833    //   void setTimeout(int timeoutMillis);834    // This currently doesn't handle cases where params use snake_case835    // and functions don't, e.g.836    //   void setExceptionHandler(EHFunc exception_handler);837    // We could improve this by replacing `equals_insensitive` with some838    // `sloppy_equals` which ignores case and also skips underscores.839    StringRef WhatItIsSetting = Name.substr(3).ltrim("_");840    return WhatItIsSetting.equals_insensitive(ParamNames[0]);841  }842 843  // Checks if the callee is one of the builtins844  // addressof, as_const, forward, move(_if_noexcept)845  static bool isSimpleBuiltin(const FunctionDecl *Callee) {846    switch (Callee->getBuiltinID()) {847    case Builtin::BIaddressof:848    case Builtin::BIas_const:849    case Builtin::BIforward:850    case Builtin::BImove:851    case Builtin::BImove_if_noexcept:852      return true;853    default:854      return false;855    }856  }857 858  bool shouldHintName(const Expr *Arg, StringRef ParamName) {859    if (ParamName.empty())860      return false;861 862    // If the argument expression is a single name and it matches the863    // parameter name exactly, omit the name hint.864    if (ParamName == getSpelledIdentifier(Arg))865      return false;866 867    // Exclude argument expressions preceded by a /*paramName*/.868    if (isPrecededByParamNameComment(Arg, ParamName))869      return false;870 871    return true;872  }873 874  bool shouldHintReference(const ParmVarDecl *Param,875                           const ParmVarDecl *ForwardedParam) {876    // We add a & hint only when the argument is passed as mutable reference.877    // For parameters that are not part of an expanded pack, this is878    // straightforward. For expanded pack parameters, it's likely that they will879    // be forwarded to another function. In this situation, we only want to add880    // the reference hint if the argument is actually being used via mutable881    // reference. This means we need to check882    // 1. whether the value category of the argument is preserved, i.e. each883    //    pack expansion uses std::forward correctly.884    // 2. whether the argument is ever copied/cast instead of passed885    //    by-reference886    // Instead of checking this explicitly, we use the following proxy:887    // 1. the value category can only change from rvalue to lvalue during888    //    forwarding, so checking whether both the parameter of the forwarding889    //    function and the forwarded function are lvalue references detects such890    //    a conversion.891    // 2. if the argument is copied/cast somewhere in the chain of forwarding892    //    calls, it can only be passed on to an rvalue reference or const lvalue893    //    reference parameter. Thus if the forwarded parameter is a mutable894    //    lvalue reference, it cannot have been copied/cast to on the way.895    // Additionally, we should not add a reference hint if the forwarded896    // parameter was only partially resolved, i.e. points to an expanded pack897    // parameter, since we do not know how it will be used eventually.898    auto Type = Param->getType();899    auto ForwardedType = ForwardedParam->getType();900    return Type->isLValueReferenceType() &&901           ForwardedType->isLValueReferenceType() &&902           !ForwardedType.getNonReferenceType().isConstQualified() &&903           !isExpandedFromParameterPack(ForwardedParam);904  }905 906  // Checks if "E" is spelled in the main file and preceded by a C-style comment907  // whose contents match ParamName (allowing for whitespace and an optional "="908  // at the end.909  bool isPrecededByParamNameComment(const Expr *E, StringRef ParamName) {910    auto &SM = AST.getSourceManager();911    auto FileLoc = SM.getFileLoc(E->getBeginLoc());912    auto Decomposed = SM.getDecomposedLoc(FileLoc);913    if (Decomposed.first != MainFileID)914      return false;915 916    StringRef SourcePrefix = MainFileBuf.substr(0, Decomposed.second);917    // Allow whitespace between comment and expression.918    SourcePrefix = SourcePrefix.rtrim();919    // Check for comment ending.920    if (!SourcePrefix.consume_back("*/"))921      return false;922    // Ignore some punctuation and whitespace around comment.923    // In particular this allows designators to match nicely.924    llvm::StringLiteral IgnoreChars = " =.";925    SourcePrefix = SourcePrefix.rtrim(IgnoreChars);926    ParamName = ParamName.trim(IgnoreChars);927    // Other than that, the comment must contain exactly ParamName.928    if (!SourcePrefix.consume_back(ParamName))929      return false;930    SourcePrefix = SourcePrefix.rtrim(IgnoreChars);931    return SourcePrefix.ends_with("/*");932  }933 934  // If "E" spells a single unqualified identifier, return that name.935  // Otherwise, return an empty string.936  static StringRef getSpelledIdentifier(const Expr *E) {937    E = E->IgnoreUnlessSpelledInSource();938 939    if (auto *DRE = dyn_cast<DeclRefExpr>(E))940      if (!DRE->getQualifier())941        return getSimpleName(*DRE->getDecl());942 943    if (auto *ME = dyn_cast<MemberExpr>(E))944      if (!ME->getQualifier() && ME->isImplicitAccess())945        return getSimpleName(*ME->getMemberDecl());946 947    return {};948  }949 950  NameVec chooseParameterNames(ArrayRef<const ParmVarDecl *> Parameters) {951    NameVec ParameterNames;952    for (const auto *P : Parameters) {953      if (isExpandedFromParameterPack(P)) {954        // If we haven't resolved a pack paramater (e.g. foo(Args... args)) to a955        // non-pack parameter, then hinting as foo(args: 1, args: 2, args: 3) is956        // unlikely to be useful.957        ParameterNames.emplace_back();958      } else {959        auto SimpleName = getSimpleName(*P);960        // If the parameter is unnamed in the declaration:961        // attempt to get its name from the definition962        if (SimpleName.empty()) {963          if (const auto *PD = getParamDefinition(P)) {964            SimpleName = getSimpleName(*PD);965          }966        }967        ParameterNames.emplace_back(SimpleName);968      }969    }970 971    // Standard library functions often have parameter names that start972    // with underscores, which makes the hints noisy, so strip them out.973    for (auto &Name : ParameterNames)974      stripLeadingUnderscores(Name);975 976    return ParameterNames;977  }978 979  // for a ParmVarDecl from a function declaration, returns the corresponding980  // ParmVarDecl from the definition if possible, nullptr otherwise.981  static const ParmVarDecl *getParamDefinition(const ParmVarDecl *P) {982    if (auto *Callee = dyn_cast<FunctionDecl>(P->getDeclContext())) {983      if (auto *Def = Callee->getDefinition()) {984        auto I = std::distance(Callee->param_begin(),985                               llvm::find(Callee->parameters(), P));986        if (I < (int)Callee->getNumParams()) {987          return Def->getParamDecl(I);988        }989      }990    }991    return nullptr;992  }993 994  // We pass HintSide rather than SourceLocation because we want to ensure995  // it is in the same file as the common file range.996  void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind,997                    llvm::StringRef Prefix, llvm::StringRef Label,998                    llvm::StringRef Suffix) {999    auto LSPRange = getHintRange(R);1000    if (!LSPRange)1001      return;1002 1003    addInlayHint(*LSPRange, Side, Kind, Prefix, Label, Suffix);1004  }1005 1006  void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,1007                    llvm::StringRef Prefix, llvm::StringRef Label,1008                    llvm::StringRef Suffix) {1009    // We shouldn't get as far as adding a hint if the category is disabled.1010    // We'd like to disable as much of the analysis as possible above instead.1011    // Assert in debug mode but add a dynamic check in production.1012    assert(Cfg.InlayHints.Enabled && "Shouldn't get here if disabled!");1013    switch (Kind) {1014#define CHECK_KIND(Enumerator, ConfigProperty)                                 \1015  case InlayHintKind::Enumerator:                                              \1016    assert(Cfg.InlayHints.ConfigProperty &&                                    \1017           "Shouldn't get here if kind is disabled!");                         \1018    if (!Cfg.InlayHints.ConfigProperty)                                        \1019      return;                                                                  \1020    break1021      CHECK_KIND(Parameter, Parameters);1022      CHECK_KIND(Type, DeducedTypes);1023      CHECK_KIND(Designator, Designators);1024      CHECK_KIND(BlockEnd, BlockEnd);1025      CHECK_KIND(DefaultArgument, DefaultArguments);1026#undef CHECK_KIND1027    }1028 1029    Position LSPPos = Side == HintSide::Left ? LSPRange.start : LSPRange.end;1030    if (RestrictRange &&1031        (LSPPos < RestrictRange->start || !(LSPPos < RestrictRange->end)))1032      return;1033    bool PadLeft = Prefix.consume_front(" ");1034    bool PadRight = Suffix.consume_back(" ");1035    Results.push_back(InlayHint{LSPPos,1036                                /*label=*/{(Prefix + Label + Suffix).str()},1037                                Kind, PadLeft, PadRight, LSPRange});1038  }1039 1040  // Get the range of the main file that *exactly* corresponds to R.1041  std::optional<Range> getHintRange(SourceRange R) {1042    const auto &SM = AST.getSourceManager();1043    auto Spelled = Tokens.spelledForExpanded(Tokens.expandedTokens(R));1044    // TokenBuffer will return null if e.g. R corresponds to only part of a1045    // macro expansion.1046    if (!Spelled || Spelled->empty())1047      return std::nullopt;1048    // Hint must be within the main file, not e.g. a non-preamble include.1049    if (SM.getFileID(Spelled->front().location()) != SM.getMainFileID() ||1050        SM.getFileID(Spelled->back().location()) != SM.getMainFileID())1051      return std::nullopt;1052    return Range{sourceLocToPosition(SM, Spelled->front().location()),1053                 sourceLocToPosition(SM, Spelled->back().endLocation())};1054  }1055 1056  void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {1057    if (!Cfg.InlayHints.DeducedTypes || T.isNull())1058      return;1059 1060    // The sugared type is more useful in some cases, and the canonical1061    // type in other cases.1062    auto Desugared = maybeDesugar(AST, T);1063    std::string TypeName = Desugared.getAsString(TypeHintPolicy);1064    if (T != Desugared && !shouldPrintTypeHint(TypeName)) {1065      // If the desugared type is too long to display, fallback to the sugared1066      // type.1067      TypeName = T.getAsString(TypeHintPolicy);1068    }1069    if (shouldPrintTypeHint(TypeName))1070      addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, TypeName,1071                   /*Suffix=*/"");1072  }1073 1074  void addDesignatorHint(SourceRange R, llvm::StringRef Text) {1075    addInlayHint(R, HintSide::Left, InlayHintKind::Designator,1076                 /*Prefix=*/"", Text, /*Suffix=*/"=");1077  }1078 1079  bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept {1080    return Cfg.InlayHints.TypeNameLimit == 0 ||1081           TypeName.size() < Cfg.InlayHints.TypeNameLimit;1082  }1083 1084  void addBlockEndHint(SourceRange BraceRange, StringRef DeclPrefix,1085                       StringRef Name, StringRef OptionalPunctuation) {1086    auto HintRange = computeBlockEndHintRange(BraceRange, OptionalPunctuation);1087    if (!HintRange)1088      return;1089 1090    std::string Label = DeclPrefix.str();1091    if (!Label.empty() && !Name.empty())1092      Label += ' ';1093    Label += Name;1094 1095    constexpr unsigned HintMaxLengthLimit = 60;1096    if (Label.length() > HintMaxLengthLimit)1097      return;1098 1099    addInlayHint(*HintRange, HintSide::Right, InlayHintKind::BlockEnd, " // ",1100                 Label, "");1101  }1102 1103  // Compute the LSP range to attach the block end hint to, if any allowed.1104  // 1. "}" is the last non-whitespace character on the line. The range of "}"1105  // is returned.1106  // 2. After "}", if the trimmed trailing text is exactly1107  // `OptionalPunctuation`, say ";". The range of "} ... ;" is returned.1108  // Otherwise, the hint shouldn't be shown.1109  std::optional<Range> computeBlockEndHintRange(SourceRange BraceRange,1110                                                StringRef OptionalPunctuation) {1111 1112    auto &SM = AST.getSourceManager();1113    auto [BlockBeginFileId, BlockBeginOffset] =1114        SM.getDecomposedLoc(SM.getFileLoc(BraceRange.getBegin()));1115    auto RBraceLoc = SM.getFileLoc(BraceRange.getEnd());1116    auto [RBraceFileId, RBraceOffset] = SM.getDecomposedLoc(RBraceLoc);1117 1118    // Because we need to check the block satisfies the minimum line limit, we1119    // require both source location to be in the main file. This prevents hint1120    // to be shown in weird cases like '{' is actually in a "#include", but it's1121    // rare anyway.1122    if (BlockBeginFileId != MainFileID || RBraceFileId != MainFileID)1123      return std::nullopt;1124 1125    StringRef RestOfLine = MainFileBuf.substr(RBraceOffset).split('\n').first;1126    if (!RestOfLine.starts_with("}"))1127      return std::nullopt;1128 1129    StringRef TrimmedTrailingText = RestOfLine.drop_front().trim();1130    if (!TrimmedTrailingText.empty() &&1131        TrimmedTrailingText != OptionalPunctuation)1132      return std::nullopt;1133 1134    auto BlockBeginLine = SM.getLineNumber(BlockBeginFileId, BlockBeginOffset);1135    auto RBraceLine = SM.getLineNumber(RBraceFileId, RBraceOffset);1136 1137    // Don't show hint on trivial blocks like `class X {};`1138    if (BlockBeginLine + HintOptions.HintMinLineLimit - 1 > RBraceLine)1139      return std::nullopt;1140 1141    // This is what we attach the hint to, usually "}" or "};".1142    StringRef HintRangeText = RestOfLine.take_front(1143        TrimmedTrailingText.empty()1144            ? 11145            : TrimmedTrailingText.bytes_end() - RestOfLine.bytes_begin());1146 1147    Position HintStart = sourceLocToPosition(SM, RBraceLoc);1148    Position HintEnd = sourceLocToPosition(1149        SM, RBraceLoc.getLocWithOffset(HintRangeText.size()));1150    return Range{HintStart, HintEnd};1151  }1152 1153  static bool isFunctionObjectCallExpr(CallExpr *E) noexcept {1154    if (auto *CallExpr = dyn_cast<CXXOperatorCallExpr>(E))1155      return CallExpr->getOperator() == OverloadedOperatorKind::OO_Call;1156    return false;1157  }1158 1159  std::vector<InlayHint> &Results;1160  ASTContext &AST;1161  const syntax::TokenBuffer &Tokens;1162  const Config &Cfg;1163  std::optional<Range> RestrictRange;1164  FileID MainFileID;1165  StringRef MainFileBuf;1166  const HeuristicResolver *Resolver;1167  PrintingPolicy TypeHintPolicy;1168  InlayHintOptions HintOptions;1169};1170 1171} // namespace1172 1173std::vector<InlayHint> inlayHints(ParsedAST &AST,1174                                  std::optional<Range> RestrictRange,1175                                  InlayHintOptions HintOptions) {1176  std::vector<InlayHint> Results;1177  const auto &Cfg = Config::current();1178  if (!Cfg.InlayHints.Enabled)1179    return Results;1180  InlayHintVisitor Visitor(Results, AST, Cfg, std::move(RestrictRange),1181                           HintOptions);1182  Visitor.TraverseAST(AST.getASTContext());1183 1184  // De-duplicate hints. Duplicates can sometimes occur due to e.g. explicit1185  // template instantiations.1186  llvm::sort(Results);1187  Results.erase(llvm::unique(Results), Results.end());1188 1189  return Results;1190}1191 1192} // namespace clangd1193} // namespace clang1194