// Copyright 2019 The Abseil Authors.
//
// 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
//
//      https://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.
//
// -----------------------------------------------------------------------------
// conformance_profiles.h
// -----------------------------------------------------------------------------
//
// This file contains templates for representing "Regularity Profiles" and
// concisely-named versions of commonly used Regularity Profiles.
//
// A Regularity Profile is a compile-time description of the types of operations
// that a given type supports, along with properties of those operations when
// they do exist. For instance, a Regularity Profile may describe a type that
// has a move-constructor that is noexcept and a copy constructor that is not
// noexcept. This description can then be examined and passed around to other
// templates for the purposes of asserting expectations on user-defined types
// via a series trait checks, or for determining what kinds of run-time tests
// are able to be performed.
//
// Regularity Profiles are also used when creating "archetypes," which are
// minimum-conforming types that meet all of the requirements of a given
// Regularity Profile. For more information regarding archetypes, see
// "conformance_archetypes.h".

#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_
#define ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_

#include <set>
#include <type_traits>
#include <utility>
#include <vector>

#include "gtest/gtest.h"
#include "absl/algorithm/container.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/internal/conformance_testing_helpers.h"
#include "absl/utility/utility.h"

// TODO(calabrese) Add support for extending profiles.

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace types_internal {

// Converts an enum to its underlying integral value.
template <typename Enum>
constexpr absl::underlying_type_t<Enum> UnderlyingValue(Enum value) {
  return static_cast<absl::underlying_type_t<Enum>>(value);
}

// A tag type used in place of a matcher when checking that an assertion result
// does not actually contain any errors.
struct NoError {};

// -----------------------------------------------------------------------------
// ConformanceErrors
// -----------------------------------------------------------------------------
class ConformanceErrors {
 public:
  // Setup the error reporting mechanism by seeding it with the name of the type
  // that is being tested.
  explicit ConformanceErrors(std::string type_name)
      : assertion_result_(false), type_name_(std::move(type_name)) {
    assertion_result_ << "\n\n"
                         "Assuming the following type alias:\n"
                         "\n"
                         "  using _T = "
                      << type_name_ << ";\n\n";
    outputDivider();
  }

  // Adds the test name to the list of successfully run tests iff it was not
  // previously reported as failing. This behavior is useful for tests that
  // have multiple parts, where failures and successes are reported individually
  // with the same test name.
  void addTestSuccess(absl::string_view test_name) {
    auto normalized_test_name = absl::AsciiStrToLower(test_name);

    // If the test is already reported as failing, do not add it to the list of
    // successes.
    if (test_failures_.find(normalized_test_name) == test_failures_.end()) {
      test_successes_.insert(std::move(normalized_test_name));
    }
  }

  // Streams a single error description into the internal buffer (a visual
  // divider is automatically inserted after the error so that multiple errors
  // are visibly distinct).
  //
  // This function increases the error count by 1.
  //
  // TODO(calabrese) Determine desired behavior when if this function throws.
  template <class... P>
  void addTestFailure(absl::string_view test_name, const P&... args) {
    // Output a message related to the test failure.
    assertion_result_ << "\n\n"
                         "Failed test: "
                      << test_name << "\n\n";
    addTestFailureImpl(args...);
    assertion_result_ << "\n\n";
    outputDivider();

    auto normalized_test_name = absl::AsciiStrToLower(test_name);

    // If previous parts of this test succeeded, remove it from that set.
    test_successes_.erase(normalized_test_name);

    // Add the test name to the list of failed tests.
    test_failures_.insert(std::move(normalized_test_name));

    has_error_ = true;
  }

  // Convert this object into a testing::AssertionResult instance such that it
  // can be used with gtest.
  ::testing::AssertionResult assertionResult() const {
    return has_error_ ? assertion_result_ : ::testing::AssertionSuccess();
  }

  // Convert this object into a testing::AssertionResult instance such that it
  // can be used with gtest. This overload expects errors, using the specified
  // matcher.
  ::testing::AssertionResult expectFailedTests(
      const std::set<std::string>& test_names) const {
    // Since we are expecting nonconformance, output an error message when the
    // type actually conformed to the specified profile.
    if (!has_error_) {
      return ::testing::AssertionFailure()
             << "Unexpected conformance of type:\n"
                "    "
             << type_name_ << "\n\n";
    }

    // Get a list of all expected failures that did not actually fail
    // (or that were not run).
    std::vector<std::string> nonfailing_tests;
    absl::c_set_difference(test_names, test_failures_,
                           std::back_inserter(nonfailing_tests));

    // Get a list of all "expected failures" that were never actually run.
    std::vector<std::string> unrun_tests;
    absl::c_set_difference(nonfailing_tests, test_successes_,
                           std::back_inserter(unrun_tests));

    // Report when the user specified tests that were not run.
    if (!unrun_tests.empty()) {
      const bool tests_were_run =
          !(test_failures_.empty() && test_successes_.empty());

      // Prepare an assertion result used in the case that tests pass that were
      // expected to fail.
      ::testing::AssertionResult result = ::testing::AssertionFailure();
      result << "When testing type:\n    " << type_name_
             << "\n\nThe following tests were expected to fail but were not "
                "run";

      if (tests_were_run) result << " (was the test name spelled correctly?)";

      result << ":\n\n";

      // List all of the tests that unexpectedly passed.
      for (const auto& test_name : unrun_tests) {
        result << "    " << test_name << "\n";
      }

      if (!tests_were_run) result << "\nNo tests were run.";

      if (!test_failures_.empty()) {
        // List test failures
        result << "\nThe tests that were run and failed are:\n\n";
        for (const auto& test_name : test_failures_) {
          result << "    " << test_name << "\n";
        }
      }

      if (!test_successes_.empty()) {
        // List test successes
        result << "\nThe tests that were run and succeeded are:\n\n";
        for (const auto& test_name : test_successes_) {
          result << "    " << test_name << "\n";
        }
      }

      return result;
    }

    // If some tests passed when they were expected to fail, alert the caller.
    if (nonfailing_tests.empty()) return ::testing::AssertionSuccess();

    // Prepare an assertion result used in the case that tests pass that were
    // expected to fail.
    ::testing::AssertionResult unexpected_successes =
        ::testing::AssertionFailure();
    unexpected_successes << "When testing type:\n    " << type_name_
                         << "\n\nThe following tests passed when they were "
                            "expected to fail:\n\n";

    // List all of the tests that unexpectedly passed.
    for (const auto& test_name : nonfailing_tests) {
      unexpected_successes << "    " << test_name << "\n";
    }

    return unexpected_successes;
  }

 private:
  void outputDivider() {
    assertion_result_ << "========================================";
  }

  void addTestFailureImpl() {}

  template <class H, class... T>
  void addTestFailureImpl(const H& head, const T&... tail) {
    assertion_result_ << head;
    addTestFailureImpl(tail...);
  }

  ::testing::AssertionResult assertion_result_;
  std::set<std::string> test_failures_;
  std::set<std::string> test_successes_;
  std::string type_name_;
  bool has_error_ = false;
};

template <class T, class /*Enabler*/ = void>
struct PropertiesOfImpl {};

template <class T>
struct PropertiesOfImpl<T, absl::void_t<typename T::properties>> {
  using type = typename T::properties;
};

template <class T>
struct PropertiesOfImpl<T, absl::void_t<typename T::profile_alias_of>> {
  using type = typename PropertiesOfImpl<typename T::profile_alias_of>::type;
};

template <class T>
struct PropertiesOf : PropertiesOfImpl<T> {};

template <class T>
using PropertiesOfT = typename PropertiesOf<T>::type;

// NOTE: These enums use this naming convention to be consistent with the
// standard trait names, which is useful since it allows us to match up each
// enum name with a corresponding trait name in macro definitions.

// An enum that describes the various expectations on an operations existence.
enum class function_support { maybe, yes, nothrow, trivial };

constexpr const char* PessimisticPropertyDescription(function_support v) {
  return v == function_support::maybe
             ? "no"
             : v == function_support::yes
                   ? "yes, potentially throwing"
                   : v == function_support::nothrow ? "yes, nothrow"
                                                    : "yes, trivial";
}

// Return a string that describes the kind of property support that was
// expected.
inline std::string ExpectedFunctionKindList(function_support min,
                                            function_support max) {
  if (min == max) {
    std::string result =
        absl::StrCat("Expected:\n  ",
                     PessimisticPropertyDescription(
                         static_cast<function_support>(UnderlyingValue(min))),
                     "\n");
    return result;
  }

  std::string result = "Expected one of:\n";
  for (auto curr_support = UnderlyingValue(min);
       curr_support <= UnderlyingValue(max); ++curr_support) {
    absl::StrAppend(&result, "  ",
                    PessimisticPropertyDescription(
                        static_cast<function_support>(curr_support)),
                    "\n");
  }

  return result;
}

template <class Enum>
void ExpectModelOfImpl(ConformanceErrors* errors, Enum min_support,
                       Enum max_support, Enum kind) {
  const auto kind_value = UnderlyingValue(kind);
  const auto min_support_value = UnderlyingValue(min_support);
  const auto max_support_value = UnderlyingValue(max_support);

  if (!(kind_value >= min_support_value && kind_value <= max_support_value)) {
    errors->addTestFailure(
        PropertyName(kind), "**Failed property expectation**\n\n",
        ExpectedFunctionKindList(
            static_cast<function_support>(min_support_value),
            static_cast<function_support>(max_support_value)),
        '\n', "Actual:\n  ",
        PessimisticPropertyDescription(
            static_cast<function_support>(kind_value)));
  } else {
    errors->addTestSuccess(PropertyName(kind));
  }
}

#define ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(description, name) \
  enum class name { maybe, yes, nothrow, trivial };                   \
                                                                      \
  constexpr const char* PropertyName(name v) { return description; }  \
  static_assert(true, "")  // Force a semicolon when using this macro.

ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for default construction",
                                           default_constructible);
ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move construction",
                                           move_constructible);
ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy construction",
                                           copy_constructible);
ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move assignment",
                                           move_assignable);
ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy assignment",
                                           copy_assignable);
ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for destruction",
                                           destructible);

