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 
24   bool VisitCXXConstructExpr(CXXConstructExpr *E) {
25     // Weed out constructor calls that don't look like a function call with
26     // an argument list, by checking the validity of getParenOrBraceRange().
27     // Also weed out std::initializer_list constructors as there are no names
28     // for the individual arguments.
29     if (!E->getParenOrBraceRange().isValid() ||
30         E->isStdInitListInitialization()) {
31       return true;
32     }
33 
34     processCall(E->getParenOrBraceRange().getBegin(), E->getConstructor(),
35                 {E->getArgs(), E->getNumArgs()});
36     return true;
37   }
38 
39   bool VisitCallExpr(CallExpr *E) {
40     // Do not show parameter hints for operator calls written using operator
41     // syntax or user-defined literals. (Among other reasons, the resulting
42     // hints can look awkard, e.g. the expression can itself be a function
43     // argument and then we'd get two hints side by side).
44     if (isa<CXXOperatorCallExpr>(E) || isa<UserDefinedLiteral>(E))
45       return true;
46 
47     processCall(E->getRParenLoc(),
48                 dyn_cast_or_null<FunctionDecl>(E->getCalleeDecl()),
49                 {E->getArgs(), E->getNumArgs()});
50     return true;
51   }
52 
53   // FIXME: Handle RecoveryExpr to try to hint some invalid calls.
54 
55 private:
56   // The purpose of Anchor is to deal with macros. It should be the call's
57   // opening or closing parenthesis or brace. (Always using the opening would
58   // make more sense but CallExpr only exposes the closing.) We heuristically
59   // assume that if this location does not come from a macro definition, then
60   // the entire argument list likely appears in the main file and can be hinted.
61   void processCall(SourceLocation Anchor, const FunctionDecl *Callee,
62                    llvm::ArrayRef<const Expr *const> Args) {
63     if (Args.size() == 0 || !Callee)
64       return;
65 
66     // If the anchor location comes from a macro defintion, there's nowhere to
67     // put hints.
68     if (!AST.getSourceManager().getTopMacroCallerLoc(Anchor).isFileID())
69       return;
70 
71     // The parameter name of a move or copy constructor is not very interesting.
72     if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee))
73       if (Ctor->isCopyOrMoveConstructor())
74         return;
75 
76     // FIXME: Exclude setters (i.e. functions with one argument whose name
77     // begins with "set"), as their parameter name is also not likely to be
78     // interesting.
79 
80     // Don't show hints for variadic parameters.
81     size_t FixedParamCount = getFixedParamCount(Callee);
82     size_t ArgCount = std::min(FixedParamCount, Args.size());
83 
84     NameVec ParameterNames = chooseParameterNames(Callee, ArgCount);
85 
86     for (size_t I = 0; I < ArgCount; ++I) {
87       StringRef Name = ParameterNames[I];
88       if (!shouldHint(Args[I], Name))
89         continue;
90 
91       addInlayHint(Args[I]->getSourceRange(), InlayHintKind::ParameterHint,
92                    Name.str() + ": ");
93     }
94   }
95 
96   bool shouldHint(const Expr *Arg, StringRef ParamName) {
97     if (ParamName.empty())
98       return false;
99 
100     // If the argument expression is a single name and it matches the
101     // parameter name exactly, omit the hint.
102     if (ParamName == getSpelledIdentifier(Arg))
103       return false;
104 
105     // FIXME: Exclude argument expressions preceded by a /*paramName=*/ comment.
106 
107     return true;
108   }
109 
110   // If "E" spells a single unqualified identifier, return that name.
111   // Otherwise, return an empty string.
112   static StringRef getSpelledIdentifier(const Expr *E) {
113     E = E->IgnoreUnlessSpelledInSource();
114 
115     if (auto *DRE = dyn_cast<DeclRefExpr>(E))
116       if (!DRE->getQualifier())
117         return getSimpleName(*DRE->getDecl());
118 
119     if (auto *ME = dyn_cast<MemberExpr>(E))
120       if (!ME->getQualifier() && ME->isImplicitAccess())
121         return getSimpleName(*ME->getMemberDecl());
122 
123     return {};
124   }
125 
126   using NameVec = SmallVector<StringRef, 8>;
127 
128   NameVec chooseParameterNames(const FunctionDecl *Callee, size_t ArgCount) {
129     // The current strategy here is to use all the parameter names from the
130     // canonical declaration, unless they're all empty, in which case we
131     // use all the parameter names from the definition (in present in the
132     // translation unit).
133     // We could try a bit harder, e.g.:
134     //   - try all re-declarations, not just canonical + definition
135     //   - fall back arg-by-arg rather than wholesale
136 
137     NameVec ParameterNames = getParameterNamesForDecl(Callee, ArgCount);
138 
139     if (llvm::all_of(ParameterNames, std::mem_fn(&StringRef::empty))) {
140       if (const FunctionDecl *Def = Callee->getDefinition()) {
141         ParameterNames = getParameterNamesForDecl(Def, ArgCount);
142       }
143     }
144     assert(ParameterNames.size() == ArgCount);
145 
146     // Standard library functions often have parameter names that start
147     // with underscores, which makes the hints noisy, so strip them out.
148     for (auto &Name : ParameterNames)
149       stripLeadingUnderscores(Name);
150 
151     return ParameterNames;
152   }
153 
154   static void stripLeadingUnderscores(StringRef &Name) {
155     Name = Name.ltrim('_');
156   }
157 
158   // Return the number of fixed parameters Function has, that is, not counting
159   // parameters that are variadic (instantiated from a parameter pack) or
160   // C-style varargs.
161   static size_t getFixedParamCount(const FunctionDecl *Function) {
162     if (FunctionTemplateDecl *Template = Function->getPrimaryTemplate()) {
163       FunctionDecl *F = Template->getTemplatedDecl();
164       size_t Result = 0;
165       for (ParmVarDecl *Parm : F->parameters()) {
166         if (Parm->isParameterPack()) {
167           break;
168         }
169         ++Result;
170       }
171       return Result;
172     }
173     // C-style varargs don't need special handling, they're already
174     // not included in getNumParams().
175     return Function->getNumParams();
176   }
177 
178   static StringRef getSimpleName(const NamedDecl &D) {
179     if (IdentifierInfo *Ident = D.getDeclName().getAsIdentifierInfo()) {
180       return Ident->getName();
181     }
182 
183     return StringRef();
184   }
185 
186   NameVec getParameterNamesForDecl(const FunctionDecl *Function,
187                                    size_t ArgCount) {
188     NameVec Result;
189     for (size_t I = 0; I < ArgCount; ++I) {
190       const ParmVarDecl *Parm = Function->getParamDecl(I);
191       assert(Parm);
192       Result.emplace_back(getSimpleName(*Parm));
193     }
194     return Result;
195   }
196 
197   void addInlayHint(SourceRange R, InlayHintKind Kind, llvm::StringRef Label) {
198     auto FileRange =
199         toHalfOpenFileRange(AST.getSourceManager(), AST.getLangOpts(), R);
200     if (!FileRange)
201       return;
202     Results.push_back(InlayHint{
203         Range{
204             sourceLocToPosition(AST.getSourceManager(), FileRange->getBegin()),
205             sourceLocToPosition(AST.getSourceManager(), FileRange->getEnd())},
206         Kind, Label.str()});
207   }
208 
209   std::vector<InlayHint> &Results;
210   ASTContext &AST;
211 };
212 
213 std::vector<InlayHint> inlayHints(ParsedAST &AST) {
214   std::vector<InlayHint> Results;
215   InlayHintVisitor Visitor(Results, AST);
216   Visitor.TraverseAST(AST.getASTContext());
217   return Results;
218 }
219 
220 } // namespace clangd
221 } // namespace clang
222