[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