#undef ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM

#define ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(description, name)     \
  enum class name { maybe, yes, nothrow };                           \
                                                                     \
  constexpr const char* PropertyName(name v) { return description; } \
  static_assert(true, "")  // Force a semicolon when using this macro.

ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for ==", equality_comparable);
ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for !=", inequality_comparable);
ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <", less_than_comparable);
ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <=", less_equal_comparable);
ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >=",
                                      greater_equal_comparable);
ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >", greater_than_comparable);

ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for swap", swappable);

#undef ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM

enum class hashable { maybe, yes };

constexpr const char* PropertyName(hashable v) {
  return "support for std::hash";
}

template <class T>
using AlwaysFalse = std::false_type;

#define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(name, property)   \
  template <class T>                                                        \
  constexpr property property##_support_of() {                              \
    return std::is_##property<T>::value                                     \
               ? std::is_nothrow_##property<T>::value                       \
                     ? absl::is_trivially_##property<T>::value              \
                           ? property::trivial                              \
                           : property::nothrow                              \
                     : property::yes                                        \
               : property::maybe;                                           \
  }                                                                         \
                                                                            \
  template <class T, class MinProf, class MaxProf>                          \
  void ExpectModelOf##name(ConformanceErrors* errors) {                     \
    (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::property##_support, \
                        PropertiesOfT<MaxProf>::property##_support,         \
                        property##_support_of<T>());                        \
  }

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(DefaultConstructible,
                                                  default_constructible);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveConstructible,
                                                  move_constructible);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyConstructible,
                                                  copy_constructible);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveAssignable,
                                                  move_assignable);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyAssignable,
                                                  copy_assignable);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(Destructible, destructible);

#undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER

void BoolFunction(bool) noexcept;

////////////////////////////////////////////////////////////////////////////////
//
// A metafunction for checking if an operation exists through SFINAE.
//
// `T` is the type to test and Op is an alias containing the expression to test.
template <class T, template <class...> class Op, class = void>
struct IsOpableImpl : std::false_type {};

template <class T, template <class...> class Op>
struct IsOpableImpl<T, Op, absl::void_t<Op<T>>> : std::true_type {};

template <template <class...> class Op>
struct IsOpable {
  template <class T>
  using apply = typename IsOpableImpl<T, Op>::type;
};
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// A metafunction for checking if an operation exists and is also noexcept
// through SFINAE and the noexcept operator.
///
// `T` is the type to test and Op is an alias containing the expression to test.
template <class T, template <class...> class Op, class = void>
struct IsNothrowOpableImpl : std::false_type {};

template <class T, template <class...> class Op>
struct IsNothrowOpableImpl<T, Op, absl::enable_if_t<Op<T>::value>>
    : std::true_type {};

template <template <class...> class Op>
struct IsNothrowOpable {
  template <class T>
  using apply = typename IsNothrowOpableImpl<T, Op>::type;
};
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// A macro that produces the necessary function for reporting what kind of
// support a specific comparison operation has and a function for reporting an
// error if a given type's support for that operation does not meet the expected
// requirements.
#define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(name, property, op)      \
  template <class T,                                                           \
            class Result = std::integral_constant<                             \
                bool, noexcept((BoolFunction)(std::declval<const T&>() op      \
                                                  std::declval<const T&>()))>> \
  using name = Result;                                                         \
                                                                               \
  template <class T>                                                           \
  constexpr property property##_support_of() {                                 \
    return IsOpable<name>::apply<T>::value                                     \
               ? IsNothrowOpable<name>::apply<T>::value ? property::nothrow    \
                                                        : property::yes        \
               : property::maybe;                                              \
  }                                                                            \
                                                                               \
  template <class T, class MinProf, class MaxProf>                             \
  void ExpectModelOf##name(ConformanceErrors* errors) {                        \
    (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::property##_support,    \
                        PropertiesOfT<MaxProf>::property##_support,            \
                        property##_support_of<T>());                           \
  }
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// Generate the necessary support-checking and error reporting functions for
// each of the comparison operators.
ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(EqualityComparable,
                                              equality_comparable, ==);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(InequalityComparable,
                                              inequality_comparable, !=);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(LessThanComparable,
                                              less_than_comparable, <);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(LessEqualComparable,
                                              less_equal_comparable, <=);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(GreaterEqualComparable,
                                              greater_equal_comparable, >=);

ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(GreaterThanComparable,
                                              greater_than_comparable, >);

#undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// The necessary support-checking and error-reporting functions for swap.
template <class T>
constexpr swappable swappable_support_of() {
  return type_traits_internal::IsSwappable<T>::value
             ? type_traits_internal::IsNothrowSwappable<T>::value
                   ? swappable::nothrow
                   : swappable::yes
             : swappable::maybe;
}

template <class T, class MinProf, class MaxProf>
void ExpectModelOfSwappable(ConformanceErrors* errors) {
  (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::swappable_support,
                      PropertiesOfT<MaxProf>::swappable_support,
                      swappable_support_of<T>());
}
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// The necessary support-checking and error-reporting functions for std::hash.
template <class T>
constexpr hashable hashable_support_of() {
  return type_traits_internal::IsHashable<T>::value ? hashable::yes
                                                    : hashable::maybe;
}

template <class T, class MinProf, class MaxProf>
void ExpectModelOfHashable(ConformanceErrors* errors) {
  (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::hashable_support,
                      PropertiesOfT<MaxProf>::hashable_support,
                      hashable_support_of<T>());
}
//
////////////////////////////////////////////////////////////////////////////////

template <
    default_constructible DefaultConstructibleValue =
        default_constructible::maybe,
    move_constructible MoveConstructibleValue = move_constructible::maybe,
    copy_constructible CopyConstructibleValue = copy_constructible::maybe,
    move_assignable MoveAssignableValue = move_assignable::maybe,
    copy_assignable CopyAssignableValue = copy_assignable::maybe,
    destructible DestructibleValue = destructible::maybe,
    equality_comparable EqualityComparableValue = equality_comparable::maybe,
    inequality_comparable InequalityComparableValue =
        inequality_comparable::maybe,
    less_than_comparable LessThanComparableValue = less_than_comparable::maybe,
    less_equal_comparable LessEqualComparableValue =
        less_equal_comparable::maybe,
    greater_equal_comparable GreaterEqualComparableValue =
        greater_equal_comparable::maybe,
    greater_than_comparable GreaterThanComparableValue =
        greater_than_comparable::maybe,
    swappable SwappableValue = swappable::maybe,
    hashable HashableValue = hashable::maybe>
