//===-- InlayHintTests.cpp  -------------------------------*- C++ -*-------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Annotations.h"
#include "Config.h"
#include "InlayHints.h"
#include "Protocol.h"
#include "TestTU.h"
#include "TestWorkspace.h"
#include "XRefs.h"
#include "support/Context.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>

namespace clang {
namespace clangd {

llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
                              const InlayHint &Hint) {
  return Stream << Hint.joinLabels() << "@" << Hint.range;
}

namespace {

using ::testing::ElementsAre;
using ::testing::IsEmpty;

constexpr InlayHintOptions DefaultOptsForTests{2};

std::vector<InlayHint> hintsOfKind(ParsedAST &AST, InlayHintKind Kind,
                                   InlayHintOptions Opts) {
  std::vector<InlayHint> Result;
  for (auto &Hint : inlayHints(AST, /*RestrictRange=*/std::nullopt, Opts)) {
    if (Hint.kind == Kind)
      Result.push_back(Hint);
  }
  return Result;
}

enum HintSide { Left, Right };

struct ExpectedHint {
  std::string Label;
  std::string RangeName;
  HintSide Side = Left;

  friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
                                       const ExpectedHint &Hint) {
    return Stream << Hint.Label << "@$" << Hint.RangeName;
  }
};

MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
  llvm::StringRef ExpectedView(Expected.Label);
  std::string ResultLabel = arg.joinLabels();
  if (ResultLabel != ExpectedView.trim(" ") ||
      arg.paddingLeft != ExpectedView.starts_with(" ") ||
      arg.paddingRight != ExpectedView.ends_with(" ")) {
    *result_listener << "label is '" << ResultLabel << "'";
    return false;
  }
  if (arg.range != Code.range(Expected.RangeName)) {
    *result_listener << "range is " << llvm::to_string(arg.range) << " but $"
                     << Expected.RangeName << " is "
                     << llvm::to_string(Code.range(Expected.RangeName));
    return false;
  }
  return true;
}

MATCHER_P(labelIs, Label, "") { return arg.joinLabels() == Label; }

Config noHintsConfig() {
  Config C;
  C.InlayHints.Parameters = false;
  C.InlayHints.DeducedTypes = false;
  C.InlayHints.Designators = false;
  C.InlayHints.BlockEnd = false;
  C.InlayHints.DefaultArguments = false;
  return C;
}

template <typename... ExpectedHints>
void assertHintsWithHeader(InlayHintKind Kind, llvm::StringRef AnnotatedSource,
                           llvm::StringRef HeaderContent, InlayHintOptions Opts,
                           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, Opts),
              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, Opts), IsEmpty());
}

template <typename... ExpectedHints>
void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource,
                 InlayHintOptions Opts, ExpectedHints... Expected) {
  return assertHintsWithHeader(Kind, AnnotatedSource, "", Opts,
                               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, DefaultOptsForTests,
              Expected...);
}

template <typename... ExpectedHints>
void assertTypeHints(llvm::StringRef AnnotatedSource,
                     ExpectedHints... Expected) {
  ignore(Expected.Side = Right...);
  assertHints(InlayHintKind::Type, AnnotatedSource, DefaultOptsForTests,
              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, DefaultOptsForTests,
              Expected...);
}

template <typename... ExpectedHints>
void assertBlockEndHintsWithOpts(llvm::StringRef AnnotatedSource,
                                 InlayHintOptions Opts,
                                 ExpectedHints... Expected) {
  Config Cfg;
  Cfg.InlayHints.BlockEnd = true;
  WithContextValue WithCfg(Config::Key, std::move(Cfg));
  assertHints(InlayHintKind::BlockEnd, AnnotatedSource, Opts, Expected...);
}

template <typename... ExpectedHints>
void assertBlockEndHints(llvm::StringRef AnnotatedSource,
                         ExpectedHints... Expected) {
  assertBlockEndHintsWithOpts(AnnotatedSource, DefaultOptsForTests,
                              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{"&param: ", "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{"&param: ", "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, DependentDeducingThis) {
  assertParameterHints(R"cpp(
   template <typename T>
   struct S {
      void f1(this S& obj);
      void f2(this S& obj, int x, int y);
      void g(S s) {
        s.f1();  // no crash
        s.f2($x[[42]], $y[[43]]);
      }
    };
  )cpp",
                       ExpectedHint{"x: ", "x"}, ExpectedHint{"y: ", "y"});
}

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;
    __attribute__((noreturn)) f4_t f5;
    void bar() {
      f1($f1[[42]]);
      f2($f2[[42]]);
      f3($f3[[42]]);
      f4($f4[[42]]);
      // This one runs into an edge case in clang's type model
      // and we can't extract the parameter name. But at least
      // we shouldn't crash.
      f5(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, 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, DefaultOptsForTests).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"}, ExpectedHint{": 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",
                  ExpectedHint{": T", "var2"});
}

