[clang-tools-extra] 0be2657 - [clangd] Type hints for variables with 'auto' type

Nathan Ridge via cfe-commits cfe-commits at lists.llvm.org
Mon May 31 23:21:55 PDT 2021


Author: Nathan Ridge
Date: 2021-06-01T02:21:02-04:00
New Revision: 0be2657c2f486ffc006a037684cb658f4bf6cf11

URL: https://github.com/llvm/llvm-project/commit/0be2657c2f486ffc006a037684cb658f4bf6cf11
DIFF: https://github.com/llvm/llvm-project/commit/0be2657c2f486ffc006a037684cb658f4bf6cf11.diff

LOG: [clangd] Type hints for variables with 'auto' type

Differential Revision: https://reviews.llvm.org/D102148

Added: 
    

Modified: 
    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

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 3880273f85908..9fbce419edd2f 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -22,11 +22,16 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
   InlayHintVisitor(std::vector<InlayHint> &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 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
     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<DecompositionDecl>(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 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
   FileID MainFileID;
   StringRef MainFileBuf;
   const HeuristicResolver *Resolver;
+  PrintingPolicy TypeHintPolicy;
 };
 
 std::vector<InlayHint> inlayHints(ParsedAST &AST) {

diff  --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 71a89ccdc587d..06dbdaebb9a46 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1314,6 +1314,8 @@ llvm::json::Value toJSON(InlayHintKind K) {
   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/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index f0d46ab8a0105..3f6ce566ef2f4 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1500,9 +1500,14 @@ enum class InlayHintKind {
   /// 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/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 3cf1c6d70c0d3..026479275a472 100644
--- 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<InlayHint> parameterHints(ParsedAST &AST) {
+std::vector<InlayHint> hintsOfKind(ParsedAST &AST, InlayHintKind Kind) {
   std::vector<InlayHint> 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 @@ std::vector<InlayHint> parameterHints(ParsedAST &AST) {
 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 @@ MATCHER_P2(HintMatcher, Expected, Code, "") {
 }
 
 template <typename... ExpectedHints>
-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 <typename... ExpectedHints>
+void assertParameterHints(llvm::StringRef AnnotatedSource,
+                          ExpectedHints... Expected) {
+  assertHints(InlayHintKind::ParameterHint, AnnotatedSource, Expected...);
+}
+
+template <typename... ExpectedHints>
+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 @@ TEST(ParameterHints, SetterFunctions) {
                        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 <typename T>
+          struct Inner {};
+        };
+        S2::Inner<int> bar();
+        auto $y[[y]] = bar();
+      }
+    }
+  )cpp",
+                  ExpectedHint{": S1", "x"}, ExpectedHint{": Inner<int>", "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 <typename T>
+    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<TypeName>(...);  // and other built-in casts
+
+// Annoyances for which a heuristic is not obvious:
+//  - auto x = llvm::dyn_cast<LongTypeName>(y);  // and similar
+//  - stdlib algos return unwieldy __normal_iterator<X*, ...> 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


        


More information about the cfe-commits mailing list