//===----------------------------------------------------------------------===//
//
// 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, c++17
// UNSUPPORTED: libcpp-has-no-incomplete-ranges

// <memory>
//
// namespace ranges {
//   template<destructible T>
//     constexpr void destroy_at(T* location) noexcept; // since C++20
// }

#include <cassert>
#include <memory>
#include <type_traits>

#include "test_macros.h"

// TODO(varconst): consolidate the ADL checks into a single file.
// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
// implementations are allowed to use a different mechanism to achieve this effect, so this check is
// libc++-specific.
LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::destroy_at)>);

struct NotNothrowDtrable {
  ~NotNothrowDtrable() noexcept(false) {}
};
static_assert(!std::is_invocable_v<decltype(std::ranges::destroy_at), NotNothrowDtrable*>);

struct Counted {
  int& count;

  constexpr Counted(int& count_ref) : count(count_ref) { ++count; }
  constexpr ~Counted() { --count; }

  friend void operator&(Counted) = delete;
};

struct VirtualCountedBase {
  int& count;

  constexpr VirtualCountedBase(int& count_ref) : count(count_ref) { ++count; }
  constexpr virtual ~VirtualCountedBase() { --count; }

  void operator&() const = delete;
};

struct VirtualCountedDerived : VirtualCountedBase {
  constexpr VirtualCountedDerived(int& count_ref) : VirtualCountedBase(count_ref) {}

  // Without a definition, GCC gives an error when the destructor is invoked in a constexpr context (see
  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93413).
  constexpr ~VirtualCountedDerived() override {}
};

constexpr bool test() {
  // Destroying a "trivial" object.
  {
    std::allocator<Counted> alloc;
    using Traits = std::allocator_traits<decltype(alloc)>;
    int counter = 0;

    Counted* buffer = Traits::allocate(alloc, 2);
    Traits::construct(alloc, buffer, counter);
    Traits::construct(alloc, buffer + 1, counter);
    assert(counter == 2);

    std::ranges::destroy_at(buffer);
    assert(counter == 1);
    std::ranges::destroy_at(buffer + 1);
    assert(counter == 0);

    Traits::deallocate(alloc, buffer, 2);
  }

  // Destroying a derived object with a virtual destructor.
  {
    std::allocator<VirtualCountedDerived> alloc;
    using Traits = std::allocator_traits<decltype(alloc)>;
    int counter = 0;

    VirtualCountedDerived* buffer = Traits::allocate(alloc, 2);
    Traits::construct(alloc, buffer, counter);
    Traits::construct(alloc, buffer + 1, counter);
    assert(counter == 2);

    std::ranges::destroy_at(buffer);
    assert(counter == 1);
    std::ranges::destroy_at(buffer + 1);
    assert(counter == 0);

    Traits::deallocate(alloc, buffer, 2);
  }

  return true;
}

constexpr bool test_arrays() {
  // Pointer to an array.
  {
    using Array = Counted[3];
    std::allocator<Array> alloc;
    using Traits = std::allocator_traits<decltype(alloc)>;
    int counter = 0;

    Array* array = Traits::allocate(alloc, 1);
    Array& array_ref = *array;
    for (int i = 0; i != 3; ++i) {
      Traits::construct(alloc, std::addressof(array_ref[i]), counter);
    }
    assert(counter == 3);

    std::ranges::destroy_at(array);
    assert(counter == 0);

    Traits::deallocate(alloc, array, 1);
  }

  // Pointer to a two-dimensional array.
  {
    using Array = Counted[3][2];
    std::allocator<Array> alloc;
    using Traits = std::allocator_traits<decltype(alloc)>;
    int counter = 0;

    Array* array = Traits::allocate(alloc, 1);
    Array& array_ref = *array;
    for (int i = 0; i != 3; ++i) {
      for (int j = 0; j != 2; ++j) {
        Traits::construct(alloc, std::addressof(array_ref[i][j]), counter);
      }
    }
    assert(counter == 3 * 2);

    std::ranges::destroy_at(array);
    assert(counter == 0);

    Traits::deallocate(alloc, array, 1);
  }

  return true;
}

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

  static_assert(test());
  // TODO: Until std::construct_at has support for arrays, it's impossible to test this
  //       in a constexpr context (see https://reviews.llvm.org/D114903).
  // static_assert(test_arrays());

  return 0;
}
