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

Differential Revision: https://reviews.llvm.org/D102148
This commit is contained in:
Nathan Ridge 2021-05-09 02:59:57 -04:00
parent 4d788fb80f
commit 0be2657c2f
4 changed files with 176 additions and 8 deletions

View File

@ -22,11 +22,16 @@ public:
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 @@ public:
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 @@ private:
FileID MainFileID;
StringRef MainFileBuf;
const HeuristicResolver *Resolver;
PrintingPolicy TypeHintPolicy;
};
std::vector<InlayHint> inlayHints(ParsedAST &AST) {

View File

@ -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");
}

View File

@ -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.

View File

@ -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