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