struct ConformanceProfile {
  using properties = ConformanceProfile;

  static constexpr default_constructible
      default_constructible_support =  // NOLINT
      DefaultConstructibleValue;

  static constexpr move_constructible move_constructible_support =  // NOLINT
      MoveConstructibleValue;

  static constexpr copy_constructible copy_constructible_support =  // NOLINT
      CopyConstructibleValue;

  static constexpr move_assignable move_assignable_support =  // NOLINT
      MoveAssignableValue;

  static constexpr copy_assignable copy_assignable_support =  // NOLINT
      CopyAssignableValue;

  static constexpr destructible destructible_support =  // NOLINT
      DestructibleValue;

  static constexpr equality_comparable equality_comparable_support =  // NOLINT
      EqualityComparableValue;

  static constexpr inequality_comparable
      inequality_comparable_support =  // NOLINT
      InequalityComparableValue;

  static constexpr less_than_comparable
      less_than_comparable_support =  // NOLINT
      LessThanComparableValue;

  static constexpr less_equal_comparable
      less_equal_comparable_support =  // NOLINT
      LessEqualComparableValue;

  static constexpr greater_equal_comparable
      greater_equal_comparable_support =  // NOLINT
      GreaterEqualComparableValue;

  static constexpr greater_than_comparable
      greater_than_comparable_support =  // NOLINT
      GreaterThanComparableValue;

  static constexpr swappable swappable_support = SwappableValue;  // NOLINT

  static constexpr hashable hashable_support = HashableValue;  // NOLINT

  static constexpr bool is_default_constructible =  // NOLINT
      DefaultConstructibleValue != default_constructible::maybe;

  static constexpr bool is_move_constructible =  // NOLINT
      MoveConstructibleValue != move_constructible::maybe;

  static constexpr bool is_copy_constructible =  // NOLINT
      CopyConstructibleValue != copy_constructible::maybe;

  static constexpr bool is_move_assignable =  // NOLINT
      MoveAssignableValue != move_assignable::maybe;

  static constexpr bool is_copy_assignable =  // NOLINT
      CopyAssignableValue != copy_assignable::maybe;

  static constexpr bool is_destructible =  // NOLINT
      DestructibleValue != destructible::maybe;

  static constexpr bool is_equality_comparable =  // NOLINT
      EqualityComparableValue != equality_comparable::maybe;

  static constexpr bool is_inequality_comparable =  // NOLINT
      InequalityComparableValue != inequality_comparable::maybe;

  static constexpr bool is_less_than_comparable =  // NOLINT
      LessThanComparableValue != less_than_comparable::maybe;

  static constexpr bool is_less_equal_comparable =  // NOLINT
      LessEqualComparableValue != less_equal_comparable::maybe;

  static constexpr bool is_greater_equal_comparable =  // NOLINT
      GreaterEqualComparableValue != greater_equal_comparable::maybe;

  static constexpr bool is_greater_than_comparable =  // NOLINT
      GreaterThanComparableValue != greater_than_comparable::maybe;

  static constexpr bool is_swappable =  // NOLINT
      SwappableValue != swappable::maybe;

  static constexpr bool is_hashable =  // NOLINT
      HashableValue != hashable::maybe;
};

////////////////////////////////////////////////////////////////////////////////
//
// Compliant SFINAE-friendliness is not always present on the standard library
// implementations that we support. This helper-struct (and associated enum) is
// used as a means to conditionally check the hashability support of a type.
enum class CheckHashability { no, yes };

template <class T, CheckHashability ShouldCheckHashability>
struct conservative_hashable_support_of;

template <class T>
struct conservative_hashable_support_of<T, CheckHashability::no> {
  static constexpr hashable Invoke() { return hashable::maybe; }
};

template <class T>
struct conservative_hashable_support_of<T, CheckHashability::yes> {
  static constexpr hashable Invoke() { return hashable_support_of<T>(); }
};
//
////////////////////////////////////////////////////////////////////////////////

