/*
 * 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/Poly.h>

#include <array>

#include <folly/Conv.h>
#include <folly/CppAttributes.h>
#include <folly/poly/Nullable.h>
#include <folly/poly/Regular.h>
#include <folly/portability/GTest.h>

using namespace folly;
using namespace folly::poly;

namespace {
template <class T>
struct Big_t {
 private:
  std::array<char, sizeof(Poly<ISemiRegular>) + 1> data_;
  T t_;

 public:
  Big_t() : data_{}, t_() { ++s_count; }
  explicit Big_t(T t) : data_{}, t_(t) { ++s_count; }
  Big_t(Big_t const& that) : data_(that.data_), t_(that.t_) { ++s_count; }
  ~Big_t() { --s_count; }
  Big_t& operator=(Big_t const&) = default;
  T value() const { return t_; }
  friend bool operator==(Big_t const& a, Big_t const& b) {
    return a.value() == b.value();
  }
  friend bool operator!=(Big_t const& a, Big_t const& b) { return !(a == b); }
  friend bool operator<(Big_t const& a, Big_t const& b) {
    return a.value() < b.value();
  }
  static std::ptrdiff_t s_count;
};
template <class T>
std::ptrdiff_t Big_t<T>::s_count = 0;

using Big = Big_t<int>;
using BigDouble = Big_t<double>;
} // namespace

TEST(Poly, SemiRegular) {
  {
    // A small object, storable in-situ:
    Poly<ISemiRegular> p = 42;
    EXPECT_EQ(typeid(int), poly_type(p));
    EXPECT_EQ(42, poly_cast<int>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    Poly<ISemiRegular> p2 = p;
    EXPECT_EQ(typeid(int), poly_type(p2));
    EXPECT_EQ(42, poly_cast<int>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  }

  EXPECT_EQ(0, Big::s_count);
  {
    // A big object, stored on the heap:
    Poly<ISemiRegular> p = Big(42);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p));
    EXPECT_EQ(42, poly_cast<Big>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    Poly<ISemiRegular> p2 = p;
    EXPECT_EQ(2, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p2));
    EXPECT_EQ(42, poly_cast<Big>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  }
  EXPECT_EQ(0, Big::s_count);

  // four swap cases
  //

  {
    // A small object, storable in-situ:
    Poly<ISemiRegular> p = 42;
    EXPECT_EQ(typeid(int), poly_type(p));
    EXPECT_EQ(42, poly_cast<int>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    // A small object, storable in-situ:
    Poly<ISemiRegular> p2 = 4.2;
    EXPECT_EQ(typeid(double), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<double>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    std::swap(p, p2);
    EXPECT_EQ(typeid(double), poly_type(p));
    EXPECT_EQ(4.2, poly_cast<double>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(int), poly_type(p2));
    EXPECT_EQ(42, poly_cast<int>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    using std::swap;
    swap(p, p2);
    EXPECT_EQ(typeid(int), poly_type(p));
    EXPECT_EQ(42, poly_cast<int>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(double), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<double>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  }

  EXPECT_EQ(0, Big::s_count);
  EXPECT_EQ(0, BigDouble::s_count);
  {
    // A big object, stored on the heap:
    Poly<ISemiRegular> p = Big(42);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p));
    EXPECT_EQ(42, poly_cast<Big>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    // A big object, stored on the heap:
    Poly<ISemiRegular> p2 = BigDouble(4.2);
    EXPECT_EQ(1, BigDouble::s_count);
    EXPECT_EQ(typeid(BigDouble), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<BigDouble>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    std::swap(p, p2);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(1, BigDouble::s_count);
    EXPECT_EQ(typeid(BigDouble), poly_type(p));
    EXPECT_EQ(4.2, poly_cast<BigDouble>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(Big), poly_type(p2));
    EXPECT_EQ(42, poly_cast<Big>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    using std::swap;
    swap(p, p2);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(1, BigDouble::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p));
    EXPECT_EQ(42, poly_cast<Big>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(BigDouble), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<BigDouble>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  }
  EXPECT_EQ(0, BigDouble::s_count);
  EXPECT_EQ(0, Big::s_count);

  EXPECT_EQ(0, Big::s_count);
  {
    // A big object, stored on the heap:
    Poly<ISemiRegular> p = Big(42);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p));
    EXPECT_EQ(42, poly_cast<Big>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    // A small object, storable in-situ:
    Poly<ISemiRegular> p2 = 4.2;
    EXPECT_EQ(typeid(double), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<double>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    std::swap(p, p2);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(typeid(double), poly_type(p));
    EXPECT_EQ(4.2, poly_cast<double>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(Big), poly_type(p2));
    EXPECT_EQ(42, poly_cast<Big>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    using std::swap;
    swap(p, p2);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p));
    EXPECT_EQ(42, poly_cast<Big>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(double), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<double>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  }
  EXPECT_EQ(0, Big::s_count);

  EXPECT_EQ(0, BigDouble::s_count);
  {
    // A small object, storable in-situ:
    Poly<ISemiRegular> p = 42;
    EXPECT_EQ(typeid(int), poly_type(p));
    EXPECT_EQ(42, poly_cast<int>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    // A big object, stored on the heap:
    Poly<ISemiRegular> p2 = BigDouble(4.2);
    EXPECT_EQ(1, BigDouble::s_count);
    EXPECT_EQ(typeid(BigDouble), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<BigDouble>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    std::swap(p, p2);
    EXPECT_EQ(1, BigDouble::s_count);
    EXPECT_EQ(typeid(BigDouble), poly_type(p));
    EXPECT_EQ(4.2, poly_cast<BigDouble>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(int), poly_type(p2));
    EXPECT_EQ(42, poly_cast<int>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    using std::swap;
    swap(p, p2);
    EXPECT_EQ(1, BigDouble::s_count);
    EXPECT_EQ(typeid(int), poly_type(p));
    EXPECT_EQ(42, poly_cast<int>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    EXPECT_EQ(typeid(BigDouble), poly_type(p2));
    EXPECT_EQ(4.2, poly_cast<BigDouble>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  }
  EXPECT_EQ(0, BigDouble::s_count);
}

TEST(Poly, EqualityComparable) {
  {
    Poly<IEqualityComparable> p = 42;
    Poly<IEqualityComparable> q = 42;
    EXPECT_TRUE(p == q);
    EXPECT_TRUE(q == p);
    EXPECT_FALSE(p != q);
    EXPECT_FALSE(q != p);
    p = 43;
    EXPECT_FALSE(p == q);
    EXPECT_FALSE(q == p);
    EXPECT_TRUE(p != q);
    EXPECT_TRUE(q != p);
  }
  {
    // empty not equal
    Poly<IEqualityComparable> p;
    Poly<IEqualityComparable> q = 42;
    EXPECT_FALSE(p == q);
    EXPECT_FALSE(q == p);
  }
  {
    // empty equal
    Poly<IEqualityComparable> p;
    Poly<IEqualityComparable> q;
    EXPECT_TRUE(p == q);
    EXPECT_TRUE(q == p);
  }
  {
    // mismatched types throws
    Poly<IEqualityComparable> p = 4.2;
    Poly<IEqualityComparable> q = 42;
    EXPECT_THROW((void)(q == p), BadPolyCast);
  }
}

TEST(Poly, StrictlyOrderable) {
  {
    // A small object, storable in-situ:
    Poly<IStrictlyOrderable> p = 42;
    Poly<IStrictlyOrderable> q = 43;
    EXPECT_TRUE(p < q);
    EXPECT_TRUE(p <= q);
    EXPECT_FALSE(p > q);
    EXPECT_FALSE(p >= q);
    EXPECT_TRUE(q > p);
    EXPECT_TRUE(q >= p);
    EXPECT_FALSE(q < p);
    EXPECT_FALSE(q <= p);
  }
  {
    // A big object, stored on the heap:
    Poly<IStrictlyOrderable> p = Big(42);
    Poly<IStrictlyOrderable> q = Big(43);
    EXPECT_TRUE(p < q);
  }
  {
    // if equal, no one is bigger
    Poly<IStrictlyOrderable> p = 42;
    Poly<IStrictlyOrderable> q = 42;
    EXPECT_FALSE(p < q);
    EXPECT_TRUE(p <= q);
    EXPECT_FALSE(p > q);
    EXPECT_TRUE(p >= q);
    EXPECT_FALSE(q < p);
    EXPECT_TRUE(q <= p);
    EXPECT_FALSE(q > p);
    EXPECT_TRUE(q >= p);
  }
  {
    // empty is always smaller
    Poly<IStrictlyOrderable> p;
    Poly<IStrictlyOrderable> q = 42;
    EXPECT_TRUE(p < q);
    EXPECT_FALSE(q < p);
  }
  {
    // mismatched types throws
    Poly<IStrictlyOrderable> p = 4.2;
    Poly<IStrictlyOrderable> q = 42;
    EXPECT_THROW((void)(p < q), BadPolyCast);
    EXPECT_THROW((void)(q < p), BadPolyCast);
  }
}

TEST(Poly, SemiRegularReference) {
  int i = 42;
  Poly<ISemiRegular&> p = i;
  EXPECT_EQ(42, i);
  EXPECT_EQ(typeid(int), poly_type(p));
  EXPECT_EQ(42, poly_cast<int>(p));
  EXPECT_EQ(&i, &poly_cast<int>(p));
  EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
  Poly<ISemiRegular&> p2 = p;
  EXPECT_EQ(typeid(int), poly_type(p2));
  EXPECT_EQ(42, poly_cast<int>(p2));
  EXPECT_EQ(&i, &poly_cast<int>(p2));
  EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  std::swap(p, p2);
  EXPECT_EQ(typeid(int), poly_type(p2));
  EXPECT_EQ(42, poly_cast<int>(p2));
  EXPECT_EQ(&i, &poly_cast<int>(p2));
  EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  using std::swap;
  swap(p, p2);
  EXPECT_EQ(typeid(int), poly_type(p2));
  EXPECT_EQ(42, poly_cast<int>(p2));
  EXPECT_EQ(&i, &poly_cast<int>(p2));
  EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
  // Can't default-initialize reference-like Poly's:
  static_assert(!std::is_default_constructible<Poly<ISemiRegular&>>::value, "");
}

TEST(Poly, Conversions) {
  int i = 42;
  Poly<ISemiRegular> p1 = i;
  Poly<ISemiRegular&> p2 = p1;
  EXPECT_EQ(&poly_cast<int>(p1), &poly_cast<int>(p2));
  Poly<ISemiRegular const&> p3 = p1;
  Poly<ISemiRegular const&> p4 = p2;
  EXPECT_EQ(&poly_cast<int>(p3), &poly_cast<int>(p1));
  EXPECT_EQ(&poly_cast<int>(p4), &poly_cast<int>(p1));
  static_assert(
      !std::is_constructible<Poly<ISemiRegular&>, Poly<ISemiRegular const&>&>::
          value,
      "");
  static_assert(
      !std::is_constructible<Poly<ISemiRegular>, Poly<ISemiRegular const&>&>::
          value,
      "");
}

TEST(Poly, EqualityComparableReference) {
  int i = 42;
  int j = 42;
  Poly<IEqualityComparable&> p1 = i;
  Poly<IEqualityComparable&> p2 = j;
  EXPECT_EQ(&i, &poly_cast<int>(p1));
  EXPECT_EQ(&j, &poly_cast<int>(p2));
  EXPECT_TRUE(p1 == p2);
  EXPECT_FALSE(p1 != p2);
  j = 43;
  EXPECT_FALSE(p1 == p2);
  EXPECT_TRUE(p1 != p2);
  EXPECT_EQ(42, poly_cast<int>(p1));
  EXPECT_EQ(43, poly_cast<int>(p2));
}

namespace {
struct Foo {
  template <class Base>
  struct Interface : Base {
    void foo(int& i) { folly::poly_call<0>(*this, i); }
  };

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&T::foo);
};

struct foo_ {
  foo_() = default;
  explicit foo_(int i) : j_(i) {}
  void foo(int& i) { i += j_; }

 private:
  int j_ = 0;
};
} // namespace

TEST(Poly, Singular) {
  Poly<Foo> p = foo_{42};
  int i = 1;
  p.foo(i);
  EXPECT_EQ(43, i);
  EXPECT_EQ(typeid(foo_), poly_type(p));
}

namespace {
struct FooBar : PolyExtends<Foo> {
  template <class Base>
  struct Interface : Base {
    std::string bar(int i) const { return folly::poly_call<0>(*this, i); }
  };

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&T::bar);
};

struct foo_bar {
  foo_bar() = default;
  explicit foo_bar(int i) : j_(i) {}
  void foo(int& i) { i += j_; }
  std::string bar(int i) const {
    i += j_;
    return folly::to<std::string>(i);
  }

 private:
  int j_ = 0;
};
} // namespace

TEST(Poly, SingleInheritance) {
  Poly<FooBar> p = foo_bar{42};
  int i = 1;
  p.foo(i);
  EXPECT_EQ(43, i);
  EXPECT_EQ("43", p.bar(1));
  EXPECT_EQ(typeid(foo_bar), poly_type(p));

  Poly<Foo> q = p; // OK, conversion works.
  q.foo(i);
  EXPECT_EQ(85, i);

  Poly<Foo&> r = p;
  r->foo(i);
  EXPECT_EQ(127, i);
  const_cast<Poly<Foo&> const&>(r)->foo(i);
  EXPECT_EQ(169, i);

  Poly<FooBar const&> cr = p;
  // cr->foo(i); // ERROR: calls a non-const member through a const reference
  cr->bar(i); // OK
}

namespace {
struct Baz {
  template <class Base>
  struct Interface : Base {
    std::string baz(int i, int j) const {
      return folly::poly_call<0>(*this, i, j);
    }
  };

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&T::baz);
};

struct FooBarBazFizz : PolyExtends<FooBar, Baz> {
  template <class Base>
  struct Interface : Base {
    std::string fizz() const { return folly::poly_call<0>(*this); }
  };

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&T::fizz);
};

struct foo_bar_baz_fizz {
  foo_bar_baz_fizz() = default;
  explicit foo_bar_baz_fizz(int i) : j_(i) {}
  void foo(int& i) { i += j_; }
  std::string bar(int i) const { return folly::to<std::string>(i + j_); }
  std::string baz(int i, int j) const { return folly::to<std::string>(i + j); }
  std::string fizz() const { return "fizz"; }

 private:
  int j_ = 0;
};
} // namespace

TEST(Poly, MultipleInheritance) {
  Poly<FooBarBazFizz> p = foo_bar_baz_fizz{42};
  int i = 1;
  p.foo(i);
  EXPECT_EQ(43, i);
  EXPECT_EQ("43", p.bar(1));
  EXPECT_EQ("3", p.baz(1, 2));
  EXPECT_EQ("fizz", p.fizz());
  EXPECT_EQ(typeid(foo_bar_baz_fizz), poly_type(p));
}

namespace {
struct Property {
  template <class Base>
  struct Interface : Base {
    int prop() const { return folly::poly_call<0>(*this); }
    void prop(int i) { folly::poly_call<1>(*this, i); }
  };

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(
      FOLLY_POLY_MEMBER(int() const, &T::prop),
      FOLLY_POLY_MEMBER(void(int), &T::prop));
};

struct has_property {
  has_property() = default;
  explicit has_property(int i) : j(i) {}
  int prop() const { return j; }
  void prop(int i) { j = i; }

 private:
  int j = 0;
};
} // namespace

TEST(Poly, OverloadedMembers) {
  Poly<Property> p = has_property{42};
  EXPECT_EQ(typeid(has_property), poly_type(p));
  EXPECT_EQ(42, p.prop());
  p.prop(68);
  EXPECT_EQ(68, p.prop());
}

TEST(Poly, NullablePointer) {
  Poly<INullablePointer> p = nullptr;
  Poly<INullablePointer> q{};
  EXPECT_EQ(typeid(void), poly_type(p));
  EXPECT_TRUE(poly_empty(p));
  EXPECT_TRUE(p == q);
  EXPECT_FALSE(p != q);
  EXPECT_TRUE(p == nullptr);
  EXPECT_TRUE(nullptr == p);
  EXPECT_FALSE(p != nullptr);
  EXPECT_FALSE(nullptr != p);

  // No null references ever.
  Poly<INullablePointer> r = 42;
  Poly<INullablePointer&> s = r;
  static_assert(!poly_empty(s), "");
  EXPECT_THROW(Poly<INullablePointer&> r_(q), BadPolyAccess);
}

namespace {
struct MoveOnly_ {
  MoveOnly_() = default;
  MoveOnly_(MoveOnly_&&) = default;
  MoveOnly_(MoveOnly_ const&) = delete;
  MoveOnly_& operator=(MoveOnly_&&) = default;
  MoveOnly_& operator=(MoveOnly_ const&) = delete;
};
} // namespace

TEST(Poly, Move) {
  {
    int i = 42;
    Poly<IMoveOnly&> p = i;
    static_assert(
        !std::is_convertible<Poly<IMoveOnly&>, Poly<IMoveOnly&&>>::value, "");
    auto q = poly_move(p);
    static_assert(std::is_same<decltype(q), Poly<IMoveOnly&&>>::value, "");
    EXPECT_EQ(&poly_cast<int>(p), &poly_cast<int>(q));
  }
  {
    int i = 42;
    Poly<ISemiRegular const&> p = i;
    auto q = poly_move(p);
    static_assert(
        std::is_same<decltype(q), Poly<ISemiRegular const&>>::value, "");
    EXPECT_EQ(&poly_cast<int>(p), &poly_cast<int>(q));
  }
  {
    Poly<IMoveOnly> p = MoveOnly_{};
    static_assert(!std::is_copy_constructible<Poly<IMoveOnly>>::value, "");
    auto q = poly_move(p);
    static_assert(std::is_same<decltype(q), Poly<IMoveOnly>>::value, "");
  }
}

TEST(Poly, RValueRef) {
  int i = 42;
  Poly<ISemiRegular&&> p = std::move(i);
  static_assert(std::is_same<decltype(poly_cast<int>(p)), int&>::value, "");
  EXPECT_EQ(&i, &poly_cast<int>(p));
}

namespace {
template <class Fun>
struct IFunction;

template <class R, class... As>
struct IFunction<R(As...)> {
  template <class Base>
  struct Interface : Base {
    R operator()(As... as) const {
      return static_cast<R>(
          folly::poly_call<0>(*this, std::forward<As>(as)...));
    }
  };

  template <class T>
  using Members =
      FOLLY_POLY_MEMBERS(FOLLY_POLY_MEMBER(R(As...) const, &T::operator()));
};

template <class Fun>
using Function = Poly<IFunction<Fun>>;
} // namespace

TEST(Poly, Function) {
  Function<int(int, int)> fun = std::plus<int>{};
  EXPECT_EQ(42, fun(22, 20));
  fun = std::multiplies<int>{};
  EXPECT_EQ(22 * 20, fun(22, 20));
}

namespace {
// This multiply extends IEqualityComparable
struct IDiamond : PolyExtends<IRegular, INullablePointer> {};
} // namespace

TEST(Poly, DiamondInheritance) {
  {
    // A small object, storable in-situ:
    Poly<IDiamond> p = 42;
    EXPECT_EQ(typeid(int), poly_type(p));
    EXPECT_EQ(42, poly_cast<int>(p));
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    Poly<IDiamond> p2 = p;
    EXPECT_EQ(typeid(int), poly_type(p2));
    EXPECT_EQ(42, poly_cast<int>(p2));
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    Poly<IEqualityComparable&> eq = p;
    EXPECT_EQ(&poly_cast<int>(p), &poly_cast<int>(eq));
  }

  EXPECT_EQ(0, Big::s_count);
  {
    // A big object, stored on the heap:
    Poly<IDiamond> p = Big(42);
    EXPECT_EQ(1, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p));
    EXPECT_EQ(42, poly_cast<Big>(p).value());
    EXPECT_THROW(poly_cast<short>(p), BadPolyCast);
    Poly<IDiamond> p2 = p;
    EXPECT_EQ(2, Big::s_count);
    EXPECT_EQ(typeid(Big), poly_type(p2));
    EXPECT_EQ(42, poly_cast<Big>(p2).value());
    EXPECT_THROW(poly_cast<short>(p2), BadPolyCast);
    Poly<IEqualityComparable&> eq = p;
    EXPECT_EQ(&poly_cast<Big>(p), &poly_cast<Big>(eq));
  }
  EXPECT_EQ(0, Big::s_count);
}

namespace {
struct Struct {
  int property() const { return 42; }
  void property(int) {}
};
struct Struct2 : Struct {
  int meow() { return 42; }

  int purr() { return 1; }
  int purr() const { return 2; }
};

int property(Struct const&) {
  return 42;
}
[[maybe_unused]] void property(Struct&, int) {}

int meow(Struct2&) {
  return 42;
}

int purr(Struct2&) {
  return 1;
}
int purr(Struct2 const&) {
  return 2;
}
} // namespace

TEST(Poly, Sig) {
  {
    auto m0 = folly::sig<int() const>(&Struct::property);
    EXPECT_EQ(static_cast<int (Struct::*)() const>(&Struct::property), m0);
    auto m1 = folly::sig<int()>(&Struct::property);
    EXPECT_EQ(static_cast<int (Struct::*)() const>(&Struct::property), m1);

    auto m2 = folly::sig<int() const>(&Struct2::property);
    EXPECT_EQ(static_cast<int (Struct2::*)() const>(&Struct2::property), m2);
    auto m3 = folly::sig<int()>(&Struct2::property);
    EXPECT_EQ(static_cast<int (Struct2::*)() const>(&Struct2::property), m3);

    auto m4 = folly::sig<long()>(&Struct2::meow);
    EXPECT_EQ(&Struct2::meow, m4);

    auto m5 = folly::sig<int()>(&Struct2::purr);
    EXPECT_EQ(static_cast<int (Struct2::*)()>(&Struct2::purr), m5);
    auto m6 = folly::sig<int() const>(&Struct2::purr);
    EXPECT_EQ(static_cast<int (Struct2::*)() const>(&Struct2::purr), m6);
  }
  {
    auto m0 = folly::sig<int(Struct const&)>(&::property);
    EXPECT_EQ(static_cast<int (*)(Struct const&)>(&::property), m0);
    auto m1 = folly::sig<int(Struct&)>(&::property);
    EXPECT_EQ(static_cast<int (*)(Struct const&)>(&::property), m1);

    auto m2 = folly::sig<long(Struct2&)>(&::meow);
    EXPECT_EQ(&::meow, m2);

    auto m3 = folly::sig<int(Struct2&)>(&::purr);
    EXPECT_EQ(static_cast<int (*)(Struct2&)>(&::purr), m3);
    auto m4 = folly::sig<int(Struct2 const&)>(&::purr);
    EXPECT_EQ(static_cast<int (*)(Struct2 const&)>(&::purr), m4);
  }
}

namespace {
struct IAddable {
  template <class Base>
  struct Interface : Base {
    friend PolySelf<Base, PolyDecay> operator+(
        PolySelf<Base> const& a, PolySelf<Base> const& b) {
      return folly::poly_call<0, IAddable>(a, b);
    }
  };
  template <class T>
  static auto plus_(T const& a, T const& b) -> decltype(a + b) {
    return a + b;
  }

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&plus_<std::decay_t<T>>);
};
} // namespace

TEST(Poly, Addable) {
  Poly<IAddable> a = 2, b = 3;
  Poly<IAddable> c = a + b;
  EXPECT_EQ(typeid(int), poly_type(c));
  EXPECT_EQ(5, poly_cast<int>(c));

  Poly<IAddable const&> aref = a, bref = b;
  auto cc = aref + bref;
  static_assert(std::is_same<decltype(cc), Poly<IAddable>>::value, "");
  EXPECT_EQ(typeid(int), poly_type(cc));
  EXPECT_EQ(5, poly_cast<int>(cc));
  b = 4;
  EXPECT_EQ(5, poly_cast<int>(cc));
  cc = aref + bref;
  EXPECT_EQ(6, poly_cast<int>(cc));
}

namespace {
struct IFrobnicator {
  template <class Base>
  struct Interface : Base {
    void frobnicate(folly::Poly<folly::poly::IRegular&> x) {
      folly::poly_call<0>(*this, x);
    }
  };
  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&T::frobnicate);
};
using Frobnicator = folly::Poly<IFrobnicator>;

struct my_frobnicator {
  void frobnicate(folly::Poly<folly::poly::IRegular&>) {
    // no-op
  }
};
} // namespace

TEST(Poly, PolyRefAsArg) {
  folly::Poly<folly::poly::IRegular> x = 42;
  Frobnicator frob = my_frobnicator{};
  // should not throw:
  frob.frobnicate(x);
  // should not throw:
  frob.frobnicate(folly::Poly<folly::poly::IRegular&>(x));
}

namespace {
struct ICat {
  template <class Base>
  struct Interface : Base {
    void pet() { folly::poly_call<0>(*this); }

    int meow() const { return folly::poly_call<1>(*this); }
  };

  template <class T>
  using Members = FOLLY_POLY_MEMBERS(&T::pet, &T::meow);
};

struct cat {
  void pet() noexcept { ++pet_count; }
  int meow() const noexcept { return pet_count; }
  int pet_count = 0;
};
} // namespace

TEST(Poly, NoexceptMembers) {
  cat c{};

  folly::Poly<ICat&> ref = c;
  ref->pet();

  folly::Poly<ICat const&> cref = ref;
  EXPECT_EQ(cref->meow(), 1);
}
