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

// UNSUPPORTED: c++03, c++11, c++14

// <variant>

// template <class ...Types> class variant;

// template <class T>
// variant& operator=(T&&) noexcept(see below);

#include <cassert>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
#include <memory>

#include "test_macros.h"
#include "variant_test_helpers.h"

namespace MetaHelpers {

struct Dummy {
  Dummy() = default;
};

struct ThrowsCtorT {
  ThrowsCtorT(int) noexcept(false) {}
  ThrowsCtorT& operator=(int) noexcept { return *this; }
};

struct ThrowsAssignT {
  ThrowsAssignT(int) noexcept {}
  ThrowsAssignT& operator=(int) noexcept(false) { return *this; }
};

struct NoThrowT {
  NoThrowT(int) noexcept {}
  NoThrowT& operator=(int) noexcept { return *this; }
};

} // namespace MetaHelpers

namespace RuntimeHelpers {
#ifndef TEST_HAS_NO_EXCEPTIONS

struct ThrowsCtorT {
  int value;
  ThrowsCtorT() : value(0) {}
  ThrowsCtorT(int) noexcept(false) { throw 42; }
  ThrowsCtorT& operator=(int v) noexcept {
    value = v;
    return *this;
  }
};

struct MoveCrashes {
  int value;
  MoveCrashes(int v = 0) noexcept : value{v} {}
  MoveCrashes(MoveCrashes&&) noexcept { assert(false); }
  MoveCrashes& operator=(MoveCrashes&&) noexcept {
    assert(false);
    return *this;
  }
  MoveCrashes& operator=(int v) noexcept {
    value = v;
    return *this;
  }
};

struct ThrowsCtorTandMove {
  int value;
  ThrowsCtorTandMove() : value(0) {}
  ThrowsCtorTandMove(int) noexcept(false) { throw 42; }
  ThrowsCtorTandMove(ThrowsCtorTandMove&&) noexcept(false) { assert(false); }
  ThrowsCtorTandMove& operator=(int v) noexcept {
    value = v;
    return *this;
  }
};

struct ThrowsAssignT {
  int value;
  ThrowsAssignT() : value(0) {}
  ThrowsAssignT(int v) noexcept : value(v) {}
  ThrowsAssignT& operator=(int) noexcept(false) { throw 42; }
};

struct NoThrowT {
  int value;
  NoThrowT() : value(0) {}
  NoThrowT(int v) noexcept : value(v) {}
  NoThrowT& operator=(int v) noexcept {
    value = v;
    return *this;
  }
};

#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
} // namespace RuntimeHelpers

constexpr void test_T_assignment_noexcept() {
  using namespace MetaHelpers;
  {
    using V = std::variant<Dummy, NoThrowT>;
    static_assert(std::is_nothrow_assignable<V, int>::value, "");
  }
  {
    using V = std::variant<Dummy, ThrowsCtorT>;
    static_assert(!std::is_nothrow_assignable<V, int>::value, "");
  }
  {
    using V = std::variant<Dummy, ThrowsAssignT>;
    static_assert(!std::is_nothrow_assignable<V, int>::value, "");
  }
}

constexpr void test_T_assignment_sfinae() {
  {
    using V = std::variant<long, long long>;
    static_assert(!std::is_assignable<V, int>::value, "ambiguous");
  }
  {
    using V = std::variant<std::string, std::string>;
    static_assert(!std::is_assignable<V, const char*>::value, "ambiguous");
  }
  {
    using V = std::variant<std::string, void*>;
    static_assert(!std::is_assignable<V, int>::value, "no matching operator=");
  }
  {
    using V = std::variant<std::string, float>;
    static_assert(!std::is_assignable<V, int>::value, "no matching operator=");
  }
  {
    using V = std::variant<std::unique_ptr<int>, bool>;
    static_assert(!std::is_assignable<V, std::unique_ptr<char>>::value, "no explicit bool in operator=");
    struct X {
      operator void*();
    };
    static_assert(!std::is_assignable<V, X>::value, "no boolean conversion in operator=");
    static_assert(std::is_assignable<V, std::false_type>::value, "converted to bool in operator=");
  }
  {
    struct X {};
    struct Y {
      operator X();
    };
    using V = std::variant<X>;
    static_assert(std::is_assignable<V, Y>::value, "regression on user-defined conversions in operator=");
  }
}