// The ConformanceProfile that is expected based on introspection into the type
// by way of trait checks.
template <class T, CheckHashability ShouldCheckHashability>
struct SyntacticConformanceProfileOf {
  using properties = ConformanceProfile<
      default_constructible_support_of<T>(), move_constructible_support_of<T>(),
      copy_constructible_support_of<T>(), move_assignable_support_of<T>(),
      copy_assignable_support_of<T>(), destructible_support_of<T>(),
      equality_comparable_support_of<T>(),
      inequality_comparable_support_of<T>(),
      less_than_comparable_support_of<T>(),
      less_equal_comparable_support_of<T>(),
      greater_equal_comparable_support_of<T>(),
      greater_than_comparable_support_of<T>(), swappable_support_of<T>(),
      conservative_hashable_support_of<T, ShouldCheckHashability>::Invoke()>;
};

#define ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(type, name)     \
  template <default_constructible DefaultConstructibleValue,                   \
            move_constructible MoveConstructibleValue,                         \
            copy_constructible CopyConstructibleValue,                         \
            move_assignable MoveAssignableValue,                               \
            copy_assignable CopyAssignableValue,                               \
            destructible DestructibleValue,                                    \
            equality_comparable EqualityComparableValue,                       \
            inequality_comparable InequalityComparableValue,                   \
            less_than_comparable LessThanComparableValue,                      \
            less_equal_comparable LessEqualComparableValue,                    \
            greater_equal_comparable GreaterEqualComparableValue,              \
            greater_than_comparable GreaterThanComparableValue,                \
            swappable SwappableValue, hashable HashableValue>                  \
  constexpr type ConformanceProfile<                                           \
      DefaultConstructibleValue, MoveConstructibleValue,                       \
      CopyConstructibleValue, MoveAssignableValue, CopyAssignableValue,        \
      DestructibleValue, EqualityComparableValue, InequalityComparableValue,   \
      LessThanComparableValue, LessEqualComparableValue,                       \
      GreaterEqualComparableValue, GreaterThanComparableValue, SwappableValue, \
      HashableValue>::name

#define ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(type)           \
  ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(type,            \
                                                         type##_support); \
  ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(bool, is_##type)

ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(default_constructible);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(move_constructible);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(copy_constructible);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(move_assignable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(copy_assignable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(destructible);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(equality_comparable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(inequality_comparable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(less_than_comparable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(less_equal_comparable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(greater_equal_comparable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(greater_than_comparable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(swappable);
ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(hashable);

#undef ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF
#undef ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL

// Retrieve the enum with the minimum underlying value.
// Note: std::min is not constexpr in C++11, which is why this is necessary.
template <class H>
constexpr H MinEnum(H head) {
  return head;
}

template <class H, class N, class... T>
constexpr H MinEnum(H head, N next, T... tail) {
  return (UnderlyingValue)(head) < (UnderlyingValue)(next)
             ? (MinEnum)(head, tail...)
             : (MinEnum)(next, tail...);
}

template <class... Profs>
struct MinimalProfiles {
  static constexpr default_constructible
      default_constructible_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::default_constructible_support...);

  static constexpr move_constructible move_constructible_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::move_constructible_support...);

  static constexpr copy_constructible copy_constructible_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::copy_constructible_support...);

  static constexpr move_assignable move_assignable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::move_assignable_support...);

  static constexpr copy_assignable copy_assignable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::copy_assignable_support...);

  static constexpr destructible destructible_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::destructible_support...);

  static constexpr equality_comparable equality_comparable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::equality_comparable_support...);

  static constexpr inequality_comparable
      inequality_comparable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::inequality_comparable_support...);

  static constexpr less_than_comparable
      less_than_comparable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::less_than_comparable_support...);

  static constexpr less_equal_comparable
      less_equal_comparable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::less_equal_comparable_support...);

  static constexpr greater_equal_comparable
      greater_equal_comparable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::greater_equal_comparable_support...);

  static constexpr greater_than_comparable
      greater_than_comparable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::greater_than_comparable_support...);

  static constexpr swappable swappable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::swappable_support...);

  static constexpr hashable hashable_support =  // NOLINT
      (MinEnum)(PropertiesOfT<Profs>::hashable_support...);

  using properties = ConformanceProfile<
      default_constructible_support, move_constructible_support,
      copy_constructible_support, move_assignable_support,
      copy_assignable_support, destructible_support,
      equality_comparable_support, inequality_comparable_support,
      less_than_comparable_support, less_equal_comparable_support,
      greater_equal_comparable_support, greater_than_comparable_support,
      swappable_support, hashable_support>;
};

