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