Skip to content

Commit

Permalink
Recursive traversal of types to generate required pools
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-sparus committed May 24, 2024
1 parent fab1e15 commit 3fc43af
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 84 deletions.
152 changes: 152 additions & 0 deletions immer/extra/persist/common/type_traverse.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#pragma once

#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
#include <immer/set.hpp>
#include <immer/table.hpp>
#include <immer/vector.hpp>

#include <boost/hana.hpp>

namespace immer::persist::util {

namespace detail {

namespace hana = boost::hana;

template <class T, class = void>
struct get_inner_types_t
{
static auto apply()
{
return hana::make_tuple(
hana::make_pair(hana::type_c<T>, BOOST_HANA_STRING("")));
}
};

template <class T>
struct get_inner_types_t<T, std::enable_if_t<hana::Struct<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 <typename T,
typename MemoryPolicy,
immer::detail::rbts::bits_t B,
immer::detail::rbts::bits_t BL>
struct get_inner_types_t<immer::vector<T, MemoryPolicy, B, BL>>
: get_inner_types_t<T>
{};

template <typename T,
typename MemoryPolicy,
immer::detail::rbts::bits_t B,
immer::detail::rbts::bits_t BL>
struct get_inner_types_t<immer::flex_vector<T, MemoryPolicy, B, BL>>
: get_inner_types_t<T>
{};

template <typename T, typename MemoryPolicy>
struct get_inner_types_t<immer::box<T, MemoryPolicy>> : get_inner_types_t<T>
{};

template <typename T,
typename Hash,
typename Equal,
typename MemoryPolicy,
immer::detail::hamts::bits_t B>
struct get_inner_types_t<immer::set<T, Hash, Equal, MemoryPolicy, B>>
: get_inner_types_t<T>
{};

template <typename K,
typename T,
typename Hash,
typename Equal,
typename MemoryPolicy,
immer::detail::hamts::bits_t B>
struct get_inner_types_t<immer::map<K, T, Hash, Equal, MemoryPolicy, B>>
{
static auto apply()
{
return hana::concat(get_inner_types_t<K>::apply(),
get_inner_types_t<T>::apply());
}
};

template <typename T,
typename KeyFn,
typename Hash,
typename Equal,
typename MemoryPolicy,
immer::detail::hamts::bits_t B>
struct get_inner_types_t<immer::table<T, KeyFn, Hash, Equal, MemoryPolicy, B>>
: get_inner_types_t<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<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
58 changes: 13 additions & 45 deletions immer/extra/persist/json/json_with_pool_auto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <immer/extra/persist/champ/traits.hpp>
#include <immer/extra/persist/rbts/traits.hpp>

#include <immer/extra/persist/common/type_traverse.hpp>

namespace immer::persist {

template <class T>
Expand Down Expand Up @@ -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<immer::map<K, V>>, "tracks")]
*/
template <class T>
auto get_auto_pools_types(const T& value)
auto get_pools_for_type(auto type)
{
namespace hana = boost::hana;
static_assert(hana::Struct<T>::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<Container>, "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 <typename T,
Expand Down Expand Up @@ -265,7 +233,7 @@ auto get_auto_pool(const T& serializable,
constexpr auto reload_pool_auto = [](auto wrap) {
return [wrap](std::istream& is, auto pools, bool ignore_pool_exceptions) {
using Pools = std::decay_t<decltype(pools)>;
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<cereal::JSONInputArchive, Pools>{
Expand Down
15 changes: 6 additions & 9 deletions test/extra/persist/test_circular_dependency_conversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,8 @@ TEST_CASE("Test exception while circular converting")
};
}();

const auto names = immer::persist::get_pools_for_types(
hana::tuple_t<model::value_one, model::value_two, model::two_boxed>,
hana::make_map());
const auto names =
immer::persist::get_pools_for_type(hana::type_c<model::value_one>);
const auto [json_str, model_pool] =
immer::persist::to_json_with_auto_pool(value, names);
// REQUIRE(json_str == "");
Expand Down Expand Up @@ -398,9 +397,8 @@ TEST_CASE("Test circular dependency pools", "[conversion]")
};
}();

const auto names = immer::persist::get_pools_for_types(
hana::tuple_t<model::value_one, model::value_two, model::two_boxed>,
hana::make_map());
const auto names =
immer::persist::get_pools_for_type(hana::type_c<model::value_one>);
const auto model_pools = immer::persist::get_auto_pool(value, names);

/**
Expand Down Expand Up @@ -508,9 +506,8 @@ TEST_CASE("Test circular dependency pools", "[conversion]")
(void) format_load_pools;
// show_type<decltype(format_load_pools)> qwe;

const auto format_names = immer::persist::get_pools_for_types(
hana::tuple_t<format::value_one, format::value_two, format::two_boxed>,
hana::make_map());
const auto format_names =
immer::persist::get_pools_for_type(hana::type_c<format::value_one>);

SECTION("vector")
{
Expand Down
14 changes: 8 additions & 6 deletions test/extra/persist/test_conversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <immer/extra/persist/json/json_with_pool_auto.hpp>

#include <immer/extra/persist/common/type_traverse.hpp>

#include "utils.hpp"

#define DEFINE_OPERATIONS(name) \
Expand Down Expand Up @@ -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<model::history, model::arrangement>, hana::make_map());
const auto format_names = immer::persist::get_pools_for_types(
hana::tuple_t<format::history, format::arrangement>, hana::make_map());
const auto model_names =
immer::persist::get_pools_for_type(hana::type_c<model::history>);
const auto format_names =
immer::persist::get_pools_for_type(hana::type_c<format::history>);
(void) format_names;

const auto value = model::make_example_history();
Expand Down Expand Up @@ -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<two_vectors>, hana::make_map());
const auto names =
immer::persist::get_pools_for_type(hana::type_c<two_vectors>);
const auto [json_str, pools] =
immer::persist::to_json_with_auto_pool(two_vectors{}, names);

Expand Down
Loading

0 comments on commit 3fc43af

Please sign in to comment.