From 73a865b5350e57192cb345aad5cf6fc5a72d147d Mon Sep 17 00:00:00 2001
From: eido79 <eido79@gmail.com>
Date: Sun, 30 Oct 2022 22:32:51 +0100
Subject: [PATCH] Rework attribute for alternative parser

Implement the following rules
* Remove duplicates
* Simplify variant<X> to X
* Make unused_type the first type of the variant
* Transform variant<unused_type, T> to optional<T>
---
 doc/x3/quick_reference.qbk                    |  2 +-
 .../spirit/home/x3/operator/alternative.hpp   | 92 ++++++++++++++++++-
 .../spirit/home/x3/operator/sequence.hpp      | 34 ++++++-
 include/boost/spirit/home/x3/support/meta.hpp | 57 ++++++++++++
 .../x3/support/traits/attribute_of_binary.hpp | 62 -------------
 test/x3/Jamfile                               |  1 +
 test/x3/alternative.cpp                       | 28 ++++++
 test/x3/sequence.cpp                          | 20 ++++
 8 files changed, 228 insertions(+), 68 deletions(-)
 create mode 100644 include/boost/spirit/home/x3/support/meta.hpp
 delete mode 100644 include/boost/spirit/home/x3/support/traits/attribute_of_binary.hpp

diff --git a/doc/x3/quick_reference.qbk b/doc/x3/quick_reference.qbk
index 76c5bcb745..2ab15a6921 100644
--- a/doc/x3/quick_reference.qbk
+++ b/doc/x3/quick_reference.qbk
@@ -298,7 +298,7 @@ a: vector<A>, b: vector<A> --> (a > b): vector<A>``]]
     [[__x3_alternative__ (`a | b`)]
 [``a: A, b: B --> (a | b): variant<A, B>
 a: A, b: Unused --> (a | b): optional<A>
-a: A, b: B, c: Unused --> (a | b | c): optional<variant<A, B> >
+a: A, b: B, c: Unused --> (a | b | c): variant<Unused, A, B>
 a: Unused, b: B --> (a | b): optional<B>
 a: Unused, b: Unused --> (a | b): Unused
 a: A, b: A --> (a | b): A``]]
diff --git a/include/boost/spirit/home/x3/operator/alternative.hpp b/include/boost/spirit/home/x3/operator/alternative.hpp
index aeb6998ba4..8d3ef26612 100644
--- a/include/boost/spirit/home/x3/operator/alternative.hpp
+++ b/include/boost/spirit/home/x3/operator/alternative.hpp
@@ -7,12 +7,13 @@
 #if !defined(BOOST_SPIRIT_X3_ALTERNATIVE_JAN_07_2013_1131AM)
 #define BOOST_SPIRIT_X3_ALTERNATIVE_JAN_07_2013_1131AM
 
-#include <boost/spirit/home/x3/support/traits/attribute_of_binary.hpp>
 #include <boost/spirit/home/x3/core/parser.hpp>
 #include <boost/spirit/home/x3/operator/detail/alternative.hpp>
-
+#include <boost/spirit/home/x3/support/meta.hpp>
 #include <boost/variant/variant_fwd.hpp>
 
