/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/functional/traits.h>

#include <folly/portability/GTest.h>

struct TraitsTest : testing::Test {
  template <typename... T>
  static constexpr bool same_v = std::is_same<T...>::value;

  template <typename S>
  using traits = folly::function_traits<S>;

  template <typename S>
  using result_type_t = typename traits<S>::result_type;

  template <typename R, typename S>
  static constexpr bool is_result_type_v =
      std::is_same<R, result_type_t<S>>::value;

  template <typename S>
  static constexpr bool is_nothrow_v = traits<S>::is_nothrow;

  template <size_t I, typename S>
  using argument_type_t = typename traits<S>::template argument<I>;

  template <typename R, size_t I, typename S>
  static constexpr bool is_argument_type_v =
      std::is_same<R, argument_type_t<I, S>>::value;

  template <typename... A>
  using size = folly::index_constant<sizeof...(A)>;

  template <typename S>
  static constexpr size_t arguments_size_v =
      traits<S>::template arguments<size>::value;

  template <typename T, typename S>
  using cvref_t = typename traits<S>::template value_like<T>;

  template <typename D, typename T, typename S>
  static constexpr bool is_cvref_t = std::is_same<D, cvref_t<T, S>>::value;

  template <typename F>
  using rem_cvref_t = folly::function_remove_cvref_t<F>;

  template <typename D, typename S, typename I>
  static constexpr bool is_like_v =
      same_v<D, folly::function_like_value_t<S, I>>;
};

TEST_F(TraitsTest, function_traits) {
  using vc = void const;

  //  result_type

  EXPECT_TRUE((is_result_type_v<vc, vc()>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const>));
  EXPECT_TRUE((is_result_type_v<vc, vc() volatile>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const volatile>));
  EXPECT_TRUE((is_result_type_v<vc, vc()&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() volatile&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const volatile&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() &&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const&&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() volatile&&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const volatile&&>));
  EXPECT_TRUE((is_result_type_v<vc, vc() noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() volatile noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const volatile noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc()& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() volatile& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const volatile& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc()&& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const&& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() volatile&& noexcept>));
  EXPECT_TRUE((is_result_type_v<vc, vc() const volatile&& noexcept>));

  //  is_nothrow

  EXPECT_FALSE((is_nothrow_v<void()>));
  EXPECT_FALSE((is_nothrow_v<void() const>));
  EXPECT_FALSE((is_nothrow_v<void() volatile>));
  EXPECT_FALSE((is_nothrow_v<void() const volatile>));
  EXPECT_FALSE((is_nothrow_v<void()&>));
  EXPECT_FALSE((is_nothrow_v<void() const&>));
  EXPECT_FALSE((is_nothrow_v<void() volatile&>));
  EXPECT_FALSE((is_nothrow_v<void() const volatile&>));
  EXPECT_FALSE((is_nothrow_v<void() &&>));
  EXPECT_FALSE((is_nothrow_v<void() const&&>));
  EXPECT_FALSE((is_nothrow_v<void() volatile&&>));
  EXPECT_FALSE((is_nothrow_v<void() const volatile&&>));
#if FOLLY_HAVE_NOEXCEPT_FUNCTION_TYPE
  EXPECT_TRUE((is_nothrow_v<void() noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() const noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() volatile noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() const volatile noexcept>));
  EXPECT_TRUE((is_nothrow_v<void()& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() const& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() volatile& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() const volatile& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void()&& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() const&& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() volatile&& noexcept>));
  EXPECT_TRUE((is_nothrow_v<void() const volatile&& noexcept>));
#endif

  //  argument

  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*)>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) volatile>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const volatile>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*)&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) volatile&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const volatile&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) &&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const&&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) volatile&&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const volatile&&>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) volatile noexcept>));
  EXPECT_TRUE(( //
      is_argument_type_v<vc*, 1, void(int, vc*) const volatile noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*)& noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const& noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) volatile& noexcept>));
  EXPECT_TRUE(( //
      is_argument_type_v<vc*, 1, void(int, vc*) const volatile& noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*)&& noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) const&& noexcept>));
  EXPECT_TRUE((is_argument_type_v<vc*, 1, void(int, vc*) volatile&& noexcept>));
  EXPECT_TRUE(( //
      is_argument_type_v<vc*, 1, void(int, vc*) const volatile&& noexcept>));

  //  arguments

  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float)>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) volatile>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const volatile>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float)&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) volatile&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const volatile&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) &&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const&&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) volatile&&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const volatile&&>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) volatile noexcept>));
  EXPECT_EQ(
      3, (arguments_size_v<void(int, vc*, float) const volatile noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float)& noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const& noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) volatile& noexcept>));
  EXPECT_EQ(
      3, (arguments_size_v<void(int, vc*, float) const volatile& noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float)&& noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) const&& noexcept>));
  EXPECT_EQ(3, (arguments_size_v<void(int, vc*, float) volatile&& noexcept>));
  EXPECT_EQ(
      3, (arguments_size_v<void(int, vc*, float) const volatile&& noexcept>));

  // value_like

  EXPECT_TRUE((is_cvref_t<int, int, void()>));
  EXPECT_TRUE((is_cvref_t<int const, int, void() const>));
  EXPECT_TRUE((is_cvref_t<int volatile, int, void() volatile>));
  EXPECT_TRUE((is_cvref_t<int const volatile, int, void() const volatile>));
  EXPECT_TRUE((is_cvref_t<int&, int, void()&>));
  EXPECT_TRUE((is_cvref_t<int const&, int, void() const&>));
  EXPECT_TRUE((is_cvref_t<int volatile&, int, void() volatile&>));
  EXPECT_TRUE((is_cvref_t<int const volatile&, int, void() const volatile&>));
  EXPECT_TRUE((is_cvref_t<int&&, int, void() &&>));
  EXPECT_TRUE((is_cvref_t<int const&&, int, void() const&&>));
  EXPECT_TRUE((is_cvref_t<int volatile&&, int, void() volatile&&>));
  EXPECT_TRUE((is_cvref_t<int const volatile&&, int, void() const volatile&&>));
  EXPECT_TRUE((is_cvref_t<int, int, void() noexcept>));
  EXPECT_TRUE((is_cvref_t<int const, int, void() const noexcept>));
  EXPECT_TRUE((is_cvref_t<int volatile, int, void() volatile noexcept>));
  EXPECT_TRUE(( //
      is_cvref_t<int const volatile, int, void() const volatile noexcept>));
  EXPECT_TRUE((is_cvref_t<int&, int, void()& noexcept>));
  EXPECT_TRUE((is_cvref_t<int const&, int, void() const& noexcept>));
  EXPECT_TRUE((is_cvref_t<int volatile&, int, void() volatile& noexcept>));
  EXPECT_TRUE(( //
      is_cvref_t<int const volatile&, int, void() const volatile& noexcept>));
  EXPECT_TRUE((is_cvref_t<int&&, int, void()&& noexcept>));
  EXPECT_TRUE((is_cvref_t<int const&&, int, void() const&& noexcept>));
  EXPECT_TRUE((is_cvref_t<int volatile&&, int, void() volatile&& noexcept>));
  EXPECT_TRUE((
      is_cvref_t<int const volatile&&, int, void() const volatile&& noexcept>));
}

