blob: 3cf1c6d70c0d3194852eaa7064af59c05e55a403 [file] [log] [blame]
//===-- 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 "XRefs.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace {
using ::testing::UnorderedElementsAre;
std::vector<InlayHint> parameterHints(ParsedAST &AST) {
std::vector<InlayHint> Result;
for (auto &Hint : inlayHints(AST)) {
if (Hint.kind == InlayHintKind::ParameterHint)
Result.push_back(Hint);
}
return Result;
}
struct ExpectedHint {
std::string Label;
std::string RangeName;
};
MATCHER_P2(HintMatcher, Expected, Code, "") {
return arg.label == Expected.Label &&
arg.range == Code.range(Expected.RangeName);
}
template <typename... ExpectedHints>
void assertParameterHints(llvm::StringRef AnnotatedSource,
ExpectedHints... Expected) {
Annotations Source(AnnotatedSource);
TestTU TU = TestTU::withCode(Source.code());
TU.ExtraArgs.push_back("-std=c++11");
auto AST = TU.build();
EXPECT_THAT(parameterHints(AST),
UnorderedElementsAre(HintMatcher(Expected, Source)...));
}
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]]);
// FIXME: This one does not work yet.
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"});
}
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"});
}
} // namespace
} // namespace clangd
} // namespace clang