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 "../clang-tidy/utils/DesignatedInitializers.h"
10#include "AST.h"
11#include "Config.h"
12#include "ParsedAST.h"
13#include "Protocol.h"
14#include "SourceCode.h"
15#include "clang/AST/ASTDiagnostic.h"
16#include "clang/AST/Decl.h"
17#include "clang/AST/DeclBase.h"
18#include "clang/AST/DeclarationName.h"
19#include "clang/AST/Expr.h"
20#include "clang/AST/ExprCXX.h"
21#include "clang/AST/RecursiveASTVisitor.h"
22#include "clang/AST/Stmt.h"
23#include "clang/AST/StmtVisitor.h"
24#include "clang/AST/Type.h"
25#include "clang/Basic/Builtins.h"
26#include "clang/Basic/OperatorKinds.h"
27#include "clang/Basic/SourceLocation.h"
28#include "clang/Basic/SourceManager.h"
29#include "clang/Sema/HeuristicResolver.h"
30#include "llvm/ADT/DenseSet.h"
31#include "llvm/ADT/STLExtras.h"
32#include "llvm/ADT/SmallVector.h"
33#include "llvm/ADT/StringExtras.h"
34#include "llvm/ADT/StringRef.h"
35#include "llvm/ADT/Twine.h"
36#include "llvm/ADT/identity.h"
37#include "llvm/Support/Casting.h"
38#include "llvm/Support/ErrorHandling.h"
39#include "llvm/Support/FormatVariadic.h"
40#include "llvm/Support/SaveAndRestore.h"
41#include "llvm/Support/ScopedPrinter.h"
42#include "llvm/Support/raw_ostream.h"
43#include <algorithm>
44#include <iterator>
45#include <optional>
46#include <string>
47
48namespace clang {
49namespace clangd {
50namespace {
51
52// For now, inlay hints are always anchored at the left or right of their range.
53enum class HintSide { Left, Right };
54
55void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim(Char: '_'); }
56
57// getDeclForType() returns the decl responsible for Type's spelling.
58// This is the inverse of ASTContext::getTypeDeclType().
59template <typename Ty, typename = decltype(((Ty *)nullptr)->getDecl())>
60const NamedDecl *getDeclForTypeImpl(const Ty *T) {
61 return T->getDecl();
62}
63const NamedDecl *getDeclForTypeImpl(const void *T) { return nullptr; }
64const NamedDecl *getDeclForType(const Type *T) {
65 switch (T->getTypeClass()) {
66#define ABSTRACT_TYPE(TY, BASE)
67#define TYPE(TY, BASE) \
68 case Type::TY: \
69 return getDeclForTypeImpl(llvm::cast<TY##Type>(T));
70#include "clang/AST/TypeNodes.inc"
71 }
72 llvm_unreachable("Unknown TypeClass enum");
73}
74
75// getSimpleName() returns the plain identifier for an entity, if any.
76llvm::StringRef getSimpleName(const DeclarationName &DN) {
77 if (IdentifierInfo *Ident = DN.getAsIdentifierInfo())
78 return Ident->getName();
79 return "";
80}
81llvm::StringRef getSimpleName(const NamedDecl &D) {
82 return getSimpleName(DN: D.getDeclName());
83}
84llvm::StringRef getSimpleName(QualType T) {
85 if (const auto *ET = llvm::dyn_cast<ElaboratedType>(T))
86 return getSimpleName(ET->getNamedType());
87 if (const auto *BT = llvm::dyn_cast<BuiltinType>(T)) {
88 PrintingPolicy PP(LangOptions{});
89 PP.adjustForCPlusPlus();
90 return BT->getName(PP);
91 }
92 if (const auto *D = getDeclForType(T: T.getTypePtr()))
93 return getSimpleName(DN: D->getDeclName());
94 return "";
95}
96
97// Returns a very abbreviated form of an expression, or "" if it's too complex.
98// For example: `foo->bar()` would produce "bar".
99// This is used to summarize e.g. the condition of a while loop.
100std::string summarizeExpr(const Expr *E) {
101 struct Namer : ConstStmtVisitor<Namer, std::string> {
102 std::string Visit(const Expr *E) {
103 if (E == nullptr)
104 return "";
105 return ConstStmtVisitor::Visit(E->IgnoreImplicit());
106 }
107
108 // Any sort of decl reference, we just use the unqualified name.
109 std::string VisitMemberExpr(const MemberExpr *E) {
110 return getSimpleName(*E->getMemberDecl()).str();
111 }
112 std::string VisitDeclRefExpr(const DeclRefExpr *E) {
113 return getSimpleName(D: *E->getFoundDecl()).str();
114 }
115 std::string VisitCallExpr(const CallExpr *E) {
116 std::string Result = Visit(E: E->getCallee());
117 Result += E->getNumArgs() == 0 ? "()" : "(...)";
118 return Result;
119 }
120 std::string
121 VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *E) {
122 return getSimpleName(DN: E->getMember()).str();
123 }
124 std::string
125 VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr *E) {
126 return getSimpleName(DN: E->getDeclName()).str();
127 }
128 std::string VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *E) {
129 return getSimpleName(E->getType()).str();
130 }
131 std::string VisitCXXTemporaryObjectExpr(const CXXTemporaryObjectExpr *E) {
132 return getSimpleName(E->getType()).str();
133 }
134
135 // Step through implicit nodes that clang doesn't classify as such.
136 std::string VisitCXXMemberCallExpr(const CXXMemberCallExpr *E) {
137 // Call to operator bool() inside if (X): dispatch to X.
138 if (E->getNumArgs() == 0 && E->getMethodDecl() &&
139 E->getMethodDecl()->getDeclName().getNameKind() ==
140 DeclarationName::CXXConversionFunctionName &&
141 E->getSourceRange() ==
142 E->getImplicitObjectArgument()->getSourceRange())
143 return Visit(E: E->getImplicitObjectArgument());
144 return ConstStmtVisitor::VisitCXXMemberCallExpr(E);
145 }
146 std::string VisitCXXConstructExpr(const CXXConstructExpr *E) {
147 if (E->getNumArgs() == 1)
148 return Visit(E: E->getArg(Arg: 0));
149 return "";
150 }
151
152 // Literals are just printed
153 std::string VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *E) {
154 return "nullptr";
155 }
156 std::string VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E) {
157 return E->getValue() ? "true" : "false";
158 }
159 std::string VisitIntegerLiteral(const IntegerLiteral *E) {
160 return llvm::to_string(E->getValue());
161 }
162 std::string VisitFloatingLiteral(const FloatingLiteral *E) {
163 std::string Result;
164 llvm::raw_string_ostream OS(Result);
165 E->getValue().print(OS);
166 // Printer adds newlines?!
167 Result.resize(n: llvm::StringRef(Result).rtrim().size());
168 return Result;
169 }
170 std::string VisitStringLiteral(const StringLiteral *E) {
171 std::string Result = "\"";
172 if (E->containsNonAscii()) {
173 Result += "...";
174 } else {
175 llvm::raw_string_ostream OS(Result);
176 if (E->getLength() > 10) {
177 llvm::printEscapedString(Name: E->getString().take_front(N: 7), Out&: OS);
178 Result += "...";
179 } else {
180 llvm::printEscapedString(Name: E->getString(), Out&: OS);
181 }
182 }
183 Result.push_back(c: '"');
184 return Result;
185 }
186
187 // Simple operators. Motivating cases are `!x` and `I < Length`.
188 std::string printUnary(llvm::StringRef Spelling, const Expr *Operand,
189 bool Prefix) {
190 std::string Sub = Visit(E: Operand);
191 if (Sub.empty())
192 return "";
193 if (Prefix)
194 return (Spelling + Sub).str();
195 Sub += Spelling;
196 return Sub;
197 }
198 bool InsideBinary = false; // No recursing into binary expressions.
199 std::string printBinary(llvm::StringRef Spelling, const Expr *LHSOp,
200 const Expr *RHSOp) {
201 if (InsideBinary)
202 return "";
203 llvm::SaveAndRestore InBinary(InsideBinary, true);
204
205 std::string LHS = Visit(E: LHSOp);
206 std::string RHS = Visit(E: RHSOp);
207 if (LHS.empty() && RHS.empty())
208 return "";
209
210 if (LHS.empty())
211 LHS = "...";
212 LHS.push_back(c: ' ');
213 LHS += Spelling;
214 LHS.push_back(c: ' ');
215 if (RHS.empty())
216 LHS += "...";
217 else
218 LHS += RHS;
219 return LHS;
220 }
221 std::string VisitUnaryOperator(const UnaryOperator *E) {
222 return printUnary(Spelling: E->getOpcodeStr(Op: E->getOpcode()), Operand: E->getSubExpr(),
223 Prefix: !E->isPostfix());
224 }
225 std::string VisitBinaryOperator(const BinaryOperator *E) {
226 return printBinary(Spelling: E->getOpcodeStr(Op: E->getOpcode()), LHSOp: E->getLHS(),
227 RHSOp: E->getRHS());
228 }
229 std::string VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *E) {
230 const char *Spelling = getOperatorSpelling(Operator: E->getOperator());
231 // Handle weird unary-that-look-like-binary postfix operators.
232 if ((E->getOperator() == OO_PlusPlus ||
233 E->getOperator() == OO_MinusMinus) &&
234 E->getNumArgs() == 2)
235 return printUnary(Spelling, Operand: E->getArg(0), Prefix: false);
236 if (E->isInfixBinaryOp())
237 return printBinary(Spelling, LHSOp: E->getArg(0), RHSOp: E->getArg(1));
238 if (E->getNumArgs() == 1) {
239 switch (E->getOperator()) {
240 case OO_Plus:
241 case OO_Minus:
242 case OO_Star:
243 case OO_Amp:
244 case OO_Tilde:
245 case OO_Exclaim:
246 case OO_PlusPlus:
247 case OO_MinusMinus:
248 return printUnary(Spelling, Operand: E->getArg(0), Prefix: true);
249 default:
250 break;
251 }
252 }
253 return "";
254 }
255 };
256 return Namer{}.Visit(E);
257}
258
259// Determines if any intermediate type in desugaring QualType QT is of
260// substituted template parameter type. Ignore pointer or reference wrappers.
261bool isSugaredTemplateParameter(QualType QT) {
262 static auto PeelWrapper = [](QualType QT) {
263 // Neither `PointerType` nor `ReferenceType` is considered as sugared
264 // type. Peel it.
265 QualType Peeled = QT->getPointeeType();
266 return Peeled.isNull() ? QT : Peeled;
267 };
268
269 // This is a bit tricky: we traverse the type structure and find whether or
270 // not a type in the desugaring process is of SubstTemplateTypeParmType.
271 // During the process, we may encounter pointer or reference types that are
272 // not marked as sugared; therefore, the desugar function won't apply. To
273 // move forward the traversal, we retrieve the pointees using
274 // QualType::getPointeeType().
275 //
276 // However, getPointeeType could leap over our interests: The QT::getAs<T>()
277 // invoked would implicitly desugar the type. Consequently, if the
278 // SubstTemplateTypeParmType is encompassed within a TypedefType, we may lose
279 // the chance to visit it.
280 // For example, given a QT that represents `std::vector<int *>::value_type`:
281 // `-ElaboratedType 'value_type' sugar
282 // `-TypedefType 'vector<int *>::value_type' sugar
283 // |-Typedef 'value_type'
284 // `-SubstTemplateTypeParmType 'int *' sugar class depth 0 index 0 T
285 // |-ClassTemplateSpecialization 'vector'
286 // `-PointerType 'int *'
287 // `-BuiltinType 'int'
288 // Applying `getPointeeType` to QT results in 'int', a child of our target
289 // node SubstTemplateTypeParmType.
290 //
291 // As such, we always prefer the desugared over the pointee for next type
292 // in the iteration. It could avoid the getPointeeType's implicit desugaring.
293 while (true) {
294 if (QT->getAs<SubstTemplateTypeParmType>())
295 return true;
296 QualType Desugared = QT->getLocallyUnqualifiedSingleStepDesugaredType();
297 if (Desugared != QT)
298 QT = Desugared;
299 else if (auto Peeled = PeelWrapper(Desugared); Peeled != QT)
300 QT = Peeled;
301 else
302 break;
303 }
304 return false;
305}
306
307// A simple wrapper for `clang::desugarForDiagnostic` that provides optional
308// semantic.
309std::optional<QualType> desugar(ASTContext &AST, QualType QT) {
310 bool ShouldAKA = false;
311 auto Desugared = clang::desugarForDiagnostic(Context&: AST, QT, ShouldAKA);
312 if (!ShouldAKA)
313 return std::nullopt;
314 return Desugared;
315}
316
317// Apply a series of heuristic methods to determine whether or not a QualType QT
318// is suitable for desugaring (e.g. getting the real name behind the using-alias
319// name). If so, return the desugared type. Otherwise, return the unchanged
320// parameter QT.
321//
322// This could be refined further. See
323// https://github.com/clangd/clangd/issues/1298.
324QualType maybeDesugar(ASTContext &AST, QualType QT) {
325 // Prefer desugared type for name that aliases the template parameters.
326 // This can prevent things like printing opaque `: type` when accessing std
327 // containers.
328 if (isSugaredTemplateParameter(QT))
329 return desugar(AST, QT).value_or(QT);
330
331 // Prefer desugared type for `decltype(expr)` specifiers.
332 if (QT->isDecltypeType())
333 return QT.getCanonicalType();
334 if (const AutoType *AT = QT->getContainedAutoType())
335 if (!AT->getDeducedType().isNull() &&
336 AT->getDeducedType()->isDecltypeType())
337 return QT.getCanonicalType();
338
339 return QT;
340}
341
342// Given a callee expression `Fn`, if the call is through a function pointer,
343// try to find the declaration of the corresponding function pointer type,
344// so that we can recover argument names from it.
345// FIXME: This function is mostly duplicated in SemaCodeComplete.cpp; unify.
346static FunctionProtoTypeLoc getPrototypeLoc(Expr *Fn) {
347 TypeLoc Target;
348 Expr *NakedFn = Fn->IgnoreParenCasts();
349 if (const auto *T = NakedFn->getType().getTypePtr()->getAs<TypedefType>()) {
350 Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
351 } else if (const auto *DR = dyn_cast<DeclRefExpr>(NakedFn)) {
352 const auto *D = DR->getDecl();
353 if (const auto *const VD = dyn_cast<VarDecl>(D)) {
354 Target = VD->getTypeSourceInfo()->getTypeLoc();
355 }
356 }
357
358 if (!Target)
359 return {};
360
361 // Unwrap types that may be wrapping the function type
362 while (true) {
363 if (auto P = Target.getAs<PointerTypeLoc>()) {
364 Target = P.getPointeeLoc();
365 continue;
366 }
367 if (auto A = Target.getAs<AttributedTypeLoc>()) {
368 Target = A.getModifiedLoc();
369 continue;
370 }
371 if (auto P = Target.getAs<ParenTypeLoc>()) {
372 Target = P.getInnerLoc();
373 continue;
374 }
375 break;
376 }
377
378 if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
379 // In some edge cases the AST can contain a "trivial" FunctionProtoTypeLoc
380 // which has null parameters. Avoid these as they don't contain useful
381 // information.
382 if (llvm::all_of(F.getParams(), llvm::identity<ParmVarDecl *>()))
383 return F;
384 }
385
386 return {};
387}
388
389ArrayRef<const ParmVarDecl *>
390maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
391 if (!Params.empty() && Params.front()->isExplicitObjectParameter())
392 Params = Params.drop_front(1);
393 return Params;
394}
395
396template <typename R>
397std::string joinAndTruncate(const R &Range, size_t MaxLength) {
398 std::string Out;
399 llvm::raw_string_ostream OS(Out);
400 llvm::ListSeparator Sep(", ");
401 for (auto &&Element : Range) {
402 OS << Sep;
403 if (Out.size() + Element.size() >= MaxLength) {
404 OS << "...";
405 break;
406 }
407 OS << Element;
408 }
409 OS.flush();
410 return Out;
411}
412
413struct Callee {
414 // Only one of Decl or Loc is set.
415 // Loc is for calls through function pointers.
416 const FunctionDecl *Decl = nullptr;
417 FunctionProtoTypeLoc Loc;
418};
419
420class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
421public:
422 InlayHintVisitor(std::vector<InlayHint> &Results, ParsedAST &AST,
423 const Config &Cfg, std::optional<Range> RestrictRange,
424 InlayHintOptions HintOptions)
425 : Results(Results), AST(AST.getASTContext()), Tokens(AST.getTokens()),
426 Cfg(Cfg), RestrictRange(std::move(RestrictRange)),
427 MainFileID(AST.getSourceManager().getMainFileID()),
428 Resolver(AST.getHeuristicResolver()),
429 TypeHintPolicy(this->AST.getPrintingPolicy()),
430 HintOptions(HintOptions) {
431 bool Invalid = false;
432 llvm::StringRef Buf =
433 AST.getSourceManager().getBufferData(FID: MainFileID, Invalid: &Invalid);
434 MainFileBuf = Invalid ? StringRef{} : Buf;
435
436 TypeHintPolicy.SuppressScope = true; // keep type names short
437 TypeHintPolicy.AnonymousTagLocations =
438 false; // do not print lambda locations
439
440 // Not setting PrintCanonicalTypes for "auto" allows
441 // SuppressDefaultTemplateArgs (set by default) to have an effect.
442 }
443
444 bool VisitTypeLoc(TypeLoc TL) {
445 if (const auto *DT = llvm::dyn_cast<DecltypeType>(TL.getType()))
446 if (QualType UT = DT->getUnderlyingType(); !UT->isDependentType())
447 addTypeHint(R: TL.getSourceRange(), T: UT, Prefix: ": ");
448 return true;
449 }
450
451 bool VisitCXXConstructExpr(CXXConstructExpr *E) {
452 // Weed out constructor calls that don't look like a function call with
453 // an argument list, by checking the validity of getParenOrBraceRange().
454 // Also weed out std::initializer_list constructors as there are no names
455 // for the individual arguments.
456 if (!E->getParenOrBraceRange().isValid() ||
457 E->isStdInitListInitialization()) {
458 return true;
459 }
460
461 Callee Callee;
462 Callee.Decl = E->getConstructor();
463 if (!Callee.Decl)
464 return true;
465 processCall(Callee, RParenOrBraceLoc: E->getParenOrBraceRange().getEnd(),
466 Args: {E->getArgs(), E->getNumArgs()});
467 return true;
468 }
469
470 // Carefully recurse into PseudoObjectExprs, which typically incorporate
471 // a syntactic expression and several semantic expressions.
472 bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {
473 Expr *SyntacticExpr = E->getSyntacticForm();
474 if (isa<CallExpr>(SyntacticExpr))
475 // Since the counterpart semantics usually get the identical source
476 // locations as the syntactic one, visiting those would end up presenting
477 // confusing hints e.g., __builtin_dump_struct.
478 // Thus, only traverse the syntactic forms if this is written as a
479 // CallExpr. This leaves the door open in case the arguments in the
480 // syntactic form could possibly get parameter names.
481 return RecursiveASTVisitor<InlayHintVisitor>::TraverseStmt(SyntacticExpr);
482 // We don't want the hints for some of the MS property extensions.
483 // e.g.
484 // struct S {
485 // __declspec(property(get=GetX, put=PutX)) int x[];
486 // void PutX(int y);
487 // void Work(int y) { x = y; } // Bad: `x = y: y`.
488 // };
489 if (isa<BinaryOperator>(SyntacticExpr))
490 return true;
491 // FIXME: Handle other forms of a pseudo object expression.
492 return RecursiveASTVisitor<InlayHintVisitor>::TraversePseudoObjectExpr(E);
493 }
494
495 bool VisitCallExpr(CallExpr *E) {
496 if (!Cfg.InlayHints.Parameters)
497 return true;
498
499 bool IsFunctor = isFunctionObjectCallExpr(E);
500 // Do not show parameter hints for user-defined literals or
501 // operator calls except for operator(). (Among other reasons, the resulting
502 // hints can look awkward, e.g. the expression can itself be a function
503 // argument and then we'd get two hints side by side).
504 if ((isa<CXXOperatorCallExpr>(E) && !IsFunctor) ||
505 isa<UserDefinedLiteral>(E))
506 return true;
507
508 auto CalleeDecls = Resolver->resolveCalleeOfCallExpr(E);
509 if (CalleeDecls.size() != 1)
510 return true;
511
512 Callee Callee;
513 if (const auto *FD = dyn_cast<FunctionDecl>(CalleeDecls[0]))
514 Callee.Decl = FD;
515 else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CalleeDecls[0]))
516 Callee.Decl = FTD->getTemplatedDecl();
517 else if (FunctionProtoTypeLoc Loc = getPrototypeLoc(E->getCallee()))
518 Callee.Loc = Loc;
519 else
520 return true;
521
522 // N4868 [over.call.object]p3 says,
523 // The argument list submitted to overload resolution consists of the
524 // argument expressions present in the function call syntax preceded by the
525 // implied object argument (E).
526 //
527 // As well as the provision from P0847R7 Deducing This [expr.call]p7:
528 // ...If the function is an explicit object member function and there is an
529 // implied object argument ([over.call.func]), the list of provided
530 // arguments is preceded by the implied object argument for the purposes of
531 // this correspondence...
532 llvm::ArrayRef<const Expr *> Args = {E->getArgs(), E->getNumArgs()};
533 // We don't have the implied object argument through a function pointer
534 // either.
535 if (const CXXMethodDecl *Method =
536 dyn_cast_or_null<CXXMethodDecl>(Callee.Decl))
537 if (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter())
538 Args = Args.drop_front(N: 1);
539 processCall(Callee, RParenOrBraceLoc: E->getRParenLoc(), Args);
540 return true;
541 }
542
543 bool VisitFunctionDecl(FunctionDecl *D) {
544 if (auto *FPT =
545 llvm::dyn_cast<FunctionProtoType>(D->getType().getTypePtr())) {
546 if (!FPT->hasTrailingReturn()) {
547 if (auto FTL = D->getFunctionTypeLoc())
548 addReturnTypeHint(D, Range: FTL.getRParenLoc());
549 }
550 }
551 if (Cfg.InlayHints.BlockEnd && D->isThisDeclarationADefinition()) {
552 // We use `printName` here to properly print name of ctor/dtor/operator
553 // overload.
554 if (const Stmt *Body = D->getBody())
555 addBlockEndHint(BraceRange: Body->getSourceRange(), DeclPrefix: "", Name: printName(AST, *D), OptionalPunctuation: "");
556 }
557 return true;
558 }
559
560 bool VisitForStmt(ForStmt *S) {
561 if (Cfg.InlayHints.BlockEnd) {
562 std::string Name;
563 // Common case: for (int I = 0; I < N; I++). Use "I" as the name.
564 if (auto *DS = llvm::dyn_cast_or_null<DeclStmt>(S->getInit());
565 DS && DS->isSingleDecl())
566 Name = getSimpleName(llvm::cast<NamedDecl>(*DS->getSingleDecl()));
567 else
568 Name = summarizeExpr(E: S->getCond());
569 markBlockEnd(Body: S->getBody(), Label: "for", Name);
570 }
571 return true;
572 }
573
574 bool VisitCXXForRangeStmt(CXXForRangeStmt *S) {
575 if (Cfg.InlayHints.BlockEnd)
576 markBlockEnd(Body: S->getBody(), Label: "for", Name: getSimpleName(*S->getLoopVariable()));
577 return true;
578 }
579
580 bool VisitWhileStmt(WhileStmt *S) {
581 if (Cfg.InlayHints.BlockEnd)
582 markBlockEnd(Body: S->getBody(), Label: "while", Name: summarizeExpr(E: S->getCond()));
583 return true;
584 }
585
586 bool VisitSwitchStmt(SwitchStmt *S) {
587 if (Cfg.InlayHints.BlockEnd)
588 markBlockEnd(Body: S->getBody(), Label: "switch", Name: summarizeExpr(E: S->getCond()));
589 return true;
590 }
591
592 // If/else chains are tricky.
593 // if (cond1) {
594 // } else if (cond2) {
595 // } // mark as "cond1" or "cond2"?
596 // For now, the answer is neither, just mark as "if".
597 // The ElseIf is a different IfStmt that doesn't know about the outer one.
598 llvm::DenseSet<const IfStmt *> ElseIfs; // not eligible for names
599 bool VisitIfStmt(IfStmt *S) {
600 if (Cfg.InlayHints.BlockEnd) {
601 if (const auto *ElseIf = llvm::dyn_cast_or_null<IfStmt>(S->getElse()))
602 ElseIfs.insert(ElseIf);
603 // Don't use markBlockEnd: the relevant range is [then.begin, else.end].
604 if (const auto *EndCS = llvm::dyn_cast<CompoundStmt>(
605 S->getElse() ? S->getElse() : S->getThen())) {
606 addBlockEndHint(
607 {S->getThen()->getBeginLoc(), EndCS->getRBracLoc()}, "if",
608 ElseIfs.contains(S) ? "" : summarizeExpr(S->getCond()), "");
609 }
610 }
611 return true;
612 }
613
614 void markBlockEnd(const Stmt *Body, llvm::StringRef Label,
615 llvm::StringRef Name = "") {
616 if (const auto *CS = llvm::dyn_cast_or_null<CompoundStmt>(Body))
617 addBlockEndHint(BraceRange: CS->getSourceRange(), DeclPrefix: Label, Name, OptionalPunctuation: "");
618 }
619
620 bool VisitTagDecl(TagDecl *D) {
621 if (Cfg.InlayHints.BlockEnd && D->isThisDeclarationADefinition()) {
622 std::string DeclPrefix = D->getKindName().str();
623 if (const auto *ED = dyn_cast<EnumDecl>(D)) {
624 if (ED->isScoped())
625 DeclPrefix += ED->isScopedUsingClassTag() ? " class" : " struct";
626 };
627 addBlockEndHint(BraceRange: D->getBraceRange(), DeclPrefix, Name: getSimpleName(*D), OptionalPunctuation: ";");
628 }
629 return true;
630 }
631
632 bool VisitNamespaceDecl(NamespaceDecl *D) {
633 if (Cfg.InlayHints.BlockEnd) {
634 // For namespace, the range actually starts at the namespace keyword. But
635 // it should be fine since it's usually very short.
636 addBlockEndHint(BraceRange: D->getSourceRange(), DeclPrefix: "namespace", Name: getSimpleName(*D), OptionalPunctuation: "");
637 }
638 return true;
639 }
640
641 bool VisitLambdaExpr(LambdaExpr *E) {
642 FunctionDecl *D = E->getCallOperator();
643 if (!E->hasExplicitResultType()) {
644 SourceLocation TypeHintLoc;
645 if (!E->hasExplicitParameters())
646 TypeHintLoc = E->getIntroducerRange().getEnd();
647 else if (auto FTL = D->getFunctionTypeLoc())
648 TypeHintLoc = FTL.getRParenLoc();
649 if (TypeHintLoc.isValid())
650 addReturnTypeHint(D, Range: TypeHintLoc);
651 }
652 return true;
653 }
654
655 void addReturnTypeHint(FunctionDecl *D, SourceRange Range) {
656 auto *AT = D->getReturnType()->getContainedAutoType();
657 if (!AT || AT->getDeducedType().isNull())
658 return;
659 addTypeHint(R: Range, T: D->getReturnType(), /*Prefix=*/"-> ");
660 }
661
662 bool VisitVarDecl(VarDecl *D) {
663 // Do not show hints for the aggregate in a structured binding,
664 // but show hints for the individual bindings.
665 if (auto *DD = dyn_cast<DecompositionDecl>(D)) {
666 for (auto *Binding : DD->bindings()) {
667 // For structured bindings, print canonical types. This is important
668 // because for bindings that use the tuple_element protocol, the
669 // non-canonical types would be "tuple_element<I, A>::type".
670 if (auto Type = Binding->getType();
671 !Type.isNull() && !Type->isDependentType())
672 addTypeHint(Binding->getLocation(), Type.getCanonicalType(),
673 /*Prefix=*/": ");
674 }
675 return true;
676 }
677
678 if (auto *AT = D->getType()->getContainedAutoType()) {
679 if (AT->isDeduced() && !D->getType()->isDependentType()) {
680 // Our current approach is to place the hint on the variable
681 // and accordingly print the full type
682 // (e.g. for `const auto& x = 42`, print `const int&`).
683 // Alternatively, we could place the hint on the `auto`
684 // (and then just print the type deduced for the `auto`).
685 addTypeHint(R: D->getLocation(), T: D->getType(), /*Prefix=*/": ");
686 }
687 }
688
689 // Handle templates like `int foo(auto x)` with exactly one instantiation.
690 if (auto *PVD = llvm::dyn_cast<ParmVarDecl>(D)) {
691 if (D->getIdentifier() && PVD->getType()->isDependentType() &&
692 !getContainedAutoParamType(D->getTypeSourceInfo()->getTypeLoc())
693 .isNull()) {
694 if (auto *IPVD = getOnlyParamInstantiation(PVD))
695 addTypeHint(R: D->getLocation(), T: IPVD->getType(), /*Prefix=*/": ");
696 }
697 }
698
699 return true;
700 }
701
702 ParmVarDecl *getOnlyParamInstantiation(ParmVarDecl *D) {
703 auto *TemplateFunction = llvm::dyn_cast<FunctionDecl>(D->getDeclContext());
704 if (!TemplateFunction)
705 return nullptr;
706 auto *InstantiatedFunction = llvm::dyn_cast_or_null<FunctionDecl>(
707 getOnlyInstantiation(TemplateFunction));
708 if (!InstantiatedFunction)
709 return nullptr;
710
711 unsigned ParamIdx = 0;
712 for (auto *Param : TemplateFunction->parameters()) {
713 // Can't reason about param indexes in the presence of preceding packs.
714 // And if this param is a pack, it may expand to multiple params.
715 if (Param->isParameterPack())
716 return nullptr;
717 if (Param == D)
718 break;
719 ++ParamIdx;
720 }
721 assert(ParamIdx < TemplateFunction->getNumParams() &&
722 "Couldn't find param in list?");
723 assert(ParamIdx < InstantiatedFunction->getNumParams() &&
724 "Instantiated function has fewer (non-pack) parameters?");
725 return InstantiatedFunction->getParamDecl(ParamIdx);
726 }
727
728 bool VisitInitListExpr(InitListExpr *Syn) {
729 // We receive the syntactic form here (shouldVisitImplicitCode() is false).
730 // This is the one we will ultimately attach designators to.
731 // It may have subobject initializers inlined without braces. The *semantic*
732 // form of the init-list has nested init-lists for these.
733 // getUnwrittenDesignators will look at the semantic form to determine the
734 // labels.
735 assert(Syn->isSyntacticForm() && "RAV should not visit implicit code!");
736 if (!Cfg.InlayHints.Designators)
737 return true;
738 if (Syn->isIdiomaticZeroInitializer(LangOpts: AST.getLangOpts()))
739 return true;
740 llvm::DenseMap<SourceLocation, std::string> Designators =
741 tidy::utils::getUnwrittenDesignators(Syn);
742 for (const Expr *Init : Syn->inits()) {
743 if (llvm::isa<DesignatedInitExpr>(Init))
744 continue;
745 auto It = Designators.find(Init->getBeginLoc());
746 if (It != Designators.end() &&
747 !isPrecededByParamNameComment(E: Init, ParamName: It->second))
748 addDesignatorHint(R: Init->getSourceRange(), Text: It->second);
749 }
750 return true;
751 }
752
753 // FIXME: Handle RecoveryExpr to try to hint some invalid calls.
754
755private:
756 using NameVec = SmallVector<StringRef, 8>;
757
758 void processCall(Callee Callee, SourceLocation RParenOrBraceLoc,
759 llvm::ArrayRef<const Expr *> Args) {
760 assert(Callee.Decl || Callee.Loc);
761
762 if ((!Cfg.InlayHints.Parameters && !Cfg.InlayHints.DefaultArguments) ||
763 Args.size() == 0)
764 return;
765
766 // The parameter name of a move or copy constructor is not very interesting.
767 if (Callee.Decl)
768 if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee.Decl))
769 if (Ctor->isCopyOrMoveConstructor())
770 return;
771
772 SmallVector<std::string> FormattedDefaultArgs;
773 bool HasNonDefaultArgs = false;
774
775 ArrayRef<const ParmVarDecl *> Params, ForwardedParams;
776 // Resolve parameter packs to their forwarded parameter
777 SmallVector<const ParmVarDecl *> ForwardedParamsStorage;
778 if (Callee.Decl) {
779 Params = maybeDropCxxExplicitObjectParameters(Callee.Decl->parameters());
780 ForwardedParamsStorage = resolveForwardingParameters(Callee.Decl);
781 ForwardedParams =
782 maybeDropCxxExplicitObjectParameters(ForwardedParamsStorage);
783 } else {
784 Params = maybeDropCxxExplicitObjectParameters(Callee.Loc.getParams());
785 ForwardedParams = {Params.begin(), Params.end()};
786 }
787
788 NameVec ParameterNames = chooseParameterNames(Parameters: ForwardedParams);
789
790 // Exclude setters (i.e. functions with one argument whose name begins with
791 // "set"), and builtins like std::move/forward/... as their parameter name
792 // is also not likely to be interesting.
793 if (Callee.Decl &&
794 (isSetter(Callee: Callee.Decl, ParamNames: ParameterNames) || isSimpleBuiltin(Callee: Callee.Decl)))
795 return;
796
797 for (size_t I = 0; I < ParameterNames.size() && I < Args.size(); ++I) {
798 // Pack expansion expressions cause the 1:1 mapping between arguments and
799 // parameters to break down, so we don't add further inlay hints if we
800 // encounter one.
801 if (isa<PackExpansionExpr>(Args[I])) {
802 break;
803 }
804
805 StringRef Name = ParameterNames[I];
806 const bool NameHint =
807 shouldHintName(Arg: Args[I], ParamName: Name) && Cfg.InlayHints.Parameters;
808 const bool ReferenceHint =
809 shouldHintReference(Param: Params[I], ForwardedParam: ForwardedParams[I]) &&
810 Cfg.InlayHints.Parameters;
811
812 const bool IsDefault = isa<CXXDefaultArgExpr>(Args[I]);
813 HasNonDefaultArgs |= !IsDefault;
814 if (IsDefault) {
815 if (Cfg.InlayHints.DefaultArguments) {
816 const auto SourceText = Lexer::getSourceText(
817 Range: CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()),
818 SM: AST.getSourceManager(), LangOpts: AST.getLangOpts());
819 const auto Abbrev =
820 (SourceText.size() > Cfg.InlayHints.TypeNameLimit ||
821 SourceText.contains("\n"))
822 ? "..."
823 : SourceText;
824 if (NameHint)
825 FormattedDefaultArgs.emplace_back(
826 llvm::formatv("{0}: {1}", Name, Abbrev));
827 else
828 FormattedDefaultArgs.emplace_back(llvm::formatv("{0}", Abbrev));
829 }
830 } else if (NameHint || ReferenceHint) {
831 addInlayHint(Args[I]->getSourceRange(), HintSide::Left,
832 InlayHintKind::Parameter, ReferenceHint ? "&" : "",
833 NameHint ? Name : "", ": ");
834 }
835 }
836
837 if (!FormattedDefaultArgs.empty()) {
838 std::string Hint =
839 joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit);
840 addInlayHint(R: SourceRange{RParenOrBraceLoc}, Side: HintSide::Left,
841 Kind: InlayHintKind::DefaultArgument,
842 Prefix: HasNonDefaultArgs ? ", " : "", Label: Hint, Suffix: "");
843 }
844 }
845
846 static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) {
847 if (ParamNames.size() != 1)
848 return false;
849
850 StringRef Name = getSimpleName(*Callee);
851 if (!Name.starts_with_insensitive(Prefix: "set"))
852 return false;
853
854 // In addition to checking that the function has one parameter and its
855 // name starts with "set", also check that the part after "set" matches
856 // the name of the parameter (ignoring case). The idea here is that if
857 // the parameter name differs, it may contain extra information that
858 // may be useful to show in a hint, as in:
859 // void setTimeout(int timeoutMillis);
860 // This currently doesn't handle cases where params use snake_case
861 // and functions don't, e.g.
862 // void setExceptionHandler(EHFunc exception_handler);
863 // We could improve this by replacing `equals_insensitive` with some
864 // `sloppy_equals` which ignores case and also skips underscores.
865 StringRef WhatItIsSetting = Name.substr(Start: 3).ltrim(Chars: "_");
866 return WhatItIsSetting.equals_insensitive(RHS: ParamNames[0]);
867 }
868
869 // Checks if the callee is one of the builtins
870 // addressof, as_const, forward, move(_if_noexcept)
871 static bool isSimpleBuiltin(const FunctionDecl *Callee) {
872 switch (Callee->getBuiltinID()) {
873 case Builtin::BIaddressof:
874 case Builtin::BIas_const:
875 case Builtin::BIforward:
876 case Builtin::BImove:
877 case Builtin::BImove_if_noexcept:
878 return true;
879 default:
880 return false;
881 }
882 }
883
884 bool shouldHintName(const Expr *Arg, StringRef ParamName) {
885 if (ParamName.empty())
886 return false;
887
888 // If the argument expression is a single name and it matches the
889 // parameter name exactly, omit the name hint.
890 if (ParamName == getSpelledIdentifier(E: Arg))
891 return false;
892
893 // Exclude argument expressions preceded by a /*paramName*/.
894 if (isPrecededByParamNameComment(E: Arg, ParamName))
895 return false;
896
897 return true;
898 }
899
900 bool shouldHintReference(const ParmVarDecl *Param,
901 const ParmVarDecl *ForwardedParam) {
902 // We add a & hint only when the argument is passed as mutable reference.
903 // For parameters that are not part of an expanded pack, this is
904 // straightforward. For expanded pack parameters, it's likely that they will
905 // be forwarded to another function. In this situation, we only want to add
906 // the reference hint if the argument is actually being used via mutable
907 // reference. This means we need to check
908 // 1. whether the value category of the argument is preserved, i.e. each
909 // pack expansion uses std::forward correctly.
910 // 2. whether the argument is ever copied/cast instead of passed
911 // by-reference
912 // Instead of checking this explicitly, we use the following proxy:
913 // 1. the value category can only change from rvalue to lvalue during
914 // forwarding, so checking whether both the parameter of the forwarding
915 // function and the forwarded function are lvalue references detects such
916 // a conversion.
917 // 2. if the argument is copied/cast somewhere in the chain of forwarding
918 // calls, it can only be passed on to an rvalue reference or const lvalue
919 // reference parameter. Thus if the forwarded parameter is a mutable
920 // lvalue reference, it cannot have been copied/cast to on the way.
921 // Additionally, we should not add a reference hint if the forwarded
922 // parameter was only partially resolved, i.e. points to an expanded pack
923 // parameter, since we do not know how it will be used eventually.
924 auto Type = Param->getType();
925 auto ForwardedType = ForwardedParam->getType();
926 return Type->isLValueReferenceType() &&
927 ForwardedType->isLValueReferenceType() &&
928 !ForwardedType.getNonReferenceType().isConstQualified() &&
929 !isExpandedFromParameterPack(D: ForwardedParam);
930 }
931
932 // Checks if "E" is spelled in the main file and preceded by a C-style comment
933 // whose contents match ParamName (allowing for whitespace and an optional "="
934 // at the end.
935 bool isPrecededByParamNameComment(const Expr *E, StringRef ParamName) {
936 auto &SM = AST.getSourceManager();
937 auto FileLoc = SM.getFileLoc(Loc: E->getBeginLoc());
938 auto Decomposed = SM.getDecomposedLoc(Loc: FileLoc);
939 if (Decomposed.first != MainFileID)
940 return false;
941
942 StringRef SourcePrefix = MainFileBuf.substr(Start: 0, N: Decomposed.second);
943 // Allow whitespace between comment and expression.
944 SourcePrefix = SourcePrefix.rtrim();
945 // Check for comment ending.
946 if (!SourcePrefix.consume_back(Suffix: "*/"))
947 return false;
948 // Ignore some punctuation and whitespace around comment.
949 // In particular this allows designators to match nicely.
950 llvm::StringLiteral IgnoreChars = " =.";
951 SourcePrefix = SourcePrefix.rtrim(Chars: IgnoreChars);
952 ParamName = ParamName.trim(Chars: IgnoreChars);
953 // Other than that, the comment must contain exactly ParamName.
954 if (!SourcePrefix.consume_back(Suffix: ParamName))
955 return false;
956 SourcePrefix = SourcePrefix.rtrim(Chars: IgnoreChars);
957 return SourcePrefix.ends_with(Suffix: "/*");
958 }
959
960 // If "E" spells a single unqualified identifier, return that name.
961 // Otherwise, return an empty string.
962 static StringRef getSpelledIdentifier(const Expr *E) {
963 E = E->IgnoreUnlessSpelledInSource();
964
965 if (auto *DRE = dyn_cast<DeclRefExpr>(E))
966 if (!DRE->getQualifier())
967 return getSimpleName(*DRE->getDecl());
968
969 if (auto *ME = dyn_cast<MemberExpr>(E))
970 if (!ME->getQualifier() && ME->isImplicitAccess())
971 return getSimpleName(*ME->getMemberDecl());
972
973 return {};
974 }
975
976 NameVec chooseParameterNames(ArrayRef<const ParmVarDecl *> Parameters) {
977 NameVec ParameterNames;
978 for (const auto *P : Parameters) {
979 if (isExpandedFromParameterPack(P)) {
980 // If we haven't resolved a pack paramater (e.g. foo(Args... args)) to a
981 // non-pack parameter, then hinting as foo(args: 1, args: 2, args: 3) is
982 // unlikely to be useful.
983 ParameterNames.emplace_back();
984 } else {
985 auto SimpleName = getSimpleName(*P);
986 // If the parameter is unnamed in the declaration:
987 // attempt to get its name from the definition
988 if (SimpleName.empty()) {
989 if (const auto *PD = getParamDefinition(P)) {
990 SimpleName = getSimpleName(*PD);
991 }
992 }
993 ParameterNames.emplace_back(SimpleName);
994 }
995 }
996
997 // Standard library functions often have parameter names that start
998 // with underscores, which makes the hints noisy, so strip them out.
999 for (auto &Name : ParameterNames)
1000 stripLeadingUnderscores(Name);
1001
1002 return ParameterNames;
1003 }
1004
1005 // for a ParmVarDecl from a function declaration, returns the corresponding
1006 // ParmVarDecl from the definition if possible, nullptr otherwise.
1007 static const ParmVarDecl *getParamDefinition(const ParmVarDecl *P) {
1008 if (auto *Callee = dyn_cast<FunctionDecl>(P->getDeclContext())) {
1009 if (auto *Def = Callee->getDefinition()) {
1010 auto I = std::distance(Callee->param_begin(),
1011 llvm::find(Callee->parameters(), P));
1012 if (I < (int)Callee->getNumParams()) {
1013 return Def->getParamDecl(I);
1014 }
1015 }
1016 }
1017 return nullptr;
1018 }
1019
1020 // We pass HintSide rather than SourceLocation because we want to ensure
1021 // it is in the same file as the common file range.
1022 void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind,
1023 llvm::StringRef Prefix, llvm::StringRef Label,
1024 llvm::StringRef Suffix) {
1025 auto LSPRange = getHintRange(R);
1026 if (!LSPRange)
1027 return;
1028
1029 addInlayHint(LSPRange: *LSPRange, Side, Kind, Prefix, Label, Suffix);
1030 }
1031
1032 void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,
1033 llvm::StringRef Prefix, llvm::StringRef Label,
1034 llvm::StringRef Suffix) {
1035 // We shouldn't get as far as adding a hint if the category is disabled.
1036 // We'd like to disable as much of the analysis as possible above instead.
1037 // Assert in debug mode but add a dynamic check in production.
1038 assert(Cfg.InlayHints.Enabled && "Shouldn't get here if disabled!");
1039 switch (Kind) {
1040#define CHECK_KIND(Enumerator, ConfigProperty) \
1041 case InlayHintKind::Enumerator: \
1042 assert(Cfg.InlayHints.ConfigProperty && \
1043 "Shouldn't get here if kind is disabled!"); \
1044 if (!Cfg.InlayHints.ConfigProperty) \
1045 return; \
1046 break
1047 CHECK_KIND(Parameter, Parameters);
1048 CHECK_KIND(Type, DeducedTypes);
1049 CHECK_KIND(Designator, Designators);
1050 CHECK_KIND(BlockEnd, BlockEnd);
1051 CHECK_KIND(DefaultArgument, DefaultArguments);
1052#undef CHECK_KIND
1053 }
1054
1055 Position LSPPos = Side == HintSide::Left ? LSPRange.start : LSPRange.end;
1056 if (RestrictRange &&
1057 (LSPPos < RestrictRange->start || !(LSPPos < RestrictRange->end)))
1058 return;
1059 bool PadLeft = Prefix.consume_front(Prefix: " ");
1060 bool PadRight = Suffix.consume_back(Suffix: " ");
1061 Results.push_back(InlayHint{LSPPos,
1062 /*label=*/{(Prefix + Label + Suffix).str()},
1063 Kind, PadLeft, PadRight, LSPRange});
1064 }
1065
1066 // Get the range of the main file that *exactly* corresponds to R.
1067 std::optional<Range> getHintRange(SourceRange R) {
1068 const auto &SM = AST.getSourceManager();
1069 auto Spelled = Tokens.spelledForExpanded(Tokens.expandedTokens(R));
1070 // TokenBuffer will return null if e.g. R corresponds to only part of a
1071 // macro expansion.
1072 if (!Spelled || Spelled->empty())
1073 return std::nullopt;
1074 // Hint must be within the main file, not e.g. a non-preamble include.
1075 if (SM.getFileID(Spelled->front().location()) != SM.getMainFileID() ||
1076 SM.getFileID(Spelled->back().location()) != SM.getMainFileID())
1077 return std::nullopt;
1078 return Range{sourceLocToPosition(SM, Spelled->front().location()),
1079 sourceLocToPosition(SM, Spelled->back().endLocation())};
1080 }
1081
1082 void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {
1083 if (!Cfg.InlayHints.DeducedTypes || T.isNull())
1084 return;
1085
1086 // The sugared type is more useful in some cases, and the canonical
1087 // type in other cases.
1088 auto Desugared = maybeDesugar(AST, QT: T);
1089 std::string TypeName = Desugared.getAsString(Policy: TypeHintPolicy);
1090 if (T != Desugared && !shouldPrintTypeHint(TypeName)) {
1091 // If the desugared type is too long to display, fallback to the sugared
1092 // type.
1093 TypeName = T.getAsString(Policy: TypeHintPolicy);
1094 }
1095 if (shouldPrintTypeHint(TypeName))
1096 addInlayHint(R, Side: HintSide::Right, Kind: InlayHintKind::Type, Prefix, Label: TypeName,
1097 /*Suffix=*/"");
1098 }
1099
1100 void addDesignatorHint(SourceRange R, llvm::StringRef Text) {
1101 addInlayHint(R, Side: HintSide::Left, Kind: InlayHintKind::Designator,
1102 /*Prefix=*/"", Label: Text, /*Suffix=*/"=");
1103 }
1104
1105 bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept {
1106 return Cfg.InlayHints.TypeNameLimit == 0 ||
1107 TypeName.size() < Cfg.InlayHints.TypeNameLimit;
1108 }
1109
1110 void addBlockEndHint(SourceRange BraceRange, StringRef DeclPrefix,
1111 StringRef Name, StringRef OptionalPunctuation) {
1112 auto HintRange = computeBlockEndHintRange(BraceRange, OptionalPunctuation);
1113 if (!HintRange)
1114 return;
1115
1116 std::string Label = DeclPrefix.str();
1117 if (!Label.empty() && !Name.empty())
1118 Label += ' ';
1119 Label += Name;
1120
1121 constexpr unsigned HintMaxLengthLimit = 60;
1122 if (Label.length() > HintMaxLengthLimit)
1123 return;
1124
1125 addInlayHint(LSPRange: *HintRange, Side: HintSide::Right, Kind: InlayHintKind::BlockEnd, Prefix: " // ",
1126 Label, Suffix: "");
1127 }
1128
1129 // Compute the LSP range to attach the block end hint to, if any allowed.
1130 // 1. "}" is the last non-whitespace character on the line. The range of "}"
1131 // is returned.
1132 // 2. After "}", if the trimmed trailing text is exactly
1133 // `OptionalPunctuation`, say ";". The range of "} ... ;" is returned.
1134 // Otherwise, the hint shouldn't be shown.
1135 std::optional<Range> computeBlockEndHintRange(SourceRange BraceRange,
1136 StringRef OptionalPunctuation) {
1137
1138 auto &SM = AST.getSourceManager();
1139 auto [BlockBeginFileId, BlockBeginOffset] =
1140 SM.getDecomposedLoc(SM.getFileLoc(Loc: BraceRange.getBegin()));
1141 auto RBraceLoc = SM.getFileLoc(Loc: BraceRange.getEnd());
1142 auto [RBraceFileId, RBraceOffset] = SM.getDecomposedLoc(RBraceLoc);
1143
1144 // Because we need to check the block satisfies the minimum line limit, we
1145 // require both source location to be in the main file. This prevents hint
1146 // to be shown in weird cases like '{' is actually in a "#include", but it's
1147 // rare anyway.
1148 if (BlockBeginFileId != MainFileID || RBraceFileId != MainFileID)
1149 return std::nullopt;
1150
1151 StringRef RestOfLine = MainFileBuf.substr(Start: RBraceOffset).split('\n').first;
1152 if (!RestOfLine.starts_with(Prefix: "}"))
1153 return std::nullopt;
1154
1155 StringRef TrimmedTrailingText = RestOfLine.drop_front().trim();
1156 if (!TrimmedTrailingText.empty() &&
1157 TrimmedTrailingText != OptionalPunctuation)
1158 return std::nullopt;
1159
1160 auto BlockBeginLine = SM.getLineNumber(FID: BlockBeginFileId, FilePos: BlockBeginOffset);
1161 auto RBraceLine = SM.getLineNumber(FID: RBraceFileId, FilePos: RBraceOffset);
1162
1163 // Don't show hint on trivial blocks like `class X {};`
1164 if (BlockBeginLine + HintOptions.HintMinLineLimit - 1 > RBraceLine)
1165 return std::nullopt;
1166
1167 // This is what we attach the hint to, usually "}" or "};".
1168 StringRef HintRangeText = RestOfLine.take_front(
1169 N: TrimmedTrailingText.empty()
1170 ? 1
1171 : TrimmedTrailingText.bytes_end() - RestOfLine.bytes_begin());
1172
1173 Position HintStart = sourceLocToPosition(SM, Loc: RBraceLoc);
1174 Position HintEnd = sourceLocToPosition(
1175 SM, Loc: RBraceLoc.getLocWithOffset(Offset: HintRangeText.size()));
1176 return Range{.start: HintStart, .end: HintEnd};
1177 }
1178
1179 static bool isFunctionObjectCallExpr(CallExpr *E) noexcept {
1180 if (auto *CallExpr = dyn_cast<CXXOperatorCallExpr>(E))
1181 return CallExpr->getOperator() == OverloadedOperatorKind::OO_Call;
1182 return false;
1183 }
1184
1185 std::vector<InlayHint> &Results;
1186 ASTContext &AST;
1187 const syntax::TokenBuffer &Tokens;
1188 const Config &Cfg;
1189 std::optional<Range> RestrictRange;
1190 FileID MainFileID;
1191 StringRef MainFileBuf;
1192 const HeuristicResolver *Resolver;
1193 PrintingPolicy TypeHintPolicy;
1194 InlayHintOptions HintOptions;
1195};
1196
1197} // namespace
1198
1199std::vector<InlayHint> inlayHints(ParsedAST &AST,
1200 std::optional<Range> RestrictRange,
1201 InlayHintOptions HintOptions) {
1202 std::vector<InlayHint> Results;
1203 const auto &Cfg = Config::current();
1204 if (!Cfg.InlayHints.Enabled)
1205 return Results;
1206 InlayHintVisitor Visitor(Results, AST, Cfg, std::move(RestrictRange),
1207 HintOptions);
1208 Visitor.TraverseAST(AST.getASTContext());
1209
1210 // De-duplicate hints. Duplicates can sometimes occur due to e.g. explicit
1211 // template instantiations.
1212 llvm::sort(Results);
1213 Results.erase(llvm::unique(Results), Results.end());
1214
1215 return Results;
1216}
1217
1218} // namespace clangd
1219} // namespace clang
1220

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clangd/InlayHints.cpp