TEST_CONSTEXPR_CXX20 void test_T_assignment_basic() {
  {
    std::variant<int> v(43);
    v = 42;
    assert(v.index() == 0);
    assert(std::get<0>(v) == 42);
  }
  {
    std::variant<int, long> v(43l);
    v = 42;
    assert(v.index() == 0);
    assert(std::get<0>(v) == 42);
    v = 43l;
    assert(v.index() == 1);
    assert(std::get<1>(v) == 43);
  }
  {
    std::variant<unsigned, long> v;
    v = 42;
    assert(v.index() == 1);
    assert(std::get<1>(v) == 42);
    v = 43u;
    assert(v.index() == 0);
    assert(std::get<0>(v) == 43);
  }
  {
    std::variant<std::string, bool> v = true;
    v                                 = "bar";
    assert(v.index() == 0);
    assert(std::get<0>(v) == "bar");
  }
}

void test_T_assignment_basic_no_constexpr() {
  std::variant<bool, std::unique_ptr<int>> v;
  v = nullptr;
  assert(v.index() == 1);
  assert(std::get<1>(v) == nullptr);
}

struct TraceStat {
  int construct      = 0;
  int copy_construct = 0;
  int copy_assign    = 0;
  int move_construct = 0;
  int move_assign    = 0;
  int T_copy_assign  = 0;
  int T_move_assign  = 0;
  int destroy        = 0;
};

template <bool CtorNoexcept, bool MoveCtorNoexcept>
struct Trace {
  struct T {};

  constexpr Trace(TraceStat* s) noexcept(CtorNoexcept) : stat(s) { ++s->construct; }
  constexpr Trace(T) noexcept(CtorNoexcept) : stat(nullptr) {}
  constexpr Trace(const Trace& o) : stat(o.stat) { ++stat->copy_construct; }
  constexpr Trace(Trace&& o) noexcept(MoveCtorNoexcept) : stat(o.stat) { ++stat->move_construct; }
  constexpr Trace& operator=(const Trace&) {
    ++stat->copy_assign;
    return *this;
  }
  constexpr Trace& operator=(Trace&&) noexcept {
    ++stat->move_assign;
    return *this;
  }

  constexpr Trace& operator=(const T&) {
    ++stat->T_copy_assign;
    return *this;
  }
  constexpr Trace& operator=(T&&) noexcept {
    ++stat->T_move_assign;
    return *this;
  }
  TEST_CONSTEXPR_CXX20 ~Trace() { ++stat->destroy; }

  TraceStat* stat;
};

TEST_CONSTEXPR_CXX20 void test_T_assignment_performs_construction() {
  {
    using V = std::variant<int, Trace<false, false>>;
    TraceStat stat;
    V v{1};
    v = &stat;
    assert(stat.construct == 1);
    assert(stat.copy_construct == 0);
    assert(stat.move_construct == 0);
    assert(stat.copy_assign == 0);
    assert(stat.move_assign == 0);
    assert(stat.destroy == 0);
  }
  {
    using V = std::variant<int, Trace<false, true>>;
    TraceStat stat;
    V v{1};
    v = &stat;
    assert(stat.construct == 1);
    assert(stat.copy_construct == 0);
    assert(stat.move_construct == 1);
    assert(stat.copy_assign == 0);
    assert(stat.move_assign == 0);
    assert(stat.destroy == 1);
  }

  {
    using V = std::variant<int, Trace<true, false>>;
    TraceStat stat;
    V v{1};
    v = &stat;
    assert(stat.construct == 1);
    assert(stat.copy_construct == 0);
    assert(stat.move_construct == 0);
    assert(stat.copy_assign == 0);
    assert(stat.move_assign == 0);
    assert(stat.destroy == 0);
  }

  {
    using V = std::variant<int, Trace<true, true>>;
    TraceStat stat;
    V v{1};
    v = &stat;
    assert(stat.construct == 1);
    assert(stat.copy_construct == 0);
    assert(stat.move_construct == 0);
    assert(stat.copy_assign == 0);
    assert(stat.move_assign == 0);
    assert(stat.destroy == 0);
  }
}

