| //===-- 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" |
| #include <string> |
| #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; |
| 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(TypeHints, Deduplication) { |
| assertTypeHints(R"cpp( |
| template <typename T> |
| void foo() { |
| auto $var[[var]] = 42; |
| } |
| template void foo<int>(); |
| template void foo<float>(); |
| )cpp", |
| ExpectedHint{": int", "var"}); |
| } |
| |
| TEST(TypeHints, SinglyInstantiatedTemplate) { |
| assertTypeHints(R"cpp( |
| auto $lambda[[x]] = [](auto *$param[[y]], auto) { return 42; }; |
| int m = x("foo", 3); |
| )cpp", |
| ExpectedHint{": (lambda)", "lambda"}, |
| ExpectedHint{": const char *", "param"}); |
| |
| // No hint for packs, or auto params following packs |
| assertTypeHints(R"cpp( |
| int x(auto $a[[a]], auto... b, auto c) { return 42; } |
| int m = x<void*, char, float>(nullptr, 'c', 2.0, 2); |
| )cpp", |
| ExpectedHint{": void *", "a"}); |
| } |
| |
| TEST(TypeHints, Aliased) { |
| // Check that we don't crash for functions without a FunctionTypeLoc. |
| // https://github.com/clangd/clangd/issues/1140 |
| TestTU TU = TestTU::withCode("void foo(void){} extern typeof(foo) foo;"); |
| TU.ExtraArgs.push_back("-xc"); |
| auto AST = TU.build(); |
| |
| EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty()); |
| } |
| |
| TEST(TypeHints, Decltype) { |
| assertTypeHints(R"cpp( |
| $a[[decltype(0)]] a; |
| $b[[decltype(a)]] b; |
| const $c[[decltype(0)]] &c = b; |
| |
| // Don't show for dependent type |
| template <class T> |
| constexpr decltype(T{}) d; |
| |
| $e[[decltype(0)]] e(); |
| auto f() -> $f[[decltype(0)]]; |
| |
| template <class, class> struct Foo; |
| using G = Foo<$g[[decltype(0)]], float>; |
| |
| auto $h[[h]] = $i[[decltype(0)]]{}; |
| |
| // No crash |
| /* error-ok */ |
| auto $j[[s]]; |
| )cpp", |
| ExpectedHint{": int", "a"}, ExpectedHint{": int", "b"}, |
| ExpectedHint{": int", "c"}, ExpectedHint{": int", "e"}, |
| ExpectedHint{": int", "f"}, ExpectedHint{": int", "g"}, |
| ExpectedHint{": int", "h"}, ExpectedHint{": int", "i"}); |
| } |
| |
| TEST(TypeHints, SubstTemplateParameterAliases) { |
| 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 |