//===-- 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 hintsOfKind(ParsedAST &AST, InlayHintKind Kind) { std::vector 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 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 void ignore(T &&...) {} template void assertParameterHints(llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { ignore(Expected.Side = Left...); assertHints(InlayHintKind::Parameter, AnnotatedSource, Expected...); } template void assertTypeHints(llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { ignore(Expected.Side = Right...); assertHints(InlayHintKind::Type, AnnotatedSource, Expected...); } template 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 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 T&& forward(T&); } void foo(int); template void bar(Args&&... args) { return foo(std::forward(args)...); } void baz() { bar(42); } )cpp"); } TEST(ParameterHints, NoNameVariadicPlain) { // No hint for anonymous variadic parameter assertParameterHints(R"cpp( void foo(int); template 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 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 T&& forward(T&); } struct S { S(int a); }; template T bar(Args&&... args) { return T{std::forward(args)...}; } void baz() { int b; bar($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 T bar(Args&&... args) { return T{args...}; } void baz() { int b; bar($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 T&& forward(T&); } struct S { S(int a); }; template T* bar(Args&&... args) { return new T{std::forward(args)...}; } void baz() { int b; bar($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 T* bar(Args&&... args) { return new T{args...}; } void baz() { int b; bar($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 T&& forward(T&); } void foo(int a); template void bar(Args&&... args) { return foo(std::forward(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 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 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 T&& forward(T&); } void baz(int, int b, double); template void foo(int a, Args&&... args) { return baz(1, std::forward(args)..., 1.0); } template void bar(Args&&... args) { return foo(std::forward(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 void bar(Args... args) { foo(args...); } template <> void bar(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 void foo(Args... args) { foo2(args...); } template void bar(Args... args) { foo(args...); } template <> void foo(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 T&& forward(T&); } void baz(int b, int c); void baz(int bb, int cc, int dd); template void foo(int a, Args&&... args) { return baz(std::forward(args)...); } template void bar(Args&&... args) { return foo(std::forward(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 void foo(Head head, Tail... tail) { foo(tail...); } template 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 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 void foo(int fixed, Args&& ... args); template 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 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 void foo(Args...); template void bar(Args... args) { foo(args...); } template 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 void bar(int, Args... args, int d) { foo(args..., d); } template void baz(Args... args, Args... args2) { bar(1, args..., args2...); } int main() { baz($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 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 T* allocate(); template void construct(T* ptr, Args&&... args) { ::new ((void*)ptr) T{std::forward(args)...}; } }; template struct container { template void emplace(Args&&... args) { alloc a; auto ptr = a.template allocate(); a.construct(ptr, std::forward(args)...); } }; void foo() { container 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 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 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 T&& forward(T&); } void foo(int&); template void bar(Args&&... args) { return foo(std::forward(args)...); } void baz() { int a; bar($param[[a]]); } )cpp", ExpectedHint{"&: ", "param"}); } TEST(ParameterHints, VariadicNoReferenceHintForwardingRefStdForward) { assertParameterHints(R"cpp( namespace std { template T&& forward(T&); } void foo(int); template void bar(Args&&... args) { return foo(std::forward(args)...); } void baz() { int a; bar(a); bar(1); } )cpp"); } TEST(ParameterHints, VariadicNoReferenceHintUnresolvedForward) { assertParameterHints(R"cpp( template 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 T&& forward(T&); } void foo(int a); template void bar(Args&&... args) { return foo(std::forward(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 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 class initializer_list {}; } struct S { S(std::initializer_list 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 void nonmember(T par1); template struct A { void member(T par2); static void static_member(T par3); }; void overload(int anInt); void overload(double aDouble); template struct S { void bar(A a, T t) { nonmember($par1[[t]]); a.member($par2[[t]]); A::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 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 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 struct Inner {}; }; S2::Inner 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", "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 struct tuple_size {}; template <> struct tuple_size { constexpr static unsigned value = 2; }; template struct tuple_element {}; template struct tuple_element { using type = int; }; } template 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 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 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 struct A {}; struct MultipleWords {}; A 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 struct A {}; struct MultipleWords {}; A foo(); // Should have type hint with TypeNameLimit = 0 auto $var[[var]] = foo(); )cpp", ExpectedHint{": A", "var"}); } TEST(TypeHints, DefaultTemplateArgs) { assertTypeHints(R"cpp( template struct A {}; A foo(); auto $var[[var]] = foo(); )cpp", ExpectedHint{": A", "var"}); } TEST(TypeHints, Deduplication) { assertTypeHints(R"cpp( template void foo() { auto $var[[var]] = 42; } template void foo(); template void foo(); )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(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 constexpr decltype(T{}) d; $e[[decltype(0)]] e(); auto f() -> $f[[decltype(0)]]; template 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(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 struct Array { T __elements[N]; }; Array 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 void bar(Args... args) { foo(args...); } template void baz(Args... args) { foo($param1[[Foo{args...}]], $param2[[1]]); } template 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 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(...); // and other built-in casts // Annoyances for which a heuristic is not obvious: // - auto x = llvm::dyn_cast(y); // and similar // - stdlib algos return unwieldy __normal_iterator type // (For this one, perhaps we should omit type hints that start // with a double underscore.) } // namespace } // namespace clangd } // namespace clang