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 @@ -22,11 +22,16 @@ InlayHintVisitor(std::vector &Results, ParsedAST &AST) : Results(Results), AST(AST.getASTContext()), MainFileID(AST.getSourceManager().getMainFileID()), - Resolver(AST.getHeuristicResolver()) { + Resolver(AST.getHeuristicResolver()), + TypeHintPolicy(this->AST.getPrintingPolicy()) { bool Invalid = false; llvm::StringRef Buf = AST.getSourceManager().getBufferData(MainFileID, &Invalid); MainFileBuf = Invalid ? StringRef{} : Buf; + + TypeHintPolicy.SuppressScope = true; // keep type names short + TypeHintPolicy.AnonymousTagLocations = + false; // do not print lambda locations } bool VisitCXXConstructExpr(CXXConstructExpr *E) { @@ -67,6 +72,26 @@ return true; } + 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)) + return true; + + if (auto *AT = D->getType()->getContainedAutoType()) { + if (!D->getType()->isDependentType()) { + // Our current approach is to place the hint on the variable + // and accordingly print the full type + // (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)); + } + } + return true; + } + // FIXME: Handle RecoveryExpr to try to hint some invalid calls. private: @@ -278,6 +303,7 @@ FileID MainFileID; StringRef MainFileBuf; const HeuristicResolver *Resolver; + PrintingPolicy TypeHintPolicy; }; std::vector inlayHints(ParsedAST &AST) { diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1500,9 +1500,14 @@ /// which shows the name of the corresponding parameter. ParameterHint, + /// The hint corresponds to information about a deduced type. + /// An example of a type hint is a hint in this position: + /// auto var ^ = expr; + /// which shows the deduced type of the variable. + TypeHint, + /// Other ideas for hints that are not currently implemented: /// - /// * Type hints, showing deduced types. /// * Chaining hints, showing the types of intermediate expressions /// in a chain of function calls. /// * Hints indicating implicit conversions or implicit constructor calls. diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1314,6 +1314,8 @@ switch (K) { case InlayHintKind::ParameterHint: return "parameter"; + case InlayHintKind::TypeHint: + return "type"; } llvm_unreachable("Unknown clang.clangd.InlayHintKind"); } 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 @@ -15,14 +15,19 @@ namespace clang { namespace clangd { + +std::ostream &operator<<(std::ostream &Stream, const InlayHint &Hint) { + return Stream << Hint.label; +} + namespace { using ::testing::UnorderedElementsAre; -std::vector parameterHints(ParsedAST &AST) { +std::vector hintsOfKind(ParsedAST &AST, InlayHintKind Kind) { std::vector Result; for (auto &Hint : inlayHints(AST)) { - if (Hint.kind == InlayHintKind::ParameterHint) + if (Hint.kind == Kind) Result.push_back(Hint); } return Result; @@ -31,6 +36,11 @@ struct ExpectedHint { std::string Label; std::string RangeName; + + friend std::ostream &operator<<(std::ostream &Stream, + const ExpectedHint &Hint) { + return Stream << Hint.RangeName << ": " << Hint.Label; + } }; MATCHER_P2(HintMatcher, Expected, Code, "") { @@ -39,17 +49,29 @@ } template -void assertParameterHints(llvm::StringRef AnnotatedSource, - ExpectedHints... Expected) { +void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, + ExpectedHints... Expected) { Annotations Source(AnnotatedSource); TestTU TU = TestTU::withCode(Source.code()); - TU.ExtraArgs.push_back("-std=c++11"); + TU.ExtraArgs.push_back("-std=c++14"); auto AST = TU.build(); - EXPECT_THAT(parameterHints(AST), + EXPECT_THAT(hintsOfKind(AST, Kind), UnorderedElementsAre(HintMatcher(Expected, Source)...)); } +template +void assertParameterHints(llvm::StringRef AnnotatedSource, + ExpectedHints... Expected) { + assertHints(InlayHintKind::ParameterHint, AnnotatedSource, Expected...); +} + +template +void assertTypeHints(llvm::StringRef AnnotatedSource, + ExpectedHints... Expected) { + assertHints(InlayHintKind::TypeHint, AnnotatedSource, Expected...); +} + TEST(ParameterHints, Smoke) { assertParameterHints(R"cpp( void foo(int param); @@ -376,6 +398,119 @@ ExpectedHint{"timeout_millis: ", "timeout_millis"}); } +TEST(TypeHints, Smoke) { + assertTypeHints(R"cpp( + auto $waldo[[waldo]] = 42; + )cpp", + ExpectedHint{": int", "waldo"}); +} + +TEST(TypeHints, Decorations) { + assertTypeHints(R"cpp( + int x = 42; + auto* $var1[[var1]] = &x; + auto&& $var2[[var2]] = x; + const auto& $var3[[var3]] = x; + )cpp", + ExpectedHint{": int *", "var1"}, + ExpectedHint{": int &", "var2"}, + ExpectedHint{": const int &", "var3"}); +} + +TEST(TypeHints, DecltypeAuto) { + assertTypeHints(R"cpp( + int x = 42; + int& y = x; + decltype(auto) $z[[z]] = y; + )cpp", + ExpectedHint{": int &", "z"}); +} + +TEST(TypeHints, NoQualifiers) { + assertTypeHints(R"cpp( + namespace A { + namespace B { + struct S1 {}; + S1 foo(); + auto $x[[x]] = foo(); + + struct S2 { + template + struct Inner {}; + }; + S2::Inner bar(); + auto $y[[y]] = bar(); + } + } + )cpp", + ExpectedHint{": S1", "x"}, ExpectedHint{": Inner", "y"}); +} + +TEST(TypeHints, Lambda) { + // Do not print something overly verbose like the lambda's location. + // Show hints for init-captures (but not regular captures). + assertTypeHints(R"cpp( + void f() { + int cap = 42; + auto $L[[L]] = [cap, $init[[init]] = 1 + 1](int a) { + return a + cap + init; + }; + } + )cpp", + ExpectedHint{": (lambda)", "L"}, + ExpectedHint{": int", "init"}); +} + +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. + assertTypeHints(R"cpp( + struct Point { + int x; + int y; + }; + Point foo(); + auto [x, y] = foo(); + )cpp"); +} + +TEST(TypeHints, ReturnTypeDeduction) { + // FIXME: Not handled yet. + // This test is currently here mostly because a naive implementation + // might have us print something not super helpful like the function type. + assertTypeHints(R"cpp( + auto func(int x) { + return x + 1; + } + )cpp"); +} + +TEST(TypeHints, DependentType) { + assertTypeHints(R"cpp( + template + void foo(T arg) { + // The hint would just be "auto" and we can't do any better. + auto var1 = arg.method(); + // FIXME: It would be nice to show "T" as the hint. + auto $var2[[var2]] = arg; + } + )cpp"); +} + +// FIXME: Low-hanging fruit where we could omit a type hint: +// - auto x = TypeName(...); +// - auto x = (TypeName) (...); +// - auto x = static_cast(...); // and other built-in casts + +// Annoyances for which a heuristic is not obvious: +// - auto x = llvm::dyn_cast(y); // and similar +// - stdlib algos return unwieldy __normal_iterator type +// (For this one, perhaps we should omit type hints that start +// with a double underscore.) + } // namespace } // namespace clangd } // namespace clang \ No newline at end of file