Skip to content

Commit

Permalink
Add fmt support to the Choice type
Browse files Browse the repository at this point in the history
  • Loading branch information
danakj committed Jun 4, 2023
1 parent c254c23 commit 1d6b6db
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 3 deletions.
85 changes: 85 additions & 0 deletions subspace/choice/choice.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <type_traits>

#include "subspace/assertions/debug_check.h"
#include "subspace/choice/__private/all_values_are_unique.h"
#include "subspace/choice/__private/index_of_value.h"
#include "subspace/choice/__private/index_type.h"
Expand Down Expand Up @@ -46,6 +47,13 @@ namespace sus::choice_type {
template <class TypeListOfMemberTypes, auto... Tags>
class Choice;

/// A helper concept that reports if the value in a `Choice` for the given `Tag`
/// is null. When true, the accessor and setter methods are not available.
template <class Choice, auto Tag>
concept ChoiceValueIsVoid = !requires(const Choice& c) {
{ c.get<Tag>() };
};

template <class... Ts, auto... Tags>
class Choice<__private::TypeList<Ts...>, Tags...> final {
static_assert(sizeof...(Ts) > 0,
Expand Down Expand Up @@ -628,6 +636,83 @@ template <auto Tag, class... Ts>

} // namespace sus::choice_type

// fmt support.
template <class... Ts, auto... Tags, class Char>
struct fmt::formatter<
::sus::choice_type::Choice<sus::choice_type::__private::TypeList<Ts...>,
Tags...>,
Char> {
using Choice =
::sus::choice_type::Choice<sus::choice_type::__private::TypeList<Ts...>,
Tags...>;

template <class ParseContext>
constexpr decltype(auto) parse(ParseContext& ctx) {
return ctx.begin();
}

template <class FormatContext>
constexpr auto format(const Choice& choice, FormatContext& ctx) const {
return find_choice<FormatContext, Tags...>(choice, ctx);
}

private:
template <class FormatContext, auto Tag, auto... MoreTags>
requires(sizeof...(MoreTags) > 0)
static auto find_choice(const Choice& choice, FormatContext& ctx) {
if (choice.which() == Tag) {
return format_choice<Tag>(choice, ctx);
} else {
return find_choice<FormatContext, MoreTags...>(choice, ctx);
}
}

template <class FormatContext, auto Tag>
static auto find_choice(const Choice& choice, FormatContext& ctx) {
sus_debug_check(choice.which() == Tag);
return format_choice<Tag>(choice, ctx);
}

template <auto Tag, class FormatContext>
static auto format_choice(const Choice& choice, FormatContext& ctx) {
if constexpr (sus::choice_type::ChoiceValueIsVoid<Choice, Tag>) {
auto out = fmt::format_to(ctx.out(), "Choice(");
ctx.advance_to(out);
using TagFormatter =
::sus::string::__private::AnyFormatter<decltype(Tag), Char>;
out = TagFormatter().format(Tag, ctx);
return fmt::format_to(ctx.out(), ")");
} else {
auto out = fmt::format_to(ctx.out(), "Choice(");
ctx.advance_to(out);
using TagFormatter =
::sus::string::__private::AnyFormatter<decltype(Tag), Char>;
out = TagFormatter().format(Tag, ctx);
out = fmt::format_to(ctx.out(), ", ");
ctx.advance_to(out);
using ValueFormatter = ::sus::string::__private::AnyFormatter<
decltype(choice.get_unchecked<Tag>(::sus::marker::unsafe_fn)), Char>;
// SAFETY: The Tag here is the active tag as `find_choice()` calls this
// method only if `which() == Tag`.
out = ValueFormatter().format(
choice.get_unchecked<Tag>(::sus::marker::unsafe_fn), ctx);
return fmt::format_to(ctx.out(), ")");
}
}
};

// Stream support (written out manually due to use of template specialization).
namespace sus::choice_type {
template <class... Ts, auto... Tags>
inline std::basic_ostream<char>& operator<<(
std::basic_ostream<char>& stream,
const Choice<sus::choice_type::__private::TypeList<Ts...>, Tags...>&
value) {
return ::sus::string::__private::format_to_stream(stream,
fmt::format("{}", value));
}
} // namespace sus::choice_type

// Promote Choice into the `sus` namespace.
namespace sus {
using ::sus::choice_type::Choice;
Expand Down
57 changes: 56 additions & 1 deletion subspace/choice/choice_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

#include "subspace/choice/choice.h"

#include <variant>
#include <sstream>

#include "googletest/include/gtest/gtest.h"
#include "subspace/assertions/unreachable.h"
#include "subspace/num/types.h"
#include "subspace/option/option.h"
#include "subspace/prelude.h"
Expand All @@ -43,6 +44,25 @@ enum class Order {
Third,
};

template <class Char>
struct fmt::formatter<Order, Char> {
template <typename ParseContext>
constexpr decltype(auto) parse(ParseContext& ctx) {
return ctx.begin();
}

template <typename FormatContext>
constexpr auto format(const Order& t, FormatContext& ctx) const {
using enum Order;
switch (t) {
case First: return fmt::format_to(ctx.out(), "First");
case Second: return fmt::format_to(ctx.out(), "Second");
case Third: return fmt::format_to(ctx.out(), "Third");
}
sus::unreachable();
}
};

// The Choice's tag can get stashed inside the Tuple, though this doesn't happen
// on MSVC.
// NOPE this causes data to get clobbered when move-constructing into the
Expand Down Expand Up @@ -783,4 +803,39 @@ TEST(Choice, VoidValues) {
EXPECT_LT(u4, u6);
}

TEST(Choice, fmt) {
auto u = Choice<sus_choice_types(
(Order::First, u32), (Order::Second, void))>::with<Order::First>(4u);

static_assert(fmt::is_formattable<decltype(u), char>::value);

EXPECT_EQ(fmt::format("{}", u), "Choice(First, 4)");
u.set<Order::Second>();
EXPECT_EQ(fmt::format("{}", u), "Choice(Second)");

struct NoFormat {
i32 a = 0x16ae3cf2;
};
static_assert(!fmt::is_formattable<NoFormat, char>::value);
// static_assert(fmt::is_formattable<Option<NoFormat>, char>::value);
// EXPECT_EQ(fmt::format("{}", sus::Option<NoFormat>::some(NoFormat())),
// "Some(f2-3c-ae-16)");
// EXPECT_EQ(fmt::format("{}", sus::Option<NoFormat>::none()), "None");
}

TEST(Choice, Stream) {
std::stringstream s;
s << Choice<sus_choice_types((Order::First, u32),
(Order::Second, void))>::with<Order::First>(4u);
EXPECT_EQ(s.str(), "Choice(First, 4)");
}

TEST(Choice, GTest) {
EXPECT_EQ(
testing::PrintToString(
Choice<sus_choice_types((Order::First, u32), (Order::Second, void))>::
with<Order::First>(4u)),
"Choice(First, 4)");
}

} // namespace
4 changes: 2 additions & 2 deletions subspace/string/__private/any_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ struct VoidFormatter {
return ctx.begin();
}
template <class T, class FormatContext>
constexpr auto format(const T& t, FormatContext& ctx) const {
return format_void(ctx.out());
constexpr auto format(const T&, FormatContext& ctx) const {
return format_void<Char>(ctx.out());
}
};

Expand Down

0 comments on commit 1d6b6db

Please sign in to comment.