diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -32,6 +32,12 @@ TypeHintPolicy.SuppressScope = true; // keep type names short TypeHintPolicy.AnonymousTagLocations = false; // do not print lambda locations + // Print canonical types. Otherwise, SuppressScope would result in + // things like "metafunction::type" being shorted to just "type", + // which is useless. This is particularly important for structured + // bindings that use the tuple_element protocol, where the non-canonical + // types would be "tuple_element::type". + TypeHintPolicy.PrintCanonicalTypes = true; } bool VisitCXXConstructExpr(CXXConstructExpr *E) { @@ -76,9 +82,8 @@ if (auto *AT = D->getReturnType()->getContainedAutoType()) { QualType Deduced = AT->getDeducedType(); if (!Deduced.isNull()) { - addInlayHint(D->getFunctionTypeLoc().getRParenLoc(), - InlayHintKind::TypeHint, - "-> " + D->getReturnType().getAsString(TypeHintPolicy)); + addTypeHint(D->getFunctionTypeLoc().getRParenLoc(), D->getReturnType(), + "-> "); } } @@ -86,10 +91,14 @@ } bool VisitVarDecl(VarDecl *D) { - // Do not show hints for the aggregate in a structured binding. - // In the future, we may show hints for the individual bindings. - if (isa(D)) + // Do not show hints for the aggregate in a structured binding, + // but show hints for the individual bindings. + if (auto *DD = dyn_cast(D)) { + for (auto *Binding : DD->bindings()) { + addTypeHint(Binding->getLocation(), Binding->getType(), ": "); + } return true; + } if (auto *AT = D->getType()->getContainedAutoType()) { if (!D->getType()->isDependentType()) { @@ -98,8 +107,7 @@ // (e.g. for `const auto& x = 42`, print `const int&`). // Alternatively, we could place the hint on the `auto` // (and then just print the type deduced for the `auto`). - addInlayHint(D->getLocation(), InlayHintKind::TypeHint, - ": " + D->getType().getAsString(TypeHintPolicy)); + addTypeHint(D->getLocation(), D->getType(), ": "); } } return true; @@ -311,6 +319,15 @@ Kind, Label.str()}); } + void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) { + // Do not print useless "NULL TYPE" hint. + if (!T.getTypePtrOrNull()) + return; + + addInlayHint(R, InlayHintKind::TypeHint, + std::string(Prefix) + T.getAsString(TypeHintPolicy)); + } + std::vector &Results; ASTContext &AST; FileID MainFileID; diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -462,19 +462,57 @@ } TEST(TypeHints, StructuredBindings) { - // FIXME: Not handled yet. - // To handle it, we could print: - // - the aggregate type next to the 'auto', or - // - the individual types inside the brackets - // The latter is probably more useful. + // Hint individual bindings, not the aggregate. assertTypeHints(R"cpp( + // 1. Struct with public fields. struct Point { int x; int y; }; Point foo(); - auto [x, y] = foo(); - )cpp"); + auto [$x1[[x1]], $y1[[y1]]] = foo(); + + // 2. Array + int arr[2]; + auto [$x2[[x2]], $y2[[y2]]] = arr; + + // 3. Tuple-like type. + struct IntPair { + int a; + int b; + }; + namespace std { + template + struct tuple_size {}; + template <> + struct tuple_size { + constexpr static unsigned value = 2; + }; + template + struct tuple_element {}; + template + struct tuple_element { + using type = int; + }; + } + template + int get(const IntPair& p) { + if constexpr (I == 0) { + return p.a; + } else if constexpr (I == 1) { + return p.b; + } + } + IntPair bar(); + auto [$x3[[x3]], $y3[[y3]]] = bar(); + + // 4. No initializer (ill-formed). + // Do not show useless "NULL TYPE" hint. + auto [x4, y4]; /*error-ok*/ + )cpp", + ExpectedHint{": int", "x1"}, ExpectedHint{": int", "y1"}, + ExpectedHint{": int", "x2"}, ExpectedHint{": int", "y2"}, + ExpectedHint{": int", "x3"}, ExpectedHint{": int", "y3"}); } TEST(TypeHints, ReturnTypeDeduction) {