forked from OSchip/llvm-project
[clangd] Type hints for variables with 'auto' type
Differential Revision: https://reviews.llvm.org/D102148
This commit is contained in:
parent
4d788fb80f
commit
0be2657c2f
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue