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