// Retrieve the enum with the greatest underlying value.
// Note: std::max is not constexpr in C++11, which is why this is necessary.
template <class H>
constexpr H MaxEnum(H head) {
  return head;
}

template <class H, class N, class... T>
constexpr H MaxEnum(H head, N next, T... tail) {
  return (UnderlyingValue)(next) < (UnderlyingValue)(head)
             ? (MaxEnum)(head, tail...)
             : (MaxEnum)(next, tail...);
}

template <class... Profs>
struct CombineProfilesImpl {
  static constexpr default_constructible
      default_constructible_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::default_constructible_support...);

  static constexpr move_constructible move_constructible_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::move_constructible_support...);

  static constexpr copy_constructible copy_constructible_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::copy_constructible_support...);

  static constexpr move_assignable move_assignable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::move_assignable_support...);

  static constexpr copy_assignable copy_assignable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::copy_assignable_support...);

  static constexpr destructible destructible_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::destructible_support...);

  static constexpr equality_comparable equality_comparable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::equality_comparable_support...);

  static constexpr inequality_comparable
      inequality_comparable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::inequality_comparable_support...);

  static constexpr less_than_comparable
      less_than_comparable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::less_than_comparable_support...);

  static constexpr less_equal_comparable
      less_equal_comparable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::less_equal_comparable_support...);

  static constexpr greater_equal_comparable
      greater_equal_comparable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::greater_equal_comparable_support...);

  static constexpr greater_than_comparable
      greater_than_comparable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::greater_than_comparable_support...);

  static constexpr swappable swappable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::swappable_support...);

  static constexpr hashable hashable_support =  // NOLINT
      (MaxEnum)(PropertiesOfT<Profs>::hashable_support...);

  using properties = ConformanceProfile<
      default_constructible_support, move_constructible_support,
      copy_constructible_support, move_assignable_support,
      copy_assignable_support, destructible_support,
      equality_comparable_support, inequality_comparable_support,
      less_than_comparable_support, less_equal_comparable_support,
      greater_equal_comparable_support, greater_than_comparable_support,
      swappable_support, hashable_support>;
};

// NOTE: We use this as opposed to a direct alias of CombineProfilesImpl so that
// when named aliases of CombineProfiles are created (such as in
// conformance_aliases.h), we only pay for the combination algorithm on the
// profiles that are actually used.
template <class... Profs>
struct CombineProfiles {
  using profile_alias_of = CombineProfilesImpl<Profs...>;
};

template <>
struct CombineProfiles<> {
  using properties = ConformanceProfile<>;
};

template <class Profile, class Tag>
struct StrongProfileTypedef {
  using properties = PropertiesOfT<Profile>;
};

template <class T, class /*Enabler*/ = void>
struct IsProfileImpl : std::false_type {};

template <class T>
struct IsProfileImpl<T, absl::void_t<PropertiesOfT<T>>> : std::true_type {};

template <class T>
struct IsProfile : IsProfileImpl<T>::type {};

// A tag that describes which set of properties we will check when the user
// requires a strict match in conformance (as opposed to a loose match which
// allows more-refined support of any given operation).
//
// Currently only the RegularityDomain exists and it includes all operations
// that the conformance testing suite knows about. The intent is that if the
// suite is expanded to support extension, such as for checking conformance of
// concepts like Iterators or Containers, additional corresponding domains can
// be created.
struct RegularityDomain {};

}  // namespace types_internal
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_


# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENSE BLOCK *****

set(GMP_EXTRA_ARGS -enable-cxx)

if(WIN32)
  # Shared for windows because static libs will drag in a libgcc dependency.
  set(GMP_OPTIONS --disable-static --enable-shared --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32)
else()
  set(GMP_OPTIONS --enable-static --disable-shared )
endif()

if(APPLE)
  if("${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64")
    set(GMP_OPTIONS
      ${GMP_OPTIONS}
      --disable-assembly
    )
  else()
    set(GMP_OPTIONS
      ${GMP_OPTIONS}
      --with-pic
    )
  endif()
elseif(UNIX)
  set(GMP_OPTIONS
    ${GMP_OPTIONS}
    --with-pic
  )
