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