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