endif()

ExternalProject_Add(external_gmp
  URL ${GMP_URI}
  DOWNLOAD_DIR ${DOWNLOAD_DIR}
  URL_HASH MD5=${GMP_HASH}
  PREFIX ${BUILD_DIR}/gmp
  CONFIGURE_COMMAND ${CONFIGURE_ENV_NO_PERL} && cd ${BUILD_DIR}/gmp/src/external_gmp/ && ${CONFIGURE_COMMAND} --prefix=${LIBDIR}/gmp ${GMP_OPTIONS} ${GMP_EXTRA_ARGS}
  BUILD_COMMAND ${CONFIGURE_ENV_NO_PERL} && cd ${BUILD_DIR}/gmp/src/external_gmp/ && make -j${MAKE_THREADS}
  INSTALL_COMMAND ${CONFIGURE_ENV_NO_PERL} && cd ${BUILD_DIR}/gmp/src/external_gmp/ && make install
  INSTALL_DIR ${LIBDIR}/gmp
)

if(MSVC)
  set_target_properties(external_gmp PROPERTIES FOLDER Mingw)
endif()

if(BUILD_MODE STREQUAL Release AND WIN32)
  ExternalProject_Add_Step(external_gmp after_install
      COMMAND  ${CMAKE_COMMAND} -E copy ${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-3.dll.def ${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.def
      COMMAND  lib /def:${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.def /machine:x64 /out:${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.lib
      COMMAND  ${CMAKE_COMMAND} -E copy ${LIBDIR}/gmp/bin/libgmp-10.dll ${HARVEST_TARGET}/gmp/lib/libgmp-10.dll
      COMMAND  ${CMAKE_COMMAND} -E copy ${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.lib ${HARVEST_TARGET}/gmp/lib/libgmp-10.lib
      COMMAND ${CMAKE_COMMAND} -E copy_directory ${LIBDIR}/gmp/include ${HARVEST_TARGET}/gmp/include
    DEPENDEES install
  )
endif()

if(BUILD_MODE STREQUAL Debug AND WIN32)
ExternalProject_Add_Step(external_gmp after_install
      COMMAND  ${CMAKE_COMMAND} -E copy ${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-3.dll.def ${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.def
      COMMAND  lib /def:${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.def /machine:x64 /out:${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.lib
    DEPENDEES install
  )
endif()

if(WIN32)
  # gmpxx is somewhat special, it builds on top of the C style gmp library but exposes C++ bindings
  # given the C++ ABI between MSVC and mingw is not compatible, we need to build the bindings
  # with MSVC, while GMP can only be build with mingw.
  ExternalProject_Add(external_gmpxx
    URL ${GMP_URI}
    DOWNLOAD_DIR ${DOWNLOAD_DIR}
    URL_HASH MD5=${GMP_HASH}
    PREFIX ${BUILD_DIR}/gmpxx
    PATCH_COMMAND COMMAND ${CMAKE_COMMAND} -E copy ${PATCH_DIR}/cmakelists_gmpxx.txt ${BUILD_DIR}/gmpxx/src/external_gmpxx/CMakeLists.txt &&
                          ${CMAKE_COMMAND} -E copy ${PATCH_DIR}/config_gmpxx.h ${BUILD_DIR}/gmpxx/src/external_gmpxx/config.h
    CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${LIBDIR}/gmpxx ${DEFAULT_CMAKE_FLAGS} -DGMP_LIBRARY=${BUILD_DIR}/gmp/src/external_gmp/.libs/libgmp-10.lib -DGMP_INCLUDE_DIR=${BUILD_DIR}/gmp/src/external_gmp -DCMAKE_DEBUG_POSTFIX=_d
    INSTALL_DIR ${LIBDIR}/gmpxx
  )
  set_target_properties(external_gmpxx PROPERTIES FOLDER Mingw)

  add_dependencies(
    external_gmpxx
    external_gmp
  )

  ExternalProject_Add_Step(external_gmpxx after_install
      COMMAND ${CMAKE_COMMAND} -E copy_directory ${LIBDIR}/gmpxx/ ${HARVEST_TARGET}/gmp
    DEPENDEES install
  )

endif()