diff options
author | Abseil Team <absl-team@google.com> | 2020-04-17T15·13-0700 |
---|---|---|
committer | Mark Barolak <mbar@google.com> | 2020-04-17T15·58-0400 |
commit | b35973e3e35cb1eccb086d6a549c253c49579474 (patch) | |
tree | 2acab21a7c8039c1b4e7b7e5b580ac64aa743291 /absl/types | |
parent | db5773a721a50d1fc8c9b51efea0e70be4003d36 (diff) |
Export of internal Abseil changes
-- d857e6e1f9b09a3eb5abd890677a98b23346f07a by Abseil Team <absl-team@google.com>: Simplify internal TryAcquireWithSpinning. No point declaring the `result` variable: we can just return the results directly. PiperOrigin-RevId: 307045800 -- 421952252bc23be51f47f7d23f3422bad1ed382c by Derek Mauro <dmauro@google.com>: Add custom sink support for `absl::Format()` through an ADL extension mechanism. Users can now define `void AbslFormatFlush(MySink* dest, absl::string_view part)` to allow `absl::Format()` to append to a custom sink. PiperOrigin-RevId: 306929052 -- c73d5cdb62cd58ea421ed1aeeab78a0ffcfeeefb by Matt Calabrese <calabrese@google.com>: Internal-only conformance-testing macro ABSL_INTERNAL_ASSERT_CONFORMANCE_OF for compile-time and runtime checks of a specified type, expected properties of that type, and a logically-ordered series of equivalence classes of that type. PiperOrigin-RevId: 306885512 -- a8c2495a07f37d68907855e3f0535bd5c27a3b52 by Abseil Team <absl-team@google.com>: Internal change PiperOrigin-RevId: 306766753 GitOrigin-RevId: d857e6e1f9b09a3eb5abd890677a98b23346f07a Change-Id: Ic23c92ac74f9ffcbb2471ff8c6691f4b7b20354b
Diffstat (limited to 'absl/types')
-rw-r--r-- | absl/types/BUILD.bazel | 6 | ||||
-rw-r--r-- | absl/types/CMakeLists.txt | 6 | ||||
-rw-r--r-- | absl/types/internal/conformance_profile.h | 599 | ||||
-rw-r--r-- | absl/types/internal/conformance_testing.h | 1386 | ||||
-rw-r--r-- | absl/types/internal/conformance_testing_helpers.h | 391 | ||||
-rw-r--r-- | absl/types/internal/conformance_testing_test.cc | 372 | ||||
-rw-r--r-- | absl/types/internal/parentheses.h | 34 | ||||
-rw-r--r-- | absl/types/internal/transform_args.h | 246 |
8 files changed, 3016 insertions, 24 deletions
diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index c64417cc6f05..de71c7347fe8 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel @@ -216,11 +216,15 @@ cc_library( "internal/conformance_aliases.h", "internal/conformance_archetype.h", "internal/conformance_profile.h", + "internal/conformance_testing.h", + "internal/conformance_testing_helpers.h", + "internal/parentheses.h", + "internal/transform_args.h", ], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - "//absl/debugging:demangle_internal", + "//absl/algorithm:container", "//absl/meta:type_traits", "//absl/strings", "//absl/utility", diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt index 1b4d453b2efe..71b339ae5fb7 100644 --- a/absl/types/CMakeLists.txt +++ b/absl/types/CMakeLists.txt @@ -246,9 +246,14 @@ absl_cc_library( "internal/conformance_aliases.h" "internal/conformance_archetype.h" "internal/conformance_profile.h" + "internal/conformance_testing.h", + "internal/conformance_testing_helpers.h", + "internal/parentheses.h", + "internal/transform_args.h", COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::algorithm absl::debugging absl::type_traits absl::strings @@ -282,6 +287,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::conformance_testing + absl::type_traits gmock_main ) diff --git a/absl/types/internal/conformance_profile.h b/absl/types/internal/conformance_profile.h index e62004fdf271..cf64ff4fcd1e 100644 --- a/absl/types/internal/conformance_profile.h +++ b/absl/types/internal/conformance_profile.h @@ -36,10 +36,19 @@ #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. @@ -47,6 +56,187 @@ 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 {}; @@ -70,31 +260,100 @@ using PropertiesOfT = typename PropertiesOf<T>::type; // standard trait names, which is useful since it allows us to match up each // enum name with a corresponding trait name in macro definitions. -enum class function_kind { maybe, yes, nothrow, trivial }; +// 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; +} -#define ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(name) \ - enum class name { maybe, yes, nothrow, trivial } +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)); + } +} -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(default_constructible); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(move_constructible); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(copy_constructible); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(move_assignable); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(copy_assignable); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(destructible); +#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(name) \ - enum class name { maybe, yes, nothrow } +#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(equality_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(inequality_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(less_than_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(less_equal_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(greater_equal_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(greater_than_comparable); +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(swappable); +ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for swap", swappable); #undef ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM @@ -104,6 +363,184 @@ 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, @@ -216,6 +653,45 @@ struct ConformanceProfile { 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, \ @@ -261,12 +737,80 @@ 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 -// Converts an enum to its underlying integral value. -template <class Enum> -constexpr absl::underlying_type_t<Enum> UnderlyingValue(Enum value) { - return static_cast<absl::underlying_type_t<Enum>>(value); +// 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> @@ -369,6 +913,17 @@ 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 diff --git a/absl/types/internal/conformance_testing.h b/absl/types/internal/conformance_testing.h new file mode 100644 index 000000000000..487b0f786b48 --- /dev/null +++ b/absl/types/internal/conformance_testing.h @@ -0,0 +1,1386 @@ +// 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_testing.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ +#define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ + +//////////////////////////////////////////////////////////////////////////////// +// // +// Many templates in this file take a `T` and a `Prof` type as explicit // +// template arguments. These are a type to be checked and a // +// "Regularity Profile" that describes what operations that type `T` is // +// expected to support. See "regularity_profiles.h" for more details // +// regarding Regularity Profiles. // +// // +//////////////////////////////////////////////////////////////////////////////// + +#include <cstddef> +#include <set> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "gtest/gtest.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_aliases.h" +#include "absl/types/internal/conformance_archetype.h" +#include "absl/types/internal/conformance_profile.h" +#include "absl/types/internal/conformance_testing_helpers.h" +#include "absl/types/internal/parentheses.h" +#include "absl/types/internal/transform_args.h" +#include "absl/utility/utility.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace types_internal { + +// Returns true if the compiler incorrectly greedily instantiates constexpr +// templates in any unevaluated context. +constexpr bool constexpr_instantiation_when_unevaluated() { +#if defined(__apple_build_version__) // TODO(calabrese) Make more specific + return true; +#elif defined(__clang__) + return __clang_major__ < 4; +#elif defined(__GNUC__) + // TODO(calabrese) Figure out why gcc 7 fails (seems like a different bug) + return __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 2) || __GNUC__ >= 7; +#else + return false; +#endif +} + +// Returns true if the standard library being used incorrectly produces an error +// when instantiating the definition of a poisoned std::hash specialization. +constexpr bool poisoned_hash_fails_instantiation() { +#if defined(_MSC_VER) && !defined(_LIBCPP_VERSION) + return _MSC_VER < 1914; +#else + return false; +#endif +} + +template <class Fun> +struct GeneratorType { + decltype(std::declval<const Fun&>()()) operator()() const + noexcept(noexcept(std::declval<const Fun&>()())) { + return fun(); + } + + Fun fun; + const char* description; +}; + +// A "make" function for the GeneratorType template that deduces the function +// object type. +template <class Fun, + absl::enable_if_t<IsNullaryCallable<Fun>::value>** = nullptr> +GeneratorType<Fun> Generator(Fun fun, const char* description) { + return GeneratorType<Fun>{absl::move(fun), description}; +} + +// A type that contains a set of nullary function objects that each return an +// instance of the same type and value (though possibly different +// representations, such as +0 and -0 or two vectors with the same elements but +// with different capacities). +template <class... Funs> +struct EquivalenceClassType { + std::tuple<GeneratorType<Funs>...> generators; +}; + +// A "make" function for the EquivalenceClassType template that deduces the +// function object types and is constrained such that a user can only pass in +// function objects that all have the same return type. +template <class... Funs, absl::enable_if_t<AreGeneratorsWithTheSameReturnType< + Funs...>::value>** = nullptr> +EquivalenceClassType<Funs...> EquivalenceClass(GeneratorType<Funs>... funs) { + return {std::make_tuple(absl::move(funs)...)}; +} + +// A type that contains an ordered series of EquivalenceClassTypes, from +// smallest value to largest value. +template <class... EqClasses> +struct OrderedEquivalenceClasses { + std::tuple<EqClasses...> eq_classes; +}; + +// An object containing the parts of a given (name, initialization expression), +// and is capable of generating a string that describes the given. +struct GivenDeclaration { + std::string outputDeclaration(std::size_t width) const { + const std::size_t indent_size = 2; + std::string result = absl::StrCat(" ", name); + + if (!expression.empty()) { + // Indent + result.resize(indent_size + width, ' '); + absl::StrAppend(&result, " = ", expression, ";\n"); + } else { + absl::StrAppend(&result, ";\n"); + } + + return result; + } + + std::string name; + std::string expression; +}; + +// Produce a string that contains all of the givens of an error report. +template <class... Decls> +std::string PrepareGivenContext(const Decls&... decls) { + const std::size_t width = (std::max)({decls.name.size()...}); + return absl::StrCat("Given:\n", decls.outputDeclaration(width)..., "\n"); +} + +//////////////////////////////////////////////////////////////////////////////// +// Function objects that perform a check for each comparison operator // +//////////////////////////////////////////////////////////////////////////////// + +#define ABSL_INTERNAL_EXPECT_OP(name, op) \ + struct Expect##name { \ + template <class T> \ + void operator()(absl::string_view test_name, absl::string_view context, \ + const T& lhs, const T& rhs, absl::string_view lhs_name, \ + absl::string_view rhs_name) const { \ + if (!static_cast<bool>(lhs op rhs)) { \ + errors->addTestFailure( \ + test_name, absl::StrCat(context, \ + "**Unexpected comparison result**\n" \ + "\n" \ + "Expression:\n" \ + " ", \ + lhs_name, " " #op " ", rhs_name, \ + "\n" \ + "\n" \ + "Expected: true\n" \ + " Actual: false")); \ + } else { \ + errors->addTestSuccess(test_name); \ + } \ + } \ + \ + ConformanceErrors* errors; \ + }; \ + \ + struct ExpectNot##name { \ + template <class T> \ + void operator()(absl::string_view test_name, absl::string_view context, \ + const T& lhs, const T& rhs, absl::string_view lhs_name, \ + absl::string_view rhs_name) const { \ + if (lhs op rhs) { \ + errors->addTestFailure( \ + test_name, absl::StrCat(context, \ + "**Unexpected comparison result**\n" \ + "\n" \ + "Expression:\n" \ + " ", \ + lhs_name, " " #op " ", rhs_name, \ + "\n" \ + "\n" \ + "Expected: false\n" \ + " Actual: true")); \ + } else { \ + errors->addTestSuccess(test_name); \ + } \ + } \ + \ + ConformanceErrors* errors; \ + } + +ABSL_INTERNAL_EXPECT_OP(Eq, ==); +ABSL_INTERNAL_EXPECT_OP(Ne, !=); +ABSL_INTERNAL_EXPECT_OP(Lt, <); +ABSL_INTERNAL_EXPECT_OP(Le, <=); +ABSL_INTERNAL_EXPECT_OP(Ge, >=); +ABSL_INTERNAL_EXPECT_OP(Gt, >); + +#undef ABSL_INTERNAL_EXPECT_OP + +// A function object that verifies that two objects hash to the same value by +// way of the std::hash specialization. +struct ExpectSameHash { + template <class T> + void operator()(absl::string_view test_name, absl::string_view context, + const T& lhs, const T& rhs, absl::string_view lhs_name, + absl::string_view rhs_name) const { + if (std::hash<T>()(lhs) != std::hash<T>()(rhs)) { + errors->addTestFailure( + test_name, absl::StrCat(context, + "**Unexpected hash result**\n" + "\n" + "Expression:\n" + " std::hash<T>()(", + lhs_name, ") == std::hash<T>()(", rhs_name, + ")\n" + "\n" + "Expected: true\n" + " Actual: false")); + } else { + errors->addTestSuccess(test_name); + } + } + + ConformanceErrors* errors; +}; + +// A function template that takes two objects and verifies that each comparison +// operator behaves in a way that is consistent with equality. It has "OneWay" +// in the name because the first argument will always be the left-hand operand +// of the corresponding comparison operator and the second argument will +// always be the right-hand operand. It will never switch that order. +// At a higher level in the test suite, the one-way form is called once for each +// of the two possible orders whenever lhs and rhs are not the same initializer. +template <class T, class Prof> +void ExpectOneWayEquality(ConformanceErrors* errors, + absl::string_view test_name, + absl::string_view context, const T& lhs, const T& rhs, + absl::string_view lhs_name, + absl::string_view rhs_name) { + If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( + ExpectEq{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); + + If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( + ExpectNotNe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); + + If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( + ExpectNotLt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); + + If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( + ExpectLe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); + + If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( + ExpectGe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); + + If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( + ExpectNotGt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); + + If<PropertiesOfT<Prof>::is_hashable>::Invoke( + ExpectSameHash{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); +} + +// A function template that takes two objects and verifies that each comparison +// operator behaves in a way that is consistent with equality. This function +// differs from ExpectOneWayEquality in that this will do checks with argument +// order reversed in addition to in-order. +template <class T, class Prof> +void ExpectEquality(ConformanceErrors* errors, absl::string_view test_name, + absl::string_view context, const T& lhs, const T& rhs, + absl::string_view lhs_name, absl::string_view rhs_name) { + (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, lhs, rhs, + lhs_name, rhs_name); + (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, rhs, lhs, + rhs_name, lhs_name); +} + +// Given a generator, makes sure that a generated value and a moved-from +// generated value are equal. +template <class T, class Prof> +struct ExpectMoveConstructOneGenerator { + template <class Fun> + void operator()(const Fun& generator) const { + const T object = generator(); + const T moved_object = absl::move(generator()); // Force no elision. + + (ExpectEquality<T, Prof>)(errors, "Move construction", + PrepareGivenContext( + GivenDeclaration{"const _T object", + generator.description}, + GivenDeclaration{"const _T moved_object", + std::string("std::move(") + + generator.description + + ")"}), + object, moved_object, "object", "moved_object"); + } + + ConformanceErrors* errors; +}; + +// Given a generator, makes sure that a generated value and a copied-from +// generated value are equal. +template <class T, class Prof> +struct ExpectCopyConstructOneGenerator { + template <class Fun> + void operator()(const Fun& generator) const { + const T object = generator(); + const T copied_object = static_cast<const T&>(generator()); + + (ExpectEquality<T, Prof>)(errors, "Copy construction", + PrepareGivenContext( + GivenDeclaration{"const _T object", + generator.description}, + GivenDeclaration{ + "const _T copied_object", + std::string("static_cast<const _T&>(") + + generator.description + ")"}), + object, copied_object, "object", "copied_object"); + } + + ConformanceErrors* errors; +}; + +// Default-construct and do nothing before destruction. +// +// This is useful in exercising the codepath of default construction followed by +// destruction, but does not explicitly test anything. An example of where this +// might fail is a default destructor that default-initializes a scalar and a +// destructor reads the value of that member. Sanitizers can catch this as long +// as our test attempts to execute such a case. +template <class T> +struct ExpectDefaultConstructWithDestruct { + void operator()() const { + // Scoped so that destructor gets called before reporting success. + { + T object; + static_cast<void>(object); + } + + errors->addTestSuccess("Default construction"); + } + + ConformanceErrors* errors; +}; + +// Check move-assign into a default-constructed object. +template <class T, class Prof> +struct ExpectDefaultConstructWithMoveAssign { + template <class Fun> + void operator()(const Fun& generator) const { + const T source_of_truth = generator(); + T object; + object = generator(); + + (ExpectEquality<T, Prof>)(errors, "Move assignment", + PrepareGivenContext( + GivenDeclaration{"const _T object", + generator.description}, + GivenDeclaration{"_T object", ""}, + GivenDeclaration{"object", + generator.description}), + object, source_of_truth, "std::as_const(object)", + "source_of_truth"); + } + + ConformanceErrors* errors; +}; + +// Check copy-assign into a default-constructed object. +template <class T, class Prof> +struct ExpectDefaultConstructWithCopyAssign { + template <class Fun> + void operator()(const Fun& generator) const { + const T source_of_truth = generator(); + T object; + object = static_cast<const T&>(generator()); + + (ExpectEquality<T, Prof>)(errors, "Copy assignment", + PrepareGivenContext( + GivenDeclaration{"const _T source_of_truth", + generator.description}, + GivenDeclaration{"_T object", ""}, + GivenDeclaration{ + "object", + std::string("static_cast<const _T&>(") + + generator.description + ")"}), + object, source_of_truth, "std::as_const(object)", + "source_of_truth"); + } + + ConformanceErrors* errors; +}; + +// Perform a self move-assign. +template <class T, class Prof> +struct ExpectSelfMoveAssign { + template <class Fun> + void operator()(const Fun& generator) const { + T object = generator(); + object = absl::move(object); + + // NOTE: Self move-assign results in a valid-but-unspecified state. + + (ExpectEquality<T, Prof>)(errors, "Move assignment", + PrepareGivenContext( + GivenDeclaration{"_T object", + generator.description}, + GivenDeclaration{"object", + "std::move(object)"}), + object, object, "object", "object"); + } + + ConformanceErrors* errors; +}; + +// Perform a self copy-assign. +template <class T, class Prof> +struct ExpectSelfCopyAssign { + template <class Fun> + void operator()(const Fun& generator) const { + const T source_of_truth = generator(); + T object = generator(); + const T& const_object = object; + object = const_object; + + (ExpectEquality<T, Prof>)(errors, "Copy assignment", + PrepareGivenContext( + GivenDeclaration{"const _T source_of_truth", + generator.description}, + GivenDeclaration{"_T object", + generator.description}, + GivenDeclaration{"object", + "std::as_const(object)"}), + const_object, source_of_truth, + "std::as_const(object)", "source_of_truth"); + } + + ConformanceErrors* errors; +}; + +// Perform a self-swap. +template <class T, class Prof> +struct ExpectSelfSwap { + template <class Fun> + void operator()(const Fun& generator) const { + const T source_of_truth = generator(); + T object = generator(); + + type_traits_internal::Swap(object, object); + + std::string preliminary_info = absl::StrCat( + PrepareGivenContext( + GivenDeclaration{"const _T source_of_truth", generator.description}, + GivenDeclaration{"_T object", generator.description}), + "After performing a self-swap:\n" + " using std::swap;\n" + " swap(object, object);\n" + "\n"); + + (ExpectEquality<T, Prof>)(errors, "Swap", std::move(preliminary_info), + object, source_of_truth, "std::as_const(object)", + "source_of_truth"); + } + + ConformanceErrors* errors; +}; + +// Perform each of the single-generator checks when necessary operations are +// supported. +template <class T, class Prof> +struct ExpectSelfComparison { + template <class Fun> + void operator()(const Fun& generator) const { + const T object = generator(); + (ExpectOneWayEquality<T, Prof>)(errors, "Comparison", + PrepareGivenContext(GivenDeclaration{ + "const _T object", + generator.description}), + object, object, "object", "object"); + } + + ConformanceErrors* errors; +}; + +// Perform each of the single-generator checks when necessary operations are +// supported. +template <class T, class Prof> +struct ExpectConsistency { + template <class Fun> + void operator()(const Fun& generator) const { + If<PropertiesOfT<Prof>::is_move_constructible>::Invoke( + ExpectMoveConstructOneGenerator<T, Prof>{errors}, generator); + + If<PropertiesOfT<Prof>::is_copy_constructible>::Invoke( + ExpectCopyConstructOneGenerator<T, Prof>{errors}, generator); + + If<PropertiesOfT<Prof>::is_default_constructible && + PropertiesOfT<Prof>::is_move_assignable>:: + Invoke(ExpectDefaultConstructWithMoveAssign<T, Prof>{errors}, + generator); + + If<PropertiesOfT<Prof>::is_default_constructible && + PropertiesOfT<Prof>::is_copy_assignable>:: + Invoke(ExpectDefaultConstructWithCopyAssign<T, Prof>{errors}, + generator); + + If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( + ExpectSelfMoveAssign<T, Prof>{errors}, generator); + + If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( + ExpectSelfCopyAssign<T, Prof>{errors}, generator); + + If<PropertiesOfT<Prof>::is_swappable>::Invoke( + ExpectSelfSwap<T, Prof>{errors}, generator); + } + + ConformanceErrors* errors; +}; + +// Check move-assign with two different values. +template <class T, class Prof> +struct ExpectMoveAssign { + template <class Fun0, class Fun1> + void operator()(const Fun0& generator0, const Fun1& generator1) const { + const T source_of_truth1 = generator1(); + T object = generator0(); + object = generator1(); + + (ExpectEquality<T, Prof>)(errors, "Move assignment", + PrepareGivenContext( + GivenDeclaration{"const _T source_of_truth1", + generator1.description}, + GivenDeclaration{"_T object", + generator0.description}, + GivenDeclaration{"object", + generator1.description}), + object, source_of_truth1, "std::as_const(object)", + "source_of_truth1"); + } + + ConformanceErrors* errors; +}; + +// Check copy-assign with two different values. +template <class T, class Prof> +struct ExpectCopyAssign { + template <class Fun0, class Fun1> + void operator()(const Fun0& generator0, const Fun1& generator1) const { + const T source_of_truth1 = generator1(); + T object = generator0(); + object = static_cast<const T&>(generator1()); + + (ExpectEquality<T, Prof>)(errors, "Copy assignment", + PrepareGivenContext( + GivenDeclaration{"const _T source_of_truth1", + generator1.description}, + GivenDeclaration{"_T object", + generator0.description}, + GivenDeclaration{ + "object", + std::string("static_cast<const _T&>(") + + generator1.description + ")"}), + object, source_of_truth1, "std::as_const(object)", + "source_of_truth1"); + } + + ConformanceErrors* errors; +}; + +// Check swap with two different values. +template <class T, class Prof> +struct ExpectSwap { + template <class Fun0, class Fun1> + void operator()(const Fun0& generator0, const Fun1& generator1) const { + const T source_of_truth0 = generator0(); + const T source_of_truth1 = generator1(); + T object0 = generator0(); + T object1 = generator1(); + + type_traits_internal::Swap(object0, object1); + + const std::string context = + PrepareGivenContext( + GivenDeclaration{"const _T source_of_truth0", + generator0.description}, + GivenDeclaration{"const _T source_of_truth1", + generator1.description}, + GivenDeclaration{"_T object0", generator0.description}, + GivenDeclaration{"_T object1", generator1.description}) + + "After performing a swap:\n" + " using std::swap;\n" + " swap(object0, object1);\n" + "\n"; + + (ExpectEquality<T, Prof>)(errors, "Swap", context, object0, + source_of_truth1, "std::as_const(object0)", + "source_of_truth1"); + (ExpectEquality<T, Prof>)(errors, "Swap", context, object1, + source_of_truth0, "std::as_const(object1)", + "source_of_truth0"); + } + + ConformanceErrors* errors; +}; + +// Validate that `generator0` and `generator1` produce values that are equal. +template <class T, class Prof> +struct ExpectEquivalenceClassComparison { + template <class Fun0, class Fun1> + void operator()(const Fun0& generator0, const Fun1& generator1) const { + const T object0 = generator0(); + const T object1 = generator1(); + + (ExpectEquality<T, Prof>)(errors, "Comparison", + PrepareGivenContext( + GivenDeclaration{"const _T object0", + generator0.description}, + GivenDeclaration{"const _T object1", + generator1.description}), + object0, object1, "object0", "object1"); + } + + ConformanceErrors* errors; +}; + +// Validate that all objects in the same equivalence-class have the same value. +template <class T, class Prof> +struct ExpectEquivalenceClassConsistency { + template <class Fun0, class Fun1> + void operator()(const Fun0& generator0, const Fun1& generator1) const { + If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( + ExpectMoveAssign<T, Prof>{errors}, generator0, generator1); + + If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( + ExpectCopyAssign<T, Prof>{errors}, generator0, generator1); + + If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors}, + generator0, generator1); + } + + ConformanceErrors* errors; +}; + +// Given a "lesser" object and a "greater" object, perform every combination of +// comparison operators supported for the type, expecting consistent results. +template <class T, class Prof> +void ExpectOrdered(ConformanceErrors* errors, absl::string_view context, + const T& small, const T& big, absl::string_view small_name, + absl::string_view big_name) { + const absl::string_view test_name = "Comparison"; + + If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( + ExpectNotEq{errors}, test_name, context, small, big, small_name, + big_name); + If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( + ExpectNotEq{errors}, test_name, context, big, small, big_name, + small_name); + + If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( + ExpectNe{errors}, test_name, context, small, big, small_name, big_name); + If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( + ExpectNe{errors}, test_name, context, big, small, big_name, small_name); + + If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( + ExpectLt{errors}, test_name, context, small, big, small_name, big_name); + If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( + ExpectNotLt{errors}, test_name, context, big, small, big_name, + small_name); + + If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( + ExpectLe{errors}, test_name, context, small, big, small_name, big_name); + If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( + ExpectNotLe{errors}, test_name, context, big, small, big_name, + small_name); + + If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( + ExpectNotGe{errors}, test_name, context, small, big, small_name, + big_name); + If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( + ExpectGe{errors}, test_name, context, big, small, big_name, small_name); + + If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( + ExpectNotGt{errors}, test_name, context, small, big, small_name, + big_name); + If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( + ExpectGt{errors}, test_name, context, big, small, big_name, small_name); +} + +// For every two elements of an equivalence class, makes sure that those two +// elements compare equal, including checks with the same argument passed as +// both operands. +template <class T, class Prof> +struct ExpectEquivalenceClassComparisons { + template <class... Funs> + void operator()(EquivalenceClassType<Funs...> eq_class) const { + (ForEachTupleElement)(ExpectSelfComparison<T, Prof>{errors}, + eq_class.generators); + + (ForEveryTwo)(ExpectEquivalenceClassComparison<T, Prof>{errors}, + eq_class.generators); + } + + ConformanceErrors* errors; +}; + +// For every element of an equivalence class, makes sure that the element is +// self-consistent (in other words, if any of move/copy/swap are defined, +// perform those operations and make such that results and operands still +// compare equal to known values whenever it is required for that operation. +template <class T, class Prof> +struct ExpectEquivalenceClass { + template <class... Funs> + void operator()(EquivalenceClassType<Funs...> eq_class) const { + (ForEachTupleElement)(ExpectConsistency<T, Prof>{errors}, + eq_class.generators); + + (ForEveryTwo)(ExpectEquivalenceClassConsistency<T, Prof>{errors}, + eq_class.generators); + } + + ConformanceErrors* errors; +}; + +// Validate that the passed-in argument is a generator of a greater value than +// the one produced by the "small_gen" datamember with respect to all of the +// comparison operators that Prof requires, with both argument orders to test. +template <class T, class Prof, class SmallGenerator> +struct ExpectBiggerGeneratorThanComparisons { + template <class BigGenerator> + void operator()(BigGenerator big_gen) const { + const T small = small_gen(); + const T big = big_gen(); + + (ExpectOrdered<T, Prof>)(errors, + PrepareGivenContext( + GivenDeclaration{"const _T small", + small_gen.description}, + GivenDeclaration{"const _T big", + big_gen.description}), + small, big, "small", "big"); + } + + SmallGenerator small_gen; + ConformanceErrors* errors; +}; + +// Perform all of the move, copy, and swap checks on the value generated by +// `small_gen` and the value generated by `big_gen`. +template <class T, class Prof, class SmallGenerator> +struct ExpectBiggerGeneratorThan { + template <class BigGenerator> + void operator()(BigGenerator big_gen) const { + If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( + ExpectMoveAssign<T, Prof>{errors}, small_gen, big_gen); + If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( + ExpectMoveAssign<T, Prof>{errors}, big_gen, small_gen); + + If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( + ExpectCopyAssign<T, Prof>{errors}, small_gen, big_gen); + If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( + ExpectCopyAssign<T, Prof>{errors}, big_gen, small_gen); + + If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors}, + small_gen, big_gen); + } + + SmallGenerator small_gen; + ConformanceErrors* errors; +}; + +// Validate that the result of a generator is greater than the results of all +// generators in an equivalence class with respect to comparisons. +template <class T, class Prof, class SmallGenerator> +struct ExpectBiggerGeneratorThanEqClassesComparisons { + template <class BigEqClass> + void operator()(BigEqClass big_eq_class) const { + (ForEachTupleElement)( + ExpectBiggerGeneratorThanComparisons<T, Prof, SmallGenerator>{small_gen, + errors}, + big_eq_class.generators); + } + + SmallGenerator small_gen; + ConformanceErrors* errors; +}; + +// Validate that the non-comparison binary operations required by Prof are +// correct for the result of each generator of big_eq_class and a generator of +// the logically smaller value returned by small_gen. +template <class T, class Prof, class SmallGenerator> +struct ExpectBiggerGeneratorThanEqClasses { + template <class BigEqClass> + void operator()(BigEqClass big_eq_class) const { + (ForEachTupleElement)( + ExpectBiggerGeneratorThan<T, Prof, SmallGenerator>{small_gen, errors}, + big_eq_class.generators); + } + + SmallGenerator small_gen; + ConformanceErrors* errors; +}; + +// Validate that each equivalence class that is passed is logically less than +// the equivalence classes that comes later on in the argument list. +template <class T, class Prof> +struct ExpectOrderedEquivalenceClassesComparisons { + template <class... BigEqClasses> + struct Impl { + // Validate that the value produced by `small_gen` is less than all of the + // values generated by those of the logically larger equivalence classes. + template <class SmallGenerator> + void operator()(SmallGenerator small_gen) const { + (ForEachTupleElement)(ExpectBiggerGeneratorThanEqClassesComparisons< + T, Prof, SmallGenerator>{small_gen, errors}, + big_eq_classes); + } + + std::tuple<BigEqClasses...> big_eq_classes; + ConformanceErrors* errors; + }; + + // When given no equivalence classes, no validation is necessary. + void operator()() const {} + + template <class SmallEqClass, class... BigEqClasses> + void operator()(SmallEqClass small_eq_class, + BigEqClasses... big_eq_classes) const { + // For each generator in the first equivalence class, make sure that it is + // less than each of those in the logically greater equivalence classes. + (ForEachTupleElement)( + Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...), + errors}, + small_eq_class.generators); + + // Recurse so that all equivalence class combinations are checked. + (*this)(absl::move(big_eq_classes)...); + } + + ConformanceErrors* errors; +}; + +// Validate that the non-comparison binary operations required by Prof are +// correct for the result of each generator of big_eq_classes and a generator of +// the logically smaller value returned by small_gen. +template <class T, class Prof> +struct ExpectOrderedEquivalenceClasses { + template <class... BigEqClasses> + struct Impl { + template <class SmallGenerator> + void operator()(SmallGenerator small_gen) const { + (ForEachTupleElement)( + ExpectBiggerGeneratorThanEqClasses<T, Prof, SmallGenerator>{small_gen, + errors}, + big_eq_classes); + } + + std::tuple<BigEqClasses...> big_eq_classes; + ConformanceErrors* errors; + }; + + // Check that small_eq_class is logically consistent and also is logically + // less than all values in big_eq_classes. + template <class SmallEqClass, class... BigEqClasses> + void operator()(SmallEqClass small_eq_class, + BigEqClasses... big_eq_classes) const { + (ForEachTupleElement)( + Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...), + errors}, + small_eq_class.generators); + + (*this)(absl::move(big_eq_classes)...); + } + + // Terminating case of operator(). + void operator()() const {} + + ConformanceErrors* errors; +}; + +// Validate that a type meets the syntactic requirements of std::hash if the +// range of profiles requires it. +template <class T, class MinProf, class MaxProf> +struct ExpectHashable { + void operator()() const { + ExpectModelOfHashable<T, MinProf, MaxProf>(errors); + } + + ConformanceErrors* errors; +}; + +// Validate that the type `T` meets all of the requirements associated with +// `MinProf` and without going beyond the syntactic properties of `MaxProf`. +template <class T, class MinProf, class MaxProf> +struct ExpectModels { + void operator()(ConformanceErrors* errors) const { + ExpectModelOfDefaultConstructible<T, MinProf, MaxProf>(errors); + ExpectModelOfMoveConstructible<T, MinProf, MaxProf>(errors); + ExpectModelOfCopyConstructible<T, MinProf, MaxProf>(errors); + ExpectModelOfMoveAssignable<T, MinProf, MaxProf>(errors); + ExpectModelOfCopyAssignable<T, MinProf, MaxProf>(errors); + ExpectModelOfDestructible<T, MinProf, MaxProf>(errors); + ExpectModelOfEqualityComparable<T, MinProf, MaxProf>(errors); + ExpectModelOfInequalityComparable<T, MinProf, MaxProf>(errors); + ExpectModelOfLessThanComparable<T, MinProf, MaxProf>(errors); + ExpectModelOfLessEqualComparable<T, MinProf, MaxProf>(errors); + ExpectModelOfGreaterEqualComparable<T, MinProf, MaxProf>(errors); + ExpectModelOfGreaterThanComparable<T, MinProf, MaxProf>(errors); + ExpectModelOfSwappable<T, MinProf, MaxProf>(errors); + + // Only check hashability on compilers that have a compliant default-hash. + If<!poisoned_hash_fails_instantiation()>::Invoke( + ExpectHashable<T, MinProf, MaxProf>{errors}); + } +}; + +// A metafunction that yields a Profile matching the set of properties that are +// safe to be checked (lack-of-hashability is only checked on standard library +// implementations that are standards compliant in that they provide a std::hash +// primary template that is SFINAE-friendly) +template <class LogicalProf, class T> +struct MinimalCheckableProfile { + using type = + MinimalProfiles<PropertiesOfT<LogicalProf>, + PropertiesOfT<SyntacticConformanceProfileOf< + T, !PropertiesOfT<LogicalProf>::is_hashable && + poisoned_hash_fails_instantiation() + ? CheckHashability::no + : CheckHashability::yes>>>; +}; + +// An identity metafunction +template <class T> +struct Always { + using type = T; +}; + +// Validate the T meets all of the necessary requirements of LogicalProf, with +// syntactic requirements defined by the profile range [MinProf, MaxProf]. +template <class T, class LogicalProf, class MinProf, class MaxProf, + class... EqClasses> +ConformanceErrors ExpectRegularityImpl( + OrderedEquivalenceClasses<EqClasses...> vals) { + ConformanceErrors errors((NameOf<T>())); + + If<!constexpr_instantiation_when_unevaluated()>::Invoke( + ExpectModels<T, MinProf, MaxProf>(), &errors); + + using minimal_profile = typename absl::conditional_t< + constexpr_instantiation_when_unevaluated(), Always<LogicalProf>, + MinimalCheckableProfile<LogicalProf, T>>::type; + + If<PropertiesOfT<minimal_profile>::is_default_constructible>::Invoke( + ExpectDefaultConstructWithDestruct<T>{&errors}); + + ////////////////////////////////////////////////////////////////////////////// + // Perform all comparison checks first, since later checks depend on their + // correctness. + // + // Check all of the comparisons for all values in the same equivalence + // class (equal with respect to comparison operators and hash the same). + (ForEachTupleElement)( + ExpectEquivalenceClassComparisons<T, minimal_profile>{&errors}, + vals.eq_classes); + + // Check all of the comparisons for each combination of values that are in + // different equivalence classes (not equal with respect to comparison + // operators). + absl::apply( + ExpectOrderedEquivalenceClassesComparisons<T, minimal_profile>{&errors}, + vals.eq_classes); + // + ////////////////////////////////////////////////////////////////////////////// + + // Perform remaining checks, relying on comparisons. + // TODO(calabrese) short circuit if any comparisons above failed. + (ForEachTupleElement)(ExpectEquivalenceClass<T, minimal_profile>{&errors}, + vals.eq_classes); + + absl::apply(ExpectOrderedEquivalenceClasses<T, minimal_profile>{&errors}, + vals.eq_classes); + + return errors; +} + +// A type that represents a range of profiles that are acceptable to be matched. +// +// `MinProf` is the minimum set of syntactic requirements that must be met. +// +// `MaxProf` is the maximum set of syntactic requirements that must be met. +// This maximum is particularly useful for certain "strictness" checking. Some +// examples for when this is useful: +// +// * Making sure that a type is move-only (rather than simply movable) +// +// * Making sure that a member function is *not* noexcept in cases where it +// cannot be noexcept, such as if a dependent datamember has certain +// operations that are not noexcept. +// +// * Making sure that a type tightly matches a spec, such as the standard. +// +// `LogicalProf` is the Profile for which run-time testing is to take place. +// +// Note: The reason for `LogicalProf` is because it is often the case, when +// dealing with templates, that a declaration of a given operation is specified, +// but whose body would fail to instantiate. Examples include the +// copy-constructor of a standard container when the element-type is move-only, +// or the comparison operators of a standard container when the element-type +// does not have the necessary comparison operations defined. The `LogicalProf` +// parameter allows us to capture the intent of what should be tested at +// run-time, even in the cases where syntactically it might otherwise appear as +// though the type undergoing testing supports more than it actually does. +template <class LogicalProf, class MinProf = LogicalProf, + class MaxProf = MinProf> +struct ProfileRange { + using logical_profile = LogicalProf; + using min_profile = MinProf; + using max_profile = MaxProf; +}; + +// Similar to ProfileRange except that it creates a profile range that is +// coupled with a Domain and is used when testing that a type matches exactly +// the "minimum" requirements of LogicalProf. +template <class StrictnessDomain, class LogicalProf, + class MinProf = LogicalProf, class MaxProf = MinProf> +struct StrictProfileRange { + // We do not yet support extension. + static_assert( + std::is_same<StrictnessDomain, RegularityDomain>::value, + "Currently, the only valid StrictnessDomain is RegularityDomain."); + using strictness_domain = StrictnessDomain; + using logical_profile = LogicalProf; + using min_profile = MinProf; + using max_profile = MaxProf; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// A metafunction that creates a StrictProfileRange from a Domain and either a +// Profile or ProfileRange. +template <class StrictnessDomain, class ProfOrRange> +struct MakeStrictProfileRange; + +template <class StrictnessDomain, class LogicalProf> +struct MakeStrictProfileRange { + using type = StrictProfileRange<StrictnessDomain, LogicalProf>; +}; + +template <class StrictnessDomain, class LogicalProf, class MinProf, + class MaxProf> +struct MakeStrictProfileRange<StrictnessDomain, + ProfileRange<LogicalProf, MinProf, MaxProf>> { + using type = + StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>; +}; + +template <class StrictnessDomain, class ProfOrRange> +using MakeStrictProfileRangeT = + typename MakeStrictProfileRange<StrictnessDomain, ProfOrRange>::type; +// +//////////////////////////////////////////////////////////////////////////////// + +// A profile in the RegularityDomain with the strongest possible requirements. +using MostStrictProfile = + CombineProfiles<TriviallyCompleteProfile, NothrowComparableProfile>; + +// Forms a ProfileRange that treats the Profile as the bare minimum requirements +// of a type. +template <class LogicalProf, class MinProf = LogicalProf> +using LooseProfileRange = StrictProfileRange<RegularityDomain, LogicalProf, + MinProf, MostStrictProfile>; + +template <class Prof> +using MakeLooseProfileRangeT = Prof; + +//////////////////////////////////////////////////////////////////////////////// +// +// The following classes implement the metafunction ProfileRangeOfT<T> that +// takes either a Profile or ProfileRange and yields the ProfileRange to be +// used during testing. +// +template <class T, class /*Enabler*/ = void> +struct ProfileRangeOfImpl; + +template <class T> +struct ProfileRangeOfImpl<T, absl::void_t<PropertiesOfT<T>>> { + using type = LooseProfileRange<T>; +}; + +template <class T> +struct ProfileRangeOf : ProfileRangeOfImpl<T> {}; + +template <class StrictnessDomain, class LogicalProf, class MinProf, + class MaxProf> +struct ProfileRangeOf< + StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> { + using type = + StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>; +}; + +template <class T> +using ProfileRangeOfT = typename ProfileRangeOf<T>::type; +// +//////////////////////////////////////////////////////////////////////////////// + +// Extract the logical profile of a range (what will be runtime tested). +template <class T> +using LogicalProfileOfT = typename ProfileRangeOfT<T>::logical_profile; + +// Extract the minimal syntactic profile of a range (error if not at least). +template <class T> +using MinProfileOfT = typename ProfileRangeOfT<T>::min_profile; + +// Extract the maximum syntactic profile of a range (error if more than). +template <class T> +using MaxProfileOfT = typename ProfileRangeOfT<T>::max_profile; + +//////////////////////////////////////////////////////////////////////////////// +// +template <class T> +struct IsProfileOrProfileRange : IsProfile<T>::type {}; + +template <class StrictnessDomain, class LogicalProf, class MinProf, + class MaxProf> +struct IsProfileOrProfileRange< + StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> + : std::true_type {}; +// +//////////////////////////////////////////////////////////////////////////////// + +// TODO(calabrese): Consider naming the functions in this class the same as +// the macros (defined later on) so that auto-complete leads to the correct name +// and so that a user cannot accidentally call a function rather than the macro +// form. +template <bool ExpectSuccess, class T, class... EqClasses> +struct ExpectConformanceOf { + // Add a value to be tested. Subsequent calls to this function on the same + // object must specify logically "larger" values with respect to the + // comparison operators of the type, if any. + // + // NOTE: This function should not be called directly. A stateless lambda is + // implicitly formed and passed when using the INITIALIZER macro at the bottom + // of this file. + template <class Fun, + absl::enable_if_t<std::is_same< + ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr> + ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., + EquivalenceClassType<Fun>> + initializer(GeneratorType<Fun> fun) && { + return { + {std::tuple_cat(absl::move(ordered_vals.eq_classes), + std::make_tuple((EquivalenceClass)(absl::move(fun))))}, + std::move(expected_failed_tests)}; + } + + template <class... TestNames, + absl::enable_if_t<!ExpectSuccess && sizeof...(EqClasses) == 0 && + absl::conjunction<std::is_convertible< + TestNames, absl::string_view>...>::value>** = + nullptr> + ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...> + due_to(TestNames&&... test_names) && { + (InsertEach)(&expected_failed_tests, + absl::AsciiStrToLower(absl::string_view(test_names))...); + + return {absl::move(ordered_vals), std::move(expected_failed_tests)}; + } + + template <class... TestNames, int = 0, // MSVC disambiguator + absl::enable_if_t<ExpectSuccess && sizeof...(EqClasses) == 0 && + absl::conjunction<std::is_convertible< + TestNames, absl::string_view>...>::value>** = + nullptr> + ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...> + due_to(TestNames&&... test_names) && { + // TODO(calabrese) Instead have DUE_TO only exist via a CRTP base. + // This would produce better errors messages than the static_assert. + static_assert(!ExpectSuccess, + "DUE_TO cannot be called when conformance is expected -- did " + "you mean to use ASSERT_NONCONFORMANCE_OF?"); + } + + // Add a value to be tested. Subsequent calls to this function on the same + // object must specify logically "larger" values with respect to the + // comparison operators of the type, if any. + // + // NOTE: This function should not be called directly. A stateful lambda is + // implicitly formed and passed when using the INITIALIZER macro at the bottom + // of this file. + template <class Fun, + absl::enable_if_t<std::is_same< + ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr> + ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., + EquivalenceClassType<Fun>> + dont_class_directly_stateful_initializer(GeneratorType<Fun> fun) && { + return { + {std::tuple_cat(absl::move(ordered_vals.eq_classes), + std::make_tuple((EquivalenceClass)(absl::move(fun))))}, + std::move(expected_failed_tests)}; + } + + // Add a set of value to be tested, where each value is equal with respect to + // the comparison operators and std::hash specialization, if defined. + template < + class... Funs, + absl::void_t<absl::enable_if_t<std::is_same< + ResultOfGeneratorT<GeneratorType<Funs>>, T>::value>...>** = nullptr> + ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., + EquivalenceClassType<Funs...>> + equivalence_class(GeneratorType<Funs>... funs) && { + return {{std::tuple_cat( + absl::move(ordered_vals.eq_classes), + std::make_tuple((EquivalenceClass)(absl::move(funs)...)))}, + std::move(expected_failed_tests)}; + } + + // Execute the tests for the captured set of values, strictly matching a range + // of expected profiles in a given domain. + template < + class ProfRange, + absl::enable_if_t<IsProfileOrProfileRange<ProfRange>::value>** = nullptr> + ABSL_MUST_USE_RESULT ::testing::AssertionResult with_strict_profile( + ProfRange /*profile*/) { + ConformanceErrors test_result = + (ExpectRegularityImpl< + T, LogicalProfileOfT<ProfRange>, MinProfileOfT<ProfRange>, + MaxProfileOfT<ProfRange>>)(absl::move(ordered_vals)); + + return ExpectSuccess ? test_result.assertionResult() + : test_result.expectFailedTests(expected_failed_tests); + } + + // Execute the tests for the captured set of values, loosely matching a range + // of expected profiles (loose in that an interface is allowed to be more + // refined that a profile suggests, such as a type having a noexcept copy + // constructor when all that is required is that the copy constructor exists). + template <class Prof, absl::enable_if_t<IsProfile<Prof>::value>** = nullptr> + ABSL_MUST_USE_RESULT ::testing::AssertionResult with_loose_profile( + Prof /*profile*/) { + ConformanceErrors test_result = + (ExpectRegularityImpl< + T, Prof, Prof, + CombineProfiles<TriviallyCompleteProfile, + NothrowComparableProfile>>)(absl:: + move(ordered_vals)); + + return ExpectSuccess ? test_result.assertionResult() + : test_result.expectFailedTests(expected_failed_tests); + } + + OrderedEquivalenceClasses<EqClasses...> ordered_vals; + std::set<std::string> expected_failed_tests; +}; + +template <class T> +using ExpectConformanceOfType = ExpectConformanceOf</*ExpectSuccess=*/true, T>; + +template <class T> +using ExpectNonconformanceOfType = + ExpectConformanceOf</*ExpectSuccess=*/false, T>; + +struct EquivalenceClassMaker { + // TODO(calabrese) Constrain to callable + template <class Fun> + static GeneratorType<Fun> initializer(GeneratorType<Fun> fun) { + return fun; + } +}; + +// A top-level macro that begins the builder pattern. +// +// The argument here takes the datatype to be tested. +#define ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(...) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if ABSL_INTERNAL_LPAREN \ + const ::testing::AssertionResult gtest_ar = \ + ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectConformanceOfType< \ + __VA_ARGS__>() + +// Akin to ASSERT_CONFORMANCE_OF except that it expects failure and tries to +// match text. +#define ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(...) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if ABSL_INTERNAL_LPAREN \ + const ::testing::AssertionResult gtest_ar = \ + ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectNonconformanceOfType< \ + __VA_ARGS__>() + +//////////////////////////////////////////////////////////////////////////////// +// NOTE: The following macros look like they are recursive, but are not (macros +// cannot recurse). These actually refer to member functions of the same name. +// This is done intentionally so that a user cannot accidentally invoke a +// member function of the conformance-testing suite without going through the +// macro. +//////////////////////////////////////////////////////////////////////////////// + +// Specify expected test failures as comma-separated strings. +#define DUE_TO(...) due_to(__VA_ARGS__) + +// Specify a value to be tested. +// +// Note: Internally, this takes an expression and turns it into the return value +// of lambda that captures no data. The expression is stringized during +// preprocessing so that it can be used in error reports. +#define INITIALIZER(...) \ + initializer(::absl::types_internal::Generator( \ + [] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__))) + +// Specify a value to be tested. +// +// Note: Internally, this takes an expression and turns it into the return value +// of lambda that captures data by reference. The expression is stringized +// during preprocessing so that it can be used in error reports. +#define STATEFUL_INITIALIZER(...) \ + stateful_initializer(::absl::types_internal::Generator( \ + [&] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__))) + +// Used in the builder-pattern. +// +// Takes a series of INITIALIZER and/or STATEFUL_INITIALIZER invocations and +// forwards them along to be tested, grouping them such that the testing suite +// knows that they are supposed to represent the same logical value (the values +// compare the same, hash the same, etc.). +#define EQUIVALENCE_CLASS(...) \ + equivalence_class(ABSL_INTERNAL_TRANSFORM_ARGS( \ + ABSL_INTERNAL_PREPEND_EQ_MAKER, __VA_ARGS__)) + +// An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern. +// It takes a Profile as its argument. +// +// This executes the tests and allows types that are "more referined" than the +// profile specifies, but not less. For instance, if the Profile specifies +// noexcept copy-constructiblity, the test will fail if the copy-constructor is +// not noexcept, however, it will succeed if the copy constructor is trivial. +// +// This is useful for testing that a type meets some minimum set of +// requirements. +#define WITH_LOOSE_PROFILE(...) \ + with_loose_profile( \ + ::absl::types_internal::MakeLooseProfileRangeT<__VA_ARGS__>()) \ + ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \ + else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT + +// An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern. +// It takes a Domain and a Profile as its arguments. +// +// This executes the tests and disallows types that differ at all from the +// properties of the Profile. For instance, if the Profile specifies noexcept +// copy-constructiblity, the test will fail if the copy constructor is trivial. +// +// This is useful for testing that a type does not do anything more than a +// specification requires, such as to minimize things like Hyrum's Law, or more +// commonly, to prevent a type from being "accidentally" copy-constructible in +// a way that may produce incorrect results, simply because the user forget to +// delete that operation. +#define WITH_STRICT_PROFILE(...) \ + with_strict_profile( \ + ::absl::types_internal::MakeStrictProfileRangeT<__VA_ARGS__>()) \ + ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \ + else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT + +// Internal macro that is used in the internals of the EDSL when forming +// equivalence classes. +#define ABSL_INTERNAL_PREPEND_EQ_MAKER(arg) \ + ::absl::types_internal::EquivalenceClassMaker().arg + +} // namespace types_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ diff --git a/absl/types/internal/conformance_testing_helpers.h b/absl/types/internal/conformance_testing_helpers.h new file mode 100644 index 000000000000..00775f960c36 --- /dev/null +++ b/absl/types/internal/conformance_testing_helpers.h @@ -0,0 +1,391 @@ +// 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. + +#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_ +#define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_ + +// Checks to determine whether or not we can use abi::__cxa_demangle +#if (defined(__ANDROID__) || defined(ANDROID)) && !defined(OS_ANDROID) +#define ABSL_INTERNAL_OS_ANDROID +#endif + +// We support certain compilers only. See demangle.h for details. +#if defined(OS_ANDROID) && (defined(__i386__) || defined(__x86_64__)) +#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 0 +#elif (__GNUC__ >= 4 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4)) && \ + !defined(__mips__) +#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 1 +#elif defined(__clang__) && !defined(_MSC_VER) +#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 1 +#else +#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 0 +#endif + +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/meta/type_traits.h" +#include "absl/strings/string_view.h" +#include "absl/utility/utility.h" + +#if ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE +#include <cxxabi.h> + +#include <cstdlib> +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace types_internal { + +// Return a readable name for type T. +template <class T> +absl::string_view NameOfImpl() { +// TODO(calabrese) Investigate using debugging:internal_demangle as a fallback. +#if ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE + int status = 0; + char* demangled_name = nullptr; + + demangled_name = + abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status); + + if (status == 0 && demangled_name != nullptr) { + return demangled_name; + } else { + return typeid(T).name(); + } +#else + return typeid(T).name(); +#endif + // NOTE: We intentionally leak demangled_name so that it remains valid + // throughout the remainder of the program. +} + +// Given a type, returns as nice of a type name as we can produce (demangled). +// +// Note: This currently strips cv-qualifiers and references, but that is okay +// because we only use this internally with unqualified object types. +template <class T> +std::string NameOf() { + static const absl::string_view result = NameOfImpl<T>(); + return std::string(result); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Metafunction to check if a type is callable with no explicit arguments +template <class Fun, class /*Enabler*/ = void> +struct IsNullaryCallableImpl : std::false_type {}; + +template <class Fun> +struct IsNullaryCallableImpl< + Fun, absl::void_t<decltype(std::declval<const Fun&>()())>> + : std::true_type { + using result_type = decltype(std::declval<const Fun&>()()); + + template <class ValueType> + using for_type = std::is_same<ValueType, result_type>; + + using void_if_true = void; +}; + +template <class Fun> +struct IsNullaryCallable : IsNullaryCallableImpl<Fun> {}; +// +//////////////////////////////////////////////////////////////////////////////// + +// A type that contains a function object that returns an instance of a type +// that is undergoing conformance testing. This function is required to always +// return the same value upon invocation. +template <class Fun> +struct GeneratorType; + +// A type that contains a tuple of GeneratorType<Fun> where each Fun has the +// same return type. The result of each of the different generators should all +// be equal values, though the underlying object representation may differ (such +// as if one returns 0.0 and another return -0.0, or if one returns an empty +// vector and another returns an empty vector with a different capacity. +template <class... Funs> +struct EquivalenceClassType; + +//////////////////////////////////////////////////////////////////////////////// +// +// A metafunction to check if a type is a specialization of EquivalenceClassType +template <class T> +struct IsEquivalenceClass : std::false_type {}; + +template <> +struct IsEquivalenceClass<EquivalenceClassType<>> : std::true_type { + using self = IsEquivalenceClass; + + // A metafunction to check if this EquivalenceClassType is a valid + // EquivalenceClassType for a type `ValueType` that is undergoing testing + template <class ValueType> + using for_type = std::true_type; +}; + +template <class Head, class... Tail> +struct IsEquivalenceClass<EquivalenceClassType<Head, Tail...>> + : std::true_type { + using self = IsEquivalenceClass; + + // The type undergoing conformance testing that this EquivalenceClass + // corresponds to + using result_type = typename IsNullaryCallable<Head>::result_type; + + // A metafunction to check if this EquivalenceClassType is a valid + // EquivalenceClassType for a type `ValueType` that is undergoing testing + template <class ValueType> + using for_type = std::is_same<ValueType, result_type>; +}; +// +//////////////////////////////////////////////////////////////////////////////// + +// A type that contains an ordered series of EquivalenceClassTypes, where the +// the function object of each underlying GeneratorType has the same return type +// +// These equivalence classes are required to be in a logical ascending order +// that is consistent with comparison operators that are defined for the return +// type of each GeneratorType, if any. +template <class... EqClasses> +struct OrderedEquivalenceClasses; + +//////////////////////////////////////////////////////////////////////////////// +// +// A metafunction to determine the return type of the function object contained +// in a GeneratorType specialization. +template <class T> +struct ResultOfGenerator {}; + +template <class Fun> +struct ResultOfGenerator<GeneratorType<Fun>> { + using type = decltype(std::declval<const Fun&>()()); +}; + +template <class Fun> +using ResultOfGeneratorT = typename ResultOfGenerator<GeneratorType<Fun>>::type; +// +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// +// A metafunction that yields true iff each of Funs is a GeneratorType +// specialization and they all contain functions with the same return type +template <class /*Enabler*/, class... Funs> +struct AreGeneratorsWithTheSameReturnTypeImpl : std::false_type {}; + +template <> +struct AreGeneratorsWithTheSameReturnTypeImpl<void> : std::true_type {}; + +template <class Head, class... Tail> +struct AreGeneratorsWithTheSameReturnTypeImpl< + typename std::enable_if<absl::conjunction<std::is_same< + ResultOfGeneratorT<Head>, ResultOfGeneratorT<Tail>>...>::value>::type, + Head, Tail...> : std::true_type {}; + +template <class... Funs> +struct AreGeneratorsWithTheSameReturnType + : AreGeneratorsWithTheSameReturnTypeImpl<void, Funs...>::type {}; +// +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// +// A metafunction that yields true iff each of Funs is an EquivalenceClassType +// specialization and they all contain GeneratorType specializations that have +// the same return type +template <class... EqClasses> +struct AreEquivalenceClassesOfTheSameType { + static_assert(sizeof...(EqClasses) != sizeof...(EqClasses), ""); +}; + +template <> +struct AreEquivalenceClassesOfTheSameType<> : std::true_type { + using self = AreEquivalenceClassesOfTheSameType; + + // Metafunction to check that a type is the same as all of the equivalence + // classes, if any. + // Note: In this specialization there are no equivalence classes, so the + // value type is always compatible. + template <class /*ValueType*/> + using for_type = std::true_type; +}; + +template <class... Funs> +struct AreEquivalenceClassesOfTheSameType<EquivalenceClassType<Funs...>> + : std::true_type { + using self = AreEquivalenceClassesOfTheSameType; + + // Metafunction to check that a type is the same as all of the equivalence + // classes, if any. + template <class ValueType> + using for_type = typename IsEquivalenceClass< + EquivalenceClassType<Funs...>>::template for_type<ValueType>; +}; + +template <class... TailEqClasses> +struct AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<>, EquivalenceClassType<>, TailEqClasses...> + : AreEquivalenceClassesOfTheSameType<TailEqClasses...>::self {}; + +template <class HeadNextFun, class... TailNextFuns, class... TailEqClasses> +struct AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<>, EquivalenceClassType<HeadNextFun, TailNextFuns...>, + TailEqClasses...> + : AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<HeadNextFun, TailNextFuns...>, + TailEqClasses...>::self {}; + +template <class HeadHeadFun, class... TailHeadFuns, class... TailEqClasses> +struct AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, EquivalenceClassType<>, + TailEqClasses...> + : AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, + TailEqClasses...>::self {}; + +template <class HeadHeadFun, class... TailHeadFuns, class HeadNextFun, + class... TailNextFuns, class... TailEqClasses> +struct AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, + EquivalenceClassType<HeadNextFun, TailNextFuns...>, TailEqClasses...> + : absl::conditional_t< + IsNullaryCallable<HeadNextFun>::template for_type< + typename IsNullaryCallable<HeadHeadFun>::result_type>::value, + AreEquivalenceClassesOfTheSameType< + EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, + TailEqClasses...>, + std::false_type> {}; +// +//////////////////////////////////////////////////////////////////////////////// + +// Execute a function for each passed-in parameter. +template <class Fun, class... Cases> +void ForEachParameter(const Fun& fun, const Cases&... cases) { + const std::initializer_list<bool> results = { + (static_cast<void>(fun(cases)), true)...}; + + (void)results; +} + +// Execute a function on each passed-in parameter (using a bound function). +template <class Fun> +struct ForEachParameterFun { + template <class... T> + void operator()(const T&... cases) const { + (ForEachParameter)(fun, cases...); + } + + Fun fun; +}; + +// Execute a function on each element of a tuple. +template <class Fun, class Tup> +void ForEachTupleElement(const Fun& fun, const Tup& tup) { + absl::apply(ForEachParameterFun<Fun>{fun}, tup); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Execute a function for each combination of two elements of a tuple, including +// combinations of an element with itself. +template <class Fun, class... T> +struct ForEveryTwoImpl { + template <class Lhs> + struct WithBoundLhs { + template <class Rhs> + void operator()(const Rhs& rhs) const { + fun(lhs, rhs); + } + + Fun fun; + Lhs lhs; + }; + + template <class Lhs> + void operator()(const Lhs& lhs) const { + (ForEachTupleElement)(WithBoundLhs<Lhs>{fun, lhs}, args); + } + + Fun fun; + std::tuple<T...> args; +}; + +template <class Fun, class... T> +void ForEveryTwo(const Fun& fun, std::tuple<T...> args) { + (ForEachTupleElement)(ForEveryTwoImpl<Fun, T...>{fun, args}, args); +} +// +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// +// Insert all values into an associative container +template<class Container> +void InsertEach(Container* cont) { +} + +template<class Container, class H, class... T> +void InsertEach(Container* cont, H&& head, T&&... tail) { + cont->insert(head); + (InsertEach)(cont, tail...); +} +// +//////////////////////////////////////////////////////////////////////////////// +// A template with a nested "Invoke" static-member-function that executes a +// passed-in Callable when `Condition` is true, otherwise it ignores the +// Callable. This is useful for executing a function object with a condition +// that corresponds to whether or not the Callable can be safely instantiated. +// It has some overlapping uses with C++17 `if constexpr`. +template <bool Condition> +struct If; + +template <> +struct If</*Condition =*/false> { + template <class Fun, class... P> + static void Invoke(const Fun& /*fun*/, P&&... /*args*/) {} +}; + +template <> +struct If</*Condition =*/true> { + template <class Fun, class... P> + static void Invoke(const Fun& fun, P&&... args) { + // TODO(calabrese) Use std::invoke equivalent instead of function-call. + fun(absl::forward<P>(args)...); + } +}; + +// +// ABSL_INTERNAL_STRINGIZE(...) +// +// This variadic macro transforms its arguments into a c-string literal after +// expansion. +// +// Example: +// +// ABSL_INTERNAL_STRINGIZE(std::array<int, 10>) +// +// Results in: +// +// "std::array<int, 10>" +#define ABSL_INTERNAL_STRINGIZE(...) ABSL_INTERNAL_STRINGIZE_IMPL((__VA_ARGS__)) +#define ABSL_INTERNAL_STRINGIZE_IMPL(arg) ABSL_INTERNAL_STRINGIZE_IMPL2 arg +#define ABSL_INTERNAL_STRINGIZE_IMPL2(...) #__VA_ARGS__ + +} // namespace types_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_ diff --git a/absl/types/internal/conformance_testing_test.cc b/absl/types/internal/conformance_testing_test.cc index 3dcf53056703..cf262fa6c222 100644 --- a/absl/types/internal/conformance_testing_test.cc +++ b/absl/types/internal/conformance_testing_test.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "absl/types/internal/conformance_testing.h" + #include <new> #include <type_traits> #include <utility> @@ -19,6 +21,7 @@ #include "gtest/gtest.h" #include "absl/meta/type_traits.h" #include "absl/types/internal/conformance_aliases.h" +#include "absl/types/internal/conformance_profile.h" namespace { @@ -1181,6 +1184,373 @@ INSTANTIATE_TYPED_TEST_SUITE_P(CommonComparable, ProfileTest, CommonComparableProfilesToTest); INSTANTIATE_TYPED_TEST_SUITE_P(Trivial, ProfileTest, TrivialProfilesToTest); -// TODO(calabrese) Test runtime results +TEST(ConformanceTestingTest, Basic) { + using profile = ti::CombineProfiles<ti::TriviallyCompleteProfile, + ti::NothrowComparableProfile>; + + using lim = std::numeric_limits<float>; + + ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(float) + .INITIALIZER(-lim::infinity()) + .INITIALIZER(lim::lowest()) + .INITIALIZER(-1.f) + .INITIALIZER(-lim::min()) + .EQUIVALENCE_CLASS(INITIALIZER(-0.f), INITIALIZER(0.f)) + .INITIALIZER(lim::min()) + .INITIALIZER(1.f) + .INITIALIZER(lim::max()) + .INITIALIZER(lim::infinity()) + .WITH_STRICT_PROFILE(absl::types_internal::RegularityDomain, profile); +} + +struct BadMoveConstruct { + BadMoveConstruct() = default; + BadMoveConstruct(BadMoveConstruct&& other) noexcept + : value(other.value + 1) {} + BadMoveConstruct& operator=(BadMoveConstruct&& other) noexcept = default; + int value = 0; + + friend bool operator==(BadMoveConstruct const& lhs, + BadMoveConstruct const& rhs) { + return lhs.value == rhs.value; + } + friend bool operator!=(BadMoveConstruct const& lhs, + BadMoveConstruct const& rhs) { + return lhs.value != rhs.value; + } +}; + +struct BadMoveAssign { + BadMoveAssign() = default; + BadMoveAssign(BadMoveAssign&& other) noexcept = default; + BadMoveAssign& operator=(BadMoveAssign&& other) noexcept { + int new_value = other.value + 1; + value = new_value; + return *this; + } + int value = 0; + + friend bool operator==(BadMoveAssign const& lhs, BadMoveAssign const& rhs) { + return lhs.value == rhs.value; + } + friend bool operator!=(BadMoveAssign const& lhs, BadMoveAssign const& rhs) { + return lhs.value != rhs.value; + } +}; + +enum class WhichCompIsBad { eq, ne, lt, le, ge, gt }; + +template <WhichCompIsBad Which> +struct BadCompare { + int value; + + friend bool operator==(BadCompare const& lhs, BadCompare const& rhs) { + return Which == WhichCompIsBad::eq ? lhs.value != rhs.value + : lhs.value == rhs.value; + } + + friend bool operator!=(BadCompare const& lhs, BadCompare const& rhs) { + return Which == WhichCompIsBad::ne ? lhs.value == rhs.value + : lhs.value != rhs.value; + } + + friend bool operator<(BadCompare const& lhs, BadCompare const& rhs) { + return Which == WhichCompIsBad::lt ? lhs.value >= rhs.value + : lhs.value < rhs.value; + } + + friend bool operator<=(BadCompare const& lhs, BadCompare const& rhs) { + return Which == WhichCompIsBad::le ? lhs.value > rhs.value + : lhs.value <= rhs.value; + } + + friend bool operator>=(BadCompare const& lhs, BadCompare const& rhs) { + return Which == WhichCompIsBad::ge ? lhs.value < rhs.value + : lhs.value >= rhs.value; + } + + friend bool operator>(BadCompare const& lhs, BadCompare const& rhs) { + return Which == WhichCompIsBad::gt ? lhs.value <= rhs.value + : lhs.value > rhs.value; + } +}; + +TEST(ConformanceTestingDeathTest, Failures) { + { + using profile = ti::CombineProfiles<ti::TriviallyCompleteProfile, + ti::NothrowComparableProfile>; + + // Note: The initializers are intentionally in the wrong order. + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(float) + .INITIALIZER(1.f) + .INITIALIZER(0.f) + .WITH_LOOSE_PROFILE(profile); + } + + { + using profile = + ti::CombineProfiles<ti::NothrowMovableProfile, ti::EquatableProfile>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadMoveConstruct) + .DUE_TO("Move construction") + .INITIALIZER(BadMoveConstruct()) + .WITH_LOOSE_PROFILE(profile); + } + + { + using profile = + ti::CombineProfiles<ti::NothrowMovableProfile, ti::EquatableProfile>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadMoveAssign) + .DUE_TO("Move assignment") + .INITIALIZER(BadMoveAssign()) + .WITH_LOOSE_PROFILE(profile); + } +} + +TEST(ConformanceTestingDeathTest, CompFailures) { + using profile = ti::ComparableProfile; + + { + using BadComp = BadCompare<WhichCompIsBad::eq>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) + .DUE_TO("Comparison") + .INITIALIZER(BadComp{0}) + .INITIALIZER(BadComp{1}) + .WITH_LOOSE_PROFILE(profile); + } + + { + using BadComp = BadCompare<WhichCompIsBad::ne>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) + .DUE_TO("Comparison") + .INITIALIZER(BadComp{0}) + .INITIALIZER(BadComp{1}) + .WITH_LOOSE_PROFILE(profile); + } + + { + using BadComp = BadCompare<WhichCompIsBad::lt>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) + .DUE_TO("Comparison") + .INITIALIZER(BadComp{0}) + .INITIALIZER(BadComp{1}) + .WITH_LOOSE_PROFILE(profile); + } + + { + using BadComp = BadCompare<WhichCompIsBad::le>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) + .DUE_TO("Comparison") + .INITIALIZER(BadComp{0}) + .INITIALIZER(BadComp{1}) + .WITH_LOOSE_PROFILE(profile); + } + + { + using BadComp = BadCompare<WhichCompIsBad::ge>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) + .DUE_TO("Comparison") + .INITIALIZER(BadComp{0}) + .INITIALIZER(BadComp{1}) + .WITH_LOOSE_PROFILE(profile); + } + + { + using BadComp = BadCompare<WhichCompIsBad::gt>; + + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) + .DUE_TO("Comparison") + .INITIALIZER(BadComp{0}) + .INITIALIZER(BadComp{1}) + .WITH_LOOSE_PROFILE(profile); + } +} + +struct BadSelfMove { + BadSelfMove() = default; + BadSelfMove(BadSelfMove&&) = default; + BadSelfMove& operator=(BadSelfMove&& other) noexcept { + if (this == &other) { + broken_state = true; + } + return *this; + } + + friend bool operator==(const BadSelfMove& lhs, const BadSelfMove& rhs) { + return !(lhs.broken_state || rhs.broken_state); + } + + friend bool operator!=(const BadSelfMove& lhs, const BadSelfMove& rhs) { + return lhs.broken_state || rhs.broken_state; + } + + bool broken_state = false; +}; + +TEST(ConformanceTestingDeathTest, SelfMoveFailure) { + using profile = ti::EquatableNothrowMovableProfile; + + { + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfMove) + .DUE_TO("Move assignment") + .INITIALIZER(BadSelfMove()) + .WITH_LOOSE_PROFILE(profile); + } +} + +struct BadSelfCopy { + BadSelfCopy() = default; + BadSelfCopy(BadSelfCopy&&) = default; + BadSelfCopy(const BadSelfCopy&) = default; + BadSelfCopy& operator=(BadSelfCopy&&) = default; + BadSelfCopy& operator=(BadSelfCopy const& other) { + if (this == &other) { + broken_state = true; + } + return *this; + } + + friend bool operator==(const BadSelfCopy& lhs, const BadSelfCopy& rhs) { + return !(lhs.broken_state || rhs.broken_state); + } + + friend bool operator!=(const BadSelfCopy& lhs, const BadSelfCopy& rhs) { + return lhs.broken_state || rhs.broken_state; + } + + bool broken_state = false; +}; + +TEST(ConformanceTestingDeathTest, SelfCopyFailure) { + using profile = ti::EquatableValueProfile; + + { + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfCopy) + .DUE_TO("Copy assignment") + .INITIALIZER(BadSelfCopy()) + .WITH_LOOSE_PROFILE(profile); + } +} + +struct BadSelfSwap { + friend void swap(BadSelfSwap& lhs, BadSelfSwap& rhs) noexcept { + if (&lhs == &rhs) lhs.broken_state = true; + } + + friend bool operator==(const BadSelfSwap& lhs, const BadSelfSwap& rhs) { + return !(lhs.broken_state || rhs.broken_state); + } + + friend bool operator!=(const BadSelfSwap& lhs, const BadSelfSwap& rhs) { + return lhs.broken_state || rhs.broken_state; + } + + bool broken_state = false; +}; + +TEST(ConformanceTestingDeathTest, SelfSwapFailure) { + using profile = ti::EquatableNothrowMovableProfile; + + { + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfSwap) + .DUE_TO("Swap") + .INITIALIZER(BadSelfSwap()) + .WITH_LOOSE_PROFILE(profile); + } +} + +struct BadDefaultInitializedMoveAssign { + BadDefaultInitializedMoveAssign() : default_initialized(true) {} + explicit BadDefaultInitializedMoveAssign(int v) : value(v) {} + BadDefaultInitializedMoveAssign( + BadDefaultInitializedMoveAssign&& other) noexcept + : value(other.value) {} + BadDefaultInitializedMoveAssign& operator=( + BadDefaultInitializedMoveAssign&& other) noexcept { + value = other.value; + if (default_initialized) ++value; // Bad move if lhs is default initialized + return *this; + } + + friend bool operator==(const BadDefaultInitializedMoveAssign& lhs, + const BadDefaultInitializedMoveAssign& rhs) { + return lhs.value == rhs.value; + } + + friend bool operator!=(const BadDefaultInitializedMoveAssign& lhs, + const BadDefaultInitializedMoveAssign& rhs) { + return lhs.value != rhs.value; + } + + bool default_initialized = false; + int value = 0; +}; + +TEST(ConformanceTestingDeathTest, DefaultInitializedMoveAssignFailure) { + using profile = + ti::CombineProfiles<ti::DefaultConstructibleNothrowMovableProfile, + ti::EquatableProfile>; + + { + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadDefaultInitializedMoveAssign) + .DUE_TO("move assignment") + .INITIALIZER(BadDefaultInitializedMoveAssign(0)) + .WITH_LOOSE_PROFILE(profile); + } +} + +struct BadDefaultInitializedCopyAssign { + BadDefaultInitializedCopyAssign() : default_initialized(true) {} + explicit BadDefaultInitializedCopyAssign(int v) : value(v) {} + BadDefaultInitializedCopyAssign( + BadDefaultInitializedCopyAssign&& other) noexcept + : value(other.value) {} + BadDefaultInitializedCopyAssign(const BadDefaultInitializedCopyAssign& other) + : value(other.value) {} + + BadDefaultInitializedCopyAssign& operator=( + BadDefaultInitializedCopyAssign&& other) noexcept { + value = other.value; + return *this; + } + + BadDefaultInitializedCopyAssign& operator=( + const BadDefaultInitializedCopyAssign& other) { + value = other.value; + if (default_initialized) ++value; // Bad move if lhs is default initialized + return *this; + } + + friend bool operator==(const BadDefaultInitializedCopyAssign& lhs, + const BadDefaultInitializedCopyAssign& rhs) { + return lhs.value == rhs.value; + } + + friend bool operator!=(const BadDefaultInitializedCopyAssign& lhs, + const BadDefaultInitializedCopyAssign& rhs) { + return lhs.value != rhs.value; + } + + bool default_initialized = false; + int value = 0; +}; + +TEST(ConformanceTestingDeathTest, DefaultInitializedAssignFailure) { + using profile = ti::CombineProfiles<ti::DefaultConstructibleValueProfile, + ti::EquatableProfile>; + + { + ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadDefaultInitializedCopyAssign) + .DUE_TO("copy assignment") + .INITIALIZER(BadDefaultInitializedCopyAssign(0)) + .WITH_LOOSE_PROFILE(profile); + } +} } // namespace diff --git a/absl/types/internal/parentheses.h b/absl/types/internal/parentheses.h new file mode 100644 index 000000000000..5aebee8fdeb9 --- /dev/null +++ b/absl/types/internal/parentheses.h @@ -0,0 +1,34 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// parentheses.h +// ----------------------------------------------------------------------------- +// +// This file contains macros that expand to a left parenthesis and a right +// parenthesis. These are in their own file and are generated from macros +// because otherwise clang-format gets confused and clang-format off directives +// do not help. +// +// The parentheses macros are used when wanting to require a rescan before +// expansion of parenthesized text appearing after a function-style macro name. + +#ifndef ABSL_TYPES_INTERNAL_PARENTHESES_H_ +#define ABSL_TYPES_INTERNAL_PARENTHESES_H_ + +#define ABSL_INTERNAL_LPAREN ( + +#define ABSL_INTERNAL_RPAREN ) + +#endif // ABSL_TYPES_INTERNAL_PARENTHESES_H_ diff --git a/absl/types/internal/transform_args.h b/absl/types/internal/transform_args.h new file mode 100644 index 000000000000..4a0ab42ac49f --- /dev/null +++ b/absl/types/internal/transform_args.h @@ -0,0 +1,246 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// transform_args.h +// ----------------------------------------------------------------------------- +// +// This file contains a higher-order macro that "transforms" each element of a +// a variadic argument by a provided secondary macro. + +#ifndef ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_ +#define ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_ + +// +// ABSL_INTERNAL_CAT(a, b) +// +// This macro takes two arguments and concatenates them together via ## after +// expansion. +// +// Example: +// +// ABSL_INTERNAL_CAT(foo_, bar) +// +// Results in: +// +// foo_bar +#define ABSL_INTERNAL_CAT(a, b) ABSL_INTERNAL_CAT_IMPL(a, b) +#define ABSL_INTERNAL_CAT_IMPL(a, b) a##b + +// +// ABSL_INTERNAL_TRANSFORM_ARGS(m, ...) +// +// This macro takes another macro as an argument followed by a trailing series +// of additional parameters (up to 32 additional arguments). It invokes the +// passed-in macro once for each of the additional arguments, with the +// expansions separated by commas. +// +// Example: +// +// ABSL_INTERNAL_TRANSFORM_ARGS(MY_MACRO, a, b, c) +// +// Results in: +// +// MY_MACRO(a), MY_MACRO(b), MY_MACRO(c) +// +// TODO(calabrese) Handle no arguments as a special case. +#define ABSL_INTERNAL_TRANSFORM_ARGS(m, ...) \ + ABSL_INTERNAL_CAT(ABSL_INTERNAL_TRANSFORM_ARGS, \ + ABSL_INTERNAL_NUM_ARGS(__VA_ARGS__)) \ + (m, __VA_ARGS__) + +#define ABSL_INTERNAL_TRANSFORM_ARGS1(m, a0) m(a0) + +#define ABSL_INTERNAL_TRANSFORM_ARGS2(m, a0, a1) m(a0), m(a1) + +#define ABSL_INTERNAL_TRANSFORM_ARGS3(m, a0, a1, a2) m(a0), m(a1), m(a2) + +#define ABSL_INTERNAL_TRANSFORM_ARGS4(m, a0, a1, a2, a3) \ + m(a0), m(a1), m(a2), m(a3) + +#define ABSL_INTERNAL_TRANSFORM_ARGS5(m, a0, a1, a2, a3, a4) \ + m(a0), m(a1), m(a2), m(a3), m(a4) + +#define ABSL_INTERNAL_TRANSFORM_ARGS6(m, a0, a1, a2, a3, a4, a5) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5) + +#define ABSL_INTERNAL_TRANSFORM_ARGS7(m, a0, a1, a2, a3, a4, a5, a6) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6) + +#define ABSL_INTERNAL_TRANSFORM_ARGS8(m, a0, a1, a2, a3, a4, a5, a6, a7) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7) + +#define ABSL_INTERNAL_TRANSFORM_ARGS9(m, a0, a1, a2, a3, a4, a5, a6, a7, a8) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8) + +#define ABSL_INTERNAL_TRANSFORM_ARGS10(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9) + +#define ABSL_INTERNAL_TRANSFORM_ARGS11(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), m(a10) + +#define ABSL_INTERNAL_TRANSFORM_ARGS12(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11) + +#define ABSL_INTERNAL_TRANSFORM_ARGS13(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12) + +#define ABSL_INTERNAL_TRANSFORM_ARGS14(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13) + +#define ABSL_INTERNAL_TRANSFORM_ARGS15(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14) + +#define ABSL_INTERNAL_TRANSFORM_ARGS16(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15) + +#define ABSL_INTERNAL_TRANSFORM_ARGS17(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16) + +#define ABSL_INTERNAL_TRANSFORM_ARGS18(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17) + +#define ABSL_INTERNAL_TRANSFORM_ARGS19(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18) + +#define ABSL_INTERNAL_TRANSFORM_ARGS20(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19) + +#define ABSL_INTERNAL_TRANSFORM_ARGS21(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19, a20) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20) + +#define ABSL_INTERNAL_TRANSFORM_ARGS22(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19, a20, a21) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21) + +#define ABSL_INTERNAL_TRANSFORM_ARGS23(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19, a20, a21, a22) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22) + +#define ABSL_INTERNAL_TRANSFORM_ARGS24(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19, a20, a21, a22, a23) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23) + +#define ABSL_INTERNAL_TRANSFORM_ARGS25(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19, a20, a21, a22, a23, a24) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24) + +#define ABSL_INTERNAL_TRANSFORM_ARGS26( \ + m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ + a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25) + +#define ABSL_INTERNAL_TRANSFORM_ARGS27( \ + m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ + a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26) + +#define ABSL_INTERNAL_TRANSFORM_ARGS28( \ + m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ + a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27) + +#define ABSL_INTERNAL_TRANSFORM_ARGS29( \ + m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ + a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ + m(a28) + +#define ABSL_INTERNAL_TRANSFORM_ARGS30( \ + m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ + a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ + m(a28), m(a29) + +#define ABSL_INTERNAL_TRANSFORM_ARGS31( \ + m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ + a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ + m(a28), m(a29), m(a30) + +#define ABSL_INTERNAL_TRANSFORM_ARGS32(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, \ + a17, a18, a19, a20, a21, a22, a23, a24, \ + a25, a26, a27, a28, a29, a30, a31) \ + m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ + m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ + m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ + m(a28), m(a29), m(a30), m(a31) + +#define ABSL_INTERNAL_NUM_ARGS_IMPL(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ + a10, a11, a12, a13, a14, a15, a16, a17, \ + a18, a19, a20, a21, a22, a23, a24, a25, \ + a26, a27, a28, a29, a30, a31, result, ...) \ + result + +#define ABSL_INTERNAL_FORCE_EXPANSION(...) __VA_ARGS__ + +#define ABSL_INTERNAL_NUM_ARGS(...) \ + ABSL_INTERNAL_FORCE_EXPANSION(ABSL_INTERNAL_NUM_ARGS_IMPL( \ + __VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, \ + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, )) + +#endif // ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_ |