mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-27 23:51:56 +00:00
5cdb906f1e
(This patch addresses the comment from https://reviews.llvm.org/D151785#4402460.) Previously, we used a special printing policy that enabled `PrintCanonicalTypes` to print type hints for structure bindings. This was intended to eliminate type aliases like `tuple_element::type`. However, this also caused TypePrinter to print default template arguments, which could result in losing the ability to see types like `std::basic_string<char>` if the fully expanded template-id exceeded the default inlay hint threshold. Simply getting the canonical type at the call site could help us get rid of the side effect. This also merges overloaded `addTypeHint` into one function without `PrintingPolicy`. Reviewed By: nridge Differential Revision: https://reviews.llvm.org/D152520
1646 lines
44 KiB
C++
1646 lines
44 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 "Config.h"
|
|
#include "InlayHints.h"
|
|
#include "Protocol.h"
|
|
#include "TestTU.h"
|
|
#include "TestWorkspace.h"
|
|
#include "XRefs.h"
|
|
#include "support/Context.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
|
|
const InlayHint &Hint) {
|
|
return Stream << Hint.label << "@" << Hint.range;
|
|
}
|
|
|
|
namespace {
|
|
|
|
using ::testing::ElementsAre;
|
|
using ::testing::IsEmpty;
|
|
|
|
std::vector<InlayHint> hintsOfKind(ParsedAST &AST, InlayHintKind Kind) {
|
|
std::vector<InlayHint> Result;
|
|
for (auto &Hint : inlayHints(AST, /*RestrictRange=*/std::nullopt)) {
|
|
if (Hint.kind == Kind)
|
|
Result.push_back(Hint);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
enum HintSide { Left, Right };
|
|
|
|
struct ExpectedHint {
|
|
std::string Label;
|
|
std::string RangeName;
|
|
HintSide Side = Left;
|
|
|
|
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
|
|
const ExpectedHint &Hint) {
|
|
return Stream << Hint.Label << "@$" << Hint.RangeName;
|
|
}
|
|
};
|
|
|
|
MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
|
|
llvm::StringRef ExpectedView(Expected.Label);
|
|
if (arg.label != ExpectedView.trim(" ") ||
|
|
arg.paddingLeft != ExpectedView.startswith(" ") ||
|
|
arg.paddingRight != ExpectedView.endswith(" ")) {
|
|
*result_listener << "label is '" << arg.label << "'";
|
|
return false;
|
|
}
|
|
if (arg.range != Code.range(Expected.RangeName)) {
|
|
*result_listener << "range is " << llvm::to_string(arg.range) << " but $"
|
|
<< Expected.RangeName << " is "
|
|
<< llvm::to_string(Code.range(Expected.RangeName));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MATCHER_P(labelIs, Label, "") { return arg.label == Label; }
|
|
|
|
Config noHintsConfig() {
|
|
Config C;
|
|
C.InlayHints.Parameters = false;
|
|
C.InlayHints.DeducedTypes = false;
|
|
C.InlayHints.Designators = false;
|
|
return C;
|
|
}
|
|
|
|
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++20");
|
|
auto AST = TU.build();
|
|
|
|
EXPECT_THAT(hintsOfKind(AST, Kind),
|
|
ElementsAre(HintMatcher(Expected, Source)...));
|
|
// Sneak in a cross-cutting check that hints are disabled by config.
|
|
// We'll hit an assertion failure if addInlayHint still gets called.
|
|
WithContextValue WithCfg(Config::Key, noHintsConfig());
|
|
EXPECT_THAT(inlayHints(AST, std::nullopt), IsEmpty());
|
|
}
|
|
|
|
// Hack to allow expression-statements operating on parameter packs in C++14.
|
|
template <typename... T> void ignore(T &&...) {}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertParameterHints(llvm::StringRef AnnotatedSource,
|
|
ExpectedHints... Expected) {
|
|
ignore(Expected.Side = Left...);
|
|
assertHints(InlayHintKind::Parameter, AnnotatedSource, Expected...);
|
|
}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertTypeHints(llvm::StringRef AnnotatedSource,
|
|
ExpectedHints... Expected) {
|
|
ignore(Expected.Side = Right...);
|
|
assertHints(InlayHintKind::Type, AnnotatedSource, Expected...);
|
|
}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertDesignatorHints(llvm::StringRef AnnotatedSource,
|
|
ExpectedHints... Expected) {
|
|
Config Cfg;
|
|
Cfg.InlayHints.Designators = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
assertHints(InlayHintKind::Designator, 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, NoNameConstReference) {
|
|
// No hint for anonymous const l-value ref parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(const int&);
|
|
void bar() {
|
|
foo(42);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, NoNameReference) {
|
|
// Reference hint for anonymous l-value ref parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int&);
|
|
void bar() {
|
|
int i;
|
|
foo($param[[i]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"&: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NoNameRValueReference) {
|
|
// No reference hint for anonymous r-value ref parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int&&);
|
|
void bar() {
|
|
foo(42);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, NoNameVariadicDeclaration) {
|
|
// No hint for anonymous variadic parameter
|
|
assertParameterHints(R"cpp(
|
|
template <typename... Args>
|
|
void foo(Args&& ...);
|
|
void bar() {
|
|
foo(42);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, NoNameVariadicForwarded) {
|
|
// No hint for anonymous variadic parameter
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void foo(int);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void baz() {
|
|
bar(42);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, NoNameVariadicPlain) {
|
|
// No hint for anonymous variadic parameter
|
|
assertParameterHints(R"cpp(
|
|
void foo(int);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(args...); }
|
|
void baz() {
|
|
bar(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, NamePartiallyInDefinition) {
|
|
// Parameter name picked up from definition if necessary.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int, int b);
|
|
void bar() {
|
|
foo($param1[[42]], $param2[[42]]);
|
|
}
|
|
void foo(int a, int) {};
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"},
|
|
ExpectedHint{"b: ", "param2"});
|
|
}
|
|
|
|
TEST(ParameterHints, NameInDefinitionVariadic) {
|
|
// Parameter name picked up from definition in a resolved forwarded parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int, int);
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(args...);
|
|
}
|
|
void baz() {
|
|
bar($param1[[42]], $param2[[42]]);
|
|
}
|
|
void foo(int a, int b) {};
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"},
|
|
ExpectedHint{"b: ", "param2"});
|
|
}
|
|
|
|
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, NameConstReference) {
|
|
// Only name hint for const l-value ref parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(const int& param);
|
|
void bar() {
|
|
foo($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NameTypeAliasConstReference) {
|
|
// Only name hint for const l-value ref parameter via type alias.
|
|
assertParameterHints(R"cpp(
|
|
using alias = const int&;
|
|
void foo(alias param);
|
|
void bar() {
|
|
int i;
|
|
foo($param[[i]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NameReference) {
|
|
// Reference and name hint for l-value ref parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int& param);
|
|
void bar() {
|
|
int i;
|
|
foo($param[[i]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"¶m: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NameTypeAliasReference) {
|
|
// Reference and name hint for l-value ref parameter via type alias.
|
|
assertParameterHints(R"cpp(
|
|
using alias = int&;
|
|
void foo(alias param);
|
|
void bar() {
|
|
int i;
|
|
foo($param[[i]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"¶m: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, NameRValueReference) {
|
|
// Only name hint for r-value ref parameter.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int&& param);
|
|
void bar() {
|
|
foo($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicForwardedConstructor) {
|
|
// Name hint for variadic parameter using std::forward in a constructor call
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
struct S { S(int a); };
|
|
template <typename T, typename... Args>
|
|
T bar(Args&&... args) { return T{std::forward<Args>(args)...}; }
|
|
void baz() {
|
|
int b;
|
|
bar<S>($param[[b]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicPlainConstructor) {
|
|
// Name hint for variadic parameter in a constructor call
|
|
assertParameterHints(R"cpp(
|
|
struct S { S(int a); };
|
|
template <typename T, typename... Args>
|
|
T bar(Args&&... args) { return T{args...}; }
|
|
void baz() {
|
|
int b;
|
|
bar<S>($param[[b]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicForwardedNewConstructor) {
|
|
// Name hint for variadic parameter using std::forward in a new expression
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
struct S { S(int a); };
|
|
template <typename T, typename... Args>
|
|
T* bar(Args&&... args) { return new T{std::forward<Args>(args)...}; }
|
|
void baz() {
|
|
int b;
|
|
bar<S>($param[[b]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicPlainNewConstructor) {
|
|
// Name hint for variadic parameter in a new expression
|
|
assertParameterHints(R"cpp(
|
|
struct S { S(int a); };
|
|
template <typename T, typename... Args>
|
|
T* bar(Args&&... args) { return new T{args...}; }
|
|
void baz() {
|
|
int b;
|
|
bar<S>($param[[b]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicForwarded) {
|
|
// Name for variadic parameter using std::forward
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void foo(int a);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void baz() {
|
|
int b;
|
|
bar($param[[b]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicPlain) {
|
|
// Name hint for variadic parameter
|
|
assertParameterHints(R"cpp(
|
|
void foo(int a);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(args...); }
|
|
void baz() {
|
|
bar($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicPlainWithPackFirst) {
|
|
// Name hint for variadic parameter when the parameter pack is not the last
|
|
// template parameter
|
|
assertParameterHints(R"cpp(
|
|
void foo(int a);
|
|
template <typename... Args, typename Arg>
|
|
void bar(Arg, Args&&... args) { return foo(args...); }
|
|
void baz() {
|
|
bar(1, $param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicSplitTwolevel) {
|
|
// Name for variadic parameter that involves both head and tail parameters to
|
|
// deal with.
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void baz(int, int b, double);
|
|
template <typename... Args>
|
|
void foo(int a, Args&&... args) {
|
|
return baz(1, std::forward<Args>(args)..., 1.0);
|
|
}
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void bazz() {
|
|
bar($param1[[32]], $param2[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"},
|
|
ExpectedHint{"b: ", "param2"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicNameFromSpecialization) {
|
|
// We don't try to resolve forwarding parameters if the function call uses a
|
|
// specialization.
|
|
assertParameterHints(R"cpp(
|
|
void foo(int a);
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(args...);
|
|
}
|
|
template <>
|
|
void bar<int>(int b);
|
|
void baz() {
|
|
bar($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"b: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicNameFromSpecializationRecursive) {
|
|
// We don't try to resolve forwarding parameters inside a forwarding function
|
|
// call if that function call uses a specialization.
|
|
assertParameterHints(R"cpp(
|
|
void foo2(int a);
|
|
template <typename... Args>
|
|
void foo(Args... args) {
|
|
foo2(args...);
|
|
}
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(args...);
|
|
}
|
|
template <>
|
|
void foo<int>(int b);
|
|
void baz() {
|
|
bar($param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"b: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicOverloaded) {
|
|
// Name for variadic parameter for an overloaded function with unique number
|
|
// of parameters.
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(
|
|
R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void baz(int b, int c);
|
|
void baz(int bb, int cc, int dd);
|
|
template <typename... Args>
|
|
void foo(int a, Args&&... args) {
|
|
return baz(std::forward<Args>(args)...);
|
|
}
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void bazz() {
|
|
bar($param1[[32]], $param2[[42]], $param3[[52]]);
|
|
bar($param4[[1]], $param5[[2]], $param6[[3]], $param7[[4]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"},
|
|
ExpectedHint{"c: ", "param3"}, ExpectedHint{"a: ", "param4"},
|
|
ExpectedHint{"bb: ", "param5"}, ExpectedHint{"cc: ", "param6"},
|
|
ExpectedHint{"dd: ", "param7"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicRecursive) {
|
|
// make_tuple-like recursive variadic call
|
|
assertParameterHints(
|
|
R"cpp(
|
|
void foo();
|
|
|
|
template <typename Head, typename... Tail>
|
|
void foo(Head head, Tail... tail) {
|
|
foo(tail...);
|
|
}
|
|
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(args...);
|
|
}
|
|
|
|
int main() {
|
|
bar(1, 2, 3);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicVarargs) {
|
|
// variadic call involving varargs (to make sure we don't crash)
|
|
assertParameterHints(R"cpp(
|
|
void foo(int fixed, ...);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) {
|
|
foo(args...);
|
|
}
|
|
|
|
void baz() {
|
|
bar($fixed[[41]], 42, 43);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicTwolevelUnresolved) {
|
|
// the same setting as VariadicVarargs, only with parameter pack
|
|
assertParameterHints(R"cpp(
|
|
template <typename... Args>
|
|
void foo(int fixed, Args&& ... args);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) {
|
|
foo(args...);
|
|
}
|
|
|
|
void baz() {
|
|
bar($fixed[[41]], 42, 43);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"fixed: ", "fixed"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicTwoCalls) {
|
|
// only the first call using the parameter pack should be picked up
|
|
assertParameterHints(
|
|
R"cpp(
|
|
void f1(int a, int b);
|
|
void f2(int c, int d);
|
|
|
|
bool cond;
|
|
|
|
template <typename... Args>
|
|
void foo(Args... args) {
|
|
if (cond) {
|
|
f1(args...);
|
|
} else {
|
|
f2(args...);
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
foo($param1[[1]], $param2[[2]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicInfinite) {
|
|
// infinite recursion should not break clangd
|
|
assertParameterHints(
|
|
R"cpp(
|
|
template <typename... Args>
|
|
void foo(Args...);
|
|
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(args...);
|
|
}
|
|
|
|
template <typename... Args>
|
|
void foo(Args... args) {
|
|
bar(args...);
|
|
}
|
|
|
|
int main() {
|
|
foo(1, 2);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicDuplicatePack) {
|
|
// edge cases with multiple adjacent packs should work
|
|
assertParameterHints(
|
|
R"cpp(
|
|
void foo(int a, int b, int c, int);
|
|
|
|
template <typename... Args>
|
|
void bar(int, Args... args, int d) {
|
|
foo(args..., d);
|
|
}
|
|
|
|
template <typename... Args>
|
|
void baz(Args... args, Args... args2) {
|
|
bar<Args..., int>(1, args..., args2...);
|
|
}
|
|
|
|
int main() {
|
|
baz<int, int>($p1[[1]], $p2[[2]], $p3[[3]], $p4[[4]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "p1"}, ExpectedHint{"b: ", "p2"},
|
|
ExpectedHint{"c: ", "p3"}, ExpectedHint{"d: ", "p4"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicEmplace) {
|
|
// emplace-like calls should forward constructor parameters
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(
|
|
R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
using size_t = decltype(sizeof(0));
|
|
void *operator new(size_t, void *);
|
|
struct S {
|
|
S(int A);
|
|
S(int B, int C);
|
|
};
|
|
struct alloc {
|
|
template <typename T>
|
|
T* allocate();
|
|
template <typename T, typename... Args>
|
|
void construct(T* ptr, Args&&... args) {
|
|
::new ((void*)ptr) T{std::forward<Args>(args)...};
|
|
}
|
|
};
|
|
template <typename T>
|
|
struct container {
|
|
template <typename... Args>
|
|
void emplace(Args&&... args) {
|
|
alloc a;
|
|
auto ptr = a.template allocate<T>();
|
|
a.construct(ptr, std::forward<Args>(args)...);
|
|
}
|
|
};
|
|
void foo() {
|
|
container<S> c;
|
|
c.emplace($param1[[1]]);
|
|
c.emplace($param2[[2]], $param3[[3]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"A: ", "param1"}, ExpectedHint{"B: ", "param2"},
|
|
ExpectedHint{"C: ", "param3"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicReferenceHint) {
|
|
assertParameterHints(R"cpp(
|
|
void foo(int&);
|
|
template <typename... Args>
|
|
void bar(Args... args) { return foo(args...); }
|
|
void baz() {
|
|
int a;
|
|
bar(a);
|
|
bar(1);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicReferenceHintForwardingRef) {
|
|
assertParameterHints(R"cpp(
|
|
void foo(int&);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(args...); }
|
|
void baz() {
|
|
int a;
|
|
bar($param[[a]]);
|
|
bar(1);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"&: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicReferenceHintForwardingRefStdForward) {
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void foo(int&);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void baz() {
|
|
int a;
|
|
bar($param[[a]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"&: ", "param"});
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicNoReferenceHintForwardingRefStdForward) {
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void foo(int);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void baz() {
|
|
int a;
|
|
bar(a);
|
|
bar(1);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, VariadicNoReferenceHintUnresolvedForward) {
|
|
assertParameterHints(R"cpp(
|
|
template <typename... Args>
|
|
void foo(Args&&... args);
|
|
void bar() {
|
|
int a;
|
|
foo(a);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, MatchingNameVariadicForwarded) {
|
|
// No name hint for variadic parameter with matching name
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void foo(int a);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(std::forward<Args>(args)...); }
|
|
void baz() {
|
|
int a;
|
|
bar(a);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST(ParameterHints, MatchingNameVariadicPlain) {
|
|
// No name hint for variadic parameter with matching name
|
|
assertParameterHints(R"cpp(
|
|
void foo(int a);
|
|
template <typename... Args>
|
|
void bar(Args&&... args) { return foo(args...); }
|
|
void baz() {
|
|
int a;
|
|
bar(a);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
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"});
|
|
|
|
// If the macro expands to multiple arguments, don't hint it.
|
|
assertParameterHints(R"cpp(
|
|
void foo(double x, double y);
|
|
#define CONSTANTS 3.14, 2.72
|
|
void bar() {
|
|
foo(CONSTANTS);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
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, ArgMatchesParamReference) {
|
|
assertParameterHints(R"cpp(
|
|
void foo(int& param);
|
|
void foo2(const int& param);
|
|
void bar() {
|
|
int param;
|
|
// show reference hint on mutable reference
|
|
foo($param[[param]]);
|
|
// but not on const reference
|
|
foo2(param);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"&: ", "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);
|
|
#define X 42
|
|
#define Y X
|
|
#define Z(...) Y
|
|
foo(/*param=*/Z(a));
|
|
foo($macro[[Z(a)]]);
|
|
foo(/* the answer */$param[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "macro"},
|
|
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, BuiltinFunctions) {
|
|
// This prototype of std::forward is sufficient for clang to recognize it
|
|
assertParameterHints(R"cpp(
|
|
namespace std { template <typename T> T&& forward(T&); }
|
|
void foo() {
|
|
int i;
|
|
std::forward(i);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
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::Parameter).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"},
|
|
// FIXME: We want to suppress scope specifiers
|
|
// here because we are into the whole
|
|
// brevity thing, but the ElaboratedType
|
|
// printer does not honor the SuppressScope
|
|
// flag by design, so we need to extend the
|
|
// PrintingPolicy to support this use case.
|
|
ExpectedHint{": S2::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$ret[[)]] {
|
|
return a + cap + init;
|
|
};
|
|
}
|
|
)cpp",
|
|
ExpectedHint{": (lambda)", "L"},
|
|
ExpectedHint{": int", "init"}, ExpectedHint{"-> int", "ret"});
|
|
|
|
// Lambda return hint shown even if no param list.
|
|
// (The digraph :> is just a ] that doesn't conflict with the annotations).
|
|
assertTypeHints("auto $L[[x]] = <:$ret[[:>]]{return 42;};",
|
|
ExpectedHint{": (lambda)", "L"},
|
|
ExpectedHint{"-> int", "ret"});
|
|
}
|
|
|
|
// 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, InvalidType) {
|
|
assertTypeHints(R"cpp(
|
|
auto x = (unknown_type)42; /*error-ok*/
|
|
auto *y = (unknown_ptr)nullptr;
|
|
)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;
|
|
|
|
// Do not hint when a trailing return type is specified.
|
|
auto f4() -> auto* { return "foo"; }
|
|
|
|
auto f5($noreturn[[)]] {}
|
|
|
|
// `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{"-> void", "noreturn"},
|
|
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");
|
|
|
|
Config Cfg;
|
|
Cfg.InlayHints.TypeNameLimit = 0;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
|
|
assertTypeHints(
|
|
R"cpp(
|
|
template <typename, typename, typename>
|
|
struct A {};
|
|
struct MultipleWords {};
|
|
A<MultipleWords, MultipleWords, MultipleWords> foo();
|
|
// Should have type hint with TypeNameLimit = 0
|
|
auto $var[[var]] = foo();
|
|
)cpp",
|
|
ExpectedHint{": A<MultipleWords, MultipleWords, MultipleWords>", "var"});
|
|
}
|
|
|
|
TEST(TypeHints, DefaultTemplateArgs) {
|
|
assertTypeHints(R"cpp(
|
|
template <typename, typename = int>
|
|
struct A {};
|
|
A<float> foo();
|
|
auto $var[[var]] = foo();
|
|
A<float> bar[1];
|
|
auto [$binding[[value]]] = bar;
|
|
)cpp",
|
|
ExpectedHint{": A<float>", "var"},
|
|
ExpectedHint{": A<float>", "binding"});
|
|
}
|
|
|
|
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"});
|
|
}
|
|
|
|
TEST(TypeHints, SinglyInstantiatedTemplate) {
|
|
assertTypeHints(R"cpp(
|
|
auto $lambda[[x]] = [](auto *$param[[y]], auto) { return 42; };
|
|
int m = x("foo", 3);
|
|
)cpp",
|
|
ExpectedHint{": (lambda)", "lambda"},
|
|
ExpectedHint{": const char *", "param"});
|
|
|
|
// No hint for packs, or auto params following packs
|
|
assertTypeHints(R"cpp(
|
|
int x(auto $a[[a]], auto... b, auto c) { return 42; }
|
|
int m = x<void*, char, float>(nullptr, 'c', 2.0, 2);
|
|
)cpp",
|
|
ExpectedHint{": void *", "a"});
|
|
}
|
|
|
|
TEST(TypeHints, Aliased) {
|
|
// Check that we don't crash for functions without a FunctionTypeLoc.
|
|
// https://github.com/clangd/clangd/issues/1140
|
|
TestTU TU = TestTU::withCode("void foo(void){} extern typeof(foo) foo;");
|
|
TU.ExtraArgs.push_back("-xc");
|
|
auto AST = TU.build();
|
|
|
|
EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty());
|
|
}
|
|
|
|
TEST(TypeHints, Decltype) {
|
|
assertTypeHints(R"cpp(
|
|
$a[[decltype(0)]] a;
|
|
$b[[decltype(a)]] b;
|
|
const $c[[decltype(0)]] &c = b;
|
|
|
|
// Don't show for dependent type
|
|
template <class T>
|
|
constexpr decltype(T{}) d;
|
|
|
|
$e[[decltype(0)]] e();
|
|
auto f() -> $f[[decltype(0)]];
|
|
|
|
template <class, class> struct Foo;
|
|
using G = Foo<$g[[decltype(0)]], float>;
|
|
|
|
auto $h[[h]] = $i[[decltype(0)]]{};
|
|
|
|
// No crash
|
|
/* error-ok */
|
|
auto $j[[s]];
|
|
)cpp",
|
|
ExpectedHint{": int", "a"}, ExpectedHint{": int", "b"},
|
|
ExpectedHint{": int", "c"}, ExpectedHint{": int", "e"},
|
|
ExpectedHint{": int", "f"}, ExpectedHint{": int", "g"},
|
|
ExpectedHint{": int", "h"}, ExpectedHint{": int", "i"});
|
|
}
|
|
|
|
TEST(TypeHints, SubstTemplateParameterAliases) {
|
|
assertTypeHints(
|
|
R"cpp(
|
|
template <class T> struct allocator {};
|
|
|
|
template <class T, class A>
|
|
struct vector_base {
|
|
using pointer = T*;
|
|
};
|
|
|
|
template <class T, class A>
|
|
struct internal_iterator_type_template_we_dont_expect {};
|
|
|
|
struct my_iterator {};
|
|
|
|
template <class T, class A = allocator<T>>
|
|
struct vector : vector_base<T, A> {
|
|
using base = vector_base<T, A>;
|
|
typedef T value_type;
|
|
typedef base::pointer pointer;
|
|
using allocator_type = A;
|
|
using size_type = int;
|
|
using iterator = internal_iterator_type_template_we_dont_expect<T, A>;
|
|
using non_template_iterator = my_iterator;
|
|
|
|
value_type& operator[](int index) { return elements[index]; }
|
|
const value_type& at(int index) const { return elements[index]; }
|
|
pointer data() { return &elements[0]; }
|
|
allocator_type get_allocator() { return A(); }
|
|
size_type size() const { return 10; }
|
|
iterator begin() { return iterator(); }
|
|
non_template_iterator end() { return non_template_iterator(); }
|
|
|
|
T elements[10];
|
|
};
|
|
|
|
vector<int> array;
|
|
|
|
auto $no_modifier[[by_value]] = array[3];
|
|
auto* $ptr_modifier[[ptr]] = &array[3];
|
|
auto& $ref_modifier[[ref]] = array[3];
|
|
auto& $at[[immutable]] = array.at(3);
|
|
|
|
auto $data[[data]] = array.data();
|
|
auto $allocator[[alloc]] = array.get_allocator();
|
|
auto $size[[size]] = array.size();
|
|
auto $begin[[begin]] = array.begin();
|
|
auto $end[[end]] = array.end();
|
|
|
|
|
|
// If the type alias is not of substituted template parameter type,
|
|
// do not show desugared type.
|
|
using VeryLongLongTypeName = my_iterator;
|
|
using Short = VeryLongLongTypeName;
|
|
|
|
auto $short_name[[my_value]] = Short();
|
|
|
|
// Same applies with templates.
|
|
template <typename T, typename A>
|
|
using basic_static_vector = vector<T, A>;
|
|
template <typename T>
|
|
using static_vector = basic_static_vector<T, allocator<T>>;
|
|
|
|
auto $vector_name[[vec]] = static_vector<int>();
|
|
)cpp",
|
|
ExpectedHint{": int", "no_modifier"},
|
|
ExpectedHint{": int *", "ptr_modifier"},
|
|
ExpectedHint{": int &", "ref_modifier"},
|
|
ExpectedHint{": const int &", "at"}, ExpectedHint{": int *", "data"},
|
|
ExpectedHint{": allocator<int>", "allocator"},
|
|
ExpectedHint{": size_type", "size"}, ExpectedHint{": iterator", "begin"},
|
|
ExpectedHint{": non_template_iterator", "end"},
|
|
ExpectedHint{": Short", "short_name"},
|
|
ExpectedHint{": static_vector<int>", "vector_name"});
|
|
}
|
|
|
|
TEST(DesignatorHints, Basic) {
|
|
assertDesignatorHints(R"cpp(
|
|
struct S { int x, y, z; };
|
|
S s {$x[[1]], $y[[2+2]]};
|
|
|
|
int x[] = {$0[[0]], $1[[1]]};
|
|
)cpp",
|
|
ExpectedHint{".x=", "x"}, ExpectedHint{".y=", "y"},
|
|
ExpectedHint{"[0]=", "0"}, ExpectedHint{"[1]=", "1"});
|
|
}
|
|
|
|
TEST(DesignatorHints, Nested) {
|
|
assertDesignatorHints(R"cpp(
|
|
struct Inner { int x, y; };
|
|
struct Outer { Inner a, b; };
|
|
Outer o{ $a[[{ $x[[1]], $y[[2]] }]], $bx[[3]] };
|
|
)cpp",
|
|
ExpectedHint{".a=", "a"}, ExpectedHint{".x=", "x"},
|
|
ExpectedHint{".y=", "y"}, ExpectedHint{".b.x=", "bx"});
|
|
}
|
|
|
|
TEST(DesignatorHints, AnonymousRecord) {
|
|
assertDesignatorHints(R"cpp(
|
|
struct S {
|
|
union {
|
|
struct {
|
|
struct {
|
|
int y;
|
|
};
|
|
} x;
|
|
};
|
|
};
|
|
S s{$xy[[42]]};
|
|
)cpp",
|
|
ExpectedHint{".x.y=", "xy"});
|
|
}
|
|
|
|
TEST(DesignatorHints, Suppression) {
|
|
assertDesignatorHints(R"cpp(
|
|
struct Point { int a, b, c, d, e, f, g, h; };
|
|
Point p{/*a=*/1, .c=2, /* .d = */3, $e[[4]]};
|
|
)cpp",
|
|
ExpectedHint{".e=", "e"});
|
|
}
|
|
|
|
TEST(DesignatorHints, StdArray) {
|
|
// Designators for std::array should be [0] rather than .__elements[0].
|
|
// While technically correct, the designator is useless and horrible to read.
|
|
assertDesignatorHints(R"cpp(
|
|
template <typename T, int N> struct Array { T __elements[N]; };
|
|
Array<int, 2> x = {$0[[0]], $1[[1]]};
|
|
)cpp",
|
|
ExpectedHint{"[0]=", "0"}, ExpectedHint{"[1]=", "1"});
|
|
}
|
|
|
|
TEST(DesignatorHints, OnlyAggregateInit) {
|
|
assertDesignatorHints(R"cpp(
|
|
struct Copyable { int x; } c;
|
|
Copyable d{c};
|
|
|
|
struct Constructible { Constructible(int x); };
|
|
Constructible x{42};
|
|
)cpp" /*no designator hints expected (but param hints!)*/);
|
|
}
|
|
|
|
TEST(DesignatorHints, NoCrash) {
|
|
assertDesignatorHints(R"cpp(
|
|
/*error-ok*/
|
|
struct A {};
|
|
struct Foo {int a; int b;};
|
|
void test() {
|
|
Foo f{A(), $b[[1]]};
|
|
}
|
|
)cpp", ExpectedHint{".b=", "b"});
|
|
}
|
|
|
|
TEST(InlayHints, RestrictRange) {
|
|
Annotations Code(R"cpp(
|
|
auto a = false;
|
|
[[auto b = 1;
|
|
auto c = '2';]]
|
|
auto d = 3.f;
|
|
)cpp");
|
|
auto AST = TestTU::withCode(Code.code()).build();
|
|
EXPECT_THAT(inlayHints(AST, Code.range()),
|
|
ElementsAre(labelIs(": int"), labelIs(": char")));
|
|
}
|
|
|
|
TEST(ParameterHints, ArgPacksAndConstructors) {
|
|
assertParameterHints(
|
|
R"cpp(
|
|
struct Foo{ Foo(); Foo(int x); };
|
|
void foo(Foo a, int b);
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(args...);
|
|
}
|
|
template <typename... Args>
|
|
void baz(Args... args) { foo($param1[[Foo{args...}]], $param2[[1]]); }
|
|
|
|
template <typename... Args>
|
|
void bax(Args... args) { foo($param3[[{args...}]], args...); }
|
|
|
|
void foo() {
|
|
bar($param4[[Foo{}]], $param5[[42]]);
|
|
bar($param6[[42]], $param7[[42]]);
|
|
baz($param8[[42]]);
|
|
bax($param9[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"},
|
|
ExpectedHint{"a: ", "param3"}, ExpectedHint{"a: ", "param4"},
|
|
ExpectedHint{"b: ", "param5"}, ExpectedHint{"a: ", "param6"},
|
|
ExpectedHint{"b: ", "param7"}, ExpectedHint{"x: ", "param8"},
|
|
ExpectedHint{"b: ", "param9"});
|
|
}
|
|
|
|
TEST(ParameterHints, DoesntExpandAllArgs) {
|
|
assertParameterHints(
|
|
R"cpp(
|
|
void foo(int x, int y);
|
|
int id(int a, int b, int c);
|
|
template <typename... Args>
|
|
void bar(Args... args) {
|
|
foo(id($param1[[args]], $param2[[1]], $param3[[args]])...);
|
|
}
|
|
void foo() {
|
|
bar(1, 2); // FIXME: We could have `bar(a: 1, a: 2)` here.
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"},
|
|
ExpectedHint{"c: ", "param3"});
|
|
}
|
|
// 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
|