[clang-tools-extra] 2eb1699 - [clangd] Add inlay hints for default function arguments (#95712)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 19 15:19:04 PDT 2024
Author: Tor Shepherd
Date: 2024-10-19T18:19:01-04:00
New Revision: 2eb1699184cf4d5de69f7825f66d7b3c04827f77
URL: https://github.com/llvm/llvm-project/commit/2eb1699184cf4d5de69f7825f66d7b3c04827f77
DIFF: https://github.com/llvm/llvm-project/commit/2eb1699184cf4d5de69f7825f66d7b3c04827f77.diff
LOG: [clangd] Add inlay hints for default function arguments (#95712)
The new inlay hints have the `DefaultArguments` kind and can be enabled in config similar to other inlay kint kinds.
Added:
Modified:
clang-tools-extra/clangd/Config.h
clang-tools-extra/clangd/ConfigCompile.cpp
clang-tools-extra/clangd/ConfigFragment.h
clang-tools-extra/clangd/ConfigYAML.cpp
clang-tools-extra/clangd/InlayHints.cpp
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/unittests/InlayHintTests.cpp
clang-tools-extra/docs/ReleaseNotes.rst
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h
index 8fcbc5c33469fa..e174f7fabe344e 100644
--- a/clang-tools-extra/clangd/Config.h
+++ b/clang-tools-extra/clangd/Config.h
@@ -162,6 +162,7 @@ struct Config {
bool DeducedTypes = true;
bool Designators = true;
bool BlockEnd = false;
+ bool DefaultArguments = false;
// Limit the length of type names in inlay hints. (0 means no limit)
uint32_t TypeNameLimit = 32;
} InlayHints;
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp
index 58610a5b87922d..fb7692998d05c7 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -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>
@@ -669,6 +668,11 @@ struct FragmentCompiler {
Out.Apply.push_back([Value(**F.BlockEnd)](const Params &, Config &C) {
C.InlayHints.BlockEnd = Value;
});
+ if (F.DefaultArguments)
+ Out.Apply.push_back(
+ [Value(**F.DefaultArguments)](const Params &, Config &C) {
+ C.InlayHints.DefaultArguments = Value;
+ });
if (F.TypeNameLimit)
Out.Apply.push_back(
[Value(**F.TypeNameLimit)](const Params &, Config &C) {
diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h
index fc1b45f5d4c3e9..36f7d04231c414 100644
--- a/clang-tools-extra/clangd/ConfigFragment.h
+++ b/clang-tools-extra/clangd/ConfigFragment.h
@@ -339,6 +339,9 @@ struct Fragment {
std::optional<Located<bool>> Designators;
/// Show defined symbol names at the end of a definition block.
std::optional<Located<bool>> BlockEnd;
+ /// Show parameter names and default values of default arguments after all
+ /// of the explicit arguments.
+ std::optional<Located<bool>> DefaultArguments;
/// Limit the length of type name hints. (0 means no limit)
std::optional<Located<uint32_t>> TypeNameLimit;
};
diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp
index bcdda99eeed67a..32e028981d4244 100644
--- a/clang-tools-extra/clangd/ConfigYAML.cpp
+++ b/clang-tools-extra/clangd/ConfigYAML.cpp
@@ -14,7 +14,6 @@
#include "llvm/Support/YAMLParser.h"
#include <optional>
#include <string>
-#include <system_error>
namespace clang {
namespace clangd {
@@ -268,6 +267,10 @@ class Parser {
if (auto Value = boolValue(N, "BlockEnd"))
F.BlockEnd = *Value;
});
+ Dict.handle("DefaultArguments", [&](Node &N) {
+ if (auto Value = boolValue(N, "DefaultArguments"))
+ F.DefaultArguments = *Value;
+ });
Dict.handle("TypeNameLimit", [&](Node &N) {
if (auto Value = uint32Value(N, "TypeNameLimit"))
F.TypeNameLimit = *Value;
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index cd4f1931b3ce1d..c4053fced81d6f 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -11,9 +11,11 @@
#include "Config.h"
#include "HeuristicResolver.h"
#include "ParsedAST.h"
+#include "Protocol.h"
#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"
@@ -23,15 +25,22 @@
#include "clang/AST/Type.h"
#include "clang/Basic/Builtins.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/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/ErrorHandling.h"
+#include "llvm/Support/FormatVariadic.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>
@@ -372,6 +381,23 @@ maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
return Params;
}
+template <typename R>
+std::string joinAndTruncate(const R &Range, size_t MaxLength) {
+ std::string Out;
+ llvm::raw_string_ostream OS(Out);
+ llvm::ListSeparator Sep(", ");
+ for (auto &&Element : Range) {
+ OS << Sep;
+ if (Out.size() + Element.size() >= MaxLength) {
+ OS << "...";
+ break;
+ }
+ OS << Element;
+ }
+ OS.flush();
+ return Out;
+}
+
struct Callee {
// Only one of Decl or Loc is set.
// Loc is for calls through function pointers.
@@ -422,7 +448,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;
}
@@ -495,7 +522,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;
}
@@ -709,10 +736,12 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
private:
using NameVec = SmallVector<StringRef, 8>;
- void processCall(Callee Callee, llvm::ArrayRef<const Expr *> Args) {
+ void processCall(Callee Callee, SourceLocation RParenOrBraceLoc,
+ llvm::ArrayRef<const Expr *> Args) {
assert(Callee.Decl || Callee.Loc);
- if (!Cfg.InlayHints.Parameters || Args.size() == 0)
+ if ((!Cfg.InlayHints.Parameters && !Cfg.InlayHints.DefaultArguments) ||
+ Args.size() == 0)
return;
// The parameter name of a move or copy constructor is not very interesting.
@@ -721,6 +750,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;
@@ -752,15 +784,44 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
}
StringRef Name = ParameterNames[I];
- bool NameHint = shouldHintName(Args[I], Name);
- bool ReferenceHint = shouldHintReference(Params[I], ForwardedParams[I]);
-
- if (NameHint || ReferenceHint) {
+ const bool NameHint =
+ shouldHintName(Args[I], Name) && Cfg.InlayHints.Parameters;
+ const bool ReferenceHint =
+ shouldHintReference(Params[I], ForwardedParams[I]) &&
+ Cfg.InlayHints.Parameters;
+
+ const 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) {
addInlayHint(Args[I]->getSourceRange(), HintSide::Left,
InlayHintKind::Parameter, ReferenceHint ? "&" : "",
NameHint ? Name : "", ": ");
}
}
+
+ if (!FormattedDefaultArgs.empty()) {
+ std::string Hint =
+ joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit);
+ addInlayHint(SourceRange{RParenOrBraceLoc}, HintSide::Left,
+ InlayHintKind::DefaultArgument,
+ HasNonDefaultArgs ? ", " : "", Hint, "");
+ }
}
static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) {
@@ -968,6 +1029,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
CHECK_KIND(Type, DeducedTypes);
CHECK_KIND(Designator, Designators);
CHECK_KIND(BlockEnd, BlockEnd);
+ CHECK_KIND(DefaultArgument, DefaultArguments);
#undef CHECK_KIND
}
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index c08f80442eaa06..295ccd26a40454 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1477,6 +1477,7 @@ llvm::json::Value toJSON(const InlayHintKind &Kind) {
return 2;
case InlayHintKind::Designator:
case InlayHintKind::BlockEnd:
+ case InlayHintKind::DefaultArgument:
// This is an extension, don't serialize.
return nullptr;
}
@@ -1517,6 +1518,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, InlayHintKind Kind) {
return "designator";
case InlayHintKind::BlockEnd:
return "block-end";
+ case InlayHintKind::DefaultArgument:
+ return "default-argument";
}
llvm_unreachable("Unknown clang.clangd.InlayHintKind");
};
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index a0f8b04bc4ffdb..5b28095758198d 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1681,6 +1681,15 @@ enum class InlayHintKind {
/// This is a clangd extension.
BlockEnd = 4,
+ /// 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.
+ DefaultArgument = 6,
+
/// Other ideas for hints that are not currently implemented:
///
/// * Chaining hints, showing the types of intermediate expressions
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index a5a349e93037ad..73dd273d6c39d4 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -15,9 +15,12 @@
#include "support/Context.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ScopedPrinter.h"
+#include "llvm/Support/raw_ostream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <optional>
#include <string>
+#include <utility>
#include <vector>
namespace clang {
@@ -81,6 +84,7 @@ Config noHintsConfig() {
C.InlayHints.DeducedTypes = false;
C.InlayHints.Designators = false;
C.InlayHints.BlockEnd = false;
+ C.InlayHints.DefaultArguments = false;
return C;
}
@@ -1465,6 +1469,75 @@ TEST(TypeHints, DefaultTemplateArgs) {
ExpectedHint{": A<float>", "binding"});
}
+TEST(DefaultArguments, Smoke) {
+ Config Cfg;
+ Cfg.InlayHints.Parameters =
+ true; // To test interplay of parameters and default parameters
+ Cfg.InlayHints.DeducedTypes = false;
+ Cfg.InlayHints.Designators = false;
+ Cfg.InlayHints.BlockEnd = false;
+
+ Cfg.InlayHints.DefaultArguments = true;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+
+ const auto *Code = R"cpp(
+ int foo(int A = 4) { return A; }
+ int bar(int A, int B = 1, bool C = foo($default1[[)]]) { return A; }
+ int A = bar($explicit[[2]]$default2[[)]];
+
+ void baz(int = 5) { if (false) baz($unnamed[[)]]; };
+ )cpp";
+
+ assertHints(InlayHintKind::DefaultArgument, Code,
+ ExpectedHint{"A: 4", "default1", Left},
+ ExpectedHint{", B: 1, C: foo()", "default2", Left},
+ ExpectedHint{"5", "unnamed", Left});
+
+ assertHints(InlayHintKind::Parameter, Code,
+ ExpectedHint{"A: ", "explicit", Left});
+}
+
+TEST(DefaultArguments, WithoutParameterNames) {
+ Config Cfg;
+ Cfg.InlayHints.Parameters = false; // To test just default args this time
+ Cfg.InlayHints.DeducedTypes = false;
+ Cfg.InlayHints.Designators = false;
+ Cfg.InlayHints.BlockEnd = false;
+
+ Cfg.InlayHints.DefaultArguments = true;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+
+ const auto *Code = R"cpp(
+ struct Baz {
+ Baz(float a = 3 //
+ + 2);
+ };
+ struct Foo {
+ Foo(int, Baz baz = //
+ Baz{$abbreviated[[}]]
+
+ //
+ ) {}
+ };
+
+ int main() {
+ Foo foo1(1$paren[[)]];
+ Foo foo2{2$brace1[[}]];
+ Foo foo3 = {3$brace2[[}]];
+ auto foo4 = Foo{4$brace3[[}]];
+ }
+ )cpp";
+
+ assertHints(InlayHintKind::DefaultArgument, Code,
+ ExpectedHint{"...", "abbreviated", Left},
+ ExpectedHint{", Baz{}", "paren", Left},
+ ExpectedHint{", Baz{}", "brace1", Left},
+ ExpectedHint{", Baz{}", "brace2", Left},
+ ExpectedHint{", Baz{}", "brace3", Left});
+
+ assertHints(InlayHintKind::Parameter, Code);
+}
+
TEST(TypeHints, Deduplication) {
assertTypeHints(R"cpp(
template <typename T>
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index e8148e06b6af28..a9b1ab367f538a 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -56,6 +56,8 @@ Improvements to clangd
Inlay hints
^^^^^^^^^^^
+- Added `DefaultArguments` Inlay Hints option.
+
Diagnostics
^^^^^^^^^^^
More information about the cfe-commits
mailing list