Skip to content

Commit

Permalink
[clangd] Inlay hints for default args, default initializations, impli…
Browse files Browse the repository at this point in the history
…cit this, lambda captures
  • Loading branch information
torshepherd committed Feb 8, 2025
1 parent dccf5a8 commit a8fdd0d
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 88 deletions.
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,10 @@ struct Config {
bool DeducedTypes = true;
bool Designators = true;
bool BlockEnd = false;
bool LambdaCaptures = false;
bool DefaultInitializations = false;
bool DefaultArguments = false;
bool ImplicitThis = false;
// Limit the length of type names in inlay hints. (0 means no limit)
uint32_t TypeNameLimit = 32;
} InlayHints;
Expand Down
23 changes: 19 additions & 4 deletions clang-tools-extra/clangd/ConfigCompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,10 @@ struct FragmentCompiler {
auto Fast = isFastTidyCheck(Str);
if (!Fast.has_value()) {
diag(Warning,
llvm::formatv(
"Latency of clang-tidy check '{0}' is not known. "
"It will only run if ClangTidy.FastCheckFilter is Loose or None",
Str)
llvm::formatv("Latency of clang-tidy check '{0}' is not known. "
"It will only run if ClangTidy.FastCheckFilter is "
"Loose or None",
Str)
.str(),
Arg.Range);
} else if (!*Fast) {
Expand Down Expand Up @@ -717,11 +717,26 @@ struct FragmentCompiler {
Out.Apply.push_back([Value(**F.BlockEnd)](const Params &, Config &C) {
C.InlayHints.BlockEnd = Value;
});
if (F.LambdaCaptures)
Out.Apply.push_back(
[Value(**F.LambdaCaptures)](const Params &, Config &C) {
C.InlayHints.LambdaCaptures = Value;
});
if (F.DefaultInitializations)
Out.Apply.push_back(
[Value(**F.DefaultInitializations)](const Params &, Config &C) {
C.InlayHints.DefaultInitializations = Value;
});
if (F.DefaultArguments)
Out.Apply.push_back(
[Value(**F.DefaultArguments)](const Params &, Config &C) {
C.InlayHints.DefaultArguments = Value;
});
if (F.ImplicitThis)
Out.Apply.push_back(
[Value(**F.ImplicitThis)](const Params &, Config &C) {
C.InlayHints.ImplicitThis = Value;
});
if (F.TypeNameLimit)
Out.Apply.push_back(
[Value(**F.TypeNameLimit)](const Params &, Config &C) {
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/ConfigFragment.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,15 @@ struct Fragment {
std::optional<Located<bool>> Designators;
/// Show defined symbol names at the end of a definition block.
std::optional<Located<bool>> BlockEnd;
/// Show names of captured variables by default capture groups in lambdas.
std::optional<Located<bool>> LambdaCaptures;
/// Show curly braces at the end of implicit default initializations.
std::optional<Located<bool>> DefaultInitializations;
/// Show parameter names and default values of default arguments after all
/// of the explicit arguments.
std::optional<Located<bool>> DefaultArguments;
/// Show implicit dereferencing of this pointer.
std::optional<Located<bool>> ImplicitThis;
/// Limit the length of type name hints. (0 means no limit)
std::optional<Located<uint32_t>> TypeNameLimit;
};
Expand Down
12 changes: 12 additions & 0 deletions clang-tools-extra/clangd/ConfigYAML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,22 @@ class Parser {
if (auto Value = boolValue(N, "BlockEnd"))
F.BlockEnd = *Value;
});
Dict.handle("LambdaCaptures", [&](Node &N) {
if (auto Value = boolValue(N, "LambdaCaptures"))
F.LambdaCaptures = *Value;
});
Dict.handle("DefaultInitializations", [&](Node &N) {
if (auto Value = boolValue(N, "DefaultInitializations"))
F.DefaultInitializations = *Value;
});
Dict.handle("DefaultArguments", [&](Node &N) {
if (auto Value = boolValue(N, "DefaultArguments"))
F.DefaultArguments = *Value;
});
Dict.handle("ImplicitThis", [&](Node &N) {
if (auto Value = boolValue(N, "ImplicitThis"))
F.ImplicitThis = *Value;
});
Dict.handle("TypeNameLimit", [&](Node &N) {
if (auto Value = uint32Value(N, "TypeNameLimit"))
F.TypeNameLimit = *Value;
Expand Down
109 changes: 74 additions & 35 deletions clang-tools-extra/clangd/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclarationName.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/LambdaCapture.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/Lambda.h"
#include "clang/Basic/OperatorKinds.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
Expand Down Expand Up @@ -381,20 +384,31 @@ maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
return Params;
}

template <typename R>
std::string joinAndTruncate(const R &Range, size_t MaxLength) {
llvm::StringRef getLambdaCaptureName(const LambdaCapture &Capture) {
if (Capture.capturesVariable())
return Capture.getCapturedVar()->getName();
if (Capture.capturesThis())
return llvm::StringRef{"this"};
return llvm::StringRef{"unknown"};
}

template <typename R, typename P>
std::string joinAndTruncate(R &&Range, size_t MaxLength,
P &&GetAsStringFunction) {
std::string Out;
llvm::raw_string_ostream OS(Out);
llvm::ListSeparator Sep(", ");
bool IsFirst = true;
for (auto &&Element : Range) {
OS << Sep;
if (Out.size() + Element.size() >= MaxLength) {
OS << "...";
if (!IsFirst)
Out.append(", ");
else
IsFirst = false;
auto AsString = GetAsStringFunction(Element);
if (Out.size() + AsString.size() >= MaxLength) {
Out.append("...");
break;
}
OS << Element;
Out.append(AsString);
}
OS.flush();
return Out;
}

Expand Down Expand Up @@ -478,6 +492,13 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
return RecursiveASTVisitor<InlayHintVisitor>::TraversePseudoObjectExpr(E);
}

bool VisitCXXThisExpr(CXXThisExpr *CTE) {
if (Cfg.InlayHints.ImplicitThis && CTE->isImplicit())
addInlayHint(CTE->getLocation(), HintSide::Left,
InlayHintKind::ImplicitThis, "", "this->", "");
return true;
}

bool VisitCallExpr(CallExpr *E) {
if (!Cfg.InlayHints.Parameters)
return true;
Expand Down Expand Up @@ -625,6 +646,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
}

bool VisitLambdaExpr(LambdaExpr *E) {
if (Cfg.InlayHints.LambdaCaptures && E->getCaptureDefault() != LCD_None &&
!E->implicit_captures().empty()) {
std::string FormattedCaptureList =
joinAndTruncate(E->implicit_captures(), Cfg.InlayHints.TypeNameLimit,
[](const LambdaCapture &ImplicitCapture) {
return getLambdaCaptureName(ImplicitCapture);
});
addImplicitCaptureHint(E->getCaptureDefaultLoc(), FormattedCaptureList);
}
FunctionDecl *D = E->getCallOperator();
if (!E->hasExplicitResultType()) {
SourceLocation TypeHintLoc;
Expand All @@ -646,6 +676,14 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
}

bool VisitVarDecl(VarDecl *D) {
if (Cfg.InlayHints.DefaultInitializations && D->hasInit() &&
isa<CXXConstructExpr>(D->getInit()) &&
!llvm::dyn_cast<CXXConstructExpr>(D->getInit())
->getParenOrBraceRange()
.isValid())
addInlayHint(D->getEndLoc(), HintSide::Right, InlayHintKind::DefaultInit,
"", "{}", "");

// Do not show hints for the aggregate in a structured binding,
// but show hints for the individual bindings.
if (auto *DD = dyn_cast<DecompositionDecl>(D)) {
Expand Down Expand Up @@ -741,7 +779,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
private:
using NameVec = SmallVector<StringRef, 8>;

void processCall(Callee Callee, SourceLocation RParenOrBraceLoc,
void processCall(Callee Callee, SourceRange LParenOrBraceRange,
llvm::ArrayRef<const Expr *> Args) {
assert(Callee.Decl || Callee.Loc);

Expand Down Expand Up @@ -789,31 +827,23 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
}

StringRef Name = ParameterNames[I];
const bool NameHint =
shouldHintName(Args[I], Name) && Cfg.InlayHints.Parameters;
const bool ReferenceHint =
shouldHintReference(Params[I], ForwardedParams[I]) &&
Cfg.InlayHints.Parameters;
bool NameHint = shouldHintName(Args[I], Name);
bool ReferenceHint = shouldHintReference(Params[I], ForwardedParams[I]);

const bool IsDefault = isa<CXXDefaultArgExpr>(Args[I]);
bool IsDefault = isa<CXXDefaultArgExpr>(Args[I]);
HasNonDefaultArgs |= !IsDefault;
if (IsDefault) {
if (Cfg.InlayHints.DefaultArguments) {
const auto SourceText = Lexer::getSourceText(
CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()),
AST.getSourceManager(), AST.getLangOpts());
const auto Abbrev =
(SourceText.size() > Cfg.InlayHints.TypeNameLimit ||
SourceText.contains("\n"))
? "..."
: SourceText;
if (NameHint)
FormattedDefaultArgs.emplace_back(
llvm::formatv("{0}: {1}", Name, Abbrev));
else
FormattedDefaultArgs.emplace_back(llvm::formatv("{0}", Abbrev));
}
} else if (NameHint || ReferenceHint) {
if (Cfg.InlayHints.DefaultArguments && IsDefault) {
auto SourceText = Lexer::getSourceText(
CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()),
Callee.Decl->getASTContext().getSourceManager(),
Callee.Decl->getASTContext().getLangOpts());
FormattedDefaultArgs.emplace_back(llvm::formatv(
"{0} = {1}", Name,
SourceText.size() > Cfg.InlayHints.TypeNameLimit ? "..."
: SourceText));
}

if (NameHint || ReferenceHint) {
addInlayHint(Args[I]->getSourceRange(), HintSide::Left,
InlayHintKind::Parameter, ReferenceHint ? "&" : "",
NameHint ? Name : "", ": ");
Expand All @@ -822,8 +852,9 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {

if (!FormattedDefaultArgs.empty()) {
std::string Hint =
joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit);
addInlayHint(SourceRange{RParenOrBraceLoc}, HintSide::Left,
joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit,
[](const auto &E) { return E; });
addInlayHint(LParenOrBraceRange, HintSide::Left,
InlayHintKind::DefaultArgument,
HasNonDefaultArgs ? ", " : "", Hint, "");
}
Expand Down Expand Up @@ -1034,7 +1065,10 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
CHECK_KIND(Type, DeducedTypes);
CHECK_KIND(Designator, Designators);
CHECK_KIND(BlockEnd, BlockEnd);
CHECK_KIND(LambdaCapture, LambdaCaptures);
CHECK_KIND(DefaultArgument, DefaultArguments);
CHECK_KIND(DefaultInit, DefaultInitializations);
CHECK_KIND(ImplicitThis, ImplicitThis);
#undef CHECK_KIND
}

Expand Down Expand Up @@ -1088,6 +1122,11 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
/*Prefix=*/"", Text, /*Suffix=*/"=");
}

void addImplicitCaptureHint(SourceRange R, llvm::StringRef Text) {
addInlayHint(R, HintSide::Right, InlayHintKind::LambdaCapture,
/*Prefix=*/": ", Text, /*Suffix=*/"");
}

bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept {
return Cfg.InlayHints.TypeNameLimit == 0 ||
TypeName.size() < Cfg.InlayHints.TypeNameLimit;
Expand Down
11 changes: 10 additions & 1 deletion clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R,
O.map("needsConfirmation", R.needsConfirmation) &&
O.mapOptional("description", R.description);
}
llvm::json::Value toJSON(const ChangeAnnotation & CA) {
llvm::json::Value toJSON(const ChangeAnnotation &CA) {
llvm::json::Object Result{{"label", CA.label}};
if (CA.needsConfirmation)
Result["needsConfirmation"] = *CA.needsConfirmation;
Expand Down Expand Up @@ -1516,7 +1516,10 @@ llvm::json::Value toJSON(const InlayHintKind &Kind) {
return 2;
case InlayHintKind::Designator:
case InlayHintKind::BlockEnd:
case InlayHintKind::LambdaCapture:
case InlayHintKind::DefaultArgument:
case InlayHintKind::DefaultInit:
case InlayHintKind::ImplicitThis:
// This is an extension, don't serialize.
return nullptr;
}
Expand Down Expand Up @@ -1557,8 +1560,14 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, InlayHintKind Kind) {
return "designator";
case InlayHintKind::BlockEnd:
return "block-end";
case InlayHintKind::LambdaCapture:
return "lambda-capture";
case InlayHintKind::DefaultArgument:
return "default-argument";
case InlayHintKind::DefaultInit:
return "default-init";
case InlayHintKind::ImplicitThis:
return "implicit-this";
}
llvm_unreachable("Unknown clang.clangd.InlayHintKind");
};
Expand Down
25 changes: 23 additions & 2 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -1688,15 +1688,36 @@ enum class InlayHintKind {
/// This is a clangd extension.
BlockEnd = 4,

/// An inlay hint that is for a variable captured implicitly in a lambda.
///
/// An example of parameter hint for implicit lambda captures:
/// [&^] { return A; };
/// Adds an inlay hint ": A".
LambdaCapture = 5,

/// An inlay hint that is for a default argument.
///
/// An example of a parameter hint for a default argument:
/// void foo(bool A = true);
/// foo(^);
/// Adds an inlay hint "A: true".
/// This is a clangd extension.
/// Adds an inlay hint "A = true".
DefaultArgument = 6,

/// A hint for an implicit default initializer.
///
/// An example of implicit default construction:
/// MyObject O^;
/// Adds a hint for "{}".
DefaultInit = 7,

/// A hint for an implicit usage of this pointer.
///
/// An example of implicit this pointer:
/// struct MyObject { int foo; int bar(); };
/// MyObject::foo() { return ^bar; }
/// Adds a hinted "this->".
ImplicitThis = 8,

/// Other ideas for hints that are not currently implemented:
///
/// * Chaining hints, showing the types of intermediate expressions
Expand Down
Loading

0 comments on commit a8fdd0d

Please sign in to comment.