+#include <type_traits>
+
 namespace boost { namespace spirit { namespace x3
 {
     template <typename Left, typename Right>
@@ -53,11 +54,96 @@ namespace boost { namespace spirit { namespace x3
     }
 }}}
 
+
+namespace boost { namespace spirit { namespace x3 { namespace detail
+{
+    template <typename Seq, typename... Ts>
+    struct add_alternative_types_impl;
+
+    template <template<class...> typename Seq, typename... Ts>
+    struct add_alternative_types_impl<Seq<Ts...>>
+    {
+        using type = Seq<Ts...>;
+    };
+
+    template <template<class...> typename Seq, typename... Ts, typename U, typename... Us>
+    struct add_alternative_types_impl<Seq<Ts...>, U, Us...>
+    {
+        using next_sequence = conditional_t<Seq<Ts...>::template contains<U>,
+            Seq<Ts...>,
+            conditional_t<std::is_same_v<std::remove_const_t<U>, unused_type>,
+                typename Seq<Ts...>::template prepend<U>,
+                typename Seq<Ts...>::template append<U>
+            >
+        >;
+
+        using type = typename add_alternative_types_impl<next_sequence, Us...>::type;
+    };
+
+    template <typename Seq, typename... Ts>
+    using add_alternative_types = typename add_alternative_types_impl<Seq, Ts...>::type;
+
+    template <typename... Seqs>
+    struct merge_types_of_alternative_impl;
+
+    template <template <class...> typename Seq1, typename... T1s, template <class...> typename Seq2, typename... T2s>
+    struct merge_types_of_alternative_impl<Seq1<T1s...>, Seq2<T2s...>>
+    {
+        using type = add_alternative_types<Seq1<T1s...>, T2s...>;
+    };
+
+    template <typename... Seqs>
+    using merge_types_of_alternative = typename merge_types_of_alternative_impl<Seqs...>::type;
+
+    template <typename P, typename C>
+    struct get_types_of_alternative
+    {
+        using type = type_sequence<typename traits::attribute_of<P, C>::type>;
+    };
+
+    template <typename L, typename R, typename C>
+    struct get_types_of_alternative<alternative<L, R>, C>
+    {
+        using type = merge_types_of_alternative<
+            typename get_types_of_alternative<L, C>::type,
+            typename get_types_of_alternative<R, C>::type
+        >;
+    };
+
+    template <template <typename...> typename A, typename Seq>
+    struct type_sequence_to_alternative_attribute;
+
+    template <template <typename...> typename A, template <typename...> typename Seq>
+    struct type_sequence_to_alternative_attribute<A, Seq<>>
+    {
+        using type = unused_type;
+    };
+
+    template <template <typename...> typename A, template <typename...> typename Seq, typename T, typename... Ts>
+    struct type_sequence_to_alternative_attribute<A, Seq<T, Ts...>>
+    {
+        using type = conditional_t<sizeof...(Ts) == 0,
+            T,
+            A<T, Ts...>
+        >;
+    };
+
+    template <template <typename...> typename A, template <typename...> typename Seq, typename T>
+    struct type_sequence_to_alternative_attribute<A, Seq<unused_type, T>>
+    {
+        using type = boost::optional<T>;
+    };
+
+    template <template <typename...> class A, typename P, typename C>
+    using attribute_of_alternative = type_sequence_to_alternative_attribute<A,
+        typename get_types_of_alternative<P, C>::type>;
+}}}}
+
 namespace boost { namespace spirit { namespace x3 { namespace traits
 {
     template <typename Left, typename Right, typename Context>
     struct attribute_of<x3::alternative<Left, Right>, Context>
-        : x3::detail::attribute_of_binary<boost::variant, x3::alternative, Left, Right, Context> {};
+        : x3::detail::attribute_of_alternative<boost::variant, x3::alternative<Left, Right>, Context> {};
 }}}}
 
 #endif
diff --git a/include/boost/spirit/home/x3/operator/sequence.hpp b/include/boost/spirit/home/x3/operator/sequence.hpp
index 9e6e1702fc..3edbfbfd92 100644
--- a/include/boost/spirit/home/x3/operator/sequence.hpp
+++ b/include/boost/spirit/home/x3/operator/sequence.hpp
@@ -7,10 +7,11 @@
 #if !defined(BOOST_SPIRIT_X3_SEQUENCE_JAN_06_2013_1015AM)
 #define BOOST_SPIRIT_X3_SEQUENCE_JAN_06_2013_1015AM
 
-#include <boost/spirit/home/x3/support/traits/attribute_of_binary.hpp>
 #include <boost/spirit/home/x3/core/parser.hpp>
 #include <boost/spirit/home/x3/operator/detail/sequence.hpp>
 #include <boost/spirit/home/x3/directive/expect.hpp>
+#include <boost/spirit/home/x3/support/meta.hpp>
+#include <boost/spirit/home/x3/support/unused.hpp>
 
 #include <boost/fusion/include/deque_fwd.hpp>
 
@@ -64,11 +65,40 @@ namespace boost { namespace spirit { namespace x3
     }
 }}}
 
+namespace boost { namespace spirit { namespace x3 { namespace detail
+{
+    template <typename Attribute>
+    struct types_of_sequence_init : type_sequence<Attribute> {};
+    template <>
+    struct types_of_sequence_init<unused_type> : type_sequence<> {};
+    template <>
+    struct types_of_sequence_init<unused_type const> : type_sequence<> {};
+
+    template <typename P, typename C>
+    struct get_types_of_sequence
+      : types_of_sequence_init<typename traits::attribute_of<P, C>::type> {};
+
+    template <typename L, typename R, typename C>
+    struct get_types_of_sequence<x3::sequence<L, R>, C>
+      : get_types_of_sequence<L, C>::template extend<get_types_of_sequence<R, C>> {};
+
+    template <template <typename...> class A, typename T, int = T::size>
+    struct type_sequence_to_attribute { using type = typename T::template transfer_to<A>; };
+    template <template <typename...> class A, typename T>
+    struct type_sequence_to_attribute<A, T, 1> : T::template transfer_to<type_identity> {};
+    template <template <typename...> class A, typename T>
+    struct type_sequence_to_attribute<A, T, 0> { using type = unused_type; };
+
+    template <template <typename...> class A, typename P, typename C>
+    using attribute_of_sequence = type_sequence_to_attribute<A,
+                                    typename get_types_of_sequence<P, C>::type>;
+}}}}
+
 namespace boost { namespace spirit { namespace x3 { namespace traits
 {
     template <typename Left, typename Right, typename Context>
     struct attribute_of<x3::sequence<Left, Right>, Context>
-        : x3::detail::attribute_of_binary<fusion::deque, x3::sequence, Left, Right, Context> {};
+        : x3::detail::attribute_of_sequence<fusion::deque, x3::sequence<Left, Right>, Context> {};
 }}}}
 
 #endif
