|
/* |
|
* Copyright (c) Meta Platforms, Inc. and affiliates. |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
#include <folly/Utility.h> |
|
#include <folly/lang/TypeIdxMap.h> |
|
|
|
#include <folly/portability/GTest.h> |
|
#include <folly/portability/SourceLocation.h> |
|
|
|
using namespace folly; |
|
|
|
// This is here so that test "runs" show up in CI history |
|
TEST(TypeIdxMap, all_tests_run_at_build_time) {} |
|
|
|
// Better UX than `assert()` in constexpr tests. |
|
constexpr void test(bool ok) { |
|
if (!ok) { |
|
throw std::exception(); // Throwing in constexpr code is a compile error |
|
} |
|
} |
|
|
|
namespace { |
|
struct A { |
|
constexpr bool operator==(const A&) const = default; |
|
}; |
|
struct B {}; |
|
struct C {}; |
|
} // namespace |
|
|
|
static_assert(detail::is_unique_v<>); |
|
static_assert(detail::is_unique_v<A>); |
|
static_assert(detail::is_unique_v<A, B>); |
|
static_assert(detail::is_unique_v<A, B, C>); |
|
static_assert(!detail::is_unique_v<A, A>); |
|
static_assert(!detail::is_unique_v<A, B, A>); |
|
static_assert(!detail::is_unique_v<A, B, A, C>); |
|
static_assert(!detail::is_unique_v<A, B, B, C>); |
|
|
|
constexpr auto check_contains() { |
|
type_idx_map m{type_idx<A> = 5, type_idx<B> = "b"}; |
|
|
|
static_assert(std::is_trivially_copyable_v<decltype(m)>); |
|
|
|
test(m.contains(type_idx<A>)); |
|
test(m.contains(type_idx<B>)); |
|
test(!m.contains(type_idx<C>)); |
|
test(!m.contains(type_idx<int>)); |
|
|
|
test(m.contains<A>()); |
|
test(m.contains<B>()); |
|
test(!m.contains<C>()); |
|
test(!m.contains<int>()); |
|
|
|
// `contains()` is static, though most usage won't be |
|
test(decltype(m)::contains<A>()); |
|
test(decltype(m)::contains(type_idx<B>)); |
|
test(!decltype(m)::contains<C>()); |
|
test(!decltype(m)::contains(type_idx<int>)); |
|
|
|
return true; |
|
} |
|
|
|
static_assert(check_contains()); |
|
|
|
// Check that move-only types can be stored and replaced |
|
struct Moo : MoveOnly { |
|
int n; |
|
constexpr bool operator==(const Moo& other) const { return n == other.n; } |
|
}; |
|
|
|
constexpr auto check_construct_get_replace() { |
|
type_idx_map m1{type_idx<A> = 5, type_idx<B> = 7, Moo{.n = 9}}; |
|
test(m1[type_idx<A>] == 5); |
|
test(m1[type_idx<B>] == 7); |
|
test(m1[type_idx<Moo>].n == 9); |
|
test(m1.at<A>() == 5); |
|
test(m1.at<B>() == 7); |
|
test(m1.at<Moo>().n == 9); |
|
|
|
// We can't directly test invalid args to `copy_replace()`. |
|
test(m1.is_valid_key_list<tag_t<Moo>>); |
|
test(m1.is_valid_key_list<tag_t<B, Moo>>); |
|
test(m1.is_valid_key_list<tag_t<A, B, Moo>>); |
|
test(!m1.is_valid_key_list<tag_t<C>>); // Type not in map |
|
test(!m1.is_valid_key_list<tag_t<Moo, B, Moo>>); // Type repeated |
|
|
|
auto m2 = m1.copy_replace(Moo{.n = 3}, type_idx<B> = 4); |
|
test(m2[type_idx<A>] == 5); |
|
test(m2[type_idx<B>] == 4); |
|
test(m2[type_idx<Moo>].n == 3); |
|
|
|
auto m3 = std::move(m2).move_replace(type_idx<A> = 3); |
|
test(m3[type_idx<A>] == 3); |
|
test(m3[type_idx<B>] == 4); |
|
test(m3[type_idx<Moo>].n == 3); |
|
|
|
using Sig = |
|
type_idx_map<std::pair<A, int>, std::pair<B, int>, std::pair<Moo, Moo>>; |
|
test(std::is_same_v<decltype(m1), Sig>); |
|
test(std::is_same_v<decltype(m2), Sig>); |
|
test(std::is_same_v<decltype(m3), Sig>); |
|
|
|
// Yes, `m2` was moved-out above, but with the underlying data being |
|
// trivially copiable, it's safe to re-access it in this test. |
|
// |
|
// NOLINTNEXTLINE(bugprone-use-after-move) |
|
return std::tuple{std::move(m1), std::move(m2), std::move(m3)}; |
|
} |
|
|
|
static_assert( |
|
check_construct_get_replace() == |
|
std::tuple{ |
|
type_idx_map{type_idx<A> = 5, type_idx<B> = 7, Moo{.n = 9}}, |
|
type_idx_map{type_idx<A> = 5, type_idx<B> = 4, Moo{.n = 3}}, |
|
type_idx_map{type_idx<A> = 3, type_idx<B> = 4, Moo{.n = 3}}}); |
|
|
|
[[maybe_unused]] vtag_t<type_idx_map{type_idx<A> = 5, Moo{.n = 9}}> |
|
kTypeIdxMapIsStructural; |
|
|
|
constexpr auto check_append_and_equals() { |
|
const type_idx_map m1{}; |
|
test(m1 == type_idx_map{}); |
|
|
|
const auto m2 = m1.copy_append(type_idx<C> = 1); |
|
test(m2 == type_idx_map{type_idx<C> = 1}); |
|
|
|
auto m3 = m2.copy_append(type_idx<B> = Moo{.n = 2}); |
|
test(m3 == type_idx_map{type_idx<C> = 1, type_idx<B> = Moo{.n = 2}}); |
|
|
|
auto m4 = std::move(m3).move_append(type_idx<A> = 3); |
|
test( |
|
m4 == |
|
type_idx_map{ |
|
type_idx<C> = 1, type_idx<B> = Moo{.n = 2}, type_idx<A> = 3}); |
|
|
|
return true; |
|
} |
|
|
|
static_assert(check_append_and_equals()); |
|
|
|
constexpr bool comparable(auto a, auto b) { |
|
return requires { |
|
{ a < b } -> std::convertible_to<bool>; |
|
}; |
|
} |
|
|
|
constexpr auto check_less_than() { |
|
test(!(type_idx_map{} < type_idx_map{})); |
|
|
|
test(!(type_idx_map{7} < type_idx_map{7})); |
|
test(!(type_idx_map{8} > type_idx_map{8})); |
|
test(type_idx_map{7} < type_idx_map{8}); |
|
test(type_idx_map{type_idx<A> = -1} < type_idx_map{type_idx<A> = 0}); |
|
|
|
// We don't sort key types: comparison requires the key order to match. |
|
test(comparable( |
|
type_idx_map{2, type_idx<A> = 1}, type_idx_map{2, type_idx<A> = 1})); |
|
test(!comparable( |
|
type_idx_map{type_idx<A> = 1, 2}, type_idx_map{2, type_idx<A> = 1})); |
|
|
|
// Lexicographic ordering: latter keys are less significant |
|
test(!(type_idx_map{2, type_idx<A> = 1} < type_idx_map{2, type_idx<A> = 1})); |
|
test(!(type_idx_map{2, type_idx<A> = 2} < type_idx_map{2, type_idx<A> = 1})); |
|
test(type_idx_map{2, type_idx<A> = 1} < type_idx_map{2, type_idx<A> = 2}); |
|
test( |
|
type_idx_map{1, type_idx<C> = 1, type_idx<B> = 1, type_idx<A> = 1} < |
|
type_idx_map{1, type_idx<C> = 1, type_idx<B> = 2, type_idx<A> = 3}); |
|
test( |
|
type_idx_map{1, type_idx<C> = 1, type_idx<B> = 1, type_idx<A> = 1} < |
|
type_idx_map{1, type_idx<C> = 2, type_idx<B> = 9, type_idx<A> = 9}); |
|
test( |
|
type_idx_map{1, type_idx<C> = 1, type_idx<B> = 1, type_idx<A> = 1} < |
|
type_idx_map{2, type_idx<C> = 9, type_idx<B> = 9, type_idx<A> = 9}); |
|
|
|
return true; |
|
} |
|
|
|
static_assert(check_less_than()); |
|
|
|
constexpr auto check_same_key_set() { |
|
test(type_idx_map{}.has_same_key_set(tag<>)); |
|
test(!type_idx_map{}.has_same_key_set(tag<int>)); |
|
|
|
auto m1 = type_idx_map{5}; |
|
test(m1.has_same_key_set(tag<int>)); |
|
test(!m1.has_same_key_set(tag<>)); |
|
test(!m1.has_same_key_set(tag<int, double>)); |
|
test(!m1.has_same_key_set(tag<double>)); |
|
|
|
auto m2 = type_idx_map{5, type_idx<A> = 'a'}; |
|
test(m2.has_same_key_set(tag<int, A>)); |
|
test(m2.has_same_key_set(tag<A, int>)); |
|
test(!m2.has_same_key_set(tag<>)); |
|
test(!m2.has_same_key_set(tag<int>)); |
|
test(!m2.has_same_key_set(tag<A>)); |
|
test(!m2.has_same_key_set(tag<int, A, char>)); |
|
|
|
// Test `type_idx_map_of` concept wrapping `has_same_key_set`. |
|
using int_char_map = type_idx_map<std::pair<int, int>, std::pair<char, int>>; |
|
test(type_idx_map_of<int_char_map, tag<int, char>>); |
|
test(type_idx_map_of<int_char_map, tag<char, int>>); |
|
test(!type_idx_map_of<int_char_map, tag<int, char, double>>); |
|
test(!type_idx_map_of<int_char_map, tag<int>>); |
|
test(!type_idx_map_of<int, tag<int>>); |
|
|
|
return true; |
|
} |
|
|
|
static_assert(check_same_key_set()); |
|
|
|
constexpr auto check_some() { |
|
static_assert(type_idx_map{}.copy_some(tag<>) == type_idx_map{}); |
|
static_assert(type_idx_map{}.move_some(tag<>) == type_idx_map{}); |
|
|
|
auto m1 = type_idx_map{5}; |
|
test(m1.copy_some(tag<>) == type_idx_map{}); |
|
test(m1.copy_some(tag<int>) == m1); |
|
test(std::move(m1).move_some(tag<int>) == type_idx_map{5}); |
|
|
|
auto m2 = type_idx_map{5, A{}}; |
|
test(m2.copy_some(tag<>) == type_idx_map{}); |
|
test(m2.copy_some(tag<int>) == type_idx_map{5}); |
|
test(m2.copy_some(tag<A>) == type_idx_map{A{}}); |
|
test(m2.copy_some(tag<int, A>) == m2); |
|
test(!comparable(m2.copy_some(tag<A, int>), m2)); // the order changed |
|
test(m2.copy_some(tag<A, int>) == type_idx_map{A{}, 5}); |
|
|
|
return true; |
|
} |
|
|
|
static_assert(check_some()); |
|
|
|
constexpr auto check_remove() { |
|
static_assert(type_idx_map{}.copy_remove(tag<>) == type_idx_map{}); |
|
static_assert(type_idx_map{}.move_remove(tag<>) == type_idx_map{}); |
|
|
|
auto m1 = type_idx_map{5}; |
|
test(m1.copy_remove(tag<int>) == type_idx_map{}); |
|
test(m1.copy_remove(tag<>) == m1); |
|
test(std::move(m1).move_remove(tag<int>) == type_idx_map{}); |
|
|
|
auto m2 = type_idx_map{5, A{}}; |
|
test(m2.copy_remove(tag<>) == m2); |
|
test(m2.copy_remove(tag<int>) == type_idx_map{A{}}); |
|
test(m2.copy_remove(tag<A>) == type_idx_map{5}); |
|
test(m2.copy_remove(tag<int, A>) == type_idx_map{}); |
|
test(m2.copy_remove(tag<A, int>) == type_idx_map{}); |
|
|
|
return true; |
|
} |
|
|
|
static_assert(check_remove()); |