TEST(TypeHints, LongTypeName) {
  assertTypeHints(R"cpp(
    template <typename, typename, typename>
    struct A {};
    struct MultipleWords {};
    A<MultipleWords, MultipleWords, MultipleWords> foo();
    // Omit type hint past a certain length (currently 32)
    auto var = foo();
  )cpp");

  Config Cfg;
  Cfg.InlayHints.TypeNameLimit = 0;
  WithContextValue WithCfg(Config::Key, std::move(Cfg));

  assertTypeHints(
      R"cpp(
    template <typename, typename, typename>
    struct A {};
    struct MultipleWords {};
    A<MultipleWords, MultipleWords, MultipleWords> foo();
    // Should have type hint with TypeNameLimit = 0
    auto $var[[var]] = foo();
  )cpp",
      ExpectedHint{": A<MultipleWords, MultipleWords, MultipleWords>", "var"});
}

TEST(TypeHints, DefaultTemplateArgs) {
  assertTypeHints(R"cpp(
    template <typename, typename = int>
    struct A {};
    A<float> foo();
    auto $var[[var]] = foo();
    A<float> bar[1];
    auto [$binding[[value]]] = bar;
  )cpp",
                  ExpectedHint{": A<float>", "var"},
                  ExpectedHint{": A<float>", "binding"});
}

TEST(DefaultArguments, Smoke) {
  Config Cfg;
  Cfg.InlayHints.Parameters =
      true; // To test interplay of parameters and default parameters
  Cfg.InlayHints.DeducedTypes = false;
  Cfg.InlayHints.Designators = false;
  Cfg.InlayHints.BlockEnd = false;

  Cfg.InlayHints.DefaultArguments = true;
  WithContextValue WithCfg(Config::Key, std::move(Cfg));

  const auto *Code = R"cpp(
    int foo(int A = 4) { return A; }
    int bar(int A, int B = 1, bool C = foo($default1[[)]]) { return A; }
    int A = bar($explicit[[2]]$default2[[)]];

    void baz(int = 5) { if (false) baz($unnamed[[)]]; };
  )cpp";

  assertHints(InlayHintKind::DefaultArgument, Code, DefaultOptsForTests,
              ExpectedHint{"A: 4", "default1", Left},
              ExpectedHint{", B: 1, C: foo()", "default2", Left},
              ExpectedHint{"5", "unnamed", Left});

  assertHints(InlayHintKind::Parameter, Code, DefaultOptsForTests,
              ExpectedHint{"A: ", "explicit", Left});
}

TEST(DefaultArguments, WithoutParameterNames) {
  Config Cfg;
  Cfg.InlayHints.Parameters = false; // To test just default args this time
  Cfg.InlayHints.DeducedTypes = false;
  Cfg.InlayHints.Designators = false;
  Cfg.InlayHints.BlockEnd = false;

  Cfg.InlayHints.DefaultArguments = true;
  WithContextValue WithCfg(Config::Key, std::move(Cfg));

  const auto *Code = R"cpp(
    struct Baz {
      Baz(float a = 3 //
                    + 2);
    };
    struct Foo {
      Foo(int, Baz baz = //
              Baz{$abbreviated[[}]]

          //
      ) {}
    };

    int main() {
      Foo foo1(1$paren[[)]];
      Foo foo2{2$brace1[[}]];
      Foo foo3 = {3$brace2[[}]];
      auto foo4 = Foo{4$brace3[[}]];
    }
  )cpp";

  assertHints(InlayHintKind::DefaultArgument, Code, DefaultOptsForTests,
              ExpectedHint{"...", "abbreviated", Left},
              ExpectedHint{", Baz{}", "paren", Left},
              ExpectedHint{", Baz{}", "brace1", Left},
              ExpectedHint{", Baz{}", "brace2", Left},
              ExpectedHint{", Baz{}", "brace3", Left});

  assertHints(InlayHintKind::Parameter, Code, DefaultOptsForTests);
}

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, DefaultOptsForTests),
              IsEmpty());
}

