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 12, 2024
1 parent e8fb312 commit 1e3e449
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 13 deletions.
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,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
29 changes: 24 additions & 5 deletions clang-tools-extra/clangd/ConfigCompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
#include "llvm/Support/Regex.h"
#include "llvm/Support/SMLoc.h"
#include "llvm/Support/SourceMgr.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
Expand Down Expand Up @@ -504,10 +503,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 @@ -640,6 +639,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
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/ConfigFragment.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,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
17 changes: 16 additions & 1 deletion clang-tools-extra/clangd/ConfigYAML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include "llvm/Support/YAMLParser.h"
#include <optional>
#include <string>
#include <system_error>

namespace clang {
namespace clangd {
Expand Down Expand Up @@ -260,6 +259,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
102 changes: 99 additions & 3 deletions clang-tools-extra/clangd/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,33 @@
#include "SourceCode.h"
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/Decl.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"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/SaveAndRestore.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <iterator>
#include <optional>
#include <string>

Expand Down Expand Up @@ -535,6 +543,34 @@ maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
return Params;
}

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;
bool IsFirst = true;
for (auto &&Element : Range) {
if (!IsFirst)
Out.append(", ");
else
IsFirst = false;
auto AsString = GetAsStringFunction(Element);
if (Out.size() + AsString.size() >= MaxLength) {
Out.append("...");
break;
}
Out.append(AsString);
}
return Out;
}

struct Callee {
// Only one of Decl or Loc is set.
// Loc is for calls through function pointers.
Expand Down Expand Up @@ -585,7 +621,8 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
Callee.Decl = E->getConstructor();
if (!Callee.Decl)
return true;
processCall(Callee, {E->getArgs(), E->getNumArgs()});
processCall(Callee, E->getParenOrBraceRange().getEnd(),
{E->getArgs(), E->getNumArgs()});
return true;
}

Expand Down Expand Up @@ -614,6 +651,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 @@ -658,7 +702,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
dyn_cast_or_null<CXXMethodDecl>(Callee.Decl))
if (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter())
Args = Args.drop_front(1);
processCall(Callee, Args);
processCall(Callee, E->getRParenLoc(), Args);
return true;
}

Expand Down Expand Up @@ -761,6 +805,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())
addReturnTypeHint(D, E->hasExplicitParameters()
Expand All @@ -777,6 +830,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 @@ -871,7 +932,8 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
private:
using NameVec = SmallVector<StringRef, 8>;

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

if (!Cfg.InlayHints.Parameters || Args.size() == 0)
Expand All @@ -883,6 +945,9 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
if (Ctor->isCopyOrMoveConstructor())
return;

SmallVector<std::string> FormattedDefaultArgs;
bool HasNonDefaultArgs = false;

ArrayRef<const ParmVarDecl *> Params, ForwardedParams;
// Resolve parameter packs to their forwarded parameter
SmallVector<const ParmVarDecl *> ForwardedParamsStorage;
Expand Down Expand Up @@ -917,12 +982,34 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
bool NameHint = shouldHintName(Args[I], Name);
bool ReferenceHint = shouldHintReference(Params[I], ForwardedParams[I]);

bool IsDefault = isa<CXXDefaultArgExpr>(Args[I]);
HasNonDefaultArgs |= !IsDefault;
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 : "", ": ");
}
}

if (!FormattedDefaultArgs.empty()) {
std::string Hint =
joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit,
[](const auto &E) { return E; });
addInlayHint(LParenOrBraceRange, HintSide::Left,
InlayHintKind::DefaultArgument,
HasNonDefaultArgs ? ", " : "", Hint, "");
}
}

static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) {
Expand Down Expand Up @@ -1130,6 +1217,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 @@ -1182,6 +1273,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
30 changes: 30 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,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".
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 1e3e449

Please sign in to comment.