[clang-tools-extra] [clangd] Add inlay hints for forwarding direct init (PR #176635)
Mythreya Kuricheti via cfe-commits
cfe-commits at lists.llvm.org
Sun Jan 18 01:39:11 PST 2026
https://github.com/MythreyaK created https://github.com/llvm/llvm-project/pull/176635
Adds designator hints for direct init
Fixes clangd/clangd#2541
>From 605c38e4f0e9124aae95cb964f16a5adbb62d5ea Mon Sep 17 00:00:00 2001
From: Mythreya <git at mythreya.dev>
Date: Sun, 18 Jan 2026 01:13:38 -0800
Subject: [PATCH] [clangd] Add inlay hints for forwarding direct init
---
clang-tools-extra/clangd/AST.cpp | 35 +++++++++++++++++--
clang-tools-extra/clangd/AST.h | 2 +-
clang-tools-extra/clangd/Hover.cpp | 10 +++++-
clang-tools-extra/clangd/InlayHints.cpp | 30 +++++++++++++++-
.../clangd/unittests/InlayHintTests.cpp | 26 ++++++++++++++
5 files changed, 97 insertions(+), 6 deletions(-)
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 3bcc89d360cdb..c49255cf78a66 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -38,6 +38,7 @@
#include <iterator>
#include <optional>
#include <string>
+#include <utility>
#include <vector>
namespace clang {
@@ -824,6 +825,14 @@ class ForwardingCallVisitor
return !Info.has_value();
}
+ bool VisitCXXParenListInitExpr(CXXParenListInitExpr *E) {
+ auto *const Record = E->getType()->getAsCXXRecordDecl();
+ if (Record)
+ handleAggregateInit(Record, E->getInitExprs());
+
+ return true;
+ }
+
bool VisitCXXConstructExpr(CXXConstructExpr *E) {
auto *Callee = E->getConstructor();
if (Callee) {
@@ -855,6 +864,8 @@ class ForwardingCallVisitor
std::optional<FunctionDecl *> PackTarget;
};
+ SmallVector<const FieldDecl *> AggregateInitFields {};
+
// The output of this visitor
std::optional<ForwardingInfo> Info;
@@ -896,6 +907,16 @@ class ForwardingCallVisitor
Info = FI;
}
+ void handleAggregateInit(const CXXRecordDecl *Record,
+ const ArrayRef<Expr *> InitExprs) {
+ // FIXME: Handle base classes
+ if (Record->getNumFields() == InitExprs.size()) {
+ for (const auto *Field : Record->fields()) {
+ AggregateInitFields.emplace_back(Field);
+ }
+ }
+ }
+
// Returns the beginning of the expanded pack represented by Parameters
// in the given arguments, if it is there.
std::optional<size_t> findPack(typename CallExpr::arg_range Args) {
@@ -977,7 +998,7 @@ class ForwardingCallVisitor
} // namespace
-SmallVector<const ParmVarDecl *>
+std::variant<SmallVector<const ParmVarDecl *>, SmallVector<const FieldDecl *>>
resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) {
auto Parameters = D->parameters();
// If the function has a template parameter pack
@@ -1006,9 +1027,16 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) {
// Find call expressions involving the pack
ForwardingCallVisitor V{Pack};
V.TraverseStmt(CurrentFunction->getBody());
+
+ // if fields are direct-initialized, then no more forwarding
+ if (!V.AggregateInitFields.empty()) {
+ return V.AggregateInitFields;
+ }
+
if (!V.Info) {
break;
}
+
// If we found something: Fill in non-pack parameters
auto Info = *V.Info;
HeadIt = std::copy(Info.Head.begin(), Info.Head.end(), HeadIt);
@@ -1022,7 +1050,8 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) {
if (const auto *Template = CurrentFunction->getPrimaryTemplate()) {
bool NewFunction = SeenTemplates.insert(Template).second;
if (!NewFunction) {
- return {Parameters.begin(), Parameters.end()};
+ return SmallVector<const ParmVarDecl *>{Parameters.begin(),
+ Parameters.end()};
}
}
}
@@ -1032,7 +1061,7 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) {
assert(TailIt.base() == HeadIt);
return Result;
}
- return {Parameters.begin(), Parameters.end()};
+ return SmallVector<const ParmVarDecl *>{Parameters.begin(), Parameters.end()};
}
bool isExpandedFromParameterPack(const ParmVarDecl *D) {
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 2bb4943b6de0b..064c8c3cd0bdf 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -244,7 +244,7 @@ bool isDeeplyNested(const Decl *D, unsigned MaxDepth = 10);
/// parameters to another function via variadic template parameters. This can
/// for example be used to retrieve the constructor parameter ParmVarDecl for a
/// make_unique or emplace_back call.
-llvm::SmallVector<const ParmVarDecl *>
+std::variant<SmallVector<const ParmVarDecl *>, SmallVector<const FieldDecl *>>
resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth = 10);
/// Checks whether D is instantiated from a function parameter pack
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 3ce0d6258ea62..86d2b3ee83b25 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -63,6 +63,7 @@
#include <algorithm>
#include <optional>
#include <string>
+#include <variant>
#include <vector>
namespace clang {
@@ -1062,7 +1063,14 @@ void maybeAddCalleeArgInfo(const SelectionTree::Node *N, HoverInfo &HI,
HoverInfo::PassType PassType;
- auto Parameters = resolveForwardingParameters(FD);
+ const auto Params = resolveForwardingParameters(FD);
+
+ auto Parameters = [&]() -> SmallVector<const ParmVarDecl *> {
+ if (std::holds_alternative<SmallVector<const ParmVarDecl *>>(Params)) {
+ return std::get<SmallVector<const ParmVarDecl *>>(Params);
+ }
+ return {};
+ }();
// Find argument index for N.
for (unsigned I = 0; I < Args.size() && I < Parameters.size(); ++I) {
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 2290fbd98056d..c264af9d6ed10 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -773,11 +773,26 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
bool HasNonDefaultArgs = false;
ArrayRef<const ParmVarDecl *> Params, ForwardedParams;
+
// Resolve parameter packs to their forwarded parameter
SmallVector<const ParmVarDecl *> ForwardedParamsStorage;
+ // If args are direct-initialized
+ SmallVector<const FieldDecl *> CXXRecordDeclFields{};
+
if (Callee.Decl) {
Params = maybeDropCxxExplicitObjectParameters(Callee.Decl->parameters());
- ForwardedParamsStorage = resolveForwardingParameters(Callee.Decl);
+
+ [&]() {
+ auto Params = resolveForwardingParameters(Callee.Decl);
+ if (std::holds_alternative<decltype(ForwardedParamsStorage)>(Params)) {
+ ForwardedParamsStorage =
+ std::get<decltype(ForwardedParamsStorage)>(Params);
+ }
+ if (std::holds_alternative<decltype(CXXRecordDeclFields)>(Params)) {
+ CXXRecordDeclFields = std::get<decltype(CXXRecordDeclFields)>(Params);
+ }
+ }();
+
ForwardedParams =
maybeDropCxxExplicitObjectParameters(ForwardedParamsStorage);
} else {
@@ -787,6 +802,19 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
NameVec ParameterNames = chooseParameterNames(ForwardedParams);
+ if (!CXXRecordDeclFields.empty()) {
+ ParameterNames.clear();
+ for (size_t I = 0; I < Args.size(); ++I) {
+ const auto &Field = CXXRecordDeclFields[I];
+
+ addInlayHint(Args[I]->getSourceRange(), HintSide::Left,
+ InlayHintKind::Parameter,
+ Field->getType()->isReferenceType() ? "&." : ".",
+ Field->getName(), ": ");
+ }
+ return;
+ }
+
// Exclude setters (i.e. functions with one argument whose name begins with
// "set"), and builtins like std::move/forward/... as their parameter name
// is also not likely to be interesting.
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 5552aa178a354..110b368a35cb5 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1955,6 +1955,32 @@ TEST(ParameterHints, DoesntExpandAllArgs) {
ExpectedHint{"c: ", "param3"});
}
+TEST(ParameterHints, CXX20AggregateParenInitNoCtor) {
+ assertParameterHints(
+ R"cpp(
+ namespace std {
+ // This prototype of std::forward is sufficient for clang to recognize it
+ template <typename T> T&& forward(T&);
+ }
+
+ template<typename T, typename ...Args>
+ T* make_unique(Args&&...args) {
+ return new T(std::forward<Args>(args)...);
+ }
+
+ struct Point {
+ int& x;
+ int y;
+ };
+
+ int foo() {
+ int t = 42;
+ make_unique<Point>($px[[t]], $py[[2]]);
+ }
+ )cpp",
+ ExpectedHint{"&.x: ", "px"}, ExpectedHint{".y: ", "py"});
+}
+
TEST(BlockEndHints, Functions) {
assertBlockEndHints(R"cpp(
int foo() {
More information about the cfe-commits
mailing list