Skip to content

Commit a15adbc

Browse files
[clangd] Type hints for structured bindings
Hints are shown for the individual bindings, not the aggregate. Differential Revision: https://reviews.llvm.org/D104617
1 parent a39bb96 commit a15adbc

File tree

2 files changed

+85
-15
lines changed

2 files changed

+85
-15
lines changed

clang-tools-extra/clangd/InlayHints.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
3232
TypeHintPolicy.SuppressScope = true; // keep type names short
3333
TypeHintPolicy.AnonymousTagLocations =
3434
false; // do not print lambda locations
35+
// Print canonical types. Otherwise, SuppressScope would result in
36+
// things like "metafunction<args>::type" being shorted to just "type",
37+
// which is useless. This is particularly important for structured
38+
// bindings that use the tuple_element protocol, where the non-canonical
39+
// types would be "tuple_element<I, A>::type".
40+
// Note, for "auto", we would often prefer sugared types, but the AST
41+
// doesn't currently retain them in DeducedType anyways.
42+
TypeHintPolicy.PrintCanonicalTypes = true;
3543
}
3644

3745
bool VisitCXXConstructExpr(CXXConstructExpr *E) {
@@ -76,20 +84,23 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
7684
if (auto *AT = D->getReturnType()->getContainedAutoType()) {
7785
QualType Deduced = AT->getDeducedType();
7886
if (!Deduced.isNull()) {
79-
addInlayHint(D->getFunctionTypeLoc().getRParenLoc(),
80-
InlayHintKind::TypeHint,
81-
"-> " + D->getReturnType().getAsString(TypeHintPolicy));
87+
addTypeHint(D->getFunctionTypeLoc().getRParenLoc(), D->getReturnType(),
88+
"-> ");
8289
}
8390
}
8491

8592
return true;
8693
}
8794

8895
bool VisitVarDecl(VarDecl *D) {
89-
// Do not show hints for the aggregate in a structured binding.
90-
// In the future, we may show hints for the individual bindings.
91-
if (isa<DecompositionDecl>(D))
96+
// Do not show hints for the aggregate in a structured binding,
97+
// but show hints for the individual bindings.
98+
if (auto *DD = dyn_cast<DecompositionDecl>(D)) {
99+
for (auto *Binding : DD->bindings()) {
100+
addTypeHint(Binding->getLocation(), Binding->getType(), ": ");
101+
}
92102
return true;
103+
}
93104

94105
if (D->getType()->getContainedAutoType()) {
95106
if (!D->getType()->isDependentType()) {
@@ -98,8 +109,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
98109
// (e.g. for `const auto& x = 42`, print `const int&`).
99110
// Alternatively, we could place the hint on the `auto`
100111
// (and then just print the type deduced for the `auto`).
101-
addInlayHint(D->getLocation(), InlayHintKind::TypeHint,
102-
": " + D->getType().getAsString(TypeHintPolicy));
112+
addTypeHint(D->getLocation(), D->getType(), ": ");
103113
}
104114
}
105115
return true;
@@ -311,6 +321,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
311321
Kind, Label.str()});
312322
}
313323

324+
void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {
325+
// Do not print useless "NULL TYPE" hint.
326+
if (!T.getTypePtrOrNull())
327+
return;
328+
329+
addInlayHint(R, InlayHintKind::TypeHint,
330+
std::string(Prefix) + T.getAsString(TypeHintPolicy));
331+
}
332+
314333
std::vector<InlayHint> &Results;
315334
ASTContext &AST;
316335
FileID MainFileID;

clang-tools-extra/clangd/unittests/InlayHintTests.cpp

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -461,19 +461,70 @@ TEST(TypeHints, Lambda) {
461461
ExpectedHint{": int", "init"});
462462
}
463463

464-
TEST(TypeHints, StructuredBindings) {
465-
// FIXME: Not handled yet.
466-
// To handle it, we could print:
467-
// - the aggregate type next to the 'auto', or
468-
// - the individual types inside the brackets
469-
// The latter is probably more useful.
464+
// Structured bindings tests.
465+
// Note, we hint the individual bindings, not the aggregate.
466+
467+
TEST(TypeHints, StructuredBindings_PublicStruct) {
470468
assertTypeHints(R"cpp(
469+
// Struct with public fields.
471470
struct Point {
472471
int x;
473472
int y;
474473
};
475474
Point foo();
476-
auto [x, y] = foo();
475+
auto [$x[[x]], $y[[y]]] = foo();
476+
)cpp",
477+
ExpectedHint{": int", "x"}, ExpectedHint{": int", "y"});
478+
}
479+
480+
TEST(TypeHints, StructuredBindings_Array) {
481+
assertTypeHints(R"cpp(
482+
int arr[2];
483+
auto [$x[[x]], $y[[y]]] = arr;
484+
)cpp",
485+
ExpectedHint{": int", "x"}, ExpectedHint{": int", "y"});
486+
}
487+
488+
TEST(TypeHints, StructuredBindings_TupleLike) {
489+
assertTypeHints(R"cpp(
490+
// Tuple-like type.
491+
struct IntPair {
492+
int a;
493+
int b;
494+
};
495+
namespace std {
496+
template <typename T>
497+
struct tuple_size {};
498+
template <>
499+
struct tuple_size<IntPair> {
500+
constexpr static unsigned value = 2;
501+
};
502+
template <unsigned I, typename T>
503+
struct tuple_element {};
504+
template <unsigned I>
505+
struct tuple_element<I, IntPair> {
506+
using type = int;
507+
};
508+
}
509+
template <unsigned I>
510+
int get(const IntPair& p) {
511+
if constexpr (I == 0) {
512+
return p.a;
513+
} else if constexpr (I == 1) {
514+
return p.b;
515+
}
516+
}
517+
IntPair bar();
518+
auto [$x[[x]], $y[[y]]] = bar();
519+
)cpp",
520+
ExpectedHint{": int", "x"}, ExpectedHint{": int", "y"});
521+
}
522+
523+
TEST(TypeHints, StructuredBindings_NoInitializer) {
524+
assertTypeHints(R"cpp(
525+
// No initializer (ill-formed).
526+
// Do not show useless "NULL TYPE" hint.
527+
auto [x, y]; /*error-ok*/
477528
)cpp");
478529
}
479530

0 commit comments

Comments
 (0)