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