TEST(TypeHints, CallingConvention) {
  // Check that we don't crash for lambdas with an annotation
  // https://github.com/clangd/clangd/issues/2223
  Annotations Source(R"cpp(
    void test() {
      []($lambda[[)]]__cdecl {};
    }
  )cpp");
  TestTU TU = TestTU::withCode(Source.code());
  TU.ExtraArgs.push_back("--target=x86_64-w64-mingw32");
  TU.PredefineMacros = true; // for the __cdecl
  auto AST = TU.build();

  EXPECT_THAT(
      hintsOfKind(AST, InlayHintKind::Type, DefaultOptsForTests),
      ElementsAre(HintMatcher(ExpectedHint{"-> void", "lambda"}, Source)));
}

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, DefaultOptsForTests,
      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, DefaultOptsForTests,
      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,
                        DefaultOptsForTests,
                        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(DesignatorHints, ParenInit) {
  assertDesignatorHints(R"cpp(
    struct S { 
      int x;
      int y;
      int z; 
    };
    S s ($x[[1]], $y[[2+2]], $z[[4]]);
  )cpp",
                        ExpectedHint{".x=", "x"}, ExpectedHint{".y=", "y"},
                        ExpectedHint{".z=", "z"});
}

TEST(DesignatorHints, ParenInitDerived) {
  assertDesignatorHints(R"cpp(
    struct S1 {
      int a;
      int b;
    };

    struct S2 : S1 { 
      int c;
      int d; 
    };
    S2 s2 ({$a[[0]], $b[[0]]}, $c[[0]], $d[[0]]);
  )cpp",
                        // ExpectedHint{"S1:", "S1"},
                        ExpectedHint{".a=", "a"}, ExpectedHint{".b=", "b"},
                        ExpectedHint{".c=", "c"}, ExpectedHint{".d=", "d"});
}

TEST(DesignatorHints, ParenInitTemplate) {
  assertDesignatorHints(R"cpp(
    template <typename T>
    struct S1 {
      int a;
      int b;
      T* ptr;
    };

    struct S2 : S1<S2> {
      int c;
      int d;
      S1<int> mem;
    };

    int main() {
      S2 sa ({$a1[[0]], $b1[[0]]}, $c[[0]], $d[[0]], $mem[[S1<int>($a2[[1]], $b2[[2]], $ptr[[nullptr]])]]);
    }
  )cpp",
                        ExpectedHint{".a=", "a1"}, ExpectedHint{".b=", "b1"},
                        ExpectedHint{".c=", "c"}, ExpectedHint{".d=", "d"},
                        ExpectedHint{".mem=", "mem"}, ExpectedHint{".a=", "a2"},
                        ExpectedHint{".b=", "b2"},
                        ExpectedHint{".ptr=", "ptr"});
}

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) {
       void* ptr;
       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[[}]]

       if (ptr != nullptr) {
       $null_check[[}]]
    } // 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"},
      ExpectedHint{" // if ptr != nullptr", "null_check"});
}

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 func1();
      int func2(int, int);
      struct S {
        int Field;
        int method1() const;
        int method2(int, int) const;
      }; // suppress
    } // suppress
    void foo() {
      int int_a {};
      while (ns::Var) {
      $var[[}]]

      while (ns::func1()) {
      $func1[[}]]

      while (ns::func2(int_a, int_a)) {
      $func2[[}]]

      while (ns::S{}.Field) {
      $field[[}]]

      while (ns::S{}.method1()) {
      $method1[[}]]
      
      while (ns::S{}.method2(int_a, int_a)) {
      $method2[[}]]
    } // suppress
  )cpp",
      ExpectedHint{" // while Var", "var"},
      ExpectedHint{" // while func1()", "func1"},
      ExpectedHint{" // while func2(...)", "func2"},
      ExpectedHint{" // while Field", "field"},
      ExpectedHint{" // while method1()", "method1"},
      ExpectedHint{" // while method2(...)", "method2"});
}

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"});
}

TEST(BlockEndHints, MinLineLimit) {
  InlayHintOptions Opts;
  Opts.HintMinLineLimit = 10;

  // namespace ns below is exactly 10 lines
  assertBlockEndHintsWithOpts(
      R"cpp(
    namespace ns {
      int Var;
      int func1();
      int func2(int, int);
      struct S {
        int Field;
        int method1() const;
        int method2(int, int) const;
      };
    $namespace[[}]]
    void foo() {
      int int_a {};
      while (ns::Var) {
      }

      while (ns::func1()) {
      }

      while (ns::func2(int_a, int_a)) {
      }

      while (ns::S{}.Field) {
      }

      while (ns::S{}.method1()) {
      }
      
      while (ns::S{}.method2(int_a, int_a)) {
      }
    $foo[[}]]
  )cpp",
      Opts, ExpectedHint{" // namespace ns", "namespace"},
      ExpectedHint{" // foo", "foo"});
}

// 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