TEST_CONSTEXPR_CXX20 void test_T_assignment_performs_assignment() {
  {
    using V = std::variant<int, Trace<false, false>>;
    TraceStat stat;
    V v{&stat};
    v = Trace<false, false>::T{};
    assert(stat.construct == 1);
    assert(stat.copy_construct == 0);
    assert(stat.move_construct == 0);
    assert(stat.copy_assign == 0);
    assert(stat.move_assign == 0);
    assert(stat.T_copy_assign == 0);
    assert(stat.T_move_assign == 1);
    assert(stat.destroy == 0);
  }
  {
    using V = std::variant<int, Trace<false, false>>;
    TraceStat stat;
    V v{&stat};
    Trace<false, false>::T t;
    v = t;
    assert(stat.construct == 1);
    assert(stat.copy_construct == 0);
    assert(stat.move_construct == 0);
    assert(stat.copy_assign == 0);
    assert(stat.move_assign == 0);
    assert(stat.T_copy_assign == 1);
    assert(stat.T_move_assign == 0);
    assert(stat.destroy == 0);
  }
}

void test_T_assignment_performs_construction_throw() {
  using namespace RuntimeHelpers;
#ifndef TEST_HAS_NO_EXCEPTIONS
  {
    using V = std::variant<std::string, ThrowsCtorT>;
    V v(std::in_place_type<std::string>, "hello");
    try {
      v = 42;
      assert(false);
    } catch (...) { /* ... */
    }
    assert(v.index() == 0);
    assert(std::get<0>(v) == "hello");
  }
  {
    using V = std::variant<ThrowsAssignT, std::string>;
    V v(std::in_place_type<std::string>, "hello");
    v = 42;
    assert(v.index() == 0);
    assert(std::get<0>(v).value == 42);
  }
#endif // TEST_HAS_NO_EXCEPTIONS
}

void test_T_assignment_performs_assignment_throw() {
  using namespace RuntimeHelpers;
#ifndef TEST_HAS_NO_EXCEPTIONS
  {
    using V = std::variant<ThrowsCtorT>;
    V v;
    v = 42;
    assert(v.index() == 0);
    assert(std::get<0>(v).value == 42);
  }
  {
    using V = std::variant<ThrowsCtorT, std::string>;
    V v;
    v = 42;
    assert(v.index() == 0);
    assert(std::get<0>(v).value == 42);
  }
  {
    using V = std::variant<ThrowsAssignT>;
    V v(100);
    try {
      v = 42;
      assert(false);
    } catch (...) { /* ... */
    }
    assert(v.index() == 0);
    assert(std::get<0>(v).value == 100);
  }
  {
    using V = std::variant<std::string, ThrowsAssignT>;
    V v(100);
    try {
      v = 42;
      assert(false);
    } catch (...) { /* ... */
    }
    assert(v.index() == 1);
    assert(std::get<1>(v).value == 100);
  }
#endif // TEST_HAS_NO_EXCEPTIONS
}

TEST_CONSTEXPR_CXX20 void test_T_assignment_vector_bool() {
  std::vector<bool> vec = {true};
  std::variant<bool, int> v;
  v = vec[0];
  assert(v.index() == 0);
  assert(std::get<0>(v) == true);
}

void non_constexpr_test() {
  test_T_assignment_basic_no_constexpr();
  test_T_assignment_performs_construction_throw();
  test_T_assignment_performs_assignment_throw();
}

TEST_CONSTEXPR_CXX20 bool test() {
  test_T_assignment_basic();
  test_T_assignment_performs_construction();
  test_T_assignment_performs_assignment();
  test_T_assignment_noexcept();
  test_T_assignment_sfinae();
  test_T_assignment_vector_bool();

  return true;
}

int main(int, char**) {
  test();
  non_constexpr_test();

#if TEST_STD_VER >= 20
  static_assert(test());
#endif
  return 0;
}