diff --git a/include/boost/spirit/home/x3/support/meta.hpp b/include/boost/spirit/home/x3/support/meta.hpp
new file mode 100644
index 0000000000..aed1a18d6b
--- /dev/null
+++ b/include/boost/spirit/home/x3/support/meta.hpp
@@ -0,0 +1,57 @@
+/*=============================================================================
+    Copyright (c) 2020 Nikita Kniazev
+
+    Distributed under the Boost Software License, Version 1.0. (See accompanying
+    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+=============================================================================*/
+
+#ifndef BOOST_SPIRIT_X3_SUPPORT_META
+#define BOOST_SPIRIT_X3_SUPPORT_META
+
+#include <boost/spirit/home/x3/support/unused.hpp>
+#include <type_traits>
+
+namespace boost { namespace spirit { namespace x3 { namespace detail
+{
+    template <typename... T>
+    struct type_sequence
+    {
+        using type = type_sequence;
+
+        static const int size = sizeof...(T);
+
+        template <typename... U>
+        using append = type_sequence<T..., U...>;
+
+        template <typename... U>
+        using prepend = type_sequence<U..., T...>;
+
+        template <typename U>
+        using extend = typename U::template prepend<T...>;
+
+        template <template <typename...> class U>
+        using transfer_to = U<T...>;
+
+        template <typename U>
+        static constexpr bool contains = (std::is_same_v<U, T> || ...);
+    };
+
+    template <bool>
+    struct conditional_impl
+    {
+        template <typename T, typename>
+        using type = T;
+    };
+
+    template <>
+    struct conditional_impl<false>
+    {
+        template <typename, typename F>
+        using type = F;
+    };
+
+    template <bool B, typename T, typename F>
+    using conditional_t = typename conditional_impl<B>::template type<T, F>;
+}}}}
+
+#endif
diff --git a/include/boost/spirit/home/x3/support/traits/attribute_of_binary.hpp b/include/boost/spirit/home/x3/support/traits/attribute_of_binary.hpp
deleted file mode 100644
index 2cbada00bb..0000000000
--- a/include/boost/spirit/home/x3/support/traits/attribute_of_binary.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*=============================================================================
-    Copyright (c) 2020 Nikita Kniazev
-
-    Distributed under the Boost Software License, Version 1.0. (See accompanying
-    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-=============================================================================*/
-#ifndef BOOST_SPIRIT_X3_SUPPORT_TRAITS_ATTRIBUTE_OF_BINARY
-#define BOOST_SPIRIT_X3_SUPPORT_TRAITS_ATTRIBUTE_OF_BINARY
-
-#include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
-#include <boost/spirit/home/x3/support/unused.hpp>
-#include <boost/type_traits/type_identity.hpp>
-
-namespace boost { namespace spirit { namespace x3 { namespace detail
-{
-    template <typename... T>
-    struct type_sequence
-    {
-        using type = type_sequence;
-
-        static const int size = sizeof...(T);
-
-        template <typename... U>
-        using append = type_sequence<T..., U...>;
-
-        template <typename... U>
-        using prepend = type_sequence<U..., T...>;
-
-        template <typename U>
-        using extend = typename U::template prepend<T...>;
-
-        template <template <typename...> class U>
-        using transfer_to = U<T...>;
-    };
-
-    template <typename Attribute>
-    struct types_of_binary_init : type_sequence<Attribute> {};
-    template <>
-    struct types_of_binary_init<unused_type> : type_sequence<> {};
-    template <>
-    struct types_of_binary_init<unused_type const> : type_sequence<> {};
-
-    template <template <typename, typename> class B, typename P, typename C>
-    struct get_types_of_binary
-      : types_of_binary_init<typename traits::attribute_of<P, C>::type> {};
-    template <template <typename, typename> class B, typename L, typename R, typename C>
-    struct get_types_of_binary<B, B<L, R>, C>
-      : get_types_of_binary<B, L, C>::template extend<get_types_of_binary<B, R, C>> {};
-
-    template <template <typename...> class A, typename T, int = T::size>
-    struct type_sequence_to_attribute { using type = typename T::template transfer_to<A>; };
-    template <template <typename...> class A, typename T>
-    struct type_sequence_to_attribute<A, T, 1> : T::template transfer_to<type_identity> {};
-    template <template <typename...> class A, typename T>
-    struct type_sequence_to_attribute<A, T, 0> { using type = unused_type; };
-
-    template <template <typename...> class A, template <typename, typename> class B,
-        typename L, typename R, typename C>
-    using attribute_of_binary = type_sequence_to_attribute<A,
-                                    typename get_types_of_binary<B, B<L, R>, C>::type>;
-}}}}
-#endif
diff --git a/test/x3/Jamfile b/test/x3/Jamfile
index f2671e6ba1..e03ec50170 100644
--- a/test/x3/Jamfile
+++ b/test/x3/Jamfile
@@ -32,6 +32,7 @@ project spirit-x3
             cxx14_return_type_deduction         # grep -Er "auto[^\\(=\\)]+\(" *
             #cxx14_std_exchange                  # grep -r "exchange" *
             cxx14_variable_templates
+            cxx17_fold_expressions
         ]
     ;
 
