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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 #include "InlayHints.h" 9 #include "ParsedAST.h" 10 #include "support/Logger.h" 11 #include "clang/AST/DeclarationName.h" 12 #include "clang/AST/ExprCXX.h" 13 #include "clang/AST/RecursiveASTVisitor.h" 14 #include "clang/Basic/SourceManager.h" 15 16 namespace clang { 17 namespace clangd { 18 19 class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> { 20 public: 21 InlayHintVisitor(std::vector<InlayHint> &Results, ParsedAST &AST) 22 : Results(Results), AST(AST.getASTContext()) {} 23 24 bool VisitCXXConstructExpr(CXXConstructExpr *E) { 25 // Weed out constructor calls that don't look like a function call with 26 // an argument list, by checking the validity of getParenOrBraceRange(). 27 // Also weed out std::initializer_list constructors as there are no names 28 // for the individual arguments. 29 if (!E->getParenOrBraceRange().isValid() || 30 E->isStdInitListInitialization()) { 31 return true; 32 } 33 34 processCall(E->getParenOrBraceRange().getBegin(), E->getConstructor(), 35 {E->getArgs(), E->getNumArgs()}); 36 return true; 37 } 38 39 bool VisitCallExpr(CallExpr *E) { 40 // Do not show parameter hints for operator calls written using operator 41 // syntax or user-defined literals. (Among other reasons, the resulting 42 // hints can look awkard, e.g. the expression can itself be a function 43 // argument and then we'd get two hints side by side). 44 if (isa<CXXOperatorCallExpr>(E) || isa<UserDefinedLiteral>(E)) 45 return true; 46 47 processCall(E->getRParenLoc(), 48 dyn_cast_or_null<FunctionDecl>(E->getCalleeDecl()), 49 {E->getArgs(), E->getNumArgs()}); 50 return true; 51 } 52 53 // FIXME: Handle RecoveryExpr to try to hint some invalid calls. 54 55 private: 56 // The purpose of Anchor is to deal with macros. It should be the call's 57 // opening or closing parenthesis or brace. (Always using the opening would 58 // make more sense but CallExpr only exposes the closing.) We heuristically 59 // assume that if this location does not come from a macro definition, then 60 // the entire argument list likely appears in the main file and can be hinted. 61 void processCall(SourceLocation Anchor, const FunctionDecl *Callee, 62 llvm::ArrayRef<const Expr *const> Args) { 63 if (Args.size() == 0 || !Callee) 64 return; 65 66 // If the anchor location comes from a macro defintion, there's nowhere to 67 // put hints. 68 if (!AST.getSourceManager().getTopMacroCallerLoc(Anchor).isFileID()) 69 return; 70 71 // The parameter name of a move or copy constructor is not very interesting. 72 if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee)) 73 if (Ctor->isCopyOrMoveConstructor()) 74 return; 75 76 // FIXME: Exclude setters (i.e. functions with one argument whose name 77 // begins with "set"), as their parameter name is also not likely to be 78 // interesting. 79 80 // Don't show hints for variadic parameters. 81 size_t FixedParamCount = getFixedParamCount(Callee); 82 size_t ArgCount = std::min(FixedParamCount, Args.size()); 83 84 NameVec ParameterNames = chooseParameterNames(Callee, ArgCount); 85 86 for (size_t I = 0; I < ArgCount; ++I) { 87 StringRef Name = ParameterNames[I]; 88 if (!shouldHint(Args[I], Name)) 89 continue; 90 91 addInlayHint(Args[I]->getSourceRange(), InlayHintKind::ParameterHint, 92 Name.str() + ": "); 93 } 94 } 95 96 bool shouldHint(const Expr *Arg, StringRef ParamName) { 97 if (ParamName.empty()) 98 return false; 99 100 // If the argument expression is a single name and it matches the 101 // parameter name exactly, omit the hint. 102 if (ParamName == getSpelledIdentifier(Arg)) 103 return false; 104 105 // FIXME: Exclude argument expressions preceded by a /*paramName=*/ comment. 106 107 return true; 108 } 109 110 // If "E" spells a single unqualified identifier, return that name. 111 // Otherwise, return an empty string. 112 static StringRef getSpelledIdentifier(const Expr *E) { 113 E = E->IgnoreUnlessSpelledInSource(); 114 115 if (auto *DRE = dyn_cast<DeclRefExpr>(E)) 116 if (!DRE->getQualifier()) 117 return getSimpleName(*DRE->getDecl()); 118 119 if (auto *ME = dyn_cast<MemberExpr>(E)) 120 if (!ME->getQualifier() && ME->isImplicitAccess()) 121 return getSimpleName(*ME->getMemberDecl()); 122 123 return {}; 124 } 125 126 using NameVec = SmallVector<StringRef, 8>; 127 128 NameVec chooseParameterNames(const FunctionDecl *Callee, size_t ArgCount) { 129 // The current strategy here is to use all the parameter names from the 130 // canonical declaration, unless they're all empty, in which case we 131 // use all the parameter names from the definition (in present in the 132 // translation unit). 133 // We could try a bit harder, e.g.: 134 // - try all re-declarations, not just canonical + definition 135 // - fall back arg-by-arg rather than wholesale 136 137 NameVec ParameterNames = getParameterNamesForDecl(Callee, ArgCount); 138 139 if (llvm::all_of(ParameterNames, std::mem_fn(&StringRef::empty))) { 140 if (const FunctionDecl *Def = Callee->getDefinition()) { 141 ParameterNames = getParameterNamesForDecl(Def, ArgCount); 142 } 143 } 144 assert(ParameterNames.size() == ArgCount); 145 146 // Standard library functions often have parameter names that start 147 // with underscores, which makes the hints noisy, so strip them out. 148 for (auto &Name : ParameterNames) 149 stripLeadingUnderscores(Name); 150 151 return ParameterNames; 152 } 153 154 static void stripLeadingUnderscores(StringRef &Name) { 155 Name = Name.ltrim('_'); 156 } 157 158 // Return the number of fixed parameters Function has, that is, not counting 159 // parameters that are variadic (instantiated from a parameter pack) or 160 // C-style varargs. 161 static size_t getFixedParamCount(const FunctionDecl *Function) { 162 if (FunctionTemplateDecl *Template = Function->getPrimaryTemplate()) { 163 FunctionDecl *F = Template->getTemplatedDecl(); 164 size_t Result = 0; 165 for (ParmVarDecl *Parm : F->parameters()) { 166 if (Parm->isParameterPack()) { 167 break; 168 } 169 ++Result; 170 } 171 return Result; 172 } 173 // C-style varargs don't need special handling, they're already 174 // not included in getNumParams(). 175 return Function->getNumParams(); 176 } 177 178 static StringRef getSimpleName(const NamedDecl &D) { 179 if (IdentifierInfo *Ident = D.getDeclName().getAsIdentifierInfo()) { 180 return Ident->getName(); 181 } 182 183 return StringRef(); 184 } 185 186 NameVec getParameterNamesForDecl(const FunctionDecl *Function, 187 size_t ArgCount) { 188 NameVec Result; 189 for (size_t I = 0; I < ArgCount; ++I) { 190 const ParmVarDecl *Parm = Function->getParamDecl(I); 191 assert(Parm); 192 Result.emplace_back(getSimpleName(*Parm)); 193 } 194 return Result; 195 } 196 197 void addInlayHint(SourceRange R, InlayHintKind Kind, llvm::StringRef Label) { 198 auto FileRange = 199 toHalfOpenFileRange(AST.getSourceManager(), AST.getLangOpts(), R); 200 if (!FileRange) 201 return; 202 Results.push_back(InlayHint{ 203 Range{ 204 sourceLocToPosition(AST.getSourceManager(), FileRange->getBegin()), 205 sourceLocToPosition(AST.getSourceManager(), FileRange->getEnd())}, 206 Kind, Label.str()}); 207 } 208 209 std::vector<InlayHint> &Results; 210 ASTContext &AST; 211 }; 212 213 std::vector<InlayHint> inlayHints(ParsedAST &AST) { 214 std::vector<InlayHint> Results; 215 InlayHintVisitor Visitor(Results, AST); 216 Visitor.TraverseAST(AST.getASTContext()); 217 return Results; 218 } 219 220 } // namespace clangd 221 } // namespace clang 222