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 "HeuristicResolver.h" 10 #include "ParsedAST.h" 11 #include "support/Logger.h" 12 #include "clang/AST/DeclarationName.h" 13 #include "clang/AST/ExprCXX.h" 14 #include "clang/AST/RecursiveASTVisitor.h" 15 #include "clang/Basic/SourceManager.h" 16 17 namespace clang { 18 namespace clangd { 19 20 class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> { 21 public: 22 InlayHintVisitor(std::vector<InlayHint> &Results, ParsedAST &AST) 23 : Results(Results), AST(AST.getASTContext()), 24 MainFileID(AST.getSourceManager().getMainFileID()), 25 Resolver(AST.getHeuristicResolver()) { 26 bool Invalid = false; 27 llvm::StringRef Buf = 28 AST.getSourceManager().getBufferData(MainFileID, &Invalid); 29 MainFileBuf = Invalid ? StringRef{} : Buf; 30 } 31 32 bool VisitCXXConstructExpr(CXXConstructExpr *E) { 33 // Weed out constructor calls that don't look like a function call with 34 // an argument list, by checking the validity of getParenOrBraceRange(). 35 // Also weed out std::initializer_list constructors as there are no names 36 // for the individual arguments. 37 if (!E->getParenOrBraceRange().isValid() || 38 E->isStdInitListInitialization()) { 39 return true; 40 } 41 42 processCall(E->getParenOrBraceRange().getBegin(), E->getConstructor(), 43 {E->getArgs(), E->getNumArgs()}); 44 return true; 45 } 46 47 bool VisitCallExpr(CallExpr *E) { 48 // Do not show parameter hints for operator calls written using operator 49 // syntax or user-defined literals. (Among other reasons, the resulting 50 // hints can look awkard, e.g. the expression can itself be a function 51 // argument and then we'd get two hints side by side). 52 if (isa<CXXOperatorCallExpr>(E) || isa<UserDefinedLiteral>(E)) 53 return true; 54 55 auto CalleeDecls = Resolver->resolveCalleeOfCallExpr(E); 56 if (CalleeDecls.size() != 1) 57 return true; 58 const FunctionDecl *Callee = nullptr; 59 if (const auto *FD = dyn_cast<FunctionDecl>(CalleeDecls[0])) 60 Callee = FD; 61 else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CalleeDecls[0])) 62 Callee = FTD->getTemplatedDecl(); 63 if (!Callee) 64 return true; 65 66 processCall(E->getRParenLoc(), Callee, {E->getArgs(), E->getNumArgs()}); 67 return true; 68 } 69 70 // FIXME: Handle RecoveryExpr to try to hint some invalid calls. 71 72 private: 73 using NameVec = SmallVector<StringRef, 8>; 74 75 // The purpose of Anchor is to deal with macros. It should be the call's 76 // opening or closing parenthesis or brace. (Always using the opening would 77 // make more sense but CallExpr only exposes the closing.) We heuristically 78 // assume that if this location does not come from a macro definition, then 79 // the entire argument list likely appears in the main file and can be hinted. 80 void processCall(SourceLocation Anchor, const FunctionDecl *Callee, 81 llvm::ArrayRef<const Expr *const> Args) { 82 if (Args.size() == 0 || !Callee) 83 return; 84 85 // If the anchor location comes from a macro defintion, there's nowhere to 86 // put hints. 87 if (!AST.getSourceManager().getTopMacroCallerLoc(Anchor).isFileID()) 88 return; 89 90 // The parameter name of a move or copy constructor is not very interesting. 91 if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee)) 92 if (Ctor->isCopyOrMoveConstructor()) 93 return; 94 95 // Don't show hints for variadic parameters. 96 size_t FixedParamCount = getFixedParamCount(Callee); 97 size_t ArgCount = std::min(FixedParamCount, Args.size()); 98 99 NameVec ParameterNames = chooseParameterNames(Callee, ArgCount); 100 101 // Exclude setters (i.e. functions with one argument whose name begins with 102 // "set"), as their parameter name is also not likely to be interesting. 103 if (isSetter(Callee, ParameterNames)) 104 return; 105 106 for (size_t I = 0; I < ArgCount; ++I) { 107 StringRef Name = ParameterNames[I]; 108 if (!shouldHint(Args[I], Name)) 109 continue; 110 111 addInlayHint(Args[I]->getSourceRange(), InlayHintKind::ParameterHint, 112 Name.str() + ": "); 113 } 114 } 115 116 static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) { 117 if (ParamNames.size() != 1) 118 return false; 119 120 StringRef Name = getSimpleName(*Callee); 121 if (!Name.startswith_lower("set")) 122 return false; 123 124 // In addition to checking that the function has one parameter and its 125 // name starts with "set", also check that the part after "set" matches 126 // the name of the parameter (ignoring case). The idea here is that if 127 // the parameter name differs, it may contain extra information that 128 // may be useful to show in a hint, as in: 129 // void setTimeout(int timeoutMillis); 130 // This currently doesn't handle cases where params use snake_case 131 // and functions don't, e.g. 132 // void setExceptionHandler(EHFunc exception_handler); 133 // We could improve this by replacing `equals_lower` with some 134 // `sloppy_equals` which ignores case and also skips underscores. 135 StringRef WhatItIsSetting = Name.substr(3).ltrim("_"); 136 return WhatItIsSetting.equals_lower(ParamNames[0]); 137 } 138 139 bool shouldHint(const Expr *Arg, StringRef ParamName) { 140 if (ParamName.empty()) 141 return false; 142 143 // If the argument expression is a single name and it matches the 144 // parameter name exactly, omit the hint. 145 if (ParamName == getSpelledIdentifier(Arg)) 146 return false; 147 148 // Exclude argument expressions preceded by a /*paramName*/. 149 if (isPrecededByParamNameComment(Arg, ParamName)) 150 return false; 151 152 return true; 153 } 154 155 // Checks if "E" is spelled in the main file and preceded by a C-style comment 156 // whose contents match ParamName (allowing for whitespace and an optional "=" 157 // at the end. 158 bool isPrecededByParamNameComment(const Expr *E, StringRef ParamName) { 159 auto &SM = AST.getSourceManager(); 160 auto ExprStartLoc = SM.getTopMacroCallerLoc(E->getBeginLoc()); 161 auto Decomposed = SM.getDecomposedLoc(ExprStartLoc); 162 if (Decomposed.first != MainFileID) 163 return false; 164 165 StringRef SourcePrefix = MainFileBuf.substr(0, Decomposed.second); 166 // Allow whitespace between comment and expression. 167 SourcePrefix = SourcePrefix.rtrim(); 168 // Check for comment ending. 169 if (!SourcePrefix.consume_back("*/")) 170 return false; 171 // Allow whitespace and "=" at end of comment. 172 SourcePrefix = SourcePrefix.rtrim().rtrim('=').rtrim(); 173 // Other than that, the comment must contain exactly ParamName. 174 if (!SourcePrefix.consume_back(ParamName)) 175 return false; 176 return SourcePrefix.rtrim().endswith("/*"); 177 } 178 179 // If "E" spells a single unqualified identifier, return that name. 180 // Otherwise, return an empty string. 181 static StringRef getSpelledIdentifier(const Expr *E) { 182 E = E->IgnoreUnlessSpelledInSource(); 183 184 if (auto *DRE = dyn_cast<DeclRefExpr>(E)) 185 if (!DRE->getQualifier()) 186 return getSimpleName(*DRE->getDecl()); 187 188 if (auto *ME = dyn_cast<MemberExpr>(E)) 189 if (!ME->getQualifier() && ME->isImplicitAccess()) 190 return getSimpleName(*ME->getMemberDecl()); 191 192 return {}; 193 } 194 195 NameVec chooseParameterNames(const FunctionDecl *Callee, size_t ArgCount) { 196 // The current strategy here is to use all the parameter names from the 197 // canonical declaration, unless they're all empty, in which case we 198 // use all the parameter names from the definition (in present in the 199 // translation unit). 200 // We could try a bit harder, e.g.: 201 // - try all re-declarations, not just canonical + definition 202 // - fall back arg-by-arg rather than wholesale 203 204 NameVec ParameterNames = getParameterNamesForDecl(Callee, ArgCount); 205 206 if (llvm::all_of(ParameterNames, std::mem_fn(&StringRef::empty))) { 207 if (const FunctionDecl *Def = Callee->getDefinition()) { 208 ParameterNames = getParameterNamesForDecl(Def, ArgCount); 209 } 210 } 211 assert(ParameterNames.size() == ArgCount); 212 213 // Standard library functions often have parameter names that start 214 // with underscores, which makes the hints noisy, so strip them out. 215 for (auto &Name : ParameterNames) 216 stripLeadingUnderscores(Name); 217 218 return ParameterNames; 219 } 220 221 static void stripLeadingUnderscores(StringRef &Name) { 222 Name = Name.ltrim('_'); 223 } 224 225 // Return the number of fixed parameters Function has, that is, not counting 226 // parameters that are variadic (instantiated from a parameter pack) or 227 // C-style varargs. 228 static size_t getFixedParamCount(const FunctionDecl *Function) { 229 if (FunctionTemplateDecl *Template = Function->getPrimaryTemplate()) { 230 FunctionDecl *F = Template->getTemplatedDecl(); 231 size_t Result = 0; 232 for (ParmVarDecl *Parm : F->parameters()) { 233 if (Parm->isParameterPack()) { 234 break; 235 } 236 ++Result; 237 } 238 return Result; 239 } 240 // C-style varargs don't need special handling, they're already 241 // not included in getNumParams(). 242 return Function->getNumParams(); 243 } 244 245 static StringRef getSimpleName(const NamedDecl &D) { 246 if (IdentifierInfo *Ident = D.getDeclName().getAsIdentifierInfo()) { 247 return Ident->getName(); 248 } 249 250 return StringRef(); 251 } 252 253 NameVec getParameterNamesForDecl(const FunctionDecl *Function, 254 size_t ArgCount) { 255 NameVec Result; 256 for (size_t I = 0; I < ArgCount; ++I) { 257 const ParmVarDecl *Parm = Function->getParamDecl(I); 258 assert(Parm); 259 Result.emplace_back(getSimpleName(*Parm)); 260 } 261 return Result; 262 } 263 264 void addInlayHint(SourceRange R, InlayHintKind Kind, llvm::StringRef Label) { 265 auto FileRange = 266 toHalfOpenFileRange(AST.getSourceManager(), AST.getLangOpts(), R); 267 if (!FileRange) 268 return; 269 Results.push_back(InlayHint{ 270 Range{ 271 sourceLocToPosition(AST.getSourceManager(), FileRange->getBegin()), 272 sourceLocToPosition(AST.getSourceManager(), FileRange->getEnd())}, 273 Kind, Label.str()}); 274 } 275 276 std::vector<InlayHint> &Results; 277 ASTContext &AST; 278 FileID MainFileID; 279 StringRef MainFileBuf; 280 const HeuristicResolver *Resolver; 281 }; 282 283 std::vector<InlayHint> inlayHints(ParsedAST &AST) { 284 std::vector<InlayHint> Results; 285 InlayHintVisitor Visitor(Results, AST); 286 Visitor.TraverseAST(AST.getASTContext()); 287 return Results; 288 } 289 290 } // namespace clangd 291 } // namespace clang 292