mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 14:56:44 +00:00
2325 lines
61 KiB
C++
2325 lines
61 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 "llvm/Support/raw_ostream.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
|
|
const InlayHint &Hint) {
|
|
return Stream << Hint.joinLabels() << "@" << 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);
|
|
std::string ResultLabel = arg.joinLabels();
|
|
if (ResultLabel != ExpectedView.trim(" ") ||
|
|
arg.paddingLeft != ExpectedView.starts_with(" ") ||
|
|
arg.paddingRight != ExpectedView.ends_with(" ")) {
|
|
*result_listener << "label is '" << ResultLabel << "'";
|
|
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.joinLabels() == Label; }
|
|
|
|
Config noHintsConfig() {
|
|
Config C;
|
|
C.InlayHints.Parameters = false;
|
|
C.InlayHints.DeducedTypes = false;
|
|
C.InlayHints.Designators = false;
|
|
C.InlayHints.BlockEnd = false;
|
|
C.InlayHints.DefaultArguments = false;
|
|
return C;
|
|
}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertHintsWithHeader(InlayHintKind Kind, llvm::StringRef AnnotatedSource,
|
|
llvm::StringRef HeaderContent,
|
|
ExpectedHints... Expected) {
|
|
Annotations Source(AnnotatedSource);
|
|
TestTU TU = TestTU::withCode(Source.code());
|
|
TU.ExtraArgs.push_back("-std=c++23");
|
|
TU.HeaderCode = HeaderContent;
|
|
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());
|
|
}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource,
|
|
ExpectedHints... Expected) {
|
|
return assertHintsWithHeader(Kind, AnnotatedSource, "",
|
|
std::move(Expected)...);
|
|
}
|
|
|
|
// 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...);
|
|
}
|
|
|
|
template <typename... ExpectedHints>
|
|
void assertBlockEndHints(llvm::StringRef AnnotatedSource,
|
|
ExpectedHints... Expected) {
|
|
Config Cfg;
|
|
Cfg.InlayHints.BlockEnd = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
assertHints(InlayHintKind::BlockEnd, 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, FunctionCallOperator) {
|
|
assertParameterHints(R"cpp(
|
|
struct W {
|
|
void operator()(int x);
|
|
};
|
|
struct S : W {
|
|
using W::operator();
|
|
static void operator()(int x, int y);
|
|
};
|
|
void bar() {
|
|
auto l1 = [](int x) {};
|
|
auto l2 = [](int x) static {};
|
|
|
|
S s;
|
|
s($1[[1]]);
|
|
s.operator()($2[[1]]);
|
|
s.operator()($3[[1]], $4[[2]]);
|
|
S::operator()($5[[1]], $6[[2]]);
|
|
|
|
l1($7[[1]]);
|
|
l1.operator()($8[[1]]);
|
|
l2($9[[1]]);
|
|
l2.operator()($10[[1]]);
|
|
|
|
void (*ptr)(int a, int b) = &S::operator();
|
|
ptr($11[[1]], $12[[2]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"x: ", "1"}, ExpectedHint{"x: ", "2"},
|
|
ExpectedHint{"x: ", "3"}, ExpectedHint{"y: ", "4"},
|
|
ExpectedHint{"x: ", "5"}, ExpectedHint{"y: ", "6"},
|
|
ExpectedHint{"x: ", "7"}, ExpectedHint{"x: ", "8"},
|
|
ExpectedHint{"x: ", "9"}, ExpectedHint{"x: ", "10"},
|
|
ExpectedHint{"a: ", "11"}, ExpectedHint{"b: ", "12"});
|
|
}
|
|
|
|
TEST(ParameterHints, DeducingThis) {
|
|
assertParameterHints(R"cpp(
|
|
struct S {
|
|
template <typename This>
|
|
auto operator()(this This &&Self, int Param) {
|
|
return 42;
|
|
}
|
|
|
|
auto function(this auto &Self, int Param) {
|
|
return Param;
|
|
}
|
|
};
|
|
void work() {
|
|
S s;
|
|
s($1[[42]]);
|
|
s.function($2[[42]]);
|
|
S()($3[[42]]);
|
|
auto lambda = [](this auto &Self, char C) -> void {
|
|
return Self(C);
|
|
};
|
|
lambda($4[['A']]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"Param: ", "1"},
|
|
ExpectedHint{"Param: ", "2"},
|
|
ExpectedHint{"Param: ", "3"}, ExpectedHint{"C: ", "4"});
|
|
}
|
|
|
|
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 E> class initializer_list { const E *a, *b; };
|
|
}
|
|
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, FunctionPointer) {
|
|
assertParameterHints(
|
|
R"cpp(
|
|
void (*f1)(int param);
|
|
void (__stdcall *f2)(int param);
|
|
using f3_t = void(*)(int param);
|
|
f3_t f3;
|
|
using f4_t = void(__stdcall *)(int param);
|
|
f4_t f4;
|
|
void bar() {
|
|
f1($f1[[42]]);
|
|
f2($f2[[42]]);
|
|
f3($f3[[42]]);
|
|
f4($f4[[42]]);
|
|
}
|
|
)cpp",
|
|
ExpectedHint{"param: ", "f1"}, ExpectedHint{"param: ", "f2"},
|
|
ExpectedHint{"param: ", "f3"}, ExpectedHint{"param: ", "f4"});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
template <typename T>
|
|
void bar(T arg) {
|
|
auto [a, b] = 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(DefaultArguments, Smoke) {
|
|
Config Cfg;
|
|
Cfg.InlayHints.Parameters =
|
|
true; // To test interplay of parameters and default parameters
|
|
Cfg.InlayHints.DeducedTypes = false;
|
|
Cfg.InlayHints.Designators = false;
|
|
Cfg.InlayHints.BlockEnd = false;
|
|
|
|
Cfg.InlayHints.DefaultArguments = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
|
|
const auto *Code = R"cpp(
|
|
int foo(int A = 4) { return A; }
|
|
int bar(int A, int B = 1, bool C = foo($default1[[)]]) { return A; }
|
|
int A = bar($explicit[[2]]$default2[[)]];
|
|
|
|
void baz(int = 5) { if (false) baz($unnamed[[)]]; };
|
|
)cpp";
|
|
|
|
assertHints(InlayHintKind::DefaultArgument, Code,
|
|
ExpectedHint{"A: 4", "default1", Left},
|
|
ExpectedHint{", B: 1, C: foo()", "default2", Left},
|
|
ExpectedHint{"5", "unnamed", Left});
|
|
|
|
assertHints(InlayHintKind::Parameter, Code,
|
|
ExpectedHint{"A: ", "explicit", Left});
|
|
}
|
|
|
|
TEST(DefaultArguments, WithoutParameterNames) {
|
|
Config Cfg;
|
|
Cfg.InlayHints.Parameters = false; // To test just default args this time
|
|
Cfg.InlayHints.DeducedTypes = false;
|
|
Cfg.InlayHints.Designators = false;
|
|
Cfg.InlayHints.BlockEnd = false;
|
|
|
|
Cfg.InlayHints.DefaultArguments = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
|
|
const auto *Code = R"cpp(
|
|
struct Baz {
|
|
Baz(float a = 3 //
|
|
+ 2);
|
|
};
|
|
struct Foo {
|
|
Foo(int, Baz baz = //
|
|
Baz{$abbreviated[[}]]
|
|
|
|
//
|
|
) {}
|
|
};
|
|
|
|
int main() {
|
|
Foo foo1(1$paren[[)]];
|
|
Foo foo2{2$brace1[[}]];
|
|
Foo foo3 = {3$brace2[[}]];
|
|
auto foo4 = Foo{4$brace3[[}]];
|
|
}
|
|
)cpp";
|
|
|
|
assertHints(InlayHintKind::DefaultArgument, Code,
|
|
ExpectedHint{"...", "abbreviated", Left},
|
|
ExpectedHint{", Baz{}", "paren", Left},
|
|
ExpectedHint{", Baz{}", "brace1", Left},
|
|
ExpectedHint{", Baz{}", "brace2", Left},
|
|
ExpectedHint{", Baz{}", "brace3", Left});
|
|
|
|
assertHints(InlayHintKind::Parameter, Code);
|
|
}
|
|
|
|
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, CallingConvention) {
|
|
// Check that we don't crash for lambdas without a FunctionTypeLoc
|
|
// https://github.com/clangd/clangd/issues/2223
|
|
std::string Code = R"cpp(
|
|
void test() {
|
|
[]() __cdecl {};
|
|
}
|
|
)cpp";
|
|
TestTU TU = TestTU::withCode(Code);
|
|
TU.ExtraArgs.push_back("--target=x86_64-w64-mingw32");
|
|
TU.PredefineMacros = true; // for the __cdecl
|
|
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) {
|
|
llvm::StringRef Header = 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];
|
|
};
|
|
)cpp";
|
|
|
|
llvm::StringRef VectorIntPtr = R"cpp(
|
|
vector<int *> array;
|
|
auto $no_modifier[[x]] = 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();
|
|
)cpp";
|
|
|
|
assertHintsWithHeader(
|
|
InlayHintKind::Type, VectorIntPtr, Header,
|
|
ExpectedHint{": int *", "no_modifier"},
|
|
ExpectedHint{": int **", "ptr_modifier"},
|
|
ExpectedHint{": int *&", "ref_modifier"},
|
|
ExpectedHint{": int *const &", "at"}, ExpectedHint{": int **", "data"},
|
|
ExpectedHint{": allocator<int *>", "allocator"},
|
|
ExpectedHint{": size_type", "size"}, ExpectedHint{": iterator", "begin"},
|
|
ExpectedHint{": non_template_iterator", "end"});
|
|
|
|
llvm::StringRef VectorInt = R"cpp(
|
|
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();
|
|
)cpp";
|
|
|
|
assertHintsWithHeader(
|
|
InlayHintKind::Type, VectorInt, Header,
|
|
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"});
|
|
|
|
llvm::StringRef TypeAlias = R"cpp(
|
|
// 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";
|
|
|
|
assertHintsWithHeader(InlayHintKind::Type, TypeAlias, Header,
|
|
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, PseudoObjectExpr) {
|
|
Annotations Code(R"cpp(
|
|
struct S {
|
|
__declspec(property(get=GetX, put=PutX)) int x[];
|
|
int GetX(int y, int z) { return 42 + y; }
|
|
void PutX(int) { }
|
|
|
|
// This is a PseudoObjectExpression whose syntactic form is a binary
|
|
// operator.
|
|
void Work(int y) { x = y; } // Not `x = y: y`.
|
|
};
|
|
|
|
int printf(const char *Format, ...);
|
|
|
|
int main() {
|
|
S s;
|
|
__builtin_dump_struct(&s, printf); // Not `Format: __builtin_dump_struct()`
|
|
printf($Param[["Hello, %d"]], 42); // Normal calls are not affected.
|
|
// This builds a PseudoObjectExpr, but here it's useful for showing the
|
|
// arguments from the semantic form.
|
|
return s.x[ $one[[1]] ][ $two[[2]] ]; // `x[y: 1][z: 2]`
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Code.code());
|
|
TU.ExtraArgs.push_back("-fms-extensions");
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(inlayHints(AST, std::nullopt),
|
|
ElementsAre(HintMatcher(ExpectedHint{"Format: ", "Param"}, Code),
|
|
HintMatcher(ExpectedHint{"y: ", "one"}, Code),
|
|
HintMatcher(ExpectedHint{"z: ", "two"}, Code)));
|
|
}
|
|
|
|
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"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Functions) {
|
|
assertBlockEndHints(R"cpp(
|
|
int foo() {
|
|
return 41;
|
|
$foo[[}]]
|
|
|
|
template<int X>
|
|
int bar() {
|
|
// No hint for lambda for now
|
|
auto f = []() {
|
|
return X;
|
|
};
|
|
return f();
|
|
$bar[[}]]
|
|
|
|
// No hint because this isn't a definition
|
|
int buz();
|
|
|
|
struct S{};
|
|
bool operator==(S, S) {
|
|
return true;
|
|
$opEqual[[}]]
|
|
)cpp",
|
|
ExpectedHint{" // foo", "foo"},
|
|
ExpectedHint{" // bar", "bar"},
|
|
ExpectedHint{" // operator==", "opEqual"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Methods) {
|
|
assertBlockEndHints(R"cpp(
|
|
struct Test {
|
|
// No hint because there's no function body
|
|
Test() = default;
|
|
|
|
~Test() {
|
|
$dtor[[}]]
|
|
|
|
void method1() {
|
|
$method1[[}]]
|
|
|
|
// No hint because this isn't a definition
|
|
void method2();
|
|
|
|
template <typename T>
|
|
void method3() {
|
|
$method3[[}]]
|
|
|
|
// No hint because this isn't a definition
|
|
template <typename T>
|
|
void method4();
|
|
|
|
Test operator+(int) const {
|
|
return *this;
|
|
$opIdentity[[}]]
|
|
|
|
operator bool() const {
|
|
return true;
|
|
$opBool[[}]]
|
|
|
|
// No hint because there's no function body
|
|
operator int() const = delete;
|
|
} x;
|
|
|
|
void Test::method2() {
|
|
$method2[[}]]
|
|
|
|
template <typename T>
|
|
void Test::method4() {
|
|
$method4[[}]]
|
|
)cpp",
|
|
ExpectedHint{" // ~Test", "dtor"},
|
|
ExpectedHint{" // method1", "method1"},
|
|
ExpectedHint{" // method3", "method3"},
|
|
ExpectedHint{" // operator+", "opIdentity"},
|
|
ExpectedHint{" // operator bool", "opBool"},
|
|
ExpectedHint{" // Test::method2", "method2"},
|
|
ExpectedHint{" // Test::method4", "method4"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Namespaces) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
namespace {
|
|
void foo();
|
|
$anon[[}]]
|
|
|
|
namespace ns {
|
|
void bar();
|
|
$ns[[}]]
|
|
)cpp",
|
|
ExpectedHint{" // namespace", "anon"},
|
|
ExpectedHint{" // namespace ns", "ns"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Types) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
struct S {
|
|
$S[[};]]
|
|
|
|
class C {
|
|
$C[[};]]
|
|
|
|
union U {
|
|
$U[[};]]
|
|
|
|
enum E1 {
|
|
$E1[[};]]
|
|
|
|
enum class E2 {
|
|
$E2[[};]]
|
|
)cpp",
|
|
ExpectedHint{" // struct S", "S"}, ExpectedHint{" // class C", "C"},
|
|
ExpectedHint{" // union U", "U"}, ExpectedHint{" // enum E1", "E1"},
|
|
ExpectedHint{" // enum class E2", "E2"});
|
|
}
|
|
|
|
TEST(BlockEndHints, If) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
void foo(bool cond) {
|
|
if (cond)
|
|
;
|
|
|
|
if (cond) {
|
|
$simple[[}]]
|
|
|
|
if (cond) {
|
|
} else {
|
|
$ifelse[[}]]
|
|
|
|
if (cond) {
|
|
} else if (!cond) {
|
|
$elseif[[}]]
|
|
|
|
if (cond) {
|
|
} else {
|
|
if (!cond) {
|
|
$inner[[}]]
|
|
$outer[[}]]
|
|
|
|
if (auto X = cond) {
|
|
$init[[}]]
|
|
|
|
if (int i = 0; i > 10) {
|
|
$init_cond[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // if cond", "simple"},
|
|
ExpectedHint{" // if cond", "ifelse"}, ExpectedHint{" // if", "elseif"},
|
|
ExpectedHint{" // if !cond", "inner"},
|
|
ExpectedHint{" // if cond", "outer"}, ExpectedHint{" // if X", "init"},
|
|
ExpectedHint{" // if i > 10", "init_cond"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Loops) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
void foo() {
|
|
while (true)
|
|
;
|
|
|
|
while (true) {
|
|
$while[[}]]
|
|
|
|
do {
|
|
} while (true);
|
|
|
|
for (;true;) {
|
|
$forcond[[}]]
|
|
|
|
for (int I = 0; I < 10; ++I) {
|
|
$forvar[[}]]
|
|
|
|
int Vs[] = {1,2,3};
|
|
for (auto V : Vs) {
|
|
$foreach[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // while true", "while"},
|
|
ExpectedHint{" // for true", "forcond"},
|
|
ExpectedHint{" // for I", "forvar"},
|
|
ExpectedHint{" // for V", "foreach"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Switch) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
void foo(int I) {
|
|
switch (I) {
|
|
case 0: break;
|
|
$switch[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // switch I", "switch"});
|
|
}
|
|
|
|
TEST(BlockEndHints, PrintLiterals) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
void foo() {
|
|
while ("foo") {
|
|
$string[[}]]
|
|
|
|
while ("foo but this time it is very long") {
|
|
$string_long[[}]]
|
|
|
|
while (true) {
|
|
$boolean[[}]]
|
|
|
|
while (1) {
|
|
$integer[[}]]
|
|
|
|
while (1.5) {
|
|
$float[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // while \"foo\"", "string"},
|
|
ExpectedHint{" // while \"foo but...\"", "string_long"},
|
|
ExpectedHint{" // while true", "boolean"},
|
|
ExpectedHint{" // while 1", "integer"},
|
|
ExpectedHint{" // while 1.5", "float"});
|
|
}
|
|
|
|
TEST(BlockEndHints, PrintRefs) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
namespace ns {
|
|
int Var;
|
|
int func();
|
|
struct S {
|
|
int Field;
|
|
int method() const;
|
|
}; // suppress
|
|
} // suppress
|
|
void foo() {
|
|
while (ns::Var) {
|
|
$var[[}]]
|
|
|
|
while (ns::func()) {
|
|
$func[[}]]
|
|
|
|
while (ns::S{}.Field) {
|
|
$field[[}]]
|
|
|
|
while (ns::S{}.method()) {
|
|
$method[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // while Var", "var"},
|
|
ExpectedHint{" // while func", "func"},
|
|
ExpectedHint{" // while Field", "field"},
|
|
ExpectedHint{" // while method", "method"});
|
|
}
|
|
|
|
TEST(BlockEndHints, PrintConversions) {
|
|
assertBlockEndHints(
|
|
R"cpp(
|
|
struct S {
|
|
S(int);
|
|
S(int, int);
|
|
explicit operator bool();
|
|
}; // suppress
|
|
void foo(int I) {
|
|
while (float(I)) {
|
|
$convert_primitive[[}]]
|
|
|
|
while (S(I)) {
|
|
$convert_class[[}]]
|
|
|
|
while (S(I, I)) {
|
|
$construct_class[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // while float", "convert_primitive"},
|
|
ExpectedHint{" // while S", "convert_class"},
|
|
ExpectedHint{" // while S", "construct_class"});
|
|
}
|
|
|
|
TEST(BlockEndHints, PrintOperators) {
|
|
std::string AnnotatedCode = R"cpp(
|
|
void foo(Integer I) {
|
|
while(++I){
|
|
$preinc[[}]]
|
|
|
|
while(I++){
|
|
$postinc[[}]]
|
|
|
|
while(+(I + I)){
|
|
$unary_complex[[}]]
|
|
|
|
while(I < 0){
|
|
$compare[[}]]
|
|
|
|
while((I + I) < I){
|
|
$lhs_complex[[}]]
|
|
|
|
while(I < (I + I)){
|
|
$rhs_complex[[}]]
|
|
|
|
while((I + I) < (I + I)){
|
|
$binary_complex[[}]]
|
|
} // suppress
|
|
)cpp";
|
|
|
|
// We can't store shared expectations in a vector, assertHints uses varargs.
|
|
auto AssertExpectedHints = [&](llvm::StringRef Code) {
|
|
assertBlockEndHints(Code, ExpectedHint{" // while ++I", "preinc"},
|
|
ExpectedHint{" // while I++", "postinc"},
|
|
ExpectedHint{" // while", "unary_complex"},
|
|
ExpectedHint{" // while I < 0", "compare"},
|
|
ExpectedHint{" // while ... < I", "lhs_complex"},
|
|
ExpectedHint{" // while I < ...", "rhs_complex"},
|
|
ExpectedHint{" // while", "binary_complex"});
|
|
};
|
|
|
|
// First with built-in operators.
|
|
AssertExpectedHints("using Integer = int;" + AnnotatedCode);
|
|
// And now with overloading!
|
|
AssertExpectedHints(R"cpp(
|
|
struct Integer {
|
|
explicit operator bool();
|
|
Integer operator++();
|
|
Integer operator++(int);
|
|
Integer operator+(Integer);
|
|
Integer operator+();
|
|
bool operator<(Integer);
|
|
bool operator<(int);
|
|
}; // suppress
|
|
)cpp" + AnnotatedCode);
|
|
}
|
|
|
|
TEST(BlockEndHints, TrailingSemicolon) {
|
|
assertBlockEndHints(R"cpp(
|
|
// The hint is placed after the trailing ';'
|
|
struct S1 {
|
|
$S1[[} ;]]
|
|
|
|
// The hint is always placed in the same line with the closing '}'.
|
|
// So in this case where ';' is missing, it is attached to '}'.
|
|
struct S2 {
|
|
$S2[[}]]
|
|
|
|
;
|
|
|
|
// No hint because only one trailing ';' is allowed
|
|
struct S3 {
|
|
};;
|
|
|
|
// No hint because trailing ';' is only allowed for class/struct/union/enum
|
|
void foo() {
|
|
};
|
|
|
|
// Rare case, but yes we'll have a hint here.
|
|
struct {
|
|
int x;
|
|
$anon[[}]]
|
|
|
|
s2;
|
|
)cpp",
|
|
ExpectedHint{" // struct S1", "S1"},
|
|
ExpectedHint{" // struct S2", "S2"},
|
|
ExpectedHint{" // struct", "anon"});
|
|
}
|
|
|
|
TEST(BlockEndHints, TrailingText) {
|
|
assertBlockEndHints(R"cpp(
|
|
struct S1 {
|
|
$S1[[} ;]]
|
|
|
|
// No hint for S2 because of the trailing comment
|
|
struct S2 {
|
|
}; /* Put anything here */
|
|
|
|
struct S3 {
|
|
// No hint for S4 because of the trailing source code
|
|
struct S4 {
|
|
};$S3[[};]]
|
|
|
|
// No hint for ns because of the trailing comment
|
|
namespace ns {
|
|
} // namespace ns
|
|
)cpp",
|
|
ExpectedHint{" // struct S1", "S1"},
|
|
ExpectedHint{" // struct S3", "S3"});
|
|
}
|
|
|
|
TEST(BlockEndHints, Macro) {
|
|
assertBlockEndHints(R"cpp(
|
|
#define DECL_STRUCT(NAME) struct NAME {
|
|
#define RBRACE }
|
|
|
|
DECL_STRUCT(S1)
|
|
$S1[[};]]
|
|
|
|
// No hint because we require a '}'
|
|
DECL_STRUCT(S2)
|
|
RBRACE;
|
|
)cpp",
|
|
ExpectedHint{" // struct S1", "S1"});
|
|
}
|
|
|
|
TEST(BlockEndHints, PointerToMemberFunction) {
|
|
// Do not crash trying to summarize `a->*p`.
|
|
assertBlockEndHints(R"cpp(
|
|
class A {};
|
|
using Predicate = bool(A::*)();
|
|
void foo(A* a, Predicate p) {
|
|
if ((a->*p)()) {
|
|
$ptrmem[[}]]
|
|
} // suppress
|
|
)cpp",
|
|
ExpectedHint{" // if", "ptrmem"});
|
|
}
|
|
|
|
// 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
|