forked from OSchip/llvm-project
641 lines
16 KiB
C++
641 lines
16 KiB
C++
//===-- InlayHintTests.cpp -------------------------------*- C++ -*-------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include "Annotations.h"
|
|
#include "InlayHints.h"
|
|
#include "Protocol.h"
|
|
#include "TestTU.h"
|
|
#include "TestWorkspace.h"
|
|
#include "XRefs.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
std::ostream &operator<<(std::ostream &Stream, const InlayHint &Hint) {
|
|
return Stream << Hint.label;
|
|
}
|
|
|
|
namespace {
|
|
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
std::vector<InlayHint> hintsOfKind(ParsedAST &AST, InlayHintKind Kind) {
|
|
std::vector<InlayHint> Result;
|
|
for (auto &Hint : inlayHints(AST)) {
|
|
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, "") {
|
|
return arg.label == Expected.Label &&
|
|
arg.range == Code.range(Expected.RangeName);
|
|
}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource,
|
|
ExpectedHints... Expected) {
|
|
Annotations Source(AnnotatedSource);
|
|
TestTU TU = TestTU::withCode(Source.code());
|
|
TU.ExtraArgs.push_back("-std=c++14");
|
|
auto AST = TU.build();
|
|
|
|
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);
|
|
void bar() {
|
|
foo($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NoName) {
|
|
// No hint for anonymous parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int);
|
|
void bar() {
|
|
foo(42);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, NameInDefinition) {
|
|
// Parameter name picked up from definition if necessary.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int);
|
|
void bar() {
|
|
foo($param[[42]]);
|
|
}
|
|
void foo(int param) {};
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NameMismatch) {
|
|
// Prefer name from declaration.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int good);
|
|
void bar() {
|
|
foo($good[[42]]);
|
|
}
|
|
void foo(int bad) {};
|
|
)cpp",
|
|
ExpectedHint{"good: ", "good"});
|
|
}
|
|
|
|
TEST(ParameterHints, Operator) {
|
|
// No hint for operator call with operator syntax.
|
|
assertParameterHints(R"cpp(
|
|
struct S {};
|
|
void operator+(S lhs, S rhs);
|
|
void bar() {
|
|
S a, b;
|
|
a + b;
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, Macros) {
|
|
// Handling of macros depends on where the call's argument list comes from.
|
|
|
|
// If it comes from a macro definition, there's nothing to hint
|
|
// at the invocation site.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int param);
|
|
#define ExpandsToCall() foo(42)
|
|
void bar() {
|
|
ExpandsToCall();
|
|
}
|
|
)cpp");
|
|
|
|
// The argument expression being a macro invocation shouldn't interfere
|
|
// with hinting.
|
|
assertParameterHints(R"cpp(
|
|
#define PI 3.14
|
|
void foo(double param);
|
|
void bar() {
|
|
foo($param[[PI]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
|
|
// If the whole argument list comes from a macro parameter, hint it.
|
|
assertParameterHints(R"cpp(
|
|
void abort();
|
|
#define ASSERT(expr) if (!expr) abort()
|
|
int foo(int param);
|
|
void bar() {
|
|
ASSERT(foo($param[[42]]) == 0);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, ConstructorParens) {
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
S(int param);
|
|
};
|
|
void bar() {
|
|
S obj($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, ConstructorBraces) {
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
S(int param);
|
|
};
|
|
void bar() {
|
|
S obj{$param[[42]]};
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, ConstructorStdInitList) {
|
|
// Do not show hints for std::initializer_list constructors.
|
|
assertParameterHints(R"cpp(
|
|
namespace std {
|
|
template <typename> class initializer_list {};
|
|
}
|
|
struct S {
|
|
S(std::initializer_list<int> param);
|
|
};
|
|
void bar() {
|
|
S obj{42, 43};
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, MemberInit) {
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
S(int param);
|
|
};
|
|
struct T {
|
|
S member;
|
|
T() : member($param[[42]]) {}
|
|
};
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, ImplicitConstructor) {
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
S(int param);
|
|
};
|
|
void bar(S);
|
|
S foo() {
|
|
// Do not show hint for implicit constructor call in argument.
|
|
bar(42);
|
|
// Do not show hint for implicit constructor call in return.
|
|
return 42;
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, ArgMatchesParam) {
|
|
assertParameterHints(R"cpp(
|
|
void foo(int param);
|
|
struct S {
|
|
static const int param = 42;
|
|
};
|
|
void bar() {
|
|
int param = 42;
|
|
// Do not show redundant "param: param".
|
|
foo(param);
|
|
// But show it if the argument is qualified.
|
|
foo($param[[S::param]]);
|
|
}
|
|
struct A {
|
|
int param;
|
|
void bar() {
|
|
// Do not show "param: param" for member-expr.
|
|
foo(param);
|
|
}
|
|
};
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, LeadingUnderscore) {
|
|
assertParameterHints(R"cpp(
|
|
void foo(int p1, int _p2, int __p3);
|
|
void bar() {
|
|
foo($p1[[41]], $p2[[42]], $p3[[43]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"p1: ", "p1"}, ExpectedHint{"p2: ", "p2"},
|
|
ExpectedHint{"p3: ", "p3"});
|
|
}
|
|
|
|
TEST(ParameterHints, DependentCalls) {
|
|
assertParameterHints(R"cpp(
|
|
template <typename T>
|
|
void nonmember(T par1);
|
|
|
|
template <typename T>
|
|
struct A {
|
|
void member(T par2);
|
|
static void static_member(T par3);
|
|
};
|
|
|
|
void overload(int anInt);
|
|
void overload(double aDouble);
|
|
|
|
template <typename T>
|
|
struct S {
|
|
void bar(A<T> a, T t) {
|
|
nonmember($par1[[t]]);
|
|
a.member($par2[[t]]);
|
|
A<T>::static_member($par3[[t]]);
|
|
// We don't want to arbitrarily pick between
|
|
// "anInt" or "aDouble", so just show no hint.
|
|
overload(T{});
|
|
}
|
|
};
|
|
)cpp",
|
|
ExpectedHint{"par1: ", "par1"},
|
|
ExpectedHint{"par2: ", "par2"},
|
|
ExpectedHint{"par3: ", "par3"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicFunction) {
|
|
assertParameterHints(R"cpp(
|
|
template <typename... T>
|
|
void foo(int fixed, T... variadic);
|
|
|
|
void bar() {
|
|
foo($fixed[[41]], 42, 43);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"fixed: ", "fixed"});
|
|
}
|
|
|
|
TEST(ParameterHints, VarargsFunction) {
|
|
assertParameterHints(R"cpp(
|
|
void foo(int fixed, ...);
|
|
|
|
void bar() {
|
|
foo($fixed[[41]], 42, 43);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"fixed: ", "fixed"});
|
|
}
|
|
|
|
TEST(ParameterHints, CopyOrMoveConstructor) {
|
|
// Do not show hint for parameter of copy or move constructor.
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
S();
|
|
S(const S& other);
|
|
S(S&& other);
|
|
};
|
|
void bar() {
|
|
S a;
|
|
S b(a); // copy
|
|
S c(S()); // move
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, AggregateInit) {
|
|
// FIXME: This is not implemented yet, but it would be a natural
|
|
// extension to show member names as hints here.
|
|
assertParameterHints(R"cpp(
|
|
struct Point {
|
|
int x;
|
|
int y;
|
|
};
|
|
void bar() {
|
|
Point p{41, 42};
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, UserDefinedLiteral) {
|
|
// Do not hint call to user-defined literal operator.
|
|
assertParameterHints(R"cpp(
|
|
long double operator"" _w(long double param);
|
|
void bar() {
|
|
1.2_w;
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, ParamNameComment) {
|
|
// Do not hint an argument which already has a comment
|
|
// with the parameter name preceding it.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int param);
|
|
void bar() {
|
|
foo(/*param*/42);
|
|
foo( /* param = */ 42);
|
|
foo(/* the answer */$param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, SetterFunctions) {
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
void setParent(S* parent);
|
|
void set_parent(S* parent);
|
|
void setTimeout(int timeoutMillis);
|
|
void setTimeoutMillis(int timeout_millis);
|
|
};
|
|
void bar() {
|
|
S s;
|
|
// Parameter name matches setter name - omit hint.
|
|
s.setParent(nullptr);
|
|
// Support snake_case
|
|
s.set_parent(nullptr);
|
|
// Parameter name may contain extra info - show hint.
|
|
s.setTimeout($timeoutMillis[[120]]);
|
|
// FIXME: Ideally we'd want to omit this.
|
|
s.setTimeoutMillis($timeout_millis[[120]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"timeoutMillis: ", "timeoutMillis"},
|
|
ExpectedHint{"timeout_millis: ", "timeout_millis"});
|
|
}
|
|
|
|
TEST(ParameterHints, IncludeAtNonGlobalScope) {
|
|
Annotations FooInc(R"cpp(
|
|
void bar() { foo(42); }
|
|
)cpp");
|
|
Annotations FooCC(R"cpp(
|
|
struct S {
|
|
void foo(int param);
|
|
#include "foo.inc"
|
|
};
|
|
)cpp");
|
|
|
|
TestWorkspace Workspace;
|
|
Workspace.addSource("foo.inc", FooInc.code());
|
|
Workspace.addMainFile("foo.cc", FooCC.code());
|
|
|
|
auto AST = Workspace.openFile("foo.cc");
|
|
ASSERT_TRUE(bool(AST));
|
|
|
|
// Ensure the hint for the call in foo.inc is NOT materialized in foo.cc.
|
|
EXPECT_EQ(hintsOfKind(*AST, InlayHintKind::ParameterHint).size(), 0u);
|
|
}
|
|
|
|
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"});
|
|
}
|
|
|
|
// Structured bindings tests.
|
|
// Note, we hint the individual bindings, not the aggregate.
|
|
|
|
TEST(TypeHints, StructuredBindings_PublicStruct) {
|
|
assertTypeHints(R"cpp(
|
|
// Struct with public fields.
|
|
struct Point {
|
|
int x;
|
|
int y;
|
|
};
|
|
Point foo();
|
|
auto [$x[[x]], $y[[y]]] = foo();
|
|
)cpp",
|
|
ExpectedHint{": int", "x"}, ExpectedHint{": int", "y"});
|
|
}
|
|
|
|
TEST(TypeHints, StructuredBindings_Array) {
|
|
assertTypeHints(R"cpp(
|
|
int arr[2];
|
|
auto [$x[[x]], $y[[y]]] = arr;
|
|
)cpp",
|
|
ExpectedHint{": int", "x"}, ExpectedHint{": int", "y"});
|
|
}
|
|
|
|
TEST(TypeHints, StructuredBindings_TupleLike) {
|
|
assertTypeHints(R"cpp(
|
|
// Tuple-like type.
|
|
struct IntPair {
|
|
int a;
|
|
int b;
|
|
};
|
|
namespace std {
|
|
template <typename T>
|
|
struct tuple_size {};
|
|
template <>
|
|
struct tuple_size<IntPair> {
|
|
constexpr static unsigned value = 2;
|
|
};
|
|
template <unsigned I, typename T>
|
|
struct tuple_element {};
|
|
template <unsigned I>
|
|
struct tuple_element<I, IntPair> {
|
|
using type = int;
|
|
};
|
|
}
|
|
template <unsigned I>
|
|
int get(const IntPair& p) {
|
|
if constexpr (I == 0) {
|
|
return p.a;
|
|
} else if constexpr (I == 1) {
|
|
return p.b;
|
|
}
|
|
}
|
|
IntPair bar();
|
|
auto [$x[[x]], $y[[y]]] = bar();
|
|
)cpp",
|
|
ExpectedHint{": int", "x"}, ExpectedHint{": int", "y"});
|
|
}
|
|
|
|
TEST(TypeHints, StructuredBindings_NoInitializer) {
|
|
assertTypeHints(R"cpp(
|
|
// No initializer (ill-formed).
|
|
// Do not show useless "NULL TYPE" hint.
|
|
auto [x, y]; /*error-ok*/
|
|
)cpp");
|
|
}
|
|
|
|
TEST(TypeHints, ReturnTypeDeduction) {
|
|
assertTypeHints(
|
|
R"cpp(
|
|
auto f1(int x$ret1a[[)]]; // Hint forward declaration too
|
|
auto f1(int x$ret1b[[)]] { return x + 1; }
|
|
|
|
// Include pointer operators in hint
|
|
int s;
|
|
auto& f2($ret2[[)]] { return s; }
|
|
|
|
// Do not hint `auto` for trailing return type.
|
|
auto f3() -> int;
|
|
|
|
// `auto` conversion operator
|
|
struct A {
|
|
operator auto($retConv[[)]] { return 42; }
|
|
};
|
|
|
|
// FIXME: Dependent types do not work yet.
|
|
template <typename T>
|
|
struct S {
|
|
auto method() { return T(); }
|
|
};
|
|
)cpp",
|
|
ExpectedHint{"-> int", "ret1a"}, ExpectedHint{"-> int", "ret1b"},
|
|
ExpectedHint{"-> int &", "ret2"}, ExpectedHint{"-> int", "retConv"});
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
TEST(TypeHints, LongTypeName) {
|
|
assertTypeHints(R"cpp(
|
|
template <typename, typename, typename>
|
|
struct A {};
|
|
struct MultipleWords {};
|
|
A<MultipleWords, MultipleWords, MultipleWords> foo();
|
|
// Omit type hint past a certain length (currently 32)
|
|
auto var = foo();
|
|
)cpp");
|
|
}
|
|
|
|
TEST(TypeHints, DefaultTemplateArgs) {
|
|
assertTypeHints(R"cpp(
|
|
template <typename, typename = int>
|
|
struct A {};
|
|
A<float> foo();
|
|
auto $var[[var]] = foo();
|
|
)cpp",
|
|
ExpectedHint{": A<float>", "var"});
|
|
}
|
|
|
|
TEST(TypeHints, Deduplication) {
|
|
assertTypeHints(R"cpp(
|
|
template <typename T>
|
|
void foo() {
|
|
auto $var[[var]] = 42;
|
|
}
|
|
template void foo<int>();
|
|
template void foo<float>();
|
|
)cpp",
|
|
ExpectedHint{": int", "var"});
|
|
}
|
|
|
|
// 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
|