diff --git a/test/x3/alternative.cpp b/test/x3/alternative.cpp
index 6a1a63388d..be1af4faf1 100644
--- a/test/x3/alternative.cpp
+++ b/test/x3/alternative.cpp
@@ -45,6 +45,14 @@ struct stationary : boost::noncopyable
     int val;
 };
 
+template <typename Expected, typename Expr>
+constexpr void test_attribute_of_alternative(Expr)
+{
+    using namespace boost::spirit::x3;
+    using namespace boost::spirit::x3::traits;
+
+    static_assert(std::is_same_v<Expected, typename attribute_of<Expr, unused_type>::type>);
+}
 
 int
 main()
@@ -296,5 +304,25 @@ main()
         BOOST_TEST(test_attr("abaabb", +('a' >> attr(Foo{}) | 'b' >> attr(int{})), x));
     }
 
+    {   // compile checks
+        using namespace boost::spirit::x3;
+
+        test_attribute_of_alternative<boost::variant<char, int>>(char_ | int_);
+        test_attribute_of_alternative<boost::variant<char, int, double>>(char_ | int_ | double_);
+        test_attribute_of_alternative<boost::variant<unused_type, char, int, double>>(char_ | int_ | double_ | eps);
+        test_attribute_of_alternative<boost::variant<unused_type, char, int, double>>(char_ | int_ | double_ | eps | int_);
+        test_attribute_of_alternative<boost::variant<unused_type, char, int, double>>(char_ | int_ | double_ | eps | int_ | eps);
+        test_attribute_of_alternative<unused_type>(eps);
+        test_attribute_of_alternative<boost::optional<int>>(eps | int_);
+        test_attribute_of_alternative<boost::optional<int>>(eps | int_);
+        test_attribute_of_alternative<unused_type>(eps | eps);
+        test_attribute_of_alternative<int>(int_ | int_);
+        test_attribute_of_alternative<int>(int_);
+
+        test_attribute_of_alternative<boost::variant<boost::fusion::deque<int, int>, char>>((int_ >> int_) | char_);
+        test_attribute_of_alternative<boost::variant<unused_type, char, boost::fusion::deque<int, int>>>(char_ | (int_ >> int_) | eps);
+        test_attribute_of_alternative<boost::optional<boost::fusion::deque<int, int>>>(eps | (int_ >> int_));
+    }
+
     return boost::report_errors();
 }
diff --git a/test/x3/sequence.cpp b/test/x3/sequence.cpp
index 2b94dfa8aa..8948122a23 100644
--- a/test/x3/sequence.cpp
+++ b/test/x3/sequence.cpp
@@ -15,6 +15,15 @@
 #include "test.hpp"
 #include "utils.hpp"
 
+template <typename Expected, typename Expr>
+void test_attribute_of_sequence(Expr)
+{
+    using namespace boost::spirit::x3;
+    using namespace boost::spirit::x3::traits;
+
+    static_assert(std::is_same_v<Expected, typename attribute_of<Expr, unused_type>::type>);
+}
+
 int
 main()
 {
@@ -493,5 +502,16 @@ main()
         BOOST_TEST_EQ(v.size(), 4);
     }
 
+    {   // compile checks only
+        using namespace boost::spirit::x3;
+
+        test_attribute_of_sequence<boost::fusion::deque<int, int>>(int_ >> int_);
+        test_attribute_of_sequence<int>(int_ >> eps);
+        test_attribute_of_sequence<int>(eps >> int_);
+        test_attribute_of_sequence<unused_type>(eps >> eps);
+        test_attribute_of_sequence<int>(eps >> int_ >> eps);
+        test_attribute_of_sequence<boost::fusion::deque<int, int>>(int_ >> eps >> eps >> int_ >> eps);
+    }
+
     return boost::report_errors();
 }