TEST_F(TraitsTest, function_remove_cvref) {
  using f = char(int*);
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*)>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) const>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) volatile>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) const volatile>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*)&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) const&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) volatile&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) const volatile&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) &&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) const&&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) volatile&&>>));
  EXPECT_TRUE((same_v<f, rem_cvref_t<char(int*) const volatile&&>>));

  using g = char(int*) noexcept;
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) const noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) volatile noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) const volatile noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*)& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) const& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) volatile& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) const volatile& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*)&& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) const&& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) volatile&& noexcept>>));
  EXPECT_TRUE((same_v<g, rem_cvref_t<char(int*) const volatile&& noexcept>>));
}

TEST_F(TraitsTest, function_like_value) {
  using f = char(int*);
  EXPECT_TRUE((is_like_v<char(int*), int, f>));
  EXPECT_TRUE((is_like_v<char(int*) const, int const, f>));
  EXPECT_TRUE((is_like_v<char(int*) volatile, int volatile, f>));
  EXPECT_TRUE((is_like_v<char(int*) const volatile, int const volatile, f>));
  EXPECT_TRUE((is_like_v<char(int*)&, int&, f>));
  EXPECT_TRUE((is_like_v<char(int*) const&, int const&, f>));
  EXPECT_TRUE((is_like_v<char(int*) volatile&, int volatile&, f>));
  EXPECT_TRUE((is_like_v<char(int*) const volatile&, int const volatile&, f>));
  EXPECT_TRUE((is_like_v<char(int*)&&, int&&, f>));
  EXPECT_TRUE((is_like_v<char(int*) const&&, int const&&, f>));
  EXPECT_TRUE((is_like_v<char(int*) volatile&&, int volatile&&, f>));
  EXPECT_TRUE(( //
      is_like_v<char(int*) const volatile&&, int const volatile&&, f>));

  using g = char(int*) noexcept;
  EXPECT_TRUE((is_like_v<char(int*) noexcept, int, g>));
  EXPECT_TRUE((is_like_v<char(int*) const noexcept, int const, g>));
  EXPECT_TRUE((is_like_v<char(int*) volatile noexcept, int volatile, g>));
  EXPECT_TRUE(( //
      is_like_v<char(int*) const volatile noexcept, int const volatile, g>));
  EXPECT_TRUE((is_like_v<char(int*)& noexcept, int&, g>));
  EXPECT_TRUE((is_like_v<char(int*) const& noexcept, int const&, g>));
  EXPECT_TRUE((is_like_v<char(int*) volatile& noexcept, int volatile&, g>));
  EXPECT_TRUE(( //
      is_like_v<char(int*) const volatile& noexcept, int const volatile&, g>));
  EXPECT_TRUE((is_like_v<char(int*)&& noexcept, int&&, g>));
  EXPECT_TRUE((is_like_v<char(int*) const&& noexcept, int const&&, g>));
  EXPECT_TRUE((is_like_v<char(int*) volatile&& noexcept, int volatile&&, g>));
  EXPECT_TRUE(( //
      is_like_v<
          char(int*) const volatile&& noexcept,
          int const volatile&&,
          g>));
}
