From 3fc43af9e5248acd87b02547713fe0b021257857 Mon Sep 17 00:00:00 2001 From: Alex Shabalin Date: Fri, 24 May 2024 15:00:20 +0200 Subject: [PATCH] Recursive traversal of types to generate required pools --- immer/extra/persist/common/type_traverse.hpp | 152 ++++++++++++++++++ .../persist/json/json_with_pool_auto.hpp | 58 ++----- .../test_circular_dependency_conversion.cpp | 15 +- test/extra/persist/test_conversion.cpp | 14 +- test/extra/persist/test_special_pool_auto.cpp | 40 ++--- 5 files changed, 195 insertions(+), 84 deletions(-) create mode 100644 immer/extra/persist/common/type_traverse.hpp diff --git a/immer/extra/persist/common/type_traverse.hpp b/immer/extra/persist/common/type_traverse.hpp new file mode 100644 index 00000000..57878d56 --- /dev/null +++ b/immer/extra/persist/common/type_traverse.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace immer::persist::util { + +namespace detail { + +namespace hana = boost::hana; + +template +struct get_inner_types_t +{ + static auto apply() + { + return hana::make_tuple( + hana::make_pair(hana::type_c, BOOST_HANA_STRING(""))); + } +}; + +template +struct get_inner_types_t::value>> +{ + static auto apply() + { + auto value = T{}; + return hana::transform(hana::keys(value), [&](auto key) { + const auto& member = hana::at_key(value, key); + return hana::make_pair(hana::typeid_(member), key); + }); + } +}; + +template +struct get_inner_types_t> + : get_inner_types_t +{}; + +template +struct get_inner_types_t> + : get_inner_types_t +{}; + +template +struct get_inner_types_t> : get_inner_types_t +{}; + +template +struct get_inner_types_t> + : get_inner_types_t +{}; + +template +struct get_inner_types_t> +{ + static auto apply() + { + return hana::concat(get_inner_types_t::apply(), + get_inner_types_t::apply()); + } +}; + +template +struct get_inner_types_t> + : get_inner_types_t +{}; + +constexpr auto insert_conditionally = [](auto map, auto pair) { + namespace hana = hana; + using contains_t = + decltype(hana::is_just(hana::find(map, hana::first(pair)))); + if constexpr (contains_t::value) { + const auto empty_string = BOOST_HANA_STRING(""); + using is_empty_t = decltype(hana::second(pair) == empty_string); + if constexpr (is_empty_t::value) { + // Do not replace with empty string + return map; + } else { + return hana::insert(map, pair); + } + } else { + return hana::insert(map, pair); + } +}; + +} // namespace detail + +/** + * Generate a map (type, member_name) for all members of a given type, + * recursively. + */ +inline auto get_inner_types(const auto& type) +{ + namespace hana = boost::hana; + + constexpr auto get_for_one_type = [](auto type) { + using T = typename decltype(type)::type; + return detail::get_inner_types_t::apply(); + }; + + constexpr auto get_for_many = [get_for_one_type](const auto& map) { + auto new_pairs = hana::to_tuple(map) | [get_for_one_type](auto pair) { + return get_for_one_type(hana::first(pair)); + }; + + return hana::fold_left( + hana::to_map(new_pairs), map, detail::insert_conditionally); + }; + + constexpr auto can_expand = [get_for_many](const auto& set) { + return set != get_for_many(set); + }; + + auto expanded = hana::while_( + can_expand, hana::to_map(get_for_one_type(type)), get_for_many); + + // Throw away types we don't know names for + const auto empty_string = BOOST_HANA_STRING(""); + auto result = + hana::filter(hana::to_tuple(expanded), [empty_string](auto pair) { + return hana::second(pair) != empty_string; + }); + return hana::to_map(result); +} + +} // namespace immer::persist::util diff --git a/immer/extra/persist/json/json_with_pool_auto.hpp b/immer/extra/persist/json/json_with_pool_auto.hpp index fe267978..6d033c94 100644 --- a/immer/extra/persist/json/json_with_pool_auto.hpp +++ b/immer/extra/persist/json/json_with_pool_auto.hpp @@ -9,6 +9,8 @@ #include #include +#include + namespace immer::persist { template @@ -114,54 +116,20 @@ constexpr auto wrap_for_loading = exclude_internal_pool_types( make_conditional_func(is_persistable, to_persistable_loader)); /** - * Generate a hana map of persistable members for a given type T. - * Where key is a type (persistable immer container) and value is a name for - * that pool (using the member name from the given struct). Example: + * Generate a hana map of persistable members for the given type, recursively. + * Example: * [(type_c>, "tracks")] */ -template -auto get_auto_pools_types(const T& value) +auto get_pools_for_type(auto type) { - namespace hana = boost::hana; - static_assert(hana::Struct::value, - "get_auto_pools_types works only with types that " - "implement hana::Struct concept"); - - constexpr auto is_persistable_key = [](auto key) { - return is_persistable(hana::at_key(T{}, key)); - }; - - // A list of pairs like (hana::type_c, "member_name") - auto pairs = hana::transform( - hana::filter(hana::keys(value), is_persistable_key), [&](auto key) { - const auto& member = hana::at_key(value, key); - return hana::make_pair(hana::typeid_(member), key); + namespace hana = boost::hana; + auto all_types_map = util::get_inner_types(type); + auto persistable = + hana::filter(hana::to_tuple(all_types_map), [&](auto pair) { + using T = typename decltype(+hana::first(pair))::type; + return is_persistable(T{}); }); - - return hana::unpack(pairs, hana::make_map); -} - -/** - * Generate a hana map of persistable members for the given types and apply - * manual overrides for names. - * manual_overrides is a map of manual overrides. - */ -auto get_pools_for_types(auto types, - auto manual_overrides = boost::hana::make_map()) -{ - namespace hana = boost::hana; - // Automatically generate names and pools - const auto names = - hana::fold_left(hana::transform(types, - [](auto t) { - using T = - typename decltype(t)::type; - return get_auto_pools_types(T{}); - }), - hana::make_map(), - hana::union_); - // Apply the overrides - return hana::union_(names, manual_overrides); + return hana::to_map(persistable); } template ; - auto restore = util::istream_snapshot{is}; + auto restore = immer::util::istream_snapshot{is}; pools.ignore_pool_exceptions = ignore_pool_exceptions; auto previous = json_immer_input_archive{ diff --git a/test/extra/persist/test_circular_dependency_conversion.cpp b/test/extra/persist/test_circular_dependency_conversion.cpp index 85609790..035398a6 100644 --- a/test/extra/persist/test_circular_dependency_conversion.cpp +++ b/test/extra/persist/test_circular_dependency_conversion.cpp @@ -234,9 +234,8 @@ TEST_CASE("Test exception while circular converting") }; }(); - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, - hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto [json_str, model_pool] = immer::persist::to_json_with_auto_pool(value, names); // REQUIRE(json_str == ""); @@ -398,9 +397,8 @@ TEST_CASE("Test circular dependency pools", "[conversion]") }; }(); - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, - hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto model_pools = immer::persist::get_auto_pool(value, names); /** @@ -508,9 +506,8 @@ TEST_CASE("Test circular dependency pools", "[conversion]") (void) format_load_pools; // show_type qwe; - const auto format_names = immer::persist::get_pools_for_types( - hana::tuple_t, - hana::make_map()); + const auto format_names = + immer::persist::get_pools_for_type(hana::type_c); SECTION("vector") { diff --git a/test/extra/persist/test_conversion.cpp b/test/extra/persist/test_conversion.cpp index 0e02df5b..ca9db3ef 100644 --- a/test/extra/persist/test_conversion.cpp +++ b/test/extra/persist/test_conversion.cpp @@ -2,6 +2,8 @@ #include +#include + #include "utils.hpp" #define DEFINE_OPERATIONS(name) \ @@ -141,10 +143,10 @@ class Z; TEST_CASE("Convert between two hierarchies via JSON compatibility", "[conversion]") { - const auto model_names = immer::persist::get_pools_for_types( - hana::tuple_t, hana::make_map()); - const auto format_names = immer::persist::get_pools_for_types( - hana::tuple_t, hana::make_map()); + const auto model_names = + immer::persist::get_pools_for_type(hana::type_c); + const auto format_names = + immer::persist::get_pools_for_type(hana::type_c); (void) format_names; const auto value = model::make_example_history(); @@ -202,8 +204,8 @@ struct two_vectors TEST_CASE("Not every type is converted", "[conversion]") { - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto [json_str, pools] = immer::persist::to_json_with_auto_pool(two_vectors{}, names); diff --git a/test/extra/persist/test_special_pool_auto.cpp b/test/extra/persist/test_special_pool_auto.cpp index 2c4bc76d..05dacf89 100644 --- a/test/extra/persist/test_special_pool_auto.cpp +++ b/test/extra/persist/test_special_pool_auto.cpp @@ -111,8 +111,9 @@ struct test_data_with_immer TEST_CASE("Auto-persisting") { constexpr auto names = [] { - return immer::persist::get_pools_for_types( - hana::tuple_t, + return hana::union_( + immer::persist::get_pools_for_type( + hana::type_c), hana::make_map(hana::make_pair(hana::type_c>, BOOST_HANA_STRING("meta_meta")))); }; @@ -236,7 +237,8 @@ TEST_CASE("Test save and load small type") const auto value = test_data_with_one_immer_member{ .ints = ints1, }; - const auto pool_types = immer::persist::get_auto_pools_types(value); + const auto pool_types = + immer::persist::get_pools_for_type(boost::hana::typeid_(value)); const auto [json_str, pools] = immer::persist::to_json_with_auto_pool(value, pool_types); // REQUIRE(json_str == ""); @@ -335,13 +337,9 @@ TEST_CASE("Test conversion with auto-pool") }, }; - constexpr auto old_names = [] { - return immer::persist::get_pools_for_types(hana::tuple_t, - hana::make_map()); - }; + const auto old_pool_types = + immer::persist::get_pools_for_type(hana::type_c); - using OldPoolTypes = decltype(old_names()); - constexpr auto old_pool_types = OldPoolTypes{}; const auto [json_str, pools] = immer::persist::to_json_with_auto_pool(value, old_pool_types); // REQUIRE(json_str == ""); @@ -507,11 +505,8 @@ TEST_CASE("Test table with a funny value") .twos_table = t1.insert(two2), }; - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, - hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto [json_str, ar] = immer::persist::to_json_with_auto_pool(value, names); @@ -552,11 +547,8 @@ TEST_CASE("Test loading broken table") .twos_table = t1.insert(two2), }; - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, - hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto [json_str, ar] = immer::persist::to_json_with_auto_pool(value, names); @@ -651,7 +643,7 @@ TEST_CASE("Test loading broken table") json.dump(), names), ::cereal::Exception, MessageMatches(Catch::Matchers::ContainsSubstring( - "Couldn't find an element"))); + "Container ID 99 is not found"))); } } @@ -852,8 +844,8 @@ TEST_CASE("Structure breaks when hash is changed") .map = {{123, "123"}, {456, "456"}}, }; - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto [json_str, ar] = immer::persist::to_json_with_auto_pool(value, names); @@ -887,8 +879,8 @@ TEST_CASE("Converting between incompatible keys") .table = {{901}, {902}}, }; - const auto names = immer::persist::get_pools_for_types( - hana::tuple_t, hana::make_map()); + const auto names = + immer::persist::get_pools_for_type(hana::type_c); const auto ar = immer::persist::get_auto_pool(value, names);