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