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