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