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