From a653da4fa4fd3ce4d321dc6618db0e663963eedb Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 13:49:31 +0900 Subject: [PATCH 01/52] Rename and drop aliases --- .../iris/x4/core/detail/parse_alternative.hpp | 2 +- .../x4/core/detail/parse_into_container.hpp | 2 +- .../iris/x4/core/detail/parse_sequence.hpp | 2 +- include/iris/x4/core/list_like_parser.hpp | 6 +-- include/iris/x4/core/move_to.hpp | 14 ++--- .../iris/x4/string/detail/string_parse.hpp | 4 +- include/iris/x4/traits/can_hold.hpp | 2 +- include/iris/x4/traits/tuple_traits.hpp | 53 +++++++------------ 8 files changed, 34 insertions(+), 51 deletions(-) diff --git a/include/iris/x4/core/detail/parse_alternative.hpp b/include/iris/x4/core/detail/parse_alternative.hpp index 5176df180..2c67a3157 100644 --- a/include/iris/x4/core/detail/parse_alternative.hpp +++ b/include/iris/x4/core/detail/parse_alternative.hpp @@ -108,7 +108,7 @@ struct pass_non_variant_attribute // Unwrap single element sequences template - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like::value struct pass_non_variant_attribute { using attr_type = typename std::remove_reference_t< diff --git a/include/iris/x4/core/detail/parse_into_container.hpp b/include/iris/x4/core/detail/parse_into_container.hpp index 2b37abb06..4b10d30e3 100644 --- a/include/iris/x4/core/detail/parse_into_container.hpp +++ b/include/iris/x4/core/detail/parse_into_container.hpp @@ -84,7 +84,7 @@ struct parse_into_container_impl_default } } else { - if constexpr (traits::is_size_one_sequence_v) { + if constexpr (traits::is_single_element_tuple_like::value) { // attribute is single element tuple-like; unwrap and try again return parse_into_container_impl_default::call(parser, first, last, ctx, alloy::get<0>(unwrapped_attr)); } else { diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index 6c534f0dd..51d93d30a 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -77,7 +77,7 @@ struct pass_through_sequence_attribute template struct pass_sequence_attribute : std::conditional_t< - traits::is_size_one_view_v, + traits::is_single_element_tuple_like_view::value, pass_sequence_attribute_size_one_view, pass_through_sequence_attribute > diff --git a/include/iris/x4/core/list_like_parser.hpp b/include/iris/x4/core/list_like_parser.hpp index 3d9c6e21f..c09231007 100644 --- a/include/iris/x4/core/list_like_parser.hpp +++ b/include/iris/x4/core/list_like_parser.hpp @@ -29,7 +29,7 @@ template // non-variant `ExposedAttr` struct unwrap_container_candidate { - using type = traits::synthesized_value< + using type = traits::unwrap_single_element_tuple_like< unwrap_recursive_type< typename unwrap_container_appender::type > @@ -59,7 +59,7 @@ template } template - requires traits::is_size_one_sequence_v> + requires traits::is_single_element_tuple_like>::value [[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept { return std::forward_like(alloy::get<0>(std::forward(value))); @@ -72,7 +72,7 @@ struct unwrap_single_element_plain }; template - requires traits::is_size_one_sequence_v> + requires traits::is_single_element_tuple_like>::value struct unwrap_single_element_plain { using type = std::remove_cvref_t>; diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 24612e6c1..e288e0506 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -116,7 +116,7 @@ constexpr void move_to(It const&, Se const&, unused_type const&&) = delete; // t // Category specific -------------------------------------- template Dest> - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like>::value constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) @@ -127,7 +127,7 @@ move_to(Source&& src, Dest& dest) } template Dest> - requires (!traits::is_size_one_sequence_v) + requires (!traits::is_single_element_tuple_like>::value) constexpr void move_to(Source&& src, Dest& dest) noexcept(std::is_nothrow_assignable_v) @@ -139,8 +139,8 @@ move_to(Source&& src, Dest& dest) template Dest> requires - traits::is_same_size_sequence_v && - (!traits::is_size_one_sequence_v) + traits::is_same_size_tuple_like>::value && + (!traits::is_single_element_tuple_like::value) constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(alloy::tuple_assign(std::forward(src), dest))) @@ -166,7 +166,7 @@ move_to(Source&& src, Dest& dest) } template Dest> - requires (!std::is_assignable_v) && traits::is_size_one_sequence_v + requires (!std::is_assignable_v) && traits::is_single_element_tuple_like::value constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) @@ -221,7 +221,7 @@ move_to(It first, Se last, Dest& dest) } template Se, traits::CategorizedAttr Dest> - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like::value constexpr void move_to(It first, Se last, Dest& dest) noexcept(noexcept(x4::move_to(first, last, alloy::get<0>(dest)))) @@ -268,7 +268,7 @@ move_to(Source&& src, Dest& dest) // Size-one tuple-like forwarding template Dest> - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like::value constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(x4::move_to(std::forward(src), alloy::get<0>(dest)))) diff --git a/include/iris/x4/string/detail/string_parse.hpp b/include/iris/x4/string/detail/string_parse.hpp index 945a6a732..cf6a1f959 100644 --- a/include/iris/x4/string/detail/string_parse.hpp +++ b/include/iris/x4/string/detail/string_parse.hpp @@ -30,7 +30,7 @@ string_parse( Attr& attr, CaseCompareFunc const& compare ) noexcept(std::same_as, unused_container_type>) { - using synthesized_value_type = traits::synthesized_value_t; + using synthesized_value_type = traits::unwrap_single_element_tuple_like::type; static_assert(std::same_as, traits::container_attr>); using value_type = traits::container_value::type; static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); @@ -77,7 +77,7 @@ string_parse( It& first, Se const& last, Attr& attr ) noexcept(std::same_as, unused_container_type>) { - using synthesized_value_type = traits::synthesized_value_t; + using synthesized_value_type = traits::unwrap_single_element_tuple_like::type; static_assert(std::same_as, traits::container_attr>); using value_type = traits::container_value::type; static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index 469046c7f..c468caf35 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -41,7 +41,7 @@ template struct is_all_substitute_for_tuple : std::false_type {}; template - requires is_same_size_sequence_v + requires is_same_size_tuple_like::value struct is_all_substitute_for_tuple : is_all_substitute_for_tuple_impl {}; template diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index 0771aa886..1fc8380c1 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -19,73 +19,56 @@ namespace iris::x4::traits { template struct has_same_size : std::bool_constant< - alloy::tuple_size_v> == - alloy::tuple_size_v> + alloy::tuple_size_v == + alloy::tuple_size_v > {}; -template -constexpr bool has_same_size_v = has_same_size::value; - template struct has_size - : std::bool_constant> == N> + : std::bool_constant == N> {}; -template -constexpr bool has_size_v = has_size::value; - template -struct is_same_size_sequence +struct is_same_size_tuple_like : std::bool_constant>, - alloy::is_tuple_like>, + alloy::is_tuple_like, + alloy::is_tuple_like, has_same_size >> {}; -template -constexpr bool is_same_size_sequence_v = is_same_size_sequence::value; - -template -struct is_size_one_sequence +template +struct is_single_element_tuple_like : std::bool_constant>, - has_size + alloy::is_tuple_like, + has_size >> {}; -template -constexpr bool is_size_one_sequence_v = is_size_one_sequence::value; - template -struct is_size_one_view +struct is_single_element_tuple_like_view : std::bool_constant>, + alloy::is_tuple_like_view, has_size >> {}; -template -constexpr bool is_size_one_view_v = is_size_one_view::value; - - template -struct synthesized_value +struct unwrap_single_element_tuple_like { using type = T; }; template -using synthesized_value_t = typename synthesized_value::type; - -template - requires is_size_one_sequence_v> -struct synthesized_value + requires is_single_element_tuple_like::value +struct unwrap_single_element_tuple_like { - using type = std::remove_cvref_t>; + using type = alloy::tuple_element_t<0, T>; }; + + } // iris::x4::traits #endif From 6b0c90fd25ad43dad305feba36135998d5b87a5a Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 14:09:06 +0900 Subject: [PATCH 02/52] Add test --- test/x4/CMakeLists.txt | 1 + test/x4/single_element_tuple_like.cpp | 59 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/x4/single_element_tuple_like.cpp diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index ab7fdac55..6737d0ce5 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -85,6 +85,7 @@ x4_define_tests( rule4 seek sequence + single_element_tuple_like skip substitution symbols1 diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp new file mode 100644 index 000000000..003142eb3 --- /dev/null +++ b/test/x4/single_element_tuple_like.cpp @@ -0,0 +1,59 @@ +#include "iris_x4_test.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +struct Ident +{ + std::string value; +}; + +IRIS_ALLOY_ADAPT_STRUCT(Ident, value) + +using IdentRule = x4::rule; + +IRIS_X4_DECLARE_CONSTEXPR(IdentRule) + +constexpr IdentRule ident; + +constexpr auto ident_def = +x4::char_; + +IRIS_X4_DEFINE_CONSTEXPR(ident) + +TEST_CASE("single_element_tuple_like") +{ + using x4::eps; + using x4::as; + + { + constexpr auto parser = ident; + Ident attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("abc", parser, attr)); + CHECK(attr.value == "abc"); + } + { + constexpr auto parser = as(ident); + Ident attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("abc", parser, attr)); + CHECK(attr.value == "abc"); + } + { + constexpr auto parser = ident >> eps; + Ident attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("abc", parser, attr)); + CHECK(attr.value == "abc"); + } +} From f4430eed08d3463c8a3fd02cc830c15153d97afa Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 14:26:43 +0900 Subject: [PATCH 03/52] Move single element tuple like traits --- include/iris/x4/core/list_like_parser.hpp | 33 ++--------------------- include/iris/x4/traits/tuple_traits.hpp | 24 +++++++++++++++++ 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/include/iris/x4/core/list_like_parser.hpp b/include/iris/x4/core/list_like_parser.hpp index c09231007..dd886b7c2 100644 --- a/include/iris/x4/core/list_like_parser.hpp +++ b/include/iris/x4/core/list_like_parser.hpp @@ -14,11 +14,8 @@ #include #include -#include #include -#include - namespace iris::x4 { namespace list_like_parser { @@ -52,32 +49,6 @@ struct chunk_buffer_impl static_assert(traits::X4Container::type>); }; -template -[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept -{ - return std::forward(value); -} - -template - requires traits::is_single_element_tuple_like>::value -[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept -{ - return std::forward_like(alloy::get<0>(std::forward(value))); -} - -template -struct unwrap_single_element_plain -{ - using type = std::remove_cvref_t; -}; - -template - requires traits::is_single_element_tuple_like>::value -struct unwrap_single_element_plain -{ - using type = std::remove_cvref_t>; -}; - } // detail @@ -88,10 +59,10 @@ using chunk_buffer = detail::chunk_buffer_impl::type; template [[nodiscard]] constexpr auto& get_container(ExposedAttr& attr) { - using unwrapped_attr_type = detail::unwrap_single_element_plain< + using unwrapped_attr_type = traits::unwrap_single_element_plain< unwrap_recursive_type >::type; - auto& unwrapped_attr = detail::unwrap_single_element(iris::unwrap_recursive(attr)); + auto& unwrapped_attr = traits::unwrap_single_element(iris::unwrap_recursive(attr)); if constexpr (traits::is_variant_v) { using container_alternative = traits::variant_find_holdable_type< diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index 1fc8380c1..7886862e4 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -67,7 +67,31 @@ struct unwrap_single_element_tuple_like using type = alloy::tuple_element_t<0, T>; }; +template +struct unwrap_single_element_plain +{ + using type = std::remove_cvref_t; +}; + +template + requires traits::is_single_element_tuple_like>::value +struct unwrap_single_element_plain +{ + using type = std::remove_cvref_t>; +}; + +template +[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept +{ + return std::forward(value); +} +template + requires traits::is_single_element_tuple_like>::value +[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept +{ + return std::forward_like(alloy::get<0>(std::forward(value))); +} } // iris::x4::traits From cfe7cb67454ae52fa857df25c0808ceb7d4f5084 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 14:27:10 +0900 Subject: [PATCH 04/52] Unwrap single element tuple like in `string_parse` --- include/iris/x4/core/move_to.hpp | 3 ++- include/iris/x4/string/detail/string_parse.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index e288e0506..8782fd30f 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -223,9 +223,10 @@ move_to(It first, Se last, Dest& dest) template Se, traits::CategorizedAttr Dest> requires traits::is_single_element_tuple_like::value constexpr void -move_to(It first, Se last, Dest& dest) +move_to(It first, Se last, Dest& dest) // TODO: delete this overload noexcept(noexcept(x4::move_to(first, last, alloy::get<0>(dest)))) { + static_assert(false); x4::move_to(first, last, alloy::get<0>(dest)); } diff --git a/include/iris/x4/string/detail/string_parse.hpp b/include/iris/x4/string/detail/string_parse.hpp index cf6a1f959..1a344de88 100644 --- a/include/iris/x4/string/detail/string_parse.hpp +++ b/include/iris/x4/string/detail/string_parse.hpp @@ -45,7 +45,7 @@ string_parse( } } - x4::move_to(first, it, attr); + x4::move_to(first, it, traits::unwrap_single_element(attr)); first = it; return true; } From 6a17b009c96f03729da8168ada3241422cada606 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 14:35:24 +0900 Subject: [PATCH 05/52] Add test --- test/x4/single_element_tuple_like.cpp | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 003142eb3..76d46be86 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include #include #include @@ -32,8 +35,11 @@ IRIS_X4_DEFINE_CONSTEXPR(ident) TEST_CASE("single_element_tuple_like") { - using x4::eps; using x4::as; + using x4::char_; + using x4::eps; + using x4::int_; + using x4::lexeme; { constexpr auto parser = ident; @@ -56,4 +62,28 @@ TEST_CASE("single_element_tuple_like") REQUIRE(parse("abc", parser, attr)); CHECK(attr.value == "abc"); } + + // see https://github.com/boostorg/spirit_x4/issues/27 + { + constexpr auto parser = char_ >> *char_; + std::string attr; + REQUIRE(parse("abc", parser, attr)); + CHECK(attr == "abc"); + } + { + constexpr auto parser = lexeme[char_ >> *char_]; + std::string attr; + REQUIRE(parse("abc", parser, attr)); + CHECK(attr == "abc"); + } + { + constexpr auto identifier = char_ >> *char_; + constexpr auto func_call = identifier >> '(' >> int_ >> ')'; + REQUIRE(parse("abc(42)", func_call)); + } + { + constexpr auto identifier = lexeme[char_ >> *char_]; + constexpr auto func_call = identifier >> '(' >> int_ >> ')'; + REQUIRE(parse("abc(42)", func_call)); + } } From a75a454ca666824ae22078e3d84cf84f3c5317b8 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 14:42:55 +0900 Subject: [PATCH 06/52] Fix test --- test/x4/single_element_tuple_like.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 76d46be86..760ca1d07 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,8 @@ IRIS_X4_DEFINE_CONSTEXPR(ident) TEST_CASE("single_element_tuple_like") { using x4::as; + using x4::alnum; + using x4::alpha; using x4::char_; using x4::eps; using x4::int_; @@ -65,24 +68,24 @@ TEST_CASE("single_element_tuple_like") // see https://github.com/boostorg/spirit_x4/issues/27 { - constexpr auto parser = char_ >> *char_; + constexpr auto parser = alpha >> *alnum; std::string attr; REQUIRE(parse("abc", parser, attr)); CHECK(attr == "abc"); } { - constexpr auto parser = lexeme[char_ >> *char_]; + constexpr auto parser = lexeme[alpha >> *alnum]; std::string attr; REQUIRE(parse("abc", parser, attr)); CHECK(attr == "abc"); } { - constexpr auto identifier = char_ >> *char_; + constexpr auto identifier = alpha >> *alnum; constexpr auto func_call = identifier >> '(' >> int_ >> ')'; REQUIRE(parse("abc(42)", func_call)); } { - constexpr auto identifier = lexeme[char_ >> *char_]; + constexpr auto identifier = lexeme[alpha >> *alnum]; constexpr auto func_call = identifier >> '(' >> int_ >> ')'; REQUIRE(parse("abc(42)", func_call)); } From 2aa10ab040b35848a3b222816759d9ff50e7bab2 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sat, 7 Mar 2026 17:04:39 +0900 Subject: [PATCH 07/52] Unwrap single element tuple recursively --- .../iris/x4/core/detail/parse_sequence.hpp | 97 +++++++++---------- .../iris/x4/string/detail/string_parse.hpp | 70 +++++++------ test/x4/single_element_tuple_like.cpp | 57 +++++++++-- 3 files changed, 133 insertions(+), 91 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index 51d93d30a..daa5053a5 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -203,43 +203,10 @@ struct partition_attribute } }; -// Default overload, no constraints on attribute category -template Se, class Context, class Attr> -[[nodiscard]] constexpr bool -parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) - // TODO: noexcept -{ - static_assert(X4Attribute); - - using partition = partition_attribute< - typename Parser::left_type, - typename Parser::right_type, - Attr - >; - - auto&& l_part = partition::left(attr); - auto&& r_part = partition::right(attr); - auto&& l_attr = partition::l_pass::call(l_part); - auto&& r_attr = partition::r_pass::call(r_part); - - auto&& l_attr_appender = x4::make_container_appender(l_attr); - auto&& r_attr_appender = x4::make_container_appender(r_attr); - - It local_it = first; - if (parser.left.parse(local_it, last, ctx, l_attr_appender) && - parser.right.parse(local_it, last, ctx, r_attr_appender) - ) { - first = std::move(local_it); - return true; - } - - return false; -} - template Se, class Context, X4Attribute Attr> requires (parser_traits::sequence_size > 1) [[nodiscard]] constexpr bool -parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) + parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) noexcept(is_nothrow_parsable_v) { // static_assert(Parsable); @@ -249,32 +216,58 @@ parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context con template Se, class Context, X4Attribute Attr> requires (parser_traits::sequence_size <= 1) [[nodiscard]] constexpr bool -parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) + parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) noexcept(noexcept(detail::parse_into_container(parser, first, last, ctx, attr))) { return detail::parse_into_container(parser, first, last, ctx, attr); } -template< - class Parser, std::forward_iterator It, std::sentinel_for Se, class Context, - traits::CategorizedAttr ContainerAttr -> +// Default overload, no constraints on attribute category +template Se, class Context, class Attr> [[nodiscard]] constexpr bool -parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, ContainerAttr& container_attr) - noexcept( - std::is_nothrow_copy_assignable_v && - noexcept(detail::parse_sequence_impl(parser.left, first, last, ctx, container_attr)) && - noexcept(detail::parse_sequence_impl(parser.right, first, last, ctx, container_attr)) - ) +parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) + // TODO: noexcept { - It local_it = first; - if (detail::parse_sequence_impl(parser.left, local_it, last, ctx, container_attr) && - detail::parse_sequence_impl(parser.right, local_it, last, ctx, container_attr) - ) { - first = std::move(local_it); - return true; + static_assert(X4Attribute); + + if constexpr (traits::is_container_v) { + It local_it = first; + if (detail::parse_sequence_impl(parser.left, local_it, last, ctx, attr) && + detail::parse_sequence_impl(parser.right, local_it, last, ctx, attr) + ) { + first = std::move(local_it); + return true; + } + return false; + } else { + if constexpr (traits::is_single_element_tuple_like::value && !traits::can_hold::attribute_type, Attr>::value) { + return detail::parse_sequence(parser, first, last, ctx, traits::unwrap_single_element(attr)); + } else { + using partition = partition_attribute< + typename Parser::left_type, + typename Parser::right_type, + Attr + >; + + auto&& l_part = partition::left(attr); + auto&& r_part = partition::right(attr); + auto&& l_attr = partition::l_pass::call(l_part); + auto&& r_attr = partition::r_pass::call(r_part); + + auto&& l_attr_appender = x4::make_container_appender(l_attr); + auto&& r_attr_appender = x4::make_container_appender(r_attr); + + It local_it = first; + if (parser.left.parse(local_it, last, ctx, l_attr_appender) && + parser.right.parse(local_it, last, ctx, r_attr_appender) + ) { + first = std::move(local_it); + return true; + } + + return false; + } } - return false; } template diff --git a/include/iris/x4/string/detail/string_parse.hpp b/include/iris/x4/string/detail/string_parse.hpp index 1a344de88..dff71cf3a 100644 --- a/include/iris/x4/string/detail/string_parse.hpp +++ b/include/iris/x4/string/detail/string_parse.hpp @@ -30,24 +30,27 @@ string_parse( Attr& attr, CaseCompareFunc const& compare ) noexcept(std::same_as, unused_container_type>) { - using synthesized_value_type = traits::unwrap_single_element_tuple_like::type; - static_assert(std::same_as, traits::container_attr>); - using value_type = traits::container_value::type; - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); - - It it = first; - auto stri = str.begin(); - auto str_last = str.end(); - - for (; stri != str_last; ++stri, ++it) { - if (it == last || compare(*stri, *it) != 0) { - return false; + if constexpr (traits::is_single_element_tuple_like::value) { + return detail::string_parse(str, first, last, traits::unwrap_single_element(attr), compare); + } else { + static_assert(std::same_as::type, traits::container_attr>); + using value_type = traits::container_value::type; + static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + + It it = first; + auto stri = str.begin(); + auto str_last = str.end(); + + for (; stri != str_last; ++stri, ++it) { + if (it == last || compare(*stri, *it) != 0) { + return false; + } } - } - x4::move_to(first, it, traits::unwrap_single_element(attr)); - first = it; - return true; + x4::move_to(first, it, attr); + first = it; + return true; + } } template Se, class CaseCompareFunc> @@ -77,24 +80,27 @@ string_parse( It& first, Se const& last, Attr& attr ) noexcept(std::same_as, unused_container_type>) { - using synthesized_value_type = traits::unwrap_single_element_tuple_like::type; - static_assert(std::same_as, traits::container_attr>); - using value_type = traits::container_value::type; - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); - - auto uc_it = ucstr.begin(); - auto uc_last = ucstr.end(); - auto lc_it = lcstr.begin(); - It it = first; - - for (; uc_it != uc_last; ++uc_it, ++lc_it, ++it) { - if (it == last || (*uc_it != *it && *lc_it != *it)) { - return false; + if constexpr (traits::is_single_element_tuple_like::value) { + return detail::string_parse(ucstr, lcstr, first, last, traits::unwrap_single_element(attr)); + } else { + static_assert(std::same_as, traits::container_attr>); + using value_type = traits::container_value::type; + static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + + auto uc_it = ucstr.begin(); + auto uc_last = ucstr.end(); + auto lc_it = lcstr.begin(); + It it = first; + + for (; uc_it != uc_last; ++uc_it, ++lc_it, ++it) { + if (it == last || (*uc_it != *it && *lc_it != *it)) { + return false; + } } + x4::move_to(first, it, attr); + first = it; + return true; } - x4::move_to(first, it, attr); - first = it; - return true; } template Se, X4Attribute Attr> diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 760ca1d07..cd7f8b18a 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -1,14 +1,15 @@ #include "iris_x4_test.hpp" #include +#include +#include #include #include #include #include #include #include -#include -#include +#include #include #include @@ -22,37 +23,49 @@ struct Ident std::string value; }; +struct Var +{ + Ident ident; +}; + IRIS_ALLOY_ADAPT_STRUCT(Ident, value) +IRIS_ALLOY_ADAPT_STRUCT(Var, ident) using IdentRule = x4::rule; +using VarRule = x4::rule; IRIS_X4_DECLARE_CONSTEXPR(IdentRule) +IRIS_X4_DECLARE_CONSTEXPR(VarRule) constexpr IdentRule ident; +constexpr VarRule var; -constexpr auto ident_def = +x4::char_; +constexpr auto ident_def = x4::alpha >> *x4::alnum; +constexpr auto var_def = '$' >> ident; IRIS_X4_DEFINE_CONSTEXPR(ident) +IRIS_X4_DEFINE_CONSTEXPR(var) TEST_CASE("single_element_tuple_like") { using x4::as; using x4::alnum; using x4::alpha; - using x4::char_; using x4::eps; using x4::int_; using x4::lexeme; + using x4::string; + // ident { - constexpr auto parser = ident; + constexpr auto parser = string("abc"); Ident attr; - STATIC_CHECK(std::same_as::attribute_type, Ident>); + STATIC_CHECK(std::same_as::attribute_type, std::string>); REQUIRE(parse("abc", parser, attr)); CHECK(attr.value == "abc"); } { - constexpr auto parser = as(ident); + constexpr auto parser = ident; Ident attr; STATIC_CHECK(std::same_as::attribute_type, Ident>); REQUIRE(parse("abc", parser, attr)); @@ -66,6 +79,36 @@ TEST_CASE("single_element_tuple_like") CHECK(attr.value == "abc"); } + // var + { + constexpr auto parser = '$' >> string("abc"); + Var attr; + STATIC_CHECK(std::same_as::attribute_type, std::string>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + { + constexpr auto parser = '$' >> as(string("abc")); + Var attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + { + constexpr auto parser = var; + Var attr; + STATIC_CHECK(std::same_as::attribute_type, Var>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + { + constexpr auto parser = var >> eps; + Var attr; + STATIC_CHECK(std::same_as::attribute_type, Var>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + // see https://github.com/boostorg/spirit_x4/issues/27 { constexpr auto parser = alpha >> *alnum; From 9e240ee34fde7631090b3da01c60cb739e72d0e7 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 01:04:45 +0900 Subject: [PATCH 08/52] Add special handling for rule --- .../iris/x4/core/detail/parse_sequence.hpp | 79 +++++++++++-------- include/iris/x4/rule.hpp | 1 + include/iris/x4/traits/can_hold.hpp | 20 ++--- test/x4/alternative.cpp | 15 ++-- test/x4/attribute_type_check.cpp | 11 +-- test/x4/debug.cpp | 3 +- test/x4/expect.cpp | 7 +- test/x4/int.cpp | 3 +- test/x4/iris_x4_test.hpp | 14 ++++ test/x4/lit.cpp | 3 +- test/x4/move_to.cpp | 12 +-- test/x4/omit.cpp | 7 +- test/x4/optional.cpp | 5 +- test/x4/plus.cpp | 3 +- test/x4/rule4.cpp | 6 +- test/x4/sequence.cpp | 37 ++++----- test/x4/single_element_tuple_like.cpp | 2 +- test/x4/substitution.cpp | 6 +- 18 files changed, 137 insertions(+), 97 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index daa5053a5..e3b658935 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -20,6 +20,7 @@ #include #include + #include #include @@ -30,6 +31,9 @@ namespace iris::x4 { +template +struct rule; + template struct sequence; @@ -88,6 +92,24 @@ struct pass_sequence_attribute, Attr> : pass_through_sequence_attribute {}; +template + requires + traits::is_single_element_tuple_like::value && + (!traits::can_hold::value) +struct pass_sequence_attribute, Attr> +{ + using base_pass_sequence_attribute = pass_through_sequence_attribute; + + using type = alloy::tuple_element_t<0, Attr>&; + + template + [[nodiscard]] static constexpr type + call(Attr_& attribute) noexcept(noexcept(alloy::get<0>(attribute))) + { + return alloy::get<0>(attribute); + } +}; + template requires requires { typename Parser::proxy_backend_type; @@ -117,16 +139,16 @@ struct partition_attribute static_assert( actual_size >= expected_size, "Sequence size of the passed attribute is less than expected." - ); + ); static_assert( actual_size <= expected_size, "Sequence size of the passed attribute is greater than expected." - ); + ); using view = alloy::tuple_ref_t; - using splitted = alloy::tuple_split_t; - using l_part = alloy::tuple_element_t<0, splitted>; - using r_part = alloy::tuple_element_t<1, splitted>; + using split = alloy::tuple_split_t; + using l_part = alloy::tuple_element_t<0, split>; + using r_part = alloy::tuple_element_t<1, split>; using l_pass = pass_sequence_attribute; using r_pass = pass_sequence_attribute; @@ -222,7 +244,6 @@ template Se, class return detail::parse_into_container(parser, first, last, ctx, attr); } -// Default overload, no constraints on attribute category template Se, class Context, class Attr> [[nodiscard]] constexpr bool parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) @@ -240,33 +261,29 @@ parse_sequence(Parser const& parser, It& first, Se const& last, Context const& c } return false; } else { - if constexpr (traits::is_single_element_tuple_like::value && !traits::can_hold::attribute_type, Attr>::value) { - return detail::parse_sequence(parser, first, last, ctx, traits::unwrap_single_element(attr)); - } else { - using partition = partition_attribute< - typename Parser::left_type, - typename Parser::right_type, - Attr - >; - - auto&& l_part = partition::left(attr); - auto&& r_part = partition::right(attr); - auto&& l_attr = partition::l_pass::call(l_part); - auto&& r_attr = partition::r_pass::call(r_part); - - auto&& l_attr_appender = x4::make_container_appender(l_attr); - auto&& r_attr_appender = x4::make_container_appender(r_attr); - - It local_it = first; - if (parser.left.parse(local_it, last, ctx, l_attr_appender) && - parser.right.parse(local_it, last, ctx, r_attr_appender) - ) { - first = std::move(local_it); - return true; - } + using partition = partition_attribute< + typename Parser::left_type, + typename Parser::right_type, + Attr + >; - return false; + auto&& l_part = partition::left(attr); + auto&& r_part = partition::right(attr); + auto&& l_attr = partition::l_pass::call(l_part); + auto&& r_attr = partition::r_pass::call(r_part); + + auto&& l_attr_appender = x4::make_container_appender(l_attr); + auto&& r_attr_appender = x4::make_container_appender(r_attr); + + It local_it = first; + if (parser.left.parse(local_it, last, ctx, l_attr_appender) && + parser.right.parse(local_it, last, ctx, r_attr_appender) + ) { + first = std::move(local_it); + return true; } + + return false; } } diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 406ceb4e2..a7d9d2863 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -390,6 +390,7 @@ struct rule : parser> static_assert(X4Attribute); static_assert(X4UnusedAttribute || !std::is_const_v, "Rule attribute cannot be const qualified"); static_assert(!std::is_same_v, unused_container_type>, "`rule` with `unused_container_type` is not supported"); + static_assert(!is_ttp_specialization_of::value, "alloy::tuple is intended for internal use only"); using id = RuleID; using attribute_type = RuleAttr; diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index c468caf35..e21033e7e 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -31,18 +31,11 @@ struct is_variant; namespace detail { template>> -struct is_all_substitute_for_tuple_impl {}; +struct is_all_substitute_for_tuple {}; template -struct is_all_substitute_for_tuple_impl> - : std::conjunction, alloy::tuple_element_t>...> {}; - -template -struct is_all_substitute_for_tuple : std::false_type {}; - -template - requires is_same_size_tuple_like::value -struct is_all_substitute_for_tuple : is_all_substitute_for_tuple_impl {}; +struct is_all_substitute_for_tuple> + : std::conjunction>, std::remove_reference_t>>...> {}; template struct value_type_can_hold @@ -55,10 +48,9 @@ struct can_hold_impl : std::false_type {}; template requires - alloy::is_tuple_like_v && - alloy::is_tuple_like_v + is_same_size_tuple_like::value struct can_hold_impl - : detail::is_all_substitute_for_tuple + : is_all_substitute_for_tuple {}; template @@ -66,7 +58,7 @@ template is_container_v && is_container_v struct can_hold_impl - : detail::value_type_can_hold + : value_type_can_hold {}; template diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index 6c1ea50a5..6ac73d927 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -27,6 +27,7 @@ #include +#include #include #include @@ -135,7 +136,7 @@ TEST_CASE("alternative") // test if alternatives with all components having unused // attributes have an unused attribute - alloy::tuple v; + std::tuple v; REQUIRE((parse("abc", char_ >> (omit[char_] | omit[char_]) >> char_, v))); CHECK((alloy::get<0>(v) == 'a')); CHECK((alloy::get<1>(v) == 'c')); @@ -256,12 +257,12 @@ TEST_CASE("alternative") // single element tuple-like case { - alloy::tuple> fv; + std::tuple> fv; REQUIRE(parse("12345", int_ | +char_, fv)); CHECK(iris::get(alloy::get<0>(fv)) == 12345); } { - alloy::tuple> fvi; + std::tuple> fvi; REQUIRE(parse("12345", int_ | int_, fvi)); CHECK(iris::get(alloy::get<0>(fvi)) == 12345); } @@ -273,7 +274,7 @@ TEST_CASE("alternative") constexpr auto keys = key1 | key2; constexpr auto pair = keys >> lit("=") >> +char_; - alloy::tuple, std::string> attr_; + std::tuple, std::string> attr_; REQUIRE(parse("long=ABC", pair, attr_)); CHECK(iris::get_if(&alloy::get<0>(attr_)) != nullptr); @@ -357,7 +358,7 @@ TEST_CASE("alternative of same attributes (a | a)") >); STATIC_CHECK(x4::parser_traits::sequence_size == 2); - alloy::tuple var; + std::tuple var; auto const res = parse("42true", int_bool, var); REQUIRE(res.completed()); CHECK(var == decltype(var){42, true}); @@ -373,7 +374,7 @@ TEST_CASE("alternative of same attributes (a | a)") >); STATIC_CHECK(x4::parser_traits::sequence_size == 3); - alloy::tuple var; + std::tuple var; auto const res = parse("foo42true", foo_int_bool, var); REQUIRE(res.completed()); CHECK(var == decltype(var){"foo", 42, true}); @@ -406,7 +407,7 @@ TEST_CASE("alternative of same attributes (a | a)") >); STATIC_CHECK(x4::parser_traits::sequence_size == 2); - alloy::tuple> var; + std::tuple> var; auto const res = parse("footruefalse", foo_bools, var); REQUIRE(res.completed()); CHECK(var == decltype(var){"foo", {true, false}}); diff --git a/test/x4/attribute_type_check.cpp b/test/x4/attribute_type_check.cpp index d70d218e6..11b2a7910 100644 --- a/test/x4/attribute_type_check.cpp +++ b/test/x4/attribute_type_check.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -97,7 +98,7 @@ void gen_tests(Values const&... values) { gen_single_item_tests(values...); - alloy::tuple attribute(values...); + std::tuple attribute(values...); gen_sequence_tests(attribute, values...); } @@ -109,13 +110,13 @@ void make_test(Attributes const&... attrs) gen_tests(attrs...); gen_tests< std::optional..., - alloy::tuple... + std::tuple... >(attrs..., attrs...); gen_tests< - std::optional>..., - alloy::tuple>... - >(alloy::tuple(attrs)..., attrs...); + std::optional>..., + std::tuple>... + >(std::tuple(attrs)..., attrs...); } } // anonymous diff --git a/test/x4/debug.cpp b/test/x4/debug.cpp index 244d930cc..406aad55e 100644 --- a/test/x4/debug.cpp +++ b/test/x4/debug.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -112,7 +113,7 @@ TEST_CASE("debug") { // std::container attributes - using tpl = alloy::tuple; + using tpl = std::tuple; rule> start("start"); auto start_def = start = *(int_ >> alpha); diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 4d6d04d72..35dd18f38 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #include @@ -354,7 +355,7 @@ TEST_CASE("expect") // Test that attributes with > (sequences) work just like >> (sequences) { - alloy::tuple attr; + std::tuple attr; X4_TEST_ATTR_SUCCESS_PASS(" a\n b\n c", char_ > char_ > char_, space, attr); CHECK((alloy::get<0>(attr) == 'a')); CHECK((alloy::get<1>(attr) == 'b')); @@ -362,7 +363,7 @@ TEST_CASE("expect") } { - alloy::tuple attr; + std::tuple attr; X4_TEST_ATTR_SUCCESS_PASS(" a\n b\n c", char_ > char_ >> char_, space, attr); CHECK((alloy::get<0>(attr) == 'a')); CHECK((alloy::get<1>(attr) == 'b')); @@ -370,7 +371,7 @@ TEST_CASE("expect") } { - alloy::tuple attr; + std::tuple attr; X4_TEST_ATTR_SUCCESS_PASS(" a, b, c", char_ >> ',' >> expect[char_] >> ',' >> expect[char_], space, attr); CHECK((alloy::get<0>(attr) == 'a')); CHECK((alloy::get<1>(attr) == 'b')); diff --git a/test/x4/int.cpp b/test/x4/int.cpp index 7a1cc7394..e341c1dbb 100644 --- a/test/x4/int.cpp +++ b/test/x4/int.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -233,7 +234,7 @@ TEST_CASE("int") // single-element tuple tests { - alloy::tuple i{}; + std::tuple i{}; REQUIRE(parse("-123456", int_, i)); CHECK(alloy::get<0>(i) == -123456); diff --git a/test/x4/iris_x4_test.hpp b/test/x4/iris_x4_test.hpp index 5b7a38981..c144abd79 100644 --- a/test/x4/iris_x4_test.hpp +++ b/test/x4/iris_x4_test.hpp @@ -187,8 +187,22 @@ struct custom_container void insert(T*, It, Se) {} }; +template +struct single_element_struct +{ + T value; + + bool operator==(single_element_struct const&) const = default; +}; + } // x4_test +template +struct ::iris::alloy::adaptor> +{ + using getters_list = make_getters_list<&x4_test::single_element_struct::value>; +}; + using x4_test::parse; #define IRIS_X4_ASSERT_CONSTEXPR_CTORS(...) \ diff --git a/test/x4/lit.cpp b/test/x4/lit.cpp index ba370320a..8c7d5ce39 100644 --- a/test/x4/lit.cpp +++ b/test/x4/lit.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -148,7 +149,7 @@ TEST_CASE("lit") { // single-element tuple tests - alloy::tuple s; + std::tuple s; REQUIRE(parse("kimpo", x4::standard::string("kimpo"), s)); CHECK(alloy::get<0>(s) == "kimpo"); } diff --git a/test/x4/move_to.cpp b/test/x4/move_to.cpp index b7f168bde..9e654518c 100644 --- a/test/x4/move_to.cpp +++ b/test/x4/move_to.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include struct X {}; @@ -38,21 +40,21 @@ TEST_CASE("move_to") // tuple contains reference { int n = 0; - alloy::tuple ref_tuple{ n }; + std::tuple ref_tuple{ n }; x4::move_to(42, ref_tuple); CHECK(n == 42); } { int n = 42; - alloy::tuple ref_tuple{ n }; - alloy::tuple dest{ 0 }; + std::tuple ref_tuple{ n }; + std::tuple dest{ 0 }; x4::move_to(std::move(ref_tuple), dest); CHECK(alloy::get<0>(dest) == 42); } { x4_test::move_only mo; - alloy::tuple ref_tuple{ mo }; - alloy::tuple dest; + std::tuple ref_tuple{ mo }; + std::tuple dest; x4::move_to(std::move(ref_tuple), dest); // move "far" ownership } } diff --git a/test/x4/omit.cpp b/test/x4/omit.cpp index bc24f4a18..2b4dccc4a 100644 --- a/test/x4/omit.cpp +++ b/test/x4/omit.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -65,7 +66,7 @@ TEST_CASE("omit") { // omit[] means we don't receive the attribute - alloy::tuple<> attr; + std::tuple<> attr; CHECK(parse("abc", omit[char_] >> omit['b'] >> omit[char_], attr)); } @@ -81,7 +82,7 @@ TEST_CASE("omit") // omit[] means we don't receive the attribute, if all elements of a // sequence have unused attributes, the whole sequence has an unused // attribute as well - alloy::tuple attr; + std::tuple attr; REQUIRE(parse("abcde", char_ >> (omit[char_] >> omit['c'] >> omit[char_]) >> char_, attr)); CHECK(alloy::get<0>(attr) == 'a'); CHECK(alloy::get<1>(attr) == 'e'); @@ -89,7 +90,7 @@ TEST_CASE("omit") { // "hello" has an unused_type. unused attrubutes are not part of the sequence - alloy::tuple attr; + std::tuple attr; REQUIRE(parse("a hello c", char_ >> "hello" >> char_, space, attr)); CHECK(alloy::get<0>(attr) == 'a'); CHECK(alloy::get<1>(attr) == 'c'); diff --git a/test/x4/optional.cpp b/test/x4/optional.cpp index d45152329..f95768ae7 100644 --- a/test/x4/optional.cpp +++ b/test/x4/optional.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -105,7 +106,7 @@ TEST_CASE("optional") static_assert(!x4::parser_traits>::has_attribute); static_assert(std::same_as>::attribute_type, unused_type>); - alloy::tuple v; + std::tuple v; REQUIRE(parse("a1234c", char_ >> -omit[int_] >> char_, v)); CHECK(alloy::get<0>(v) == 'a'); CHECK(alloy::get<1>(v) == 'c'); @@ -132,7 +133,7 @@ TEST_CASE("optional") } } { - alloy::tuple v; + std::tuple v; REQUIRE(parse("a1234c", char_ >> omit[-int_] >> char_, v)); CHECK(alloy::get<0>(v) == 'a'); CHECK(alloy::get<1>(v) == 'c'); diff --git a/test/x4/plus.cpp b/test/x4/plus.cpp index 55ece085f..39f6c7636 100644 --- a/test/x4/plus.cpp +++ b/test/x4/plus.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -114,7 +115,7 @@ TEST_CASE("plus") // single-element tuple tests { - alloy::tuple fs; + std::tuple fs; REQUIRE(parse("12345", +char_, fs)); CHECK(alloy::get<0>(fs) == "12345"); } diff --git a/test/x4/rule4.cpp b/test/x4/rule4.cpp index 4824331fa..32ff62390 100644 --- a/test/x4/rule4.cpp +++ b/test/x4/rule4.cpp @@ -20,6 +20,7 @@ #include +#include #include #include @@ -172,9 +173,10 @@ TEST_CASE("rule4") // test handling of single element tuple { - auto r = rule>{} = int_; + using attr_type = x4_test::single_element_struct; + auto r = rule{} = int_; - alloy::tuple v(0); + attr_type v(0); REQUIRE(parse("1", r, v)); CHECK(alloy::get<0>(v) == 1); } diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index b92d6cb0a..84d1e665c 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -29,6 +29,7 @@ #include +#include #include #include @@ -71,14 +72,14 @@ TEST_CASE("sequence") CHECK(parse(" Hello, World", lit("Hello") >> ',' >> "World", space)); { - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("ab", char_ >> char_, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'b'); } { - alloy::tuple vec; + std::tuple vec; REQUIRE(parse(" a\n b\n c", char_ >> char_ >> char_, space, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'b'); @@ -87,7 +88,7 @@ TEST_CASE("sequence") { // 'b' has an unused_type. unused attributes are not part of the sequence - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("abc", char_ >> 'b' >> char_, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'c'); @@ -95,7 +96,7 @@ TEST_CASE("sequence") { // 'b' has an unused_type. unused attributes are not part of the sequence - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("acb", char_ >> char_ >> 'b', vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'c'); @@ -103,7 +104,7 @@ TEST_CASE("sequence") { // "hello" has an unused_type. unused attributes are not part of the sequence - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("a hello c", char_ >> "hello" >> char_, space, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'c'); @@ -118,7 +119,7 @@ TEST_CASE("sequence") { // a single element tuple - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("ab", char_ >> 'b', vec)); CHECK(alloy::get<0>(vec) == 'a'); } @@ -133,7 +134,7 @@ TEST_CASE("sequence") // rule version { - using attr_type = alloy::tuple; + using attr_type = std::tuple; attr_type tpl; auto r = rule{} = char_ >> ',' >> int_; @@ -144,7 +145,7 @@ TEST_CASE("sequence") // as version { - using attr_type = alloy::tuple; + using attr_type = std::tuple; attr_type tpl; auto r = as(char_ >> ',' >> int_); @@ -161,18 +162,18 @@ TEST_CASE("sequence") // rule version { - using attr_type = alloy::tuple; - attr_type tpl; + using attr_type = x4_test::single_element_struct; + attr_type ses; auto r = rule{} = int_; - REQUIRE(parse("test:1", "test:" >> r, tpl)); - CHECK((tpl == attr_type(1))); + REQUIRE(parse("test:1", "test:" >> r, ses)); + CHECK((ses == attr_type(1))); } // as version { - using attr_type = alloy::tuple; + using attr_type = x4_test::single_element_struct; attr_type tpl; auto r = as(int_); @@ -396,7 +397,7 @@ TEST_CASE("sequence") // Test from spirit mailing list // "Error with container within sequence" { - using attr_type = alloy::tuple; + using attr_type = std::tuple; attr_type vec; constexpr auto r = *alnum; @@ -405,7 +406,7 @@ TEST_CASE("sequence") CHECK(alloy::get<0>(vec) == "abcdef"); } { - using attr_type = alloy::tuple>; + using attr_type = std::tuple>; attr_type vec; constexpr auto r = *int_; @@ -418,7 +419,7 @@ TEST_CASE("sequence") { // Non-flat optional - alloy::tuple>> v; + std::tuple>> v; constexpr auto p = int_ >> -(':' >> int_ >> '-' >> int_); REQUIRE(parse("1:2-3", p, v)); REQUIRE(alloy::get<1>(v).has_value()); @@ -430,12 +431,12 @@ TEST_CASE("sequence") constexpr auto p = char_ >> -(':' >> +char_); { - alloy::tuple> v; + std::tuple> v; REQUIRE(parse("x", p, v)); CHECK(!alloy::get<1>(v).has_value()); } { - alloy::tuple> v; + std::tuple> v; REQUIRE(parse("x:abc", p, v)); REQUIRE(alloy::get<1>(v).has_value()); CHECK(*alloy::get<1>(v) == "abc"); diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index cd7f8b18a..f3cfe4880 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -40,7 +40,7 @@ IRIS_X4_DECLARE_CONSTEXPR(VarRule) constexpr IdentRule ident; constexpr VarRule var; -constexpr auto ident_def = x4::alpha >> *x4::alnum; +constexpr auto ident_def = x4::as(x4::alpha >> *x4::alnum); constexpr auto var_def = '$' >> ident; IRIS_X4_DEFINE_CONSTEXPR(ident) diff --git a/test/x4/substitution.cpp b/test/x4/substitution.cpp index 5d8d24aac..86f6aef13 100644 --- a/test/x4/substitution.cpp +++ b/test/x4/substitution.cpp @@ -7,6 +7,8 @@ #include +#include + template inline constexpr bool can_hold_v = x4::traits::can_hold::value; @@ -15,7 +17,7 @@ TEST_CASE("can_hold") // identical types STATIC_CHECK(can_hold_v); STATIC_CHECK(can_hold_v, std::vector>); - STATIC_CHECK(can_hold_v, alloy::tuple>); + STATIC_CHECK(can_hold_v, std::tuple>); STATIC_CHECK(can_hold_v, iris::rvariant>); // `iris::rvariant` is "broader" than `int` @@ -26,5 +28,5 @@ TEST_CASE("can_hold") STATIC_CHECK(can_hold_v>, std::vector>); // tuple-like types - STATIC_CHECK(can_hold_v>, alloy::tuple>); + STATIC_CHECK(can_hold_v>, std::tuple>); } From bc08648305e90cd720b4a6d45ec7472f34646090 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 01:07:19 +0900 Subject: [PATCH 09/52] Fix --- test/x4/iris_x4_test.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/x4/iris_x4_test.hpp b/test/x4/iris_x4_test.hpp index c144abd79..239a94766 100644 --- a/test/x4/iris_x4_test.hpp +++ b/test/x4/iris_x4_test.hpp @@ -198,7 +198,7 @@ struct single_element_struct } // x4_test template -struct ::iris::alloy::adaptor> +struct iris::alloy::adaptor> { using getters_list = make_getters_list<&x4_test::single_element_struct::value>; }; From 1461752be6342af929f4ebd9fdba35554d32bc66 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 17:12:33 +0900 Subject: [PATCH 10/52] Revise single element tuple like type handling --- .../iris/x4/core/detail/parse_sequence.hpp | 10 + test/x4/single_element_tuple_like.cpp | 609 +++++++++++++++++- 2 files changed, 618 insertions(+), 1 deletion(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index e3b658935..cf4a6b809 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -260,6 +260,16 @@ parse_sequence(Parser const& parser, It& first, Se const& last, Context const& c return true; } return false; + } else if constexpr ( + traits::is_single_element_tuple_like::value && + has_attribute_v && + has_attribute_v + ) { + // Both sub-parsers have attributes (expected_size >= 2), but Attr is a + // single-element tuple-like (size 1). Unwrap to get<0>(attr) and recurse, + // so the inner type (e.g. a container or compatible tuple) receives the + // sequence elements directly. + return detail::parse_sequence(parser, first, last, ctx, alloy::get<0>(attr)); } else { using partition = partition_attribute< typename Parser::left_type, diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index f3cfe4880..76278e286 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -1,5 +1,6 @@ #include "iris_x4_test.hpp" +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +17,8 @@ #include #include +#include + #include @@ -28,8 +32,18 @@ struct Var Ident ident; }; +struct TwoInts +{ + int a; + int b; +}; + IRIS_ALLOY_ADAPT_STRUCT(Ident, value) IRIS_ALLOY_ADAPT_STRUCT(Var, ident) +IRIS_ALLOY_ADAPT_STRUCT(TwoInts, a, b) + +template +using SET = x4_test::single_element_struct; using IdentRule = x4::rule; using VarRule = x4::rule; @@ -40,7 +54,7 @@ IRIS_X4_DECLARE_CONSTEXPR(VarRule) constexpr IdentRule ident; constexpr VarRule var; -constexpr auto ident_def = x4::as(x4::alpha >> *x4::alnum); +constexpr auto ident_def = x4::alpha >> *x4::alnum; constexpr auto var_def = '$' >> ident; IRIS_X4_DEFINE_CONSTEXPR(ident) @@ -133,3 +147,596 @@ TEST_CASE("single_element_tuple_like") REQUIRE(parse("abc(42)", func_call)); } } + +TEST_CASE("product_of_parent_child_attribute_categories") +{ + // as parent (e.g. Var) + // as child (e.g. Ident) + // + // x + // + // plain (e.g. int) + // container (e.g. vector) + // single element tuple (e.g. std::tuple) + // multi element tuple (e.g. std::tuple) + // + // x + // + // identity (e.g. T) + // single element tuple (e.g. std::tuple) + // variant (e.g. rvariant) + + // TODO: add container appender case (parent needs to be inside the list like parser) + + // Each participant (parent/child) independently varies over: + // attribute_category (4) x parser_attribute_form (3) = 12 combos + // + // Invalid combos (2 per participant): + // (container, single_element_tuple) - no move_to for SET -> container + // (multi_element_tuple, single_element_tuple) - no move_to for SET -> MET + // + // Valid combos per participant: 10 + // Total valid entries: 10 x 10 = 100 + // + // Notation: SET = x4_test::single_element_struct + // TwoInts = struct { int a, b; } + // + // For each entry: + // child: parsed standalone (parse(input, parser, dest)) + // parent: parsed in sequence context (parse(input, '+' >> parser, dest)) + + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // =================================================================== + // parent: plain x identity + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + } + + // =================================================================== + // parent: plain x single_element_tuple + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + } + + // =================================================================== + // parent: plain x variant + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + } + + // =================================================================== + // parent: container x identity + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + } + + // =================================================================== + // parent: container x variant + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + } + + // =================================================================== + // parent: single_element_tuple x identity + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + } + + // =================================================================== + // parent: single_element_tuple x single_element_tuple + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + } + + // =================================================================== + // parent: single_element_tuple x variant + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + } + + // =================================================================== + // parent: multi_element_tuple x identity + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + + // =================================================================== + // parent: multi_element_tuple x variant + // =================================================================== + + // child: plain x identity + { + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: plain x single_element_tuple + { + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: plain x variant + { + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: container x identity + { + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: container x variant + { + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: single_element_tuple x identity + { + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: single_element_tuple x single_element_tuple + { + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: single_element_tuple x variant + { + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: multi_element_tuple x identity + { + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } + // child: multi_element_tuple x variant + { + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } +} \ No newline at end of file From 765fe91d5f53c275fd89859617fe5e617d07f62a Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 17:45:12 +0900 Subject: [PATCH 11/52] Add tests --- .../iris/x4/core/detail/parse_sequence.hpp | 1 - test/x4/single_element_tuple_like.cpp | 805 ++++++------------ 2 files changed, 257 insertions(+), 549 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index cf4a6b809..cccfcb906 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -20,7 +20,6 @@ #include #include - #include #include diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 76278e286..6ea6fe7e0 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -168,575 +169,283 @@ TEST_CASE("product_of_parent_child_attribute_categories") // TODO: add container appender case (parent needs to be inside the list like parser) - // Each participant (parent/child) independently varies over: - // attribute_category (4) x parser_attribute_form (3) = 12 combos - // - // Invalid combos (2 per participant): - // (container, single_element_tuple) - no move_to for SET -> container - // (multi_element_tuple, single_element_tuple) - no move_to for SET -> MET + // Tests attribute compatibility across the matrix of: + // attribute_category: plain, container, single_element_tuple (SET), multi_element_tuple (MET) + // parser_form: identity, single_element_tuple, variant // + // Invalid combos: (container, SET) and (MET, SET) — no valid move_to // Valid combos per participant: 10 - // Total valid entries: 10 x 10 = 100 // // Notation: SET = x4_test::single_element_struct // TwoInts = struct { int a, b; } // - // For each entry: - // child: parsed standalone (parse(input, parser, dest)) - // parent: parsed in sequence context (parse(input, '+' >> parser, dest)) + // Tests are organized in three groups: + // 1. Standalone: each combo parsed independently + // 2. In sequence: unused >> parser → dest (attribute flow through sequences) + // 3. Composition: two attributed parsers → combined dest (partition and interaction) + + // TODO: add container appender case (parent needs to be inside the list like parser) using x4::int_; using x4::alpha; + using x4::alnum; using x4::string; using x4::standard::char_; // =================================================================== - // parent: plain x identity - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - } - - // =================================================================== - // parent: plain x single_element_tuple - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - } - - // =================================================================== - // parent: plain x variant - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - } - - // =================================================================== - // parent: container x identity + // 1. Standalone: parse(input, parser, dest) // =================================================================== - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - } + // plain × identity + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + // plain × SET + { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + // plain × variant + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + // container × identity + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + // container × variant + { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + // SET × identity + { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + // SET × SET + { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } + // SET × variant + { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + // MET × identity + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // MET × variant + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } // =================================================================== - // parent: container x variant + // 2. In sequence: parse(input, unused >> parser, dest) + // Tests partition_attribute with one unused side // =================================================================== - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } - } + // plain × identity + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + // plain × SET + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + // plain × variant + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + // container × identity + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + // container × variant + { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + // SET × identity + { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + // SET × SET + { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } + // SET × variant + { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + // MET × identity + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // MET × variant + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } // =================================================================== - // parent: single_element_tuple x identity - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - } - - // =================================================================== - // parent: single_element_tuple x single_element_tuple - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - } - - // =================================================================== - // parent: single_element_tuple x variant - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - } - - // =================================================================== - // parent: multi_element_tuple x identity - // =================================================================== - - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - - // =================================================================== - // parent: multi_element_tuple x variant + // 3. Composition: left_parser >> right_parser → combined dest + // Tests partition_attribute splitting + per-slot attribute handling + // + // Parser forms (sequence_size=1): + // P_int: int_ → int + // P_set: attr(SET{V}) → SET + // P_var: int_ | char_ → rvariant + // P_chr: alpha → char + // P_slit: string("abc") → std::string + // P_str: +alpha → std::string + // + // Parser forms (sequence_size=2): + // P_met: int_ >> ',' >> int_ + // P_metv: (int_ >> ',' >> int_) | (char_ >> ',' >> char_) + // + // Slot types tested: int, char, std::string, SET, SET, + // SET, TwoInts // =================================================================== - // child: plain x identity - { - { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: plain x single_element_tuple - { - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: plain x variant - { - { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: container x identity - { - { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: container x variant - { - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: single_element_tuple x identity - { - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: single_element_tuple x single_element_tuple - { - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: single_element_tuple x variant - { - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: multi_element_tuple x identity - { - { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } - // child: multi_element_tuple x variant - { - { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } + // --- Left: int_ --- + + // int_ × int_ → (int, int) + { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ × int_ → MET struct + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // int_ × int_ → (SET, int) + { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ × int_ → (int, SET) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } + // int_ × int_ → (SET, SET) + { std::tuple, SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 2); } + // int_ × attr(SET) + { std::tuple a; REQUIRE(parse("42", int_ >> x4::attr(SET{99}), a)); CHECK(alloy::get<0>(a) == 42); CHECK(alloy::get<1>(a) == 99); } + // int_ × variant + { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ × alpha + { std::tuple a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // int_ × alpha → (int, SET) + { std::tuple> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 'a'); } + // int_ × alpha → (SET, SET) + { std::tuple, SET> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 'a'); } + // int_ × string("abc") + { std::tuple a; REQUIRE(parse("1abc", int_ >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // int_ × +alpha + { std::tuple a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // int_ × +alpha → (int, SET) + { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } + // int_ × +alpha → (SET, string) + { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } + + // --- Left: attr(SET{V}) --- + + // attr(SET) × int_ + { std::tuple a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } + // attr(SET) × int_ → (SET, int) + { std::tuple, int> a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 42); } + // attr(SET) × attr(SET) + { std::tuple a; REQUIRE(parse("", x4::attr(SET{1}) >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // attr(SET) × variant + { std::tuple a; REQUIRE(parse("2", x4::attr(SET{1}) >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // attr(SET) × alpha + { std::tuple a; REQUIRE(parse("a", x4::attr(SET{1}) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // attr(SET) × alpha → (SET, char) + { std::tuple, char> a; REQUIRE(parse("a", x4::attr(SET{1}) >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 'a'); } + // attr(SET) × string("abc") + { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // attr(SET) × +alpha + { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + + // --- Left: int_ | char_ (variant) --- + + // variant × int_ + { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant × int_ → (SET, int) + { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } + // variant × attr(SET) + { std::tuple a; REQUIRE(parse("1", (int_ | char_) >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant × variant + { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant × variant → (int, SET) + { std::tuple> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } + // variant × alpha + { std::tuple a; REQUIRE(parse("1a", (int_ | char_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // variant × string("abc") + { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // variant × +alpha + { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + + // --- Left: alpha --- + + // alpha × int_ + { std::tuple a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 42); } + // alpha × int_ → (SET, int) + { std::tuple, int> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a) == 42); } + // alpha × int_ → (char, SET) + { std::tuple> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).value == 42); } + // alpha × attr(SET) + { std::tuple a; REQUIRE(parse("a", alpha >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 2); } + // alpha × variant + { std::tuple a; REQUIRE(parse("a1", alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); } + // alpha × alpha + { std::tuple a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 'b'); } + // alpha × alpha → (SET, SET) + { std::tuple, SET> a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a).value == 'b'); } + // alpha × string("abc") + { std::tuple a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a) == "abc"); } + // alpha × string("abc") → (char, SET) + { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } + // alpha × +alpha + { std::tuple a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == "b"); } + + // --- Left: string("abc") --- + + // string("abc") × int_ + { std::tuple a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } + // string("abc") × int_ → (SET, int) + { std::tuple, int> a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } + // string("abc") × attr(SET) + { std::tuple a; REQUIRE(parse("abc", string("abc") >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } + // string("abc") × variant + { std::tuple a; REQUIRE(parse("abc1", string("abc") >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } + // string("abc") × alpha + { std::tuple a; REQUIRE(parse("abcd", string("abc") >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } + // string("abc") × string("def") + { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } + // string("abc") × string("def") → (SET, SET) + { std::tuple, SET> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } + // string("abc") × +alpha + { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } + + // --- Left: +alpha --- + + // +alpha × int_ + { std::tuple a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } + // +alpha × int_ → (SET, int) + { std::tuple, int> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } + // +alpha × int_ → (string, SET) + { std::tuple> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a).value == 42); } + // +alpha × attr(SET) + { std::tuple a; REQUIRE(parse("abc", +alpha >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } + // +alpha × variant + { std::tuple a; REQUIRE(parse("abc1", +alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } + // +alpha × ',' >> alpha + { std::tuple a; REQUIRE(parse("abc,d", +alpha >> ',' >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } + // +alpha × ',' >> +alpha + { std::tuple a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } + // +alpha × ',' >> +alpha → (SET, SET) + { std::tuple, SET> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } + + // --- SET dest wrapping sequence result (core fix) --- + // Exercises the parse_sequence unwrap path: + // is_single_element_tuple_like && has_attribute_v && has_attribute_v + + // SET: alpha >> *alnum → SET (= Ident pattern) + { SET a; REQUIRE(parse("abc", alpha >> *alnum, a)); CHECK(a.value == "abc"); } + // SET: int_ >> ',' >> int_ → SET + { SET a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + // SET: int_ >> ',' >> int_ → SET> + { SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } + // SET with variant parser + { SET a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + + // --- MET parser compositions (sequence_size=2 parsers) --- + + // MET × int_ → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2:3", (int_ >> ',' >> int_) >> ':' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } + // MET × alpha → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2a", (int_ >> ',' >> int_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 'a'); } + // MET × +alpha → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2abc", (int_ >> ',' >> int_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == "abc"); } + // MET × attr(SET) → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) >> x4::attr(SET{99}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 99); } + // int_ × MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("1:2,3", int_ >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } + // alpha × MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("a1,2", alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // +alpha × MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("abc1,2", +alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // attr(SET) × MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2", x4::attr(SET{99}) >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 99); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // MET × MET → 4-slot tuple + { std::tuple a; REQUIRE(parse("1,2:3,4", (int_ >> ',' >> int_) >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); CHECK(alloy::get<3>(a) == 4); } + // MET_variant × alpha → 2-slot tuple (alternative has sequence_size=1) + { std::tuple a; REQUIRE(parse("1,2a", ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)) >> alpha, a)); CHECK(alloy::get<0>(a).a == 1); CHECK(alloy::get<0>(a).b == 2); CHECK(alloy::get<1>(a) == 'a'); } + // alpha × MET_variant → 2-slot tuple + { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } + + // --- Nested SET: recursive unwrap --- + // TODO: these hit a noexcept evaluation issue in move_to (recursive + // single-element-tuple-like forwarding through two layers). + // Uncomment once that is resolved. + + // SET>: double unwrap through move_to + // { + // SET> a; + // REQUIRE(parse("42", int_, a)); + // CHECK(a.value.value == 42); + // } + // SET>: double unwrap through parse_sequence (= Var pattern) + // { + // SET> a; + // REQUIRE(parse("abc", alpha >> *alnum, a)); + // CHECK(a.value.value == "abc"); + // } } \ No newline at end of file From d333ca93aa9034ccc0335306cf26a8dd7f4eab44 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 18:05:12 +0900 Subject: [PATCH 12/52] Suppress MSVC warning --- test/x4/single_element_tuple_like.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 6ea6fe7e0..ebe0f422d 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -205,7 +205,7 @@ TEST_CASE("product_of_parent_child_attribute_categories") // container × identity { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } // container × variant - { std::string a; REQUIRE(parse("a", int_ | char_, a)); CHECK(a == "a"); } + { std::string a; REQUIRE(parse("a", alpha | char_, a)); CHECK(a == "a"); } // SET × identity { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } // SET × SET @@ -231,7 +231,7 @@ TEST_CASE("product_of_parent_child_attribute_categories") // container × identity { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } // container × variant - { std::string a; REQUIRE(parse("+a", '+' >> (int_ | char_), a)); CHECK(a == "a"); } + { std::string a; REQUIRE(parse("+a", '+' >> (alpha | char_), a)); CHECK(a == "a"); } // SET × identity { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } // SET × SET From 41e0f0c1dd83f1509696f743950bced16ec3cfbb Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 18:14:39 +0900 Subject: [PATCH 13/52] Split test case --- test/x4/single_element_tuple_like.cpp | 32 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index ebe0f422d..2db69dc6e 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -196,6 +196,7 @@ TEST_CASE("product_of_parent_child_attribute_categories") // 1. Standalone: parse(input, parser, dest) // =================================================================== + SECTION("standalone") { // plain × identity { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } // plain × SET @@ -216,12 +217,14 @@ TEST_CASE("product_of_parent_child_attribute_categories") { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET × variant { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } // =================================================================== // 2. In sequence: parse(input, unused >> parser, dest) // Tests partition_attribute with one unused side // =================================================================== + SECTION("in sequence") { // plain × identity { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } // plain × SET @@ -242,6 +245,7 @@ TEST_CASE("product_of_parent_child_attribute_categories") { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET × variant { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + } // =================================================================== // 3. Composition: left_parser >> right_parser → combined dest @@ -263,8 +267,7 @@ TEST_CASE("product_of_parent_child_attribute_categories") // SET, TwoInts // =================================================================== - // --- Left: int_ --- - + SECTION("composition: left int_") { // int_ × int_ → (int, int) { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // int_ × int_ → MET struct @@ -293,9 +296,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } // int_ × +alpha → (SET, string) { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } + } - // --- Left: attr(SET{V}) --- - + SECTION("composition: left attr(SET)") { // attr(SET) × int_ { std::tuple a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } // attr(SET) × int_ → (SET, int) @@ -312,9 +315,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } // attr(SET) × +alpha { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + } - // --- Left: int_ | char_ (variant) --- - + SECTION("composition: left variant") { // variant × int_ { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // variant × int_ → (SET, int) @@ -331,9 +334,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } // variant × +alpha { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + } - // --- Left: alpha --- - + SECTION("composition: left alpha") { // alpha × int_ { std::tuple a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 42); } // alpha × int_ → (SET, int) @@ -354,9 +357,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } // alpha × +alpha { std::tuple a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == "b"); } + } - // --- Left: string("abc") --- - + SECTION("composition: left string") { // string("abc") × int_ { std::tuple a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } // string("abc") × int_ → (SET, int) @@ -373,9 +376,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple, SET> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } // string("abc") × +alpha { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } + } - // --- Left: +alpha --- - + SECTION("composition: left +alpha") { // +alpha × int_ { std::tuple a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } // +alpha × int_ → (SET, int) @@ -392,7 +395,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } // +alpha × ',' >> +alpha → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } + } + SECTION("composition: SET dest wrapping") { // --- SET dest wrapping sequence result (core fix) --- // Exercises the parse_sequence unwrap path: // is_single_element_tuple_like && has_attribute_v && has_attribute_v @@ -405,7 +410,9 @@ TEST_CASE("product_of_parent_child_attribute_categories") { SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } // SET with variant parser { SET a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + } + SECTION("composition: MET parsers") { // --- MET parser compositions (sequence_size=2 parsers) --- // MET × int_ → 3-slot tuple @@ -430,6 +437,7 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("1,2a", ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)) >> alpha, a)); CHECK(alloy::get<0>(a).a == 1); CHECK(alloy::get<0>(a).b == 2); CHECK(alloy::get<1>(a) == 'a'); } // alpha × MET_variant → 2-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } + } // --- Nested SET: recursive unwrap --- // TODO: these hit a noexcept evaluation issue in move_to (recursive From c02292657f04a1a1cb96d6e28cf96b61a62abf58 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 18:28:28 +0900 Subject: [PATCH 14/52] Reduce stack usage --- test/x4/single_element_tuple_like.cpp | 205 +++++++++++++++----------- 1 file changed, 119 insertions(+), 86 deletions(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 2db69dc6e..5951271cb 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -149,54 +149,34 @@ TEST_CASE("single_element_tuple_like") } } -TEST_CASE("product_of_parent_child_attribute_categories") +// Tests attribute compatibility across the matrix of: +// attribute_category: plain, container, single_element_tuple (SET), multi_element_tuple (MET) +// parser_form: identity, single_element_tuple, variant +// +// Invalid combos: (container, SET) and (MET, SET) — no valid move_to +// Valid combos per participant: 10 +// +// Notation: SET = x4_test::single_element_struct +// TwoInts = struct { int a, b; } +// +// Tests are organized in three groups: +// 1. Standalone: each combo parsed independently +// 2. In sequence: unused >> parser → dest (attribute flow through sequences) +// 3. Composition: two attributed parsers → combined dest (partition and interaction) + +// TODO: add container appender case (parent needs to be inside the list like parser) + +// =================================================================== +// 1. Standalone: parse(input, parser, dest) +// =================================================================== + +TEST_CASE("SET standalone") { - // as parent (e.g. Var) - // as child (e.g. Ident) - // - // x - // - // plain (e.g. int) - // container (e.g. vector) - // single element tuple (e.g. std::tuple) - // multi element tuple (e.g. std::tuple) - // - // x - // - // identity (e.g. T) - // single element tuple (e.g. std::tuple) - // variant (e.g. rvariant) - - // TODO: add container appender case (parent needs to be inside the list like parser) - - // Tests attribute compatibility across the matrix of: - // attribute_category: plain, container, single_element_tuple (SET), multi_element_tuple (MET) - // parser_form: identity, single_element_tuple, variant - // - // Invalid combos: (container, SET) and (MET, SET) — no valid move_to - // Valid combos per participant: 10 - // - // Notation: SET = x4_test::single_element_struct - // TwoInts = struct { int a, b; } - // - // Tests are organized in three groups: - // 1. Standalone: each combo parsed independently - // 2. In sequence: unused >> parser → dest (attribute flow through sequences) - // 3. Composition: two attributed parsers → combined dest (partition and interaction) - - // TODO: add container appender case (parent needs to be inside the list like parser) - using x4::int_; using x4::alpha; - using x4::alnum; using x4::string; using x4::standard::char_; - // =================================================================== - // 1. Standalone: parse(input, parser, dest) - // =================================================================== - - SECTION("standalone") { // plain × identity { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } // plain × SET @@ -217,14 +197,20 @@ TEST_CASE("product_of_parent_child_attribute_categories") { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET × variant { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } +} - // =================================================================== - // 2. In sequence: parse(input, unused >> parser, dest) - // Tests partition_attribute with one unused side - // =================================================================== +// =================================================================== +// 2. In sequence: parse(input, unused >> parser, dest) +// Tests partition_attribute with one unused side +// =================================================================== + +TEST_CASE("SET in sequence") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; - SECTION("in sequence") { // plain × identity { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } // plain × SET @@ -245,29 +231,35 @@ TEST_CASE("product_of_parent_child_attribute_categories") { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET × variant { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - } +} + +// =================================================================== +// 3. Composition: left_parser >> right_parser → combined dest +// Tests partition_attribute splitting + per-slot attribute handling +// +// Parser forms (sequence_size=1): +// P_int: int_ → int +// P_set: attr(SET{V}) → SET +// P_var: int_ | char_ → rvariant +// P_chr: alpha → char +// P_slit: string("abc") → std::string +// P_str: +alpha → std::string +// +// Parser forms (sequence_size=2): +// P_met: int_ >> ',' >> int_ +// P_metv: (int_ >> ',' >> int_) | (char_ >> ',' >> char_) +// +// Slot types tested: int, char, std::string, SET, SET, +// SET, TwoInts +// =================================================================== + +TEST_CASE("SET composition: left int_") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; - // =================================================================== - // 3. Composition: left_parser >> right_parser → combined dest - // Tests partition_attribute splitting + per-slot attribute handling - // - // Parser forms (sequence_size=1): - // P_int: int_ → int - // P_set: attr(SET{V}) → SET - // P_var: int_ | char_ → rvariant - // P_chr: alpha → char - // P_slit: string("abc") → std::string - // P_str: +alpha → std::string - // - // Parser forms (sequence_size=2): - // P_met: int_ >> ',' >> int_ - // P_metv: (int_ >> ',' >> int_) | (char_ >> ',' >> char_) - // - // Slot types tested: int, char, std::string, SET, SET, - // SET, TwoInts - // =================================================================== - - SECTION("composition: left int_") { // int_ × int_ → (int, int) { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // int_ × int_ → MET struct @@ -296,9 +288,15 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } // int_ × +alpha → (SET, string) { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } - } +} + +TEST_CASE("SET composition: left attr(SET)") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; - SECTION("composition: left attr(SET)") { // attr(SET) × int_ { std::tuple a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } // attr(SET) × int_ → (SET, int) @@ -315,9 +313,15 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } // attr(SET) × +alpha { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - } +} + +TEST_CASE("SET composition: left variant") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; - SECTION("composition: left variant") { // variant × int_ { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // variant × int_ → (SET, int) @@ -334,9 +338,16 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } // variant × +alpha { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - } +} + +TEST_CASE("SET composition: left alpha") +{ + using x4::int_; + using x4::alpha; + using x4::alnum; + using x4::string; + using x4::standard::char_; - SECTION("composition: left alpha") { // alpha × int_ { std::tuple a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 42); } // alpha × int_ → (SET, int) @@ -357,9 +368,15 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } // alpha × +alpha { std::tuple a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == "b"); } - } +} + +TEST_CASE("SET composition: left string") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; - SECTION("composition: left string") { // string("abc") × int_ { std::tuple a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } // string("abc") × int_ → (SET, int) @@ -376,9 +393,15 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple, SET> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } // string("abc") × +alpha { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } - } +} + +TEST_CASE("SET composition: left +alpha") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; - SECTION("composition: left +alpha") { // +alpha × int_ { std::tuple a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } // +alpha × int_ → (SET, int) @@ -395,9 +418,16 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } // +alpha × ',' >> +alpha → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } - } +} + +TEST_CASE("SET composition: SET dest wrapping") +{ + using x4::int_; + using x4::alpha; + using x4::alnum; + using x4::string; + using x4::standard::char_; - SECTION("composition: SET dest wrapping") { // --- SET dest wrapping sequence result (core fix) --- // Exercises the parse_sequence unwrap path: // is_single_element_tuple_like && has_attribute_v && has_attribute_v @@ -410,10 +440,14 @@ TEST_CASE("product_of_parent_child_attribute_categories") { SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } // SET with variant parser { SET a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } - } +} - SECTION("composition: MET parsers") { - // --- MET parser compositions (sequence_size=2 parsers) --- +TEST_CASE("SET composition: MET parsers") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; // MET × int_ → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2:3", (int_ >> ',' >> int_) >> ':' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } @@ -437,7 +471,6 @@ TEST_CASE("product_of_parent_child_attribute_categories") { std::tuple a; REQUIRE(parse("1,2a", ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)) >> alpha, a)); CHECK(alloy::get<0>(a).a == 1); CHECK(alloy::get<0>(a).b == 2); CHECK(alloy::get<1>(a) == 'a'); } // alpha × MET_variant → 2-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } - } // --- Nested SET: recursive unwrap --- // TODO: these hit a noexcept evaluation issue in move_to (recursive From f05a9e678722e753e2ad45fbbe07ef9d4d10840b Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 18:30:58 +0900 Subject: [PATCH 15/52] Add newline at end --- test/x4/single_element_tuple_like.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 5951271cb..d1b76a818 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -489,4 +489,4 @@ TEST_CASE("SET composition: MET parsers") // REQUIRE(parse("abc", alpha >> *alnum, a)); // CHECK(a.value.value == "abc"); // } -} \ No newline at end of file +} From d1bcdaf7a8a5aae9c43c64c6ea80ede351d2b4a9 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 19:33:40 +0900 Subject: [PATCH 16/52] Unwrap single element tuple like once in rule parser --- include/iris/x4/rule.hpp | 18 ++- test/x4/single_element_tuple_like.cpp | 219 ++++++++++++++++---------- 2 files changed, 147 insertions(+), 90 deletions(-) diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index a7d9d2863..f5ec2046c 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -178,6 +178,18 @@ struct rule_impl It start = first; // backup + auto&& unwrapped_attr = [&]() -> decltype(auto) { + if constexpr (traits::is_single_element_tuple_like::value) { + if constexpr (traits::can_hold::attribute_type, alloy::tuple_element_t<0, RHSAttr>>::value) { + return alloy::get<0>(rhs_attr); + } else { + return rhs_attr; + } + } else { + return rhs_attr; + } + }(); + // // NOTE: The branches below are intentionally written verbosely to make sure // we have the minimal call stack. DON'T extract these procedures into a @@ -187,7 +199,7 @@ struct rule_impl bool ok; if constexpr (SkipDefinitionInjection || !is_default_parse_rule) { - ok = rhs.parse(first, last, rcontext, rhs_attr); + ok = rhs.parse(first, last, rcontext, unwrapped_attr); } else { // If there is no `IRIS_X4_DEFINE` for this rule, @@ -195,13 +207,13 @@ struct rule_impl // so we can extract the rule later on in the default // `parse_rule` overload. auto const rule_id_context = x4::make_context(rhs, rcontext); - ok = rhs.parse(first, last, rule_id_context, rhs_attr); + ok = rhs.parse(first, last, rule_id_context, unwrapped_attr); } if constexpr (need_on_success) { if (ok) { x4::skip_over(start, first, rcontext); - RuleID{}.on_success(std::as_const(start), std::as_const(first), rcontext, rhs_attr); + RuleID{}.on_success(std::as_const(start), std::as_const(first), rcontext, unwrapped_attr); return true; } diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index d1b76a818..0b6ae12f5 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -26,6 +28,8 @@ struct Ident { std::string value; + + bool operator==(const Ident&) const = default; }; struct Var @@ -39,27 +43,50 @@ struct TwoInts int b; }; +struct NamePath +{ + bool is_rooted = false; + std::vector segments; +}; + +struct AnyID +{ + NamePath path; +}; + IRIS_ALLOY_ADAPT_STRUCT(Ident, value) IRIS_ALLOY_ADAPT_STRUCT(Var, ident) IRIS_ALLOY_ADAPT_STRUCT(TwoInts, a, b) +IRIS_ALLOY_ADAPT_STRUCT(NamePath, is_rooted, segments) +IRIS_ALLOY_ADAPT_STRUCT(AnyID, path) template using SET = x4_test::single_element_struct; using IdentRule = x4::rule; using VarRule = x4::rule; +using NamePathRule = x4::rule; +using AnyIDRule = x4::rule; IRIS_X4_DECLARE_CONSTEXPR(IdentRule) IRIS_X4_DECLARE_CONSTEXPR(VarRule) +IRIS_X4_DECLARE_CONSTEXPR(NamePathRule) +IRIS_X4_DECLARE_CONSTEXPR(AnyIDRule) constexpr IdentRule ident; constexpr VarRule var; +constexpr NamePathRule name_path; +constexpr AnyIDRule any_id; constexpr auto ident_def = x4::alpha >> *x4::alnum; constexpr auto var_def = '$' >> ident; +constexpr auto name_path_def = x4::matches["::"] >> (ident % "::"); +constexpr auto any_id_def = name_path; IRIS_X4_DEFINE_CONSTEXPR(ident) IRIS_X4_DEFINE_CONSTEXPR(var) +IRIS_X4_DEFINE_CONSTEXPR(name_path) +IRIS_X4_DEFINE_CONSTEXPR(any_id) TEST_CASE("single_element_tuple_like") { @@ -124,6 +151,24 @@ TEST_CASE("single_element_tuple_like") CHECK(attr.ident.value == "abc"); } + // name_path + { + constexpr auto parser = name_path; + NamePath attr; + REQUIRE(parse("::foo::bar", parser, attr)); + CHECK(attr.is_rooted); + CHECK(attr.segments == std::vector{Ident{"foo"}, Ident{"bar"}}); + } + + // any_id + { + constexpr auto parser = any_id; + AnyID attr; + REQUIRE(parse("::foo::bar", parser, attr)); + CHECK(attr.path.is_rooted); + CHECK(attr.path.segments == std::vector{Ident{"foo"}, Ident{"bar"}}); + } + // see https://github.com/boostorg/spirit_x4/issues/27 { constexpr auto parser = alpha >> *alnum; @@ -177,25 +222,25 @@ TEST_CASE("SET standalone") using x4::string; using x4::standard::char_; - // plain × identity + // plain x identity { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - // plain × SET + // plain x SET { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } - // plain × variant + // plain x variant { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } - // container × identity + // container x identity { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } - // container × variant + // container x variant { std::string a; REQUIRE(parse("a", alpha | char_, a)); CHECK(a == "a"); } - // SET × identity + // SET x identity { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - // SET × SET + // SET x SET { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - // SET × variant + // SET x variant { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } - // MET × identity + // MET x identity { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - // MET × variant + // MET x variant { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } } @@ -211,25 +256,25 @@ TEST_CASE("SET in sequence") using x4::string; using x4::standard::char_; - // plain × identity + // plain x identity { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - // plain × SET + // plain x SET { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } - // plain × variant + // plain x variant { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } - // container × identity + // container x identity { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } - // container × variant + // container x variant { std::string a; REQUIRE(parse("+a", '+' >> (alpha | char_), a)); CHECK(a == "a"); } - // SET × identity + // SET x identity { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - // SET × SET + // SET x SET { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - // SET × variant + // SET x variant { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } - // MET × identity + // MET x identity { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } - // MET × variant + // MET x variant { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } } @@ -260,33 +305,33 @@ TEST_CASE("SET composition: left int_") using x4::string; using x4::standard::char_; - // int_ × int_ → (int, int) + // int_ x int_ → (int, int) { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // int_ × int_ → MET struct + // int_ x int_ → MET struct { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - // int_ × int_ → (SET, int) + // int_ x int_ → (SET, int) { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } - // int_ × int_ → (int, SET) + // int_ x int_ → (int, SET) { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } - // int_ × int_ → (SET, SET) + // int_ x int_ → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 2); } - // int_ × attr(SET) + // int_ x attr(SET) { std::tuple a; REQUIRE(parse("42", int_ >> x4::attr(SET{99}), a)); CHECK(alloy::get<0>(a) == 42); CHECK(alloy::get<1>(a) == 99); } - // int_ × variant + // int_ x variant { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // int_ × alpha + // int_ x alpha { std::tuple a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } - // int_ × alpha → (int, SET) + // int_ x alpha → (int, SET) { std::tuple> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 'a'); } - // int_ × alpha → (SET, SET) + // int_ x alpha → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 'a'); } - // int_ × string("abc") + // int_ x string("abc") { std::tuple a; REQUIRE(parse("1abc", int_ >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - // int_ × +alpha + // int_ x +alpha { std::tuple a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - // int_ × +alpha → (int, SET) + // int_ x +alpha → (int, SET) { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } - // int_ × +alpha → (SET, string) + // int_ x +alpha → (SET, string) { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } } @@ -297,21 +342,21 @@ TEST_CASE("SET composition: left attr(SET)") using x4::string; using x4::standard::char_; - // attr(SET) × int_ + // attr(SET) x int_ { std::tuple a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } - // attr(SET) × int_ → (SET, int) + // attr(SET) x int_ → (SET, int) { std::tuple, int> a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 42); } - // attr(SET) × attr(SET) + // attr(SET) x attr(SET) { std::tuple a; REQUIRE(parse("", x4::attr(SET{1}) >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // attr(SET) × variant + // attr(SET) x variant { std::tuple a; REQUIRE(parse("2", x4::attr(SET{1}) >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // attr(SET) × alpha + // attr(SET) x alpha { std::tuple a; REQUIRE(parse("a", x4::attr(SET{1}) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } - // attr(SET) × alpha → (SET, char) + // attr(SET) x alpha → (SET, char) { std::tuple, char> a; REQUIRE(parse("a", x4::attr(SET{1}) >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 'a'); } - // attr(SET) × string("abc") + // attr(SET) x string("abc") { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - // attr(SET) × +alpha + // attr(SET) x +alpha { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } } @@ -322,21 +367,21 @@ TEST_CASE("SET composition: left variant") using x4::string; using x4::standard::char_; - // variant × int_ + // variant x int_ { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // variant × int_ → (SET, int) + // variant x int_ → (SET, int) { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } - // variant × attr(SET) + // variant x attr(SET) { std::tuple a; REQUIRE(parse("1", (int_ | char_) >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // variant × variant + // variant x variant { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // variant × variant → (int, SET) + // variant x variant → (int, SET) { std::tuple> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } - // variant × alpha + // variant x alpha { std::tuple a; REQUIRE(parse("1a", (int_ | char_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } - // variant × string("abc") + // variant x string("abc") { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - // variant × +alpha + // variant x +alpha { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } } @@ -348,25 +393,25 @@ TEST_CASE("SET composition: left alpha") using x4::string; using x4::standard::char_; - // alpha × int_ + // alpha x int_ { std::tuple a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 42); } - // alpha × int_ → (SET, int) + // alpha x int_ → (SET, int) { std::tuple, int> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a) == 42); } - // alpha × int_ → (char, SET) + // alpha x int_ → (char, SET) { std::tuple> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).value == 42); } - // alpha × attr(SET) + // alpha x attr(SET) { std::tuple a; REQUIRE(parse("a", alpha >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 2); } - // alpha × variant + // alpha x variant { std::tuple a; REQUIRE(parse("a1", alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); } - // alpha × alpha + // alpha x alpha { std::tuple a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 'b'); } - // alpha × alpha → (SET, SET) + // alpha x alpha → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a).value == 'b'); } - // alpha × string("abc") + // alpha x string("abc") { std::tuple a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a) == "abc"); } - // alpha × string("abc") → (char, SET) + // alpha x string("abc") → (char, SET) { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } - // alpha × +alpha + // alpha x +alpha { std::tuple a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == "b"); } } @@ -377,21 +422,21 @@ TEST_CASE("SET composition: left string") using x4::string; using x4::standard::char_; - // string("abc") × int_ + // string("abc") x int_ { std::tuple a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } - // string("abc") × int_ → (SET, int) + // string("abc") x int_ → (SET, int) { std::tuple, int> a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } - // string("abc") × attr(SET) + // string("abc") x attr(SET) { std::tuple a; REQUIRE(parse("abc", string("abc") >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } - // string("abc") × variant + // string("abc") x variant { std::tuple a; REQUIRE(parse("abc1", string("abc") >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } - // string("abc") × alpha + // string("abc") x alpha { std::tuple a; REQUIRE(parse("abcd", string("abc") >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } - // string("abc") × string("def") + // string("abc") x string("def") { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } - // string("abc") × string("def") → (SET, SET) + // string("abc") x string("def") → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } - // string("abc") × +alpha + // string("abc") x +alpha { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } } @@ -402,21 +447,21 @@ TEST_CASE("SET composition: left +alpha") using x4::string; using x4::standard::char_; - // +alpha × int_ + // +alpha x int_ { std::tuple a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } - // +alpha × int_ → (SET, int) + // +alpha x int_ → (SET, int) { std::tuple, int> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } - // +alpha × int_ → (string, SET) + // +alpha x int_ → (string, SET) { std::tuple> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a).value == 42); } - // +alpha × attr(SET) + // +alpha x attr(SET) { std::tuple a; REQUIRE(parse("abc", +alpha >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } - // +alpha × variant + // +alpha x variant { std::tuple a; REQUIRE(parse("abc1", +alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } - // +alpha × ',' >> alpha + // +alpha x ',' >> alpha { std::tuple a; REQUIRE(parse("abc,d", +alpha >> ',' >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } - // +alpha × ',' >> +alpha + // +alpha x ',' >> +alpha { std::tuple a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } - // +alpha × ',' >> +alpha → (SET, SET) + // +alpha x ',' >> +alpha → (SET, SET) { std::tuple, SET> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } } @@ -449,27 +494,27 @@ TEST_CASE("SET composition: MET parsers") using x4::string; using x4::standard::char_; - // MET × int_ → 3-slot tuple + // MET x int_ → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2:3", (int_ >> ',' >> int_) >> ':' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } - // MET × alpha → 3-slot tuple + // MET x alpha → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2a", (int_ >> ',' >> int_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 'a'); } - // MET × +alpha → 3-slot tuple + // MET x +alpha → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2abc", (int_ >> ',' >> int_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == "abc"); } - // MET × attr(SET) → 3-slot tuple + // MET x attr(SET) → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) >> x4::attr(SET{99}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 99); } - // int_ × MET → 3-slot tuple + // int_ x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("1:2,3", int_ >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } - // alpha × MET → 3-slot tuple + // alpha x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } - // +alpha × MET → 3-slot tuple + // +alpha x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("abc1,2", +alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } - // attr(SET) × MET → 3-slot tuple + // attr(SET) x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2", x4::attr(SET{99}) >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 99); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } - // MET × MET → 4-slot tuple + // MET x MET → 4-slot tuple { std::tuple a; REQUIRE(parse("1,2:3,4", (int_ >> ',' >> int_) >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); CHECK(alloy::get<3>(a) == 4); } - // MET_variant × alpha → 2-slot tuple (alternative has sequence_size=1) + // MET_variant x alpha → 2-slot tuple (alternative has sequence_size=1) { std::tuple a; REQUIRE(parse("1,2a", ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)) >> alpha, a)); CHECK(alloy::get<0>(a).a == 1); CHECK(alloy::get<0>(a).b == 2); CHECK(alloy::get<1>(a) == 'a'); } - // alpha × MET_variant → 2-slot tuple + // alpha x MET_variant → 2-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } // --- Nested SET: recursive unwrap --- From 114486de97b6b2db9ad1ee97e5ef9e39f97b23f1 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 19:45:51 +0900 Subject: [PATCH 17/52] Fix indentation --- test/x4/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 6737d0ce5..ffdde9ff0 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -85,7 +85,7 @@ x4_define_tests( rule4 seek sequence - single_element_tuple_like + single_element_tuple_like skip substitution symbols1 From 59c5661fe61a411c9258840b54a3c3d74561618c Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 19:46:54 +0900 Subject: [PATCH 18/52] Use remove_cvref_t consistently --- include/iris/x4/core/move_to.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 8782fd30f..28df790ab 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -166,7 +166,7 @@ move_to(Source&& src, Dest& dest) } template Dest> - requires (!std::is_assignable_v) && traits::is_single_element_tuple_like::value + requires (!std::is_assignable_v) && traits::is_single_element_tuple_like>::value constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) From 7ff873ea2a97be1cee8da48855240622a209251f Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Sun, 8 Mar 2026 19:48:12 +0900 Subject: [PATCH 19/52] Remove deprecated overload of `move_to` --- include/iris/x4/core/move_to.hpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 28df790ab..556ef07ec 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -220,16 +220,6 @@ move_to(It first, Se last, Dest& dest) traits::append(dest, first, last); // try to reuse underlying memory buffer } -template Se, traits::CategorizedAttr Dest> - requires traits::is_single_element_tuple_like::value -constexpr void -move_to(It first, Se last, Dest& dest) // TODO: delete this overload - noexcept(noexcept(x4::move_to(first, last, alloy::get<0>(dest)))) -{ - static_assert(false); - x4::move_to(first, last, alloy::get<0>(dest)); -} - // Move non-container `src` into container `dest`. // e.g. Source=std::string_view, Dest=std::string (used in `attr_parser`) template Dest> From acd02dffb0f70940a7fe67948b0d1fa447efd042 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:24:41 +0900 Subject: [PATCH 20/52] Add notes for AI generated content --- test/x4/single_element_tuple_like.cpp | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 0b6ae12f5..d6255ad61 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -194,6 +194,37 @@ TEST_CASE("single_element_tuple_like") } } + +// +// AUTOGENERATED CONTENT; DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING. +// +// The test cases below are AI-generated content that was explicitly approved +// by @saki7, the current maintainer of X4. They were produced during a pair- +// programming session led by @yaito3014, a major contributor and the author +// of the `alloy` tuple library. The session was screen-shared over a Discord +// voice chat, and we spent more than six hours in deep discussion to ensure +// that the generated content was semantically meaningful. +// +// The problem these test cases were designed to solve essentially requires +// covering the full set of parent/child parser attribute combinations. The +// only practical way to express this in the test suite was to generate the +// Cartesian product of those attribute combinations, resulting in well over +// one hundred cases in total. In other words, this task was inherently well +// suited to AI-assisted generation, and we considered it an ideal place to +// explore how AI could support X4 development. +// +// As a result, these tests identified a single point of failure in +// `parse_sequence` and directly contributed to fixing the bug. +// +// For that reason, the code below was produced under very careful supervision +// by the maintainer and should be treated as an exceptional case of accepting +// AI-generated content into our codebase. +// +// Any future contributor who attempts to use the existence of this code as +// justification for submitting vibe-coded slop will be considered harmful to +// the codebase and will receive a permanent ban. +// + // Tests attribute compatibility across the matrix of: // attribute_category: plain, container, single_element_tuple (SET), multi_element_tuple (MET) // parser_form: identity, single_element_tuple, variant From fa0cb7808cc147323d2302977bd9af5d0d5d4227 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Tue, 10 Mar 2026 20:37:59 +0900 Subject: [PATCH 21/52] Remove unneeded branch from `pass_sequence_attribute` --- include/iris/x4/core/detail/parse_sequence.hpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index cccfcb906..4883a1e1a 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -91,24 +91,6 @@ struct pass_sequence_attribute, Attr> : pass_through_sequence_attribute {}; -template - requires - traits::is_single_element_tuple_like::value && - (!traits::can_hold::value) -struct pass_sequence_attribute, Attr> -{ - using base_pass_sequence_attribute = pass_through_sequence_attribute; - - using type = alloy::tuple_element_t<0, Attr>&; - - template - [[nodiscard]] static constexpr type - call(Attr_& attribute) noexcept(noexcept(alloy::get<0>(attribute))) - { - return alloy::get<0>(attribute); - } -}; - template requires requires { typename Parser::proxy_backend_type; From 38fb7fc0a7e69668df4179c27519fd73640e38c5 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Tue, 10 Mar 2026 20:43:10 +0900 Subject: [PATCH 22/52] Add comment and adjust noexcept --- include/iris/x4/traits/tuple_traits.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index 7886862e4..d871adf98 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -88,8 +88,9 @@ template template requires traits::is_single_element_tuple_like>::value -[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept +[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept(noexcept(alloy::get<0>(std::declval()))) { + // forward_like is *required*, since when Source is `alloy::tuple` `alloy::get<0>(std::forward(src))` returns `int&` whereas we want `int&&` instead return std::forward_like(alloy::get<0>(std::forward(value))); } From f8401c30452237d77725889221ff502509bdfec3 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 20:53:47 +0900 Subject: [PATCH 23/52] Reorganize includes --- test/x4/alternative.cpp | 5 +++-- test/x4/attribute_type_check.cpp | 1 + test/x4/debug.cpp | 6 ++++-- test/x4/expect.cpp | 5 +++-- test/x4/int.cpp | 1 + test/x4/lit.cpp | 1 + test/x4/move_to.cpp | 1 + test/x4/omit.cpp | 1 + test/x4/optional.cpp | 1 + test/x4/plus.cpp | 1 + test/x4/rule4.cpp | 4 +++- test/x4/sequence.cpp | 1 + test/x4/substitution.cpp | 2 ++ 13 files changed, 23 insertions(+), 7 deletions(-) diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index 6ac73d927..9759d952f 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -31,10 +31,11 @@ #include #include +#include #include -#include +#include #include -#include +#include struct di_ignore { diff --git a/test/x4/attribute_type_check.cpp b/test/x4/attribute_type_check.cpp index 11b2a7910..47b221c67 100644 --- a/test/x4/attribute_type_check.cpp +++ b/test/x4/attribute_type_check.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace { diff --git a/test/x4/debug.cpp b/test/x4/debug.cpp index 406aad55e..bb7fb11a7 100644 --- a/test/x4/debug.cpp +++ b/test/x4/debug.cpp @@ -23,11 +23,13 @@ #include #include +#include #include -#include #include +#include +#include + #include -#include namespace { diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 35dd18f38..7f02673e7 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -51,11 +51,12 @@ #include #include -#include #include #include -#include +#include #include +#include +#include // NOLINTBEGIN(bugprone-chained-comparison) diff --git a/test/x4/int.cpp b/test/x4/int.cpp index e341c1dbb..e63d73e9b 100644 --- a/test/x4/int.cpp +++ b/test/x4/int.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include diff --git a/test/x4/lit.cpp b/test/x4/lit.cpp index 8c7d5ce39..03109374e 100644 --- a/test/x4/lit.cpp +++ b/test/x4/lit.cpp @@ -20,6 +20,7 @@ #include #include +#include TEST_CASE("lit") { diff --git a/test/x4/move_to.cpp b/test/x4/move_to.cpp index 9e654518c..fab0c21f3 100644 --- a/test/x4/move_to.cpp +++ b/test/x4/move_to.cpp @@ -9,6 +9,7 @@ #include +#include #include struct X {}; diff --git a/test/x4/omit.cpp b/test/x4/omit.cpp index 2b4dccc4a..6fd9c275e 100644 --- a/test/x4/omit.cpp +++ b/test/x4/omit.cpp @@ -21,6 +21,7 @@ #include #include +#include namespace { diff --git a/test/x4/optional.cpp b/test/x4/optional.cpp index f95768ae7..6c6e4a175 100644 --- a/test/x4/optional.cpp +++ b/test/x4/optional.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #ifdef _MSC_VER diff --git a/test/x4/plus.cpp b/test/x4/plus.cpp index 39f6c7636..164d91ca8 100644 --- a/test/x4/plus.cpp +++ b/test/x4/plus.cpp @@ -22,6 +22,7 @@ #include #include +#include #include TEST_CASE("plus") diff --git a/test/x4/rule4.cpp b/test/x4/rule4.cpp index 32ff62390..e2f5a862a 100644 --- a/test/x4/rule4.cpp +++ b/test/x4/rule4.cpp @@ -23,10 +23,12 @@ #include #include +#include #include #include +#include + #include -#include namespace { diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index 84d1e665c..6d3c88a17 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -35,6 +35,7 @@ #include #include #include +#include TEST_CASE("sequence") { diff --git a/test/x4/substitution.cpp b/test/x4/substitution.cpp index 86f6aef13..28d16b0f7 100644 --- a/test/x4/substitution.cpp +++ b/test/x4/substitution.cpp @@ -9,6 +9,8 @@ #include +#include + template inline constexpr bool can_hold_v = x4::traits::can_hold::value; From 0e7dc8c596a6472ec31c2ba52cf9e324e42f230d Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 20:54:15 +0900 Subject: [PATCH 24/52] Styling fix --- include/iris/x4/core/detail/parse_sequence.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index 4883a1e1a..c1a4893b3 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -209,7 +209,7 @@ struct partition_attribute template Se, class Context, X4Attribute Attr> requires (parser_traits::sequence_size > 1) [[nodiscard]] constexpr bool - parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) +parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) noexcept(is_nothrow_parsable_v) { // static_assert(Parsable); @@ -219,7 +219,7 @@ template Se, class template Se, class Context, X4Attribute Attr> requires (parser_traits::sequence_size <= 1) [[nodiscard]] constexpr bool - parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) +parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) noexcept(noexcept(detail::parse_into_container(parser, first, last, ctx, attr))) { return detail::parse_into_container(parser, first, last, ctx, attr); From e11c5a76db0098f1ae78b357f288e853f8724285 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 20:59:47 +0900 Subject: [PATCH 25/52] Remove unused forward declaration --- include/iris/x4/core/detail/parse_sequence.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index c1a4893b3..de9537762 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -30,9 +30,6 @@ namespace iris::x4 { -template -struct rule; - template struct sequence; From 14eea5f9cb34429b5f85c28b733bc563707c2a55 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 21:11:58 +0900 Subject: [PATCH 26/52] Remove `pass` from `partition_attribute` --- .../iris/x4/core/detail/parse_sequence.hpp | 84 ++----------------- include/iris/x4/traits/tuple_traits.hpp | 3 +- 2 files changed, 7 insertions(+), 80 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index de9537762..e4fda75d4 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -37,65 +37,6 @@ struct sequence; namespace iris::x4::detail { -struct pass_sequence_attribute_unused -{ - using type = unused_type; - - template - [[nodiscard]] static constexpr unused_type - call(T&) noexcept - { - return unused_type{}; - } -}; - -template -struct pass_sequence_attribute_size_one_view -{ - using type = alloy::tuple_element_t<0, Attr>; - - [[nodiscard]] static constexpr type - call(Attr& attribute) - noexcept(noexcept(alloy::get<0>(attribute))) - { - return alloy::get<0>(attribute); - } -}; - -template -struct pass_through_sequence_attribute -{ - using type = Attr&; - - template - [[nodiscard]] static constexpr Attr_& - call(Attr_& attribute) noexcept - { - return attribute; - } -}; - -template -struct pass_sequence_attribute : std::conditional_t< - traits::is_single_element_tuple_like_view::value, - pass_sequence_attribute_size_one_view, - pass_through_sequence_attribute -> -{}; - -template -struct pass_sequence_attribute, Attr> - : pass_through_sequence_attribute -{}; - -template - requires requires { - typename Parser::proxy_backend_type; - } -struct pass_sequence_attribute - : pass_sequence_attribute -{}; - template struct partition_attribute {}; @@ -125,21 +66,19 @@ struct partition_attribute using view = alloy::tuple_ref_t; using split = alloy::tuple_split_t; - using l_part = alloy::tuple_element_t<0, split>; - using r_part = alloy::tuple_element_t<1, split>; - using l_pass = pass_sequence_attribute; - using r_pass = pass_sequence_attribute; + using l_part = traits::unwrap_single_element_tuple_like>::type; + using r_part = traits::unwrap_single_element_tuple_like>::type; [[nodiscard]] static constexpr l_part left(Attr& s) // TODO: noexcept { - return alloy::get<0>(alloy::tuple_split(alloy::tuple_ref(s))); + return traits::unwrap_single_element(alloy::get<0>(alloy::tuple_split(alloy::tuple_ref(s)))); } [[nodiscard]] static constexpr r_part right(Attr& s) // TODO: noexcept { - return alloy::get<1>(alloy::tuple_split(alloy::tuple_ref(s))); + return traits::unwrap_single_element(alloy::get<1>(alloy::tuple_split(alloy::tuple_ref(s)))); } }; @@ -149,9 +88,6 @@ template has_attribute_v struct partition_attribute { - using l_pass = pass_sequence_attribute_unused; - using r_pass = pass_sequence_attribute; - [[nodiscard]] static constexpr unused_type left(Attr&) noexcept { return unused; @@ -169,9 +105,6 @@ template (!has_attribute_v) struct partition_attribute { - using l_pass = pass_sequence_attribute; - using r_pass = pass_sequence_attribute_unused; - [[nodiscard]] static constexpr Attr& left(Attr& s) noexcept { return s; @@ -189,9 +122,6 @@ template (!has_attribute_v) struct partition_attribute { - using l_pass = pass_sequence_attribute_unused; - using r_pass = pass_sequence_attribute_unused; - [[nodiscard]] static constexpr unused_type left(Attr&) noexcept { return unused; @@ -255,10 +185,8 @@ parse_sequence(Parser const& parser, It& first, Se const& last, Context const& c Attr >; - auto&& l_part = partition::left(attr); - auto&& r_part = partition::right(attr); - auto&& l_attr = partition::l_pass::call(l_part); - auto&& r_attr = partition::r_pass::call(r_part); + auto&& l_attr = partition::left(attr); + auto&& r_attr = partition::right(attr); auto&& l_attr_appender = x4::make_container_appender(l_attr); auto&& r_attr_appender = x4::make_container_appender(r_attr); diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index d871adf98..b9081c1a7 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -90,8 +90,7 @@ template requires traits::is_single_element_tuple_like>::value [[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept(noexcept(alloy::get<0>(std::declval()))) { - // forward_like is *required*, since when Source is `alloy::tuple` `alloy::get<0>(std::forward(src))` returns `int&` whereas we want `int&&` instead - return std::forward_like(alloy::get<0>(std::forward(value))); + return alloy::get<0>(std::forward(value)); } } // iris::x4::traits From e05a682f3c26a699dcf899566e6addc2eb2ab1a3 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 21:19:26 +0900 Subject: [PATCH 27/52] Remove unneeded metafunctions --- include/iris/x4/traits/tuple_traits.hpp | 38 ++++++++----------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index b9081c1a7..e1788595e 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -17,41 +17,27 @@ namespace iris::x4::traits { template -struct has_same_size - : std::bool_constant< - alloy::tuple_size_v == - alloy::tuple_size_v - > -{}; - -template -struct has_size - : std::bool_constant == N> +struct is_same_size_tuple_like + : std::false_type {}; template -struct is_same_size_tuple_like - : std::bool_constant, - alloy::is_tuple_like, - has_same_size - >> + requires + alloy::is_tuple_like_v && + alloy::is_tuple_like_v +struct is_same_size_tuple_like + : std::bool_constant == alloy::tuple_size_v> {}; template struct is_single_element_tuple_like - : std::bool_constant, - has_size - >> + : std::false_type {}; -template -struct is_single_element_tuple_like_view - : std::bool_constant, - has_size - >> +template + requires alloy::is_tuple_like_v +struct is_single_element_tuple_like + : std::bool_constant == 1> {}; template From 308f9d860692d3c4095b4d1c6b07c710c3f09015 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 21:29:58 +0900 Subject: [PATCH 28/52] Use metafunction to simplify constexpr if --- include/iris/x4/rule.hpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index f5ec2046c..bc6412e1b 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -42,6 +42,15 @@ struct rule; namespace detail { +template +struct rule_should_unwrap : std::false_type {}; + +template + requires traits::is_single_element_tuple_like::value +struct rule_should_unwrap + : traits::can_hold> +{}; + template struct rule_id { @@ -179,12 +188,8 @@ struct rule_impl It start = first; // backup auto&& unwrapped_attr = [&]() -> decltype(auto) { - if constexpr (traits::is_single_element_tuple_like::value) { - if constexpr (traits::can_hold::attribute_type, alloy::tuple_element_t<0, RHSAttr>>::value) { - return alloy::get<0>(rhs_attr); - } else { - return rhs_attr; - } + if constexpr (rule_should_unwrap::attribute_type, RHSAttr>::value) { + return alloy::get<0>(rhs_attr); } else { return rhs_attr; } From 2a116317bb774680569cbef8d868ad894fa60165 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 21:33:21 +0900 Subject: [PATCH 29/52] Remove unused include --- test/x4/rule4.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/x4/rule4.cpp b/test/x4/rule4.cpp index e2f5a862a..de65ad8db 100644 --- a/test/x4/rule4.cpp +++ b/test/x4/rule4.cpp @@ -20,13 +20,11 @@ #include -#include #include #include #include #include -#include #include From f15176e6945ae1b8f6479dd2626f2c6c0c668d31 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 21:36:31 +0900 Subject: [PATCH 30/52] Rename `SET` to `SES` --- test/x4/single_element_tuple_like.cpp | 226 +++++++++++++------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index d6255ad61..952386a2b 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -61,7 +61,7 @@ IRIS_ALLOY_ADAPT_STRUCT(NamePath, is_rooted, segments) IRIS_ALLOY_ADAPT_STRUCT(AnyID, path) template -using SET = x4_test::single_element_struct; +using SES = x4_test::single_element_struct; using IdentRule = x4::rule; using VarRule = x4::rule; @@ -226,13 +226,13 @@ TEST_CASE("single_element_tuple_like") // // Tests attribute compatibility across the matrix of: -// attribute_category: plain, container, single_element_tuple (SET), multi_element_tuple (MET) +// attribute_category: plain, container, single_element_tuple (SES), multi_element_tuple (MET) // parser_form: identity, single_element_tuple, variant // -// Invalid combos: (container, SET) and (MET, SET) — no valid move_to +// Invalid combos: (container, SES) and (MET, SES) — no valid move_to // Valid combos per participant: 10 // -// Notation: SET = x4_test::single_element_struct +// Notation: SES = x4_test::single_element_struct // TwoInts = struct { int a, b; } // // Tests are organized in three groups: @@ -246,7 +246,7 @@ TEST_CASE("single_element_tuple_like") // 1. Standalone: parse(input, parser, dest) // =================================================================== -TEST_CASE("SET standalone") +TEST_CASE("SES standalone") { using x4::int_; using x4::alpha; @@ -255,20 +255,20 @@ TEST_CASE("SET standalone") // plain x identity { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } - // plain x SET - { int a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a == 42); } + // plain x SES + { int a{}; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(a == 42); } // plain x variant { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } // container x identity { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } // container x variant { std::string a; REQUIRE(parse("a", alpha | char_, a)); CHECK(a == "a"); } - // SET x identity - { SET a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } - // SET x SET - { SET a{}; REQUIRE(parse("", x4::attr(SET{42}), a)); CHECK(a.value == 42); } - // SET x variant - { SET a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + // SES x identity + { SES a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + // SES x SES + { SES a{}; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(a.value == 42); } + // SES x variant + { SES a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } // MET x identity { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET x variant @@ -280,7 +280,7 @@ TEST_CASE("SET standalone") // Tests partition_attribute with one unused side // =================================================================== -TEST_CASE("SET in sequence") +TEST_CASE("SES in sequence") { using x4::int_; using x4::alpha; @@ -289,20 +289,20 @@ TEST_CASE("SET in sequence") // plain x identity { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } - // plain x SET - { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a == 42); } + // plain x SES + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(a == 42); } // plain x variant { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } // container x identity { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } // container x variant { std::string a; REQUIRE(parse("+a", '+' >> (alpha | char_), a)); CHECK(a == "a"); } - // SET x identity - { SET a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } - // SET x SET - { SET a{}; REQUIRE(parse("+", '+' >> x4::attr(SET{42}), a)); CHECK(a.value == 42); } - // SET x variant - { SET a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + // SES x identity + { SES a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + // SES x SES + { SES a{}; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(a.value == 42); } + // SES x variant + { SES a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } // MET x identity { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET x variant @@ -315,7 +315,7 @@ TEST_CASE("SET in sequence") // // Parser forms (sequence_size=1): // P_int: int_ → int -// P_set: attr(SET{V}) → SET +// P_set: attr(SES{V}) → SES // P_var: int_ | char_ → rvariant // P_chr: alpha → char // P_slit: string("abc") → std::string @@ -325,11 +325,11 @@ TEST_CASE("SET in sequence") // P_met: int_ >> ',' >> int_ // P_metv: (int_ >> ',' >> int_) | (char_ >> ',' >> char_) // -// Slot types tested: int, char, std::string, SET, SET, -// SET, TwoInts +// Slot types tested: int, char, std::string, SES, SES, +// SES, TwoInts // =================================================================== -TEST_CASE("SET composition: left int_") +TEST_CASE("SES composition: left int_") { using x4::int_; using x4::alpha; @@ -340,58 +340,58 @@ TEST_CASE("SET composition: left int_") { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // int_ x int_ → MET struct { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } - // int_ x int_ → (SET, int) - { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } - // int_ x int_ → (int, SET) - { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } - // int_ x int_ → (SET, SET) - { std::tuple, SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 2); } - // int_ x attr(SET) - { std::tuple a; REQUIRE(parse("42", int_ >> x4::attr(SET{99}), a)); CHECK(alloy::get<0>(a) == 42); CHECK(alloy::get<1>(a) == 99); } + // int_ x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → (int, SES) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } + // int_ x int_ → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 2); } + // int_ x attr(SES) + { std::tuple a; REQUIRE(parse("42", int_ >> x4::attr(SES{99}), a)); CHECK(alloy::get<0>(a) == 42); CHECK(alloy::get<1>(a) == 99); } // int_ x variant { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // int_ x alpha { std::tuple a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } - // int_ x alpha → (int, SET) - { std::tuple> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 'a'); } - // int_ x alpha → (SET, SET) - { std::tuple, SET> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 'a'); } + // int_ x alpha → (int, SES) + { std::tuple> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 'a'); } + // int_ x alpha → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 'a'); } // int_ x string("abc") { std::tuple a; REQUIRE(parse("1abc", int_ >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } // int_ x +alpha { std::tuple a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - // int_ x +alpha → (int, SET) - { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } - // int_ x +alpha → (SET, string) - { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } + // int_ x +alpha → (int, SES) + { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } + // int_ x +alpha → (SES, string) + { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } } -TEST_CASE("SET composition: left attr(SET)") +TEST_CASE("SES composition: left attr(SES)") { using x4::int_; using x4::alpha; using x4::string; using x4::standard::char_; - // attr(SET) x int_ - { std::tuple a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } - // attr(SET) x int_ → (SET, int) - { std::tuple, int> a; REQUIRE(parse("42", x4::attr(SET{1}) >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 42); } - // attr(SET) x attr(SET) - { std::tuple a; REQUIRE(parse("", x4::attr(SET{1}) >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // attr(SET) x variant - { std::tuple a; REQUIRE(parse("2", x4::attr(SET{1}) >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // attr(SET) x alpha - { std::tuple a; REQUIRE(parse("a", x4::attr(SET{1}) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } - // attr(SET) x alpha → (SET, char) - { std::tuple, char> a; REQUIRE(parse("a", x4::attr(SET{1}) >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 'a'); } - // attr(SET) x string("abc") - { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } - // attr(SET) x +alpha - { std::tuple a; REQUIRE(parse("abc", x4::attr(SET{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // attr(SES) x int_ + { std::tuple a; REQUIRE(parse("42", x4::attr(SES{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } + // attr(SES) x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("42", x4::attr(SES{1}) >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 42); } + // attr(SES) x attr(SES) + { std::tuple a; REQUIRE(parse("", x4::attr(SES{1}) >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // attr(SES) x variant + { std::tuple a; REQUIRE(parse("2", x4::attr(SES{1}) >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // attr(SES) x alpha + { std::tuple a; REQUIRE(parse("a", x4::attr(SES{1}) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // attr(SES) x alpha → (SES, char) + { std::tuple, char> a; REQUIRE(parse("a", x4::attr(SES{1}) >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 'a'); } + // attr(SES) x string("abc") + { std::tuple a; REQUIRE(parse("abc", x4::attr(SES{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // attr(SES) x +alpha + { std::tuple a; REQUIRE(parse("abc", x4::attr(SES{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } } -TEST_CASE("SET composition: left variant") +TEST_CASE("SES composition: left variant") { using x4::int_; using x4::alpha; @@ -400,14 +400,14 @@ TEST_CASE("SET composition: left variant") // variant x int_ { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // variant x int_ → (SET, int) - { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } - // variant x attr(SET) - { std::tuple a; REQUIRE(parse("1", (int_ | char_) >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } + // variant x attr(SES) + { std::tuple a; REQUIRE(parse("1", (int_ | char_) >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } // variant x variant { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } - // variant x variant → (int, SET) - { std::tuple> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } + // variant x variant → (int, SES) + { std::tuple> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } // variant x alpha { std::tuple a; REQUIRE(parse("1a", (int_ | char_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } // variant x string("abc") @@ -416,7 +416,7 @@ TEST_CASE("SET composition: left variant") { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } } -TEST_CASE("SET composition: left alpha") +TEST_CASE("SES composition: left alpha") { using x4::int_; using x4::alpha; @@ -426,27 +426,27 @@ TEST_CASE("SET composition: left alpha") // alpha x int_ { std::tuple a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 42); } - // alpha x int_ → (SET, int) - { std::tuple, int> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a) == 42); } - // alpha x int_ → (char, SET) - { std::tuple> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).value == 42); } - // alpha x attr(SET) - { std::tuple a; REQUIRE(parse("a", alpha >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 2); } + // alpha x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a) == 42); } + // alpha x int_ → (char, SES) + { std::tuple> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).value == 42); } + // alpha x attr(SES) + { std::tuple a; REQUIRE(parse("a", alpha >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 2); } // alpha x variant { std::tuple a; REQUIRE(parse("a1", alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); } // alpha x alpha { std::tuple a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 'b'); } - // alpha x alpha → (SET, SET) - { std::tuple, SET> a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a).value == 'b'); } + // alpha x alpha → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a).value == 'b'); } // alpha x string("abc") { std::tuple a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a) == "abc"); } - // alpha x string("abc") → (char, SET) - { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } + // alpha x string("abc") → (char, SES) + { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } // alpha x +alpha { std::tuple a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == "b"); } } -TEST_CASE("SET composition: left string") +TEST_CASE("SES composition: left string") { using x4::int_; using x4::alpha; @@ -455,23 +455,23 @@ TEST_CASE("SET composition: left string") // string("abc") x int_ { std::tuple a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } - // string("abc") x int_ → (SET, int) - { std::tuple, int> a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } - // string("abc") x attr(SET) - { std::tuple a; REQUIRE(parse("abc", string("abc") >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } + // string("abc") x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } + // string("abc") x attr(SES) + { std::tuple a; REQUIRE(parse("abc", string("abc") >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } // string("abc") x variant { std::tuple a; REQUIRE(parse("abc1", string("abc") >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } // string("abc") x alpha { std::tuple a; REQUIRE(parse("abcd", string("abc") >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } // string("abc") x string("def") { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } - // string("abc") x string("def") → (SET, SET) - { std::tuple, SET> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } + // string("abc") x string("def") → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } // string("abc") x +alpha { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } } -TEST_CASE("SET composition: left +alpha") +TEST_CASE("SES composition: left +alpha") { using x4::int_; using x4::alpha; @@ -480,23 +480,23 @@ TEST_CASE("SET composition: left +alpha") // +alpha x int_ { std::tuple a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } - // +alpha x int_ → (SET, int) - { std::tuple, int> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } - // +alpha x int_ → (string, SET) - { std::tuple> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a).value == 42); } - // +alpha x attr(SET) - { std::tuple a; REQUIRE(parse("abc", +alpha >> x4::attr(SET{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } + // +alpha x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } + // +alpha x int_ → (string, SES) + { std::tuple> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a).value == 42); } + // +alpha x attr(SES) + { std::tuple a; REQUIRE(parse("abc", +alpha >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } // +alpha x variant { std::tuple a; REQUIRE(parse("abc1", +alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } // +alpha x ',' >> alpha { std::tuple a; REQUIRE(parse("abc,d", +alpha >> ',' >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } // +alpha x ',' >> +alpha { std::tuple a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } - // +alpha x ',' >> +alpha → (SET, SET) - { std::tuple, SET> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } + // +alpha x ',' >> +alpha → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } } -TEST_CASE("SET composition: SET dest wrapping") +TEST_CASE("SES composition: SES dest wrapping") { using x4::int_; using x4::alpha; @@ -504,21 +504,21 @@ TEST_CASE("SET composition: SET dest wrapping") using x4::string; using x4::standard::char_; - // --- SET dest wrapping sequence result (core fix) --- + // --- SES dest wrapping sequence result (core fix) --- // Exercises the parse_sequence unwrap path: // is_single_element_tuple_like && has_attribute_v && has_attribute_v - // SET: alpha >> *alnum → SET (= Ident pattern) - { SET a; REQUIRE(parse("abc", alpha >> *alnum, a)); CHECK(a.value == "abc"); } - // SET: int_ >> ',' >> int_ → SET - { SET a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } - // SET: int_ >> ',' >> int_ → SET> - { SET> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } - // SET with variant parser - { SET a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + // SES: alpha >> *alnum → SES (= Ident pattern) + { SES a; REQUIRE(parse("abc", alpha >> *alnum, a)); CHECK(a.value == "abc"); } + // SES: int_ >> ',' >> int_ → SES + { SES a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + // SES: int_ >> ',' >> int_ → SES> + { SES> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } + // SES with variant parser + { SES a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } } -TEST_CASE("SET composition: MET parsers") +TEST_CASE("SES composition: MET parsers") { using x4::int_; using x4::alpha; @@ -531,16 +531,16 @@ TEST_CASE("SET composition: MET parsers") { std::tuple a; REQUIRE(parse("1,2a", (int_ >> ',' >> int_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 'a'); } // MET x +alpha → 3-slot tuple { std::tuple a; REQUIRE(parse("1,2abc", (int_ >> ',' >> int_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == "abc"); } - // MET x attr(SET) → 3-slot tuple - { std::tuple a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) >> x4::attr(SET{99}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 99); } + // MET x attr(SES) → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) >> x4::attr(SES{99}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 99); } // int_ x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("1:2,3", int_ >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } // alpha x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } // +alpha x MET → 3-slot tuple { std::tuple a; REQUIRE(parse("abc1,2", +alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } - // attr(SET) x MET → 3-slot tuple - { std::tuple a; REQUIRE(parse("1,2", x4::attr(SET{99}) >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 99); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // attr(SES) x MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2", x4::attr(SES{99}) >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 99); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } // MET x MET → 4-slot tuple { std::tuple a; REQUIRE(parse("1,2:3,4", (int_ >> ',' >> int_) >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); CHECK(alloy::get<3>(a) == 4); } // MET_variant x alpha → 2-slot tuple (alternative has sequence_size=1) @@ -548,20 +548,20 @@ TEST_CASE("SET composition: MET parsers") // alpha x MET_variant → 2-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } - // --- Nested SET: recursive unwrap --- + // --- Nested SES: recursive unwrap --- // TODO: these hit a noexcept evaluation issue in move_to (recursive // single-element-tuple-like forwarding through two layers). // Uncomment once that is resolved. - // SET>: double unwrap through move_to + // SES>: double unwrap through move_to // { - // SET> a; + // SES> a; // REQUIRE(parse("42", int_, a)); // CHECK(a.value.value == 42); // } - // SET>: double unwrap through parse_sequence (= Var pattern) + // SES>: double unwrap through parse_sequence (= Var pattern) // { - // SET> a; + // SES> a; // REQUIRE(parse("abc", alpha >> *alnum, a)); // CHECK(a.value.value == "abc"); // } From ebc71c2f0a54edcbc611a37ca0cfbeee1f89984d Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Wed, 11 Mar 2026 22:06:40 +0900 Subject: [PATCH 31/52] Rename to clearer name and resolve noexcept issue --- .../iris/x4/core/detail/parse_sequence.hpp | 8 +++--- include/iris/x4/core/list_like_parser.hpp | 4 +-- include/iris/x4/core/move_to.hpp | 26 ++++++++++++++++++- .../iris/x4/string/detail/string_parse.hpp | 4 +-- include/iris/x4/traits/tuple_traits.hpp | 8 +++--- test/x4/single_element_tuple_like.cpp | 26 ++++++++----------- 6 files changed, 48 insertions(+), 28 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index e4fda75d4..3c65353c7 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -66,19 +66,19 @@ struct partition_attribute using view = alloy::tuple_ref_t; using split = alloy::tuple_split_t; - using l_part = traits::unwrap_single_element_tuple_like>::type; - using r_part = traits::unwrap_single_element_tuple_like>::type; + using l_part = traits::unwrap_if_single_element_tuple_like>::type; + using r_part = traits::unwrap_if_single_element_tuple_like>::type; [[nodiscard]] static constexpr l_part left(Attr& s) // TODO: noexcept { - return traits::unwrap_single_element(alloy::get<0>(alloy::tuple_split(alloy::tuple_ref(s)))); + return traits::do_unwrap_if_single_element_tuple_like(alloy::get<0>(alloy::tuple_split(alloy::tuple_ref(s)))); } [[nodiscard]] static constexpr r_part right(Attr& s) // TODO: noexcept { - return traits::unwrap_single_element(alloy::get<1>(alloy::tuple_split(alloy::tuple_ref(s)))); + return traits::do_unwrap_if_single_element_tuple_like(alloy::get<1>(alloy::tuple_split(alloy::tuple_ref(s)))); } }; diff --git a/include/iris/x4/core/list_like_parser.hpp b/include/iris/x4/core/list_like_parser.hpp index dd886b7c2..0334e2548 100644 --- a/include/iris/x4/core/list_like_parser.hpp +++ b/include/iris/x4/core/list_like_parser.hpp @@ -26,7 +26,7 @@ template // non-variant `ExposedAttr` struct unwrap_container_candidate { - using type = traits::unwrap_single_element_tuple_like< + using type = traits::unwrap_if_single_element_tuple_like< unwrap_recursive_type< typename unwrap_container_appender::type > @@ -62,7 +62,7 @@ template using unwrapped_attr_type = traits::unwrap_single_element_plain< unwrap_recursive_type >::type; - auto& unwrapped_attr = traits::unwrap_single_element(iris::unwrap_recursive(attr)); + auto& unwrapped_attr = traits::do_unwrap_if_single_element_tuple_like(iris::unwrap_recursive(attr)); if constexpr (traits::is_variant_v) { using container_alternative = traits::variant_find_holdable_type< diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 556ef07ec..38ed061a1 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -257,12 +257,36 @@ move_to(Source&& src, Dest& dest) } } +namespace detail { + +template +struct is_single_element_move_to_nothrow + : std::bool_constant< + noexcept(x4::move_to(std::declval(), std::declval())) + > +{}; + +template + requires traits::is_single_element_tuple_like::value +struct is_single_element_move_to_nothrow + : std::bool_constant< + noexcept(alloy::get<0>(std::declval())) && + is_single_element_move_to_nothrow< + Source, + alloy::tuple_element_t<0, Dest> + >::value + > +{}; + +} // detail + + // Size-one tuple-like forwarding template Dest> requires traits::is_single_element_tuple_like::value constexpr void move_to(Source&& src, Dest& dest) - noexcept(noexcept(x4::move_to(std::forward(src), alloy::get<0>(dest)))) + noexcept(detail::is_single_element_move_to_nothrow::value) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); diff --git a/include/iris/x4/string/detail/string_parse.hpp b/include/iris/x4/string/detail/string_parse.hpp index dff71cf3a..76cc030d0 100644 --- a/include/iris/x4/string/detail/string_parse.hpp +++ b/include/iris/x4/string/detail/string_parse.hpp @@ -31,7 +31,7 @@ string_parse( ) noexcept(std::same_as, unused_container_type>) { if constexpr (traits::is_single_element_tuple_like::value) { - return detail::string_parse(str, first, last, traits::unwrap_single_element(attr), compare); + return detail::string_parse(str, first, last, traits::do_unwrap_if_single_element_tuple_like(attr), compare); } else { static_assert(std::same_as::type, traits::container_attr>); using value_type = traits::container_value::type; @@ -81,7 +81,7 @@ string_parse( ) noexcept(std::same_as, unused_container_type>) { if constexpr (traits::is_single_element_tuple_like::value) { - return detail::string_parse(ucstr, lcstr, first, last, traits::unwrap_single_element(attr)); + return detail::string_parse(ucstr, lcstr, first, last, traits::do_unwrap_if_single_element_tuple_like(attr)); } else { static_assert(std::same_as, traits::container_attr>); using value_type = traits::container_value::type; diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index e1788595e..d150a3b54 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -41,14 +41,14 @@ struct is_single_element_tuple_like {}; template -struct unwrap_single_element_tuple_like +struct unwrap_if_single_element_tuple_like { using type = T; }; template requires is_single_element_tuple_like::value -struct unwrap_single_element_tuple_like +struct unwrap_if_single_element_tuple_like { using type = alloy::tuple_element_t<0, T>; }; @@ -67,14 +67,14 @@ struct unwrap_single_element_plain }; template -[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept +[[nodiscard]] constexpr auto&& do_unwrap_if_single_element_tuple_like(T&& value) noexcept { return std::forward(value); } template requires traits::is_single_element_tuple_like>::value -[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept(noexcept(alloy::get<0>(std::declval()))) +[[nodiscard]] constexpr auto&& do_unwrap_if_single_element_tuple_like(T&& value) noexcept(noexcept(alloy::get<0>(std::declval()))) { return alloy::get<0>(std::forward(value)); } diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 952386a2b..8deacdd16 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -522,6 +522,7 @@ TEST_CASE("SES composition: MET parsers") { using x4::int_; using x4::alpha; + using x4::alnum; using x4::string; using x4::standard::char_; @@ -548,21 +549,16 @@ TEST_CASE("SES composition: MET parsers") // alpha x MET_variant → 2-slot tuple { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } - // --- Nested SES: recursive unwrap --- - // TODO: these hit a noexcept evaluation issue in move_to (recursive - // single-element-tuple-like forwarding through two layers). - // Uncomment once that is resolved. - // SES>: double unwrap through move_to - // { - // SES> a; - // REQUIRE(parse("42", int_, a)); - // CHECK(a.value.value == 42); - // } + { + SES> a; + REQUIRE(parse("42", int_, a)); + CHECK(a.value.value == 42); + } // SES>: double unwrap through parse_sequence (= Var pattern) - // { - // SES> a; - // REQUIRE(parse("abc", alpha >> *alnum, a)); - // CHECK(a.value.value == "abc"); - // } + { + SES> a; + REQUIRE(parse("abc", alpha >> *alnum, a)); + CHECK(a.value.value == "abc"); + } } From 42627b02b447b5b4668029fc0fe507f5c8933aa4 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Thu, 12 Mar 2026 02:21:14 +0900 Subject: [PATCH 32/52] Fix comment --- include/iris/x4/traits/tuple_traits.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index d150a3b54..48da60d9f 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -8,7 +8,7 @@ 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) -================================================_==============================*/ +==============================================================================*/ #include From 5a3b28e88457928b8bd6fb2c012005b5b5e5ee37 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Thu, 12 Mar 2026 02:24:43 +0900 Subject: [PATCH 33/52] Add variant case --- test/x4/single_element_tuple_like.cpp | 53 ++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index 8deacdd16..b3698f943 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -226,7 +226,7 @@ TEST_CASE("single_element_tuple_like") // // Tests attribute compatibility across the matrix of: -// attribute_category: plain, container, single_element_tuple (SES), multi_element_tuple (MET) +// attribute_category: plain, container, single_element_tuple (SES), multi_element_tuple (MET), variant // parser_form: identity, single_element_tuple, variant // // Invalid combos: (container, SES) and (MET, SES) — no valid move_to @@ -273,6 +273,18 @@ TEST_CASE("SES standalone") { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET x variant { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // variant_attr x identity + { iris::rvariant a; REQUIRE(parse("42", int_, a)); CHECK(iris::get(a) == 42); } + // variant_attr x SES + { iris::rvariant a; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(iris::get(a) == 42); } + // variant_attr x variant + { iris::rvariant a; REQUIRE(parse("42", int_ | char_, a)); CHECK(iris::get(a) == 42); } + // SES x identity + { SES> a; REQUIRE(parse("42", int_, a)); CHECK(iris::get(a.value) == 42); } + // SES x SES + { SES> a; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(iris::get(a.value) == 42); } + // SES x variant + { SES> a; REQUIRE(parse("42", int_ | char_, a)); CHECK(iris::get(a.value) == 42); } } // =================================================================== @@ -307,6 +319,18 @@ TEST_CASE("SES in sequence") { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } // MET x variant { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // variant_attr x identity + { iris::rvariant a; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(iris::get(a) == 42); } + // variant_attr x SES + { iris::rvariant a; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(iris::get(a) == 42); } + // variant_attr x variant + { iris::rvariant a; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(iris::get(a) == 42); } + // SES x identity + { SES> a; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(iris::get(a.value) == 42); } + // SES x SES + { SES> a; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(iris::get(a.value) == 42); } + // SES x variant + { SES> a; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(iris::get(a.value) == 42); } } // =================================================================== @@ -516,6 +540,33 @@ TEST_CASE("SES composition: SES dest wrapping") { SES> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } // SES with variant parser { SES a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + // SES: int_ >> ',' >> int_ → SES> (variant dest wrapping) + { SES> a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(iris::get(a.value).a == 1); CHECK(iris::get(a.value).b == 2); } +} + +TEST_CASE("SES composition: variant_attr dest") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // int_ x int_ → (rvariant, int) + { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(iris::get(alloy::get<0>(a)) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → (int, rvariant) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(iris::get(alloy::get<1>(a)) == 2); } + // int_ x int_ → (SES>, int) + { std::tuple>, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(iris::get(alloy::get<0>(a).value) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → (int, SES>) + { std::tuple>> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(iris::get(alloy::get<1>(a).value) == 2); } + // variant x int_ → (rvariant, int) + { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(iris::get(alloy::get<0>(a)) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x variant → (int, rvariant) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(iris::get(alloy::get<1>(a)) == 2); } + // variant x variant → (rvariant, rvariant) + { std::tuple, iris::rvariant> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(iris::get(alloy::get<0>(a)) == 1); CHECK(iris::get(alloy::get<1>(a)) == 2); } + // alpha x +alpha → (rvariant, rvariant) + { std::tuple, iris::rvariant> a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(iris::get(alloy::get<0>(a)) == 'a'); CHECK(iris::get(alloy::get<1>(a)) == "b"); } } TEST_CASE("SES composition: MET parsers") From 2b5d184e4de3bd671364ebfa2df7e782dda25b0f Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Thu, 12 Mar 2026 02:25:02 +0900 Subject: [PATCH 34/52] Use `is_convertible_without_narrowing` --- include/iris/x4/traits/can_hold.hpp | 3 ++- modules/iris | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index e21033e7e..dd5204e7b 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -44,7 +45,7 @@ struct value_type_can_hold // This "implementation" exists for short-circuiting `can_hold` for certain trivial combinations template -struct can_hold_impl : std::false_type {}; +struct can_hold_impl : is_convertible_without_narrowing {}; template requires diff --git a/modules/iris b/modules/iris index 345e891a9..7cb7e56b3 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 345e891a934469285dfa70036c6e09ec11edc313 +Subproject commit 7cb7e56b3c63710b2e4dede5daf8f933edeb266a From bf81ed25434a1ad6dbac20971a2198d3ba043da4 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Thu, 12 Mar 2026 02:25:17 +0900 Subject: [PATCH 35/52] Resolve several issues --- .../iris/x4/core/detail/parse_sequence.hpp | 36 ++++++++++++------- include/iris/x4/core/move_to.hpp | 8 +++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index 3c65353c7..e654d1543 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -203,6 +203,27 @@ parse_sequence(Parser const& parser, It& first, Se const& last, Context const& c } } +// Whether the appender path is needed for parsing a sequence into a container. +// The primary template returns false for non-container Attr (always use the +// default path). The container specialization checks whether the container's +// value_type is broad enough to represent every possible result the sequence +// parser produces. When it is NOT, each sub-parser must decide individually +// whether to append (e.g. an optional sub-parser skips appending on no-match). +// +// e.g. sequence_attr = char, container_value = char → false (same type, direct path OK) +// e.g. sequence_attr = optional, container_value = char → true (optional may be empty; needs appender) +// e.g. sequence_attr = optional, container_value = optional → false (same type, direct path OK) +template +struct sequence_needs_appender : std::false_type {}; + +template +struct sequence_needs_appender + : std::negation::type, + typename Sequence::attribute_type + >> +{}; + template struct parse_into_container_impl> { @@ -213,18 +234,9 @@ struct parse_into_container_impl> Context const& ctx, Attr& attr ) // never noexcept (requires container insertion) { - if constexpr (traits::is_container_v) { - constexpr bool sequence_attribute_can_directly_hold_value_type = traits::can_hold< - typename sequence::attribute_type, - typename traits::container_value::type - >::value; - if constexpr (sequence_attribute_can_directly_hold_value_type) { - return parse_into_container_impl_default>::call(parser, first, last, ctx, attr); - - } else { - auto&& appender = x4::make_container_appender(x4::assume_container(attr)); - return detail::parse_sequence(parser, first, last, ctx, appender); - } + if constexpr (sequence_needs_appender, Attr>::value) { + auto&& appender = x4::make_container_appender(x4::assume_container(attr)); + return detail::parse_sequence(parser, first, last, ctx, appender); } else { return parse_into_container_impl_default>::call(parser, first, last, ctx, attr); } diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 38ed061a1..196cbd258 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -261,6 +261,14 @@ namespace detail { template struct is_single_element_move_to_nothrow + : std::false_type +{}; + +template + requires + (!traits::is_single_element_tuple_like::value) && + requires { x4::move_to(std::declval(), std::declval()); } +struct is_single_element_move_to_nothrow : std::bool_constant< noexcept(x4::move_to(std::declval(), std::declval())) > From 3f80fe530a28dcfc2c5247bfd8b10c7b5e214549 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Thu, 12 Mar 2026 02:41:30 +0900 Subject: [PATCH 36/52] Pass original attribute to `on_success` --- include/iris/x4/rule.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index bc6412e1b..9a40765f0 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -218,7 +218,7 @@ struct rule_impl if constexpr (need_on_success) { if (ok) { x4::skip_over(start, first, rcontext); - RuleID{}.on_success(std::as_const(start), std::as_const(first), rcontext, unwrapped_attr); + RuleID{}.on_success(std::as_const(start), std::as_const(first), rcontext, rhs_attr); return true; } From ff1886aef42cbee7e58c2f75e06735e3815ea778 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Thu, 12 Mar 2026 03:52:18 +0900 Subject: [PATCH 37/52] Add static_assertion that checks narrowing assignment --- include/iris/x4/core/move_to.hpp | 21 +++++++++-- include/iris/x4/rule.hpp | 27 +++++---------- include/iris/x4/traits/can_hold.hpp | 7 ++-- include/iris/x4/traits/narrowing.hpp | 50 +++++++++++++++++++++++++++ test/x4/real.hpp | 6 ++-- test/x4/single_element_tuple_like.cpp | 22 ++++++++++++ test/x4/uint.cpp | 36 +++++++++---------- 7 files changed, 126 insertions(+), 43 deletions(-) create mode 100644 include/iris/x4/traits/narrowing.hpp diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 196cbd258..def97e37d 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -122,7 +123,11 @@ move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - // TODO: preliminarily invoke static_assert to check if the assignment is valid + using element_type = std::remove_reference_t>>; + static_assert( + detail::is_assignable_without_narrowing::value, + "Narrowing conversion detected in move_to (single-element tuple-like to plain)" + ); dest = std::forward_like(alloy::get<0>(std::forward(src))); } @@ -133,7 +138,10 @@ move_to(Source&& src, Dest& dest) noexcept(std::is_nothrow_assignable_v) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - static_assert(std::is_assignable_v); + static_assert( + detail::is_assignable_without_narrowing>::value, + "Narrowing conversion detected in move_to (source to plain)" + ); dest = std::forward(src); } @@ -147,7 +155,10 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - // TODO: preliminarily invoke static_assert to check if the assignment is valid + static_assert( + detail::is_tuple_assignable_without_narrowing>::value, + "Narrowing conversion detected in move_to (tuple element-wise assignment)" + ); alloy::tuple_assign(std::forward(src), dest); } @@ -191,6 +202,10 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); + static_assert( + detail::is_assignable_without_narrowing>::value, + "Narrowing conversion detected in move_to (source to optional)" + ); dest = std::forward(src); } diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 9a40765f0..5a5149b80 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -354,18 +355,6 @@ struct rule_definition : parser -struct narrowing_checker -{ - using Dest = Exposed[]; - - // emulate `Exposed x[] = {std::forward(t)};` - template - static void operator()(T&&) - requires requires(T&& t) { { Dest{std::forward(t)} }; }; -}; - - template concept RuleAttrConvertible = X4Attribute && @@ -374,11 +363,10 @@ concept RuleAttrConvertible = template concept RuleAttrConvertibleWithoutNarrowing = RuleAttrConvertible && - requires { - narrowing_checker< - unwrap_container_appender_t> - >::operator()(std::declval()); - }; + is_assignable_without_narrowing< + unwrap_container_appender_t>&, + RuleAttr + >::value; // Resolves "The Spirit X3 rule problem" in Boost.Parser's documentation // https://www.boost.org/doc/libs/1_89_0/doc/html/boost_parser/this_library_s_relationship_to_boost_spirit.html#boost_parser.this_library_s_relationship_to_boost_spirit.the_spirit_x3_rule_problem @@ -471,7 +459,10 @@ struct rule : parser> std::make_move_iterator(traits::end(rule_attr)) ); } else { - static_assert(std::is_assignable_v); + static_assert( + detail::is_assignable_without_narrowing::value, + "Narrowing conversion detected in rule (rule attribute to exposed attribute)" + ); exposed_attr = std::move(rule_attr); } return true; diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index dd5204e7b..bf27e3ea2 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -43,9 +43,12 @@ struct value_type_can_hold : can_hold::type, typename container_value::type> {}; -// This "implementation" exists for short-circuiting `can_hold` for certain trivial combinations +// This "implementation" exists for short-circuiting `can_hold` for certain trivial combinations. +// Note: narrowing conversions are NOT rejected here. Instead, the actual conversion +// sites (e.g. move_to overloads) static_assert against narrowing, so the user gets +// a clear error message at the point of use. template -struct can_hold_impl : is_convertible_without_narrowing {}; +struct can_hold_impl : std::is_convertible {}; template requires diff --git a/include/iris/x4/traits/narrowing.hpp b/include/iris/x4/traits/narrowing.hpp new file mode 100644 index 000000000..eb02795c2 --- /dev/null +++ b/include/iris/x4/traits/narrowing.hpp @@ -0,0 +1,50 @@ +#ifndef IRIS_ZZ_X4_TRAITS_NARROWING_HPP +#define IRIS_ZZ_X4_TRAITS_NARROWING_HPP + +/*============================================================================= + Copyright (c) 2026 The Iris Project Contributors + + 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) +=============================================================================*/ + +#include + +#include + +#include +#include +#include + +namespace iris::x4::detail { + +// is_assignable_without_narrowing +// +// True when `Dest = Source` is valid AND does not involve a narrowing conversion. +// For non-arithmetic Dest types, narrowing is not checked — we trust that the +// user-defined operator= handles the conversion correctly. +template +struct is_assignable_without_narrowing + : std::bool_constant< + std::is_assignable_v && + (!std::is_arithmetic_v> || + iris::is_convertible_without_narrowing_v, std::remove_reference_t>) + > +{}; + +template>> +struct is_tuple_assignable_without_narrowing; + +template +struct is_tuple_assignable_without_narrowing> + : std::conjunction< + is_assignable_without_narrowing< + alloy::tuple_element_t&, + decltype(alloy::get(std::declval())) + >... + > +{}; + +} // iris::x4::detail + +#endif diff --git a/test/x4/real.hpp b/test/x4/real.hpp index cf0cc7128..b669182da 100644 --- a/test/x4/real.hpp +++ b/test/x4/real.hpp @@ -60,8 +60,10 @@ struct ts_real_policies : x4::ureal_policies constexpr uint_parser uint3; constexpr uint_parser uint3_3; - if (auto res = parse(first, last, uint3, result); res.ok) { - Accumulator n; + unsigned u; + if (auto res = parse(first, last, uint3, u); res.ok) { + result = u; + unsigned n; It iter = res.remainder.begin(); first = iter; diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp index b3698f943..b33a1e239 100644 --- a/test/x4/single_element_tuple_like.cpp +++ b/test/x4/single_element_tuple_like.cpp @@ -613,3 +613,25 @@ TEST_CASE("SES composition: MET parsers") CHECK(a.value.value == "abc"); } } + +// =================================================================== +// Narrowing conversion static_assert tests +// +// The following test cases are commented out because they are expected +// to produce static_assert failures. Uncomment any single case to +// verify the assertion fires. +// =================================================================== + +// --- move_to: source to plain (narrowing) --- +// TEST_CASE("narrowing: source to plain (char)") { char a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("narrowing: source to plain (short)") { short a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: single-element tuple-like to plain (narrowing) --- +// TEST_CASE("narrowing: single-element to plain") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: source to optional (narrowing) --- +// TEST_CASE("narrowing: source to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple element-wise (narrowing) --- +// TEST_CASE("narrowing: tuple element-wise 1") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("narrowing: tuple element-wise 2") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } diff --git a/test/x4/uint.cpp b/test/x4/uint.cpp index d255fad24..330c9a03f 100644 --- a/test/x4/uint.cpp +++ b/test/x4/uint.cpp @@ -237,33 +237,33 @@ TEST_CASE("uint") } { x4::uint_parser u_int8_; - std::uint8_t u8 = 0; + std::int8_t i8 = 0; - CHECK(!parse("999", u_int8_, u8)); - CHECK(!parse("-1", u_int8_, u8)); - CHECK(!parse("128", u_int8_, u8)); - CHECK(parse("127", u_int8_, u8)); - CHECK(parse("0", u_int8_, u8)); + CHECK(!parse("999", u_int8_, i8)); + CHECK(!parse("-1", u_int8_, i8)); + CHECK(!parse("128", u_int8_, i8)); + CHECK(parse("127", u_int8_, i8)); + CHECK(parse("0", u_int8_, i8)); } { x4::uint_parser u_int16_; - std::uint16_t u16 = 0; + std::int16_t i16 = 0; - CHECK(!parse("99999", u_int16_, u16)); - CHECK(!parse("-1", u_int16_, u16)); - CHECK(!parse("32768", u_int16_, u16)); - CHECK(parse("32767", u_int16_, u16)); - CHECK(parse("0", u_int16_, u16)); + CHECK(!parse("99999", u_int16_, i16)); + CHECK(!parse("-1", u_int16_, i16)); + CHECK(!parse("32768", u_int16_, i16)); + CHECK(parse("32767", u_int16_, i16)); + CHECK(parse("0", u_int16_, i16)); } { x4::uint_parser u_int32_; - std::uint32_t u32 = 0; + std::int32_t i32 = 0; - CHECK(!parse("9999999999", u_int32_, u32)); - CHECK(!parse("-1", u_int32_, u32)); - CHECK(!parse("2147483648", u_int32_, u32)); - CHECK(parse("2147483647", u_int32_, u32)); - CHECK(parse("0", u_int32_, u32)); + CHECK(!parse("9999999999", u_int32_, i32)); + CHECK(!parse("-1", u_int32_, i32)); + CHECK(!parse("2147483648", u_int32_, i32)); + CHECK(parse("2147483647", u_int32_, i32)); + CHECK(parse("0", u_int32_, i32)); } // custom uint tests From ae16031f9eeffbc54fb791b9f053064eb4c1f0c0 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 01:17:49 +0900 Subject: [PATCH 38/52] Refine static_assert of `partition_attribute` --- .../iris/x4/core/detail/parse_sequence.hpp | 20 +++++++++---------- test/x4/sequence.cpp | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index e654d1543..95f359921 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,13 @@ struct sequence; namespace iris::x4::detail { +// Note: `ExpectedAttr` parameter exists for a better debugging experience +template< + class LParser, class RParser, traits::CategorizedAttr ActualAttr, + class ExpectedAttr = typename traits::detail::attribute_of_sequence::type +> +inline constexpr bool has_same_sequence_size_v = parser_traits::sequence_size + parser_traits::sequence_size == alloy::tuple_size_v; + template struct partition_attribute {}; @@ -49,20 +57,10 @@ struct partition_attribute static constexpr std::size_t l_size = parser_traits::sequence_size; static constexpr std::size_t r_size = parser_traits::sequence_size; - static constexpr std::size_t actual_size = alloy::tuple_size_v; - static constexpr std::size_t expected_size = l_size + r_size; - // If you got an error here, then you are trying to pass // a tuple-like with the wrong number of elements // as that expected by the (sequence) parser. - static_assert( - actual_size >= expected_size, - "Sequence size of the passed attribute is less than expected." - ); - static_assert( - actual_size <= expected_size, - "Sequence size of the passed attribute is greater than expected." - ); + static_assert(has_same_sequence_size_v, "sequence size mismatch"); using view = alloy::tuple_ref_t; using split = alloy::tuple_split_t; diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index 6d3c88a17..6075cf60d 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -538,3 +538,23 @@ TEST_CASE("sequence") CHECK(v.size() == 4); } } + +// commented-out test case that verifies whether static_assert will correctly fire +// TEST_CASE("wrong_sequence_size") +// { +// using x4::char_; +// using x4::int_; + +// // actual < expected +// { +// constexpr auto parser = char_ >> int_; +// std::tuple attr; +// REQUIRE(!parse("a123", parser, attr)); +// } +// // actual > expected +// { +// constexpr auto parser = char_ >> int_; +// std::tuple attr; +// REQUIRE(!parse("a123", parser, attr)); +// } +// } From 34261dbe4040526e0d97c2f6e88ed99e3e299fab Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 01:31:24 +0900 Subject: [PATCH 39/52] Minor styling fix --- test/x4/sequence.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index 6075cf60d..96c01d003 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -544,7 +544,6 @@ TEST_CASE("sequence") // { // using x4::char_; // using x4::int_; - // // actual < expected // { // constexpr auto parser = char_ >> int_; From ab7b0ef5c4300fb8de061912f88123c37282f3a7 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 01:52:01 +0900 Subject: [PATCH 40/52] Use more accurate type to detect narrowing --- include/iris/x4/core/move_to.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index def97e37d..a5c0d3677 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -123,9 +123,8 @@ move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - using element_type = std::remove_reference_t>>; static_assert( - detail::is_assignable_without_narrowing::value, + detail::is_assignable_without_narrowing(alloy::get<0>(std::forward(src))))>::value, "Narrowing conversion detected in move_to (single-element tuple-like to plain)" ); dest = std::forward_like(alloy::get<0>(std::forward(src))); @@ -139,7 +138,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - detail::is_assignable_without_narrowing>::value, + detail::is_assignable_without_narrowing::value, "Narrowing conversion detected in move_to (source to plain)" ); dest = std::forward(src); @@ -203,7 +202,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); static_assert( - detail::is_assignable_without_narrowing>::value, + detail::is_assignable_without_narrowing::value, "Narrowing conversion detected in move_to (source to optional)" ); dest = std::forward(src); From 7567791f33f4f443f0cfe95255d405a1592fd53e Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 01:52:09 +0900 Subject: [PATCH 41/52] Add narrowing check test --- test/x4/CMakeLists.txt | 1 + test/x4/narrowing.cpp | 275 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 test/x4/narrowing.cpp diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 325d19007..33fd80d25 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -67,6 +67,7 @@ x4_define_tests( lit matches move_to + narrowing not_predicate no_case no_skip diff --git a/test/x4/narrowing.cpp b/test/x4/narrowing.cpp new file mode 100644 index 000000000..060107eba --- /dev/null +++ b/test/x4/narrowing.cpp @@ -0,0 +1,275 @@ +#include "iris_x4_test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +template +using SES = x4_test::single_element_struct; + + +// =================================================================== +// Rule definitions for narrowing tests +// =================================================================== + +using IntRule = x4::rule; +using LongLongRule = x4::rule; +using ShortRule = x4::rule; +using FloatRule = x4::rule; +using DoubleRule = x4::rule; + +IRIS_X4_DECLARE_CONSTEXPR(IntRule) +IRIS_X4_DECLARE_CONSTEXPR(LongLongRule) +IRIS_X4_DECLARE_CONSTEXPR(ShortRule) +IRIS_X4_DECLARE_CONSTEXPR(FloatRule) +IRIS_X4_DECLARE_CONSTEXPR(DoubleRule) + +constexpr IntRule int_rule; +constexpr LongLongRule long_long_rule; +constexpr ShortRule short_rule; +constexpr FloatRule float_rule; +constexpr DoubleRule double_rule; + +constexpr auto int_rule_def = x4::int_; +constexpr auto long_long_rule_def = x4::long_long; +constexpr auto short_rule_def = x4::short_; +constexpr auto float_rule_def = x4::float_; +constexpr auto double_rule_def = x4::double_; + +IRIS_X4_DEFINE_CONSTEXPR(int_rule) +IRIS_X4_DEFINE_CONSTEXPR(long_long_rule) +IRIS_X4_DEFINE_CONSTEXPR(short_rule) +IRIS_X4_DEFINE_CONSTEXPR(float_rule) +IRIS_X4_DEFINE_CONSTEXPR(double_rule) + + +// =================================================================== +// Trait-level static checks +// =================================================================== + +TEST_CASE("narrowing: is_assignable_without_narrowing trait") +{ + using x4::detail::is_assignable_without_narrowing; + + // Non-narrowing: same type + STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_narrowing::value); + + // Non-narrowing: widening + STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_narrowing::value); + + // Narrowing: lossy conversions + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + + // Signed/unsigned mismatch + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + + // Non-arithmetic dest: narrowing not checked + STATIC_CHECK(is_assignable_without_narrowing::value); +} + +TEST_CASE("narrowing: is_tuple_assignable_without_narrowing trait") +{ + using x4::detail::is_tuple_assignable_without_narrowing; + + STATIC_CHECK((is_tuple_assignable_without_narrowing< + std::tuple, + std::tuple + >::value)); + + STATIC_CHECK((!is_tuple_assignable_without_narrowing< + std::tuple, + std::tuple + >::value)); + + STATIC_CHECK((!is_tuple_assignable_without_narrowing< + std::tuple, + std::tuple + >::value)); +} + + +// =================================================================== +// Parse-level tests (non-narrowing conversions that must compile & run) +// =================================================================== + +TEST_CASE("narrowing: parse int to long long (widening)") +{ + long long a{}; + REQUIRE(parse("42", x4::int_, a)); + CHECK(a == 42); +} + +TEST_CASE("narrowing: parse short to int (widening)") +{ + int a{}; + REQUIRE(parse("7", x4::short_, a)); + CHECK(a == 7); +} + +TEST_CASE("narrowing: parse float to double (widening)") +{ + double a{}; + REQUIRE(parse("3.14", x4::float_, a)); + CHECK(a > 3.13); + CHECK(a < 3.15); +} + +TEST_CASE("narrowing: parse int to long long via SES (single-element tuple-like)") +{ + SES a{}; + REQUIRE(parse("99", x4::int_, a)); + CHECK(a.value == 99); +} + +TEST_CASE("narrowing: parse int to optional (widening)") +{ + std::optional a; + REQUIRE(parse("55", x4::int_, a)); + REQUIRE(a.has_value()); + CHECK(*a == 55); +} + +TEST_CASE("narrowing: parse tuple element-wise (all widening)") +{ + std::tuple a; + REQUIRE(parse("1,3.0", x4::int_ >> ',' >> x4::float_, a)); + CHECK(std::get<0>(a) == 1); + CHECK(std::get<1>(a) > 2.99); +} + +TEST_CASE("narrowing: parse same type (int to int)") +{ + int a{}; + REQUIRE(parse("123", x4::int_, a)); + CHECK(a == 123); +} + +TEST_CASE("narrowing: parse same type (char to char)") +{ + char a{}; + REQUIRE(parse("x", x4::char_, a)); + CHECK(a == 'x'); +} + + +// =================================================================== +// Rule-level tests +// +// When the exposed attribute type differs from the rule's attribute +// type, the rule materializes its own attribute internally and then +// assigns it to the exposed attribute. The narrowing check applies +// to that assignment. +// =================================================================== + +TEST_CASE("narrowing: RuleAttrConvertibleWithoutNarrowing concept") +{ + using x4::detail::RuleAttrConvertibleWithoutNarrowing; + + // Same type: always OK + STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); + + // Widening: OK + STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); + + // Narrowing: rejected + STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); + + // Signed/unsigned mismatch + STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); + STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); +} + +TEST_CASE("narrowing: rule with same exposed type (int rule to int)") +{ + int a{}; + REQUIRE(parse("42", int_rule, a)); + CHECK(a == 42); +} + +TEST_CASE("narrowing: rule widening (int rule to long long)") +{ + long long a{}; + REQUIRE(parse("42", int_rule, a)); + CHECK(a == 42); +} + +TEST_CASE("narrowing: rule widening (short rule to int)") +{ + int a{}; + REQUIRE(parse("7", short_rule, a)); + CHECK(a == 7); +} + +TEST_CASE("narrowing: rule widening (float rule to double)") +{ + double a{}; + REQUIRE(parse("3.14", float_rule, a)); + CHECK(a > 3.13); + CHECK(a < 3.15); +} + +TEST_CASE("narrowing: immediate rule widening (int to long long)") +{ + long long a{}; + REQUIRE(parse("42", x4::rule{} = x4::int_, a)); + CHECK(a == 42); +} + + +// =================================================================== +// Narrowing conversion static_assert tests +// +// The following test cases are commented out because they are expected +// to produce static_assert failures. Uncomment any single case to +// verify the assertion fires. +// =================================================================== + +// --- move_to: source to plain (narrowing) --- +// TEST_CASE("narrowing: int to char") { char a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("narrowing: int to short") { short a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("narrowing: long long to int") { int a{}; REQUIRE(parse("42", x4::long_long, a)); } +// TEST_CASE("narrowing: double to float") { float a{}; REQUIRE(parse("3.14", x4::double_, a)); } + +// --- move_to: single-element tuple-like to plain (narrowing) --- +// TEST_CASE("narrowing: SES int to char") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: source to optional (narrowing) --- +// TEST_CASE("narrowing: int to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple element-wise (narrowing) --- +// TEST_CASE("narrowing: tuple char,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("narrowing: tuple int,char") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } + +// --- rule: rule attribute to exposed attribute (narrowing) --- +// TEST_CASE("narrowing: int rule to char") { char a{}; REQUIRE(parse("42", int_rule, a)); } +// TEST_CASE("narrowing: int rule to short") { short a{}; REQUIRE(parse("42", int_rule, a)); } +// TEST_CASE("narrowing: long long rule to int") { int a{}; REQUIRE(parse("42", long_long_rule, a)); } +// TEST_CASE("narrowing: double rule to float") { float a{}; REQUIRE(parse("3.14", double_rule, a)); } +// TEST_CASE("narrowing: immediate rule narrowing") { char a{}; REQUIRE(parse("42", x4::rule{} = x4::int_, a)); } From 2480d49bd21683ad73a0787effcbc506e4b9af3d Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 01:54:23 +0900 Subject: [PATCH 42/52] Change `can_hold` primary definition to `is_assignable` --- include/iris/x4/traits/can_hold.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index bf27e3ea2..163103069 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -48,7 +48,7 @@ struct value_type_can_hold // sites (e.g. move_to overloads) static_assert against narrowing, so the user gets // a clear error message at the point of use. template -struct can_hold_impl : std::is_convertible {}; +struct can_hold_impl : std::is_assignable {}; template requires From 483439d460256529bc3e87cf77b3039f2b543066 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 01:58:09 +0900 Subject: [PATCH 43/52] Update iris --- modules/iris | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iris b/modules/iris index 37bf9534b..6443dfadd 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 37bf9534bfb22625885170abc5c7b75393471376 +Subproject commit 6443dfadd32d1dc275eef2543e48ba9939e79dcf From 00c88bc97efb001d4c71d0c2447bc57e3a6e3de8 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 02:20:03 +0900 Subject: [PATCH 44/52] Update iris --- modules/iris | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iris b/modules/iris index 6443dfadd..65e4638af 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 6443dfadd32d1dc275eef2543e48ba9939e79dcf +Subproject commit 65e4638af84cab7b92f774399be20229c379c5d4 From 80ce6f0ddd0ddd35648ee81b6a8b1eeb4e389cbb Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 03:25:16 +0900 Subject: [PATCH 45/52] Move `is_assignable_without_narrowing` into submodule --- include/iris/x4/core/move_to.hpp | 6 +++--- include/iris/x4/rule.hpp | 4 ++-- include/iris/x4/traits/narrowing.hpp | 16 +--------------- modules/iris | 2 +- test/x4/narrowing.cpp | 28 ---------------------------- 5 files changed, 7 insertions(+), 49 deletions(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index a5c0d3677..f5c7396f7 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -124,7 +124,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - detail::is_assignable_without_narrowing(alloy::get<0>(std::forward(src))))>::value, + iris::is_assignable_without_narrowing(alloy::get<0>(std::forward(src))))>::value, "Narrowing conversion detected in move_to (single-element tuple-like to plain)" ); dest = std::forward_like(alloy::get<0>(std::forward(src))); @@ -138,7 +138,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - detail::is_assignable_without_narrowing::value, + iris::is_assignable_without_narrowing::value, "Narrowing conversion detected in move_to (source to plain)" ); dest = std::forward(src); @@ -202,7 +202,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); static_assert( - detail::is_assignable_without_narrowing::value, + iris::is_assignable_without_narrowing::value, "Narrowing conversion detected in move_to (source to optional)" ); dest = std::forward(src); diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 9867c482f..15d03c9c9 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -364,7 +364,7 @@ concept RuleAttrConvertible = template concept RuleAttrConvertibleWithoutNarrowing = RuleAttrConvertible && - is_assignable_without_narrowing< + iris::is_assignable_without_narrowing< unwrap_container_appender_t>&, RuleAttr >::value; @@ -461,7 +461,7 @@ struct rule : parser> ); } else { static_assert( - detail::is_assignable_without_narrowing::value, + iris::is_assignable_without_narrowing::value, "Narrowing conversion detected in rule (rule attribute to exposed attribute)" ); exposed_attr = std::move(rule_attr); diff --git a/include/iris/x4/traits/narrowing.hpp b/include/iris/x4/traits/narrowing.hpp index eb02795c2..5a764f768 100644 --- a/include/iris/x4/traits/narrowing.hpp +++ b/include/iris/x4/traits/narrowing.hpp @@ -18,27 +18,13 @@ namespace iris::x4::detail { -// is_assignable_without_narrowing -// -// True when `Dest = Source` is valid AND does not involve a narrowing conversion. -// For non-arithmetic Dest types, narrowing is not checked — we trust that the -// user-defined operator= handles the conversion correctly. -template -struct is_assignable_without_narrowing - : std::bool_constant< - std::is_assignable_v && - (!std::is_arithmetic_v> || - iris::is_convertible_without_narrowing_v, std::remove_reference_t>) - > -{}; - template>> struct is_tuple_assignable_without_narrowing; template struct is_tuple_assignable_without_narrowing> : std::conjunction< - is_assignable_without_narrowing< + iris::is_assignable_without_narrowing< alloy::tuple_element_t&, decltype(alloy::get(std::declval())) >... diff --git a/modules/iris b/modules/iris index 65e4638af..1a0b140a8 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 65e4638af84cab7b92f774399be20229c379c5d4 +Subproject commit 1a0b140a878a5002f4a2520eb5834b97c11a4f3f diff --git a/test/x4/narrowing.cpp b/test/x4/narrowing.cpp index 060107eba..7832222bc 100644 --- a/test/x4/narrowing.cpp +++ b/test/x4/narrowing.cpp @@ -60,34 +60,6 @@ IRIS_X4_DEFINE_CONSTEXPR(double_rule) // Trait-level static checks // =================================================================== -TEST_CASE("narrowing: is_assignable_without_narrowing trait") -{ - using x4::detail::is_assignable_without_narrowing; - - // Non-narrowing: same type - STATIC_CHECK(is_assignable_without_narrowing::value); - STATIC_CHECK(is_assignable_without_narrowing::value); - - // Non-narrowing: widening - STATIC_CHECK(is_assignable_without_narrowing::value); - STATIC_CHECK(is_assignable_without_narrowing::value); - STATIC_CHECK(is_assignable_without_narrowing::value); - - // Narrowing: lossy conversions - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - - // Signed/unsigned mismatch - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - - // Non-arithmetic dest: narrowing not checked - STATIC_CHECK(is_assignable_without_narrowing::value); -} - TEST_CASE("narrowing: is_tuple_assignable_without_narrowing trait") { using x4::detail::is_tuple_assignable_without_narrowing; From a3dcecc657811477800b51c136fc27da790d1856 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 05:13:29 +0900 Subject: [PATCH 46/52] Revert "Move `is_assignable_without_narrowing` into submodule" This reverts commit 80ce6f0ddd0ddd35648ee81b6a8b1eeb4e389cbb. --- include/iris/x4/core/move_to.hpp | 6 +++--- include/iris/x4/rule.hpp | 4 ++-- include/iris/x4/traits/narrowing.hpp | 16 +++++++++++++++- modules/iris | 2 +- test/x4/narrowing.cpp | 28 ++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index f5c7396f7..a5c0d3677 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -124,7 +124,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - iris::is_assignable_without_narrowing(alloy::get<0>(std::forward(src))))>::value, + detail::is_assignable_without_narrowing(alloy::get<0>(std::forward(src))))>::value, "Narrowing conversion detected in move_to (single-element tuple-like to plain)" ); dest = std::forward_like(alloy::get<0>(std::forward(src))); @@ -138,7 +138,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - iris::is_assignable_without_narrowing::value, + detail::is_assignable_without_narrowing::value, "Narrowing conversion detected in move_to (source to plain)" ); dest = std::forward(src); @@ -202,7 +202,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); static_assert( - iris::is_assignable_without_narrowing::value, + detail::is_assignable_without_narrowing::value, "Narrowing conversion detected in move_to (source to optional)" ); dest = std::forward(src); diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 15d03c9c9..9867c482f 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -364,7 +364,7 @@ concept RuleAttrConvertible = template concept RuleAttrConvertibleWithoutNarrowing = RuleAttrConvertible && - iris::is_assignable_without_narrowing< + is_assignable_without_narrowing< unwrap_container_appender_t>&, RuleAttr >::value; @@ -461,7 +461,7 @@ struct rule : parser> ); } else { static_assert( - iris::is_assignable_without_narrowing::value, + detail::is_assignable_without_narrowing::value, "Narrowing conversion detected in rule (rule attribute to exposed attribute)" ); exposed_attr = std::move(rule_attr); diff --git a/include/iris/x4/traits/narrowing.hpp b/include/iris/x4/traits/narrowing.hpp index 5a764f768..eb02795c2 100644 --- a/include/iris/x4/traits/narrowing.hpp +++ b/include/iris/x4/traits/narrowing.hpp @@ -18,13 +18,27 @@ namespace iris::x4::detail { +// is_assignable_without_narrowing +// +// True when `Dest = Source` is valid AND does not involve a narrowing conversion. +// For non-arithmetic Dest types, narrowing is not checked — we trust that the +// user-defined operator= handles the conversion correctly. +template +struct is_assignable_without_narrowing + : std::bool_constant< + std::is_assignable_v && + (!std::is_arithmetic_v> || + iris::is_convertible_without_narrowing_v, std::remove_reference_t>) + > +{}; + template>> struct is_tuple_assignable_without_narrowing; template struct is_tuple_assignable_without_narrowing> : std::conjunction< - iris::is_assignable_without_narrowing< + is_assignable_without_narrowing< alloy::tuple_element_t&, decltype(alloy::get(std::declval())) >... diff --git a/modules/iris b/modules/iris index 1a0b140a8..65e4638af 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 1a0b140a878a5002f4a2520eb5834b97c11a4f3f +Subproject commit 65e4638af84cab7b92f774399be20229c379c5d4 diff --git a/test/x4/narrowing.cpp b/test/x4/narrowing.cpp index 7832222bc..060107eba 100644 --- a/test/x4/narrowing.cpp +++ b/test/x4/narrowing.cpp @@ -60,6 +60,34 @@ IRIS_X4_DEFINE_CONSTEXPR(double_rule) // Trait-level static checks // =================================================================== +TEST_CASE("narrowing: is_assignable_without_narrowing trait") +{ + using x4::detail::is_assignable_without_narrowing; + + // Non-narrowing: same type + STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_narrowing::value); + + // Non-narrowing: widening + STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_narrowing::value); + + // Narrowing: lossy conversions + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + + // Signed/unsigned mismatch + STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_narrowing::value); + + // Non-arithmetic dest: narrowing not checked + STATIC_CHECK(is_assignable_without_narrowing::value); +} + TEST_CASE("narrowing: is_tuple_assignable_without_narrowing trait") { using x4::detail::is_tuple_assignable_without_narrowing; From 4b5c3850c97ff7cdf62481fa2e95af524c4ad341 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 05:13:48 +0900 Subject: [PATCH 47/52] Update iris --- modules/iris | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iris b/modules/iris index 65e4638af..8a4f0528f 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 65e4638af84cab7b92f774399be20229c379c5d4 +Subproject commit 8a4f0528f463497fd7fb2a75f11e9ebe63ac3db2 From 6fc643f886900b619a92829d5ac785cdfbeea546 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 05:28:37 +0900 Subject: [PATCH 48/52] Rename --- include/iris/x4/core/move_to.hpp | 8 +++--- include/iris/x4/rule.hpp | 4 +-- include/iris/x4/traits/narrowing.hpp | 17 ++++++------ test/x4/narrowing.cpp | 40 ++++++++++++++-------------- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index a5c0d3677..6ba87933a 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -124,7 +124,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - detail::is_assignable_without_narrowing(alloy::get<0>(std::forward(src))))>::value, + detail::is_assignable_without_lossy_conversion(alloy::get<0>(std::forward(src))))>::value, "Narrowing conversion detected in move_to (single-element tuple-like to plain)" ); dest = std::forward_like(alloy::get<0>(std::forward(src))); @@ -138,7 +138,7 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - detail::is_assignable_without_narrowing::value, + detail::is_assignable_without_lossy_conversion::value, "Narrowing conversion detected in move_to (source to plain)" ); dest = std::forward(src); @@ -155,7 +155,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( - detail::is_tuple_assignable_without_narrowing>::value, + detail::is_tuple_assignable_without_lossy_conversion>::value, "Narrowing conversion detected in move_to (tuple element-wise assignment)" ); @@ -202,7 +202,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); static_assert( - detail::is_assignable_without_narrowing::value, + detail::is_assignable_without_lossy_conversion::value, "Narrowing conversion detected in move_to (source to optional)" ); dest = std::forward(src); diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 9867c482f..6f2c0b040 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -364,7 +364,7 @@ concept RuleAttrConvertible = template concept RuleAttrConvertibleWithoutNarrowing = RuleAttrConvertible && - is_assignable_without_narrowing< + is_assignable_without_lossy_conversion< unwrap_container_appender_t>&, RuleAttr >::value; @@ -461,7 +461,7 @@ struct rule : parser> ); } else { static_assert( - detail::is_assignable_without_narrowing::value, + detail::is_assignable_without_lossy_conversion::value, "Narrowing conversion detected in rule (rule attribute to exposed attribute)" ); exposed_attr = std::move(rule_attr); diff --git a/include/iris/x4/traits/narrowing.hpp b/include/iris/x4/traits/narrowing.hpp index eb02795c2..a404912f0 100644 --- a/include/iris/x4/traits/narrowing.hpp +++ b/include/iris/x4/traits/narrowing.hpp @@ -18,13 +18,14 @@ namespace iris::x4::detail { -// is_assignable_without_narrowing +// is_assignable_without_lossy_conversion // -// True when `Dest = Source` is valid AND does not involve a narrowing conversion. -// For non-arithmetic Dest types, narrowing is not checked — we trust that the -// user-defined operator= handles the conversion correctly. +// True when `Dest = Source` is valid AND does not involve a lossy conversion. +// Currently this only rejects arithmetic narrowing ([dcl.init.list]), but +// the intent is broader: any conversion that silently loses information +// should eventually be caught here. template -struct is_assignable_without_narrowing +struct is_assignable_without_lossy_conversion : std::bool_constant< std::is_assignable_v && (!std::is_arithmetic_v> || @@ -33,12 +34,12 @@ struct is_assignable_without_narrowing {}; template>> -struct is_tuple_assignable_without_narrowing; +struct is_tuple_assignable_without_lossy_conversion; template -struct is_tuple_assignable_without_narrowing> +struct is_tuple_assignable_without_lossy_conversion> : std::conjunction< - is_assignable_without_narrowing< + is_assignable_without_lossy_conversion< alloy::tuple_element_t&, decltype(alloy::get(std::declval())) >... diff --git a/test/x4/narrowing.cpp b/test/x4/narrowing.cpp index 060107eba..8ea5df42a 100644 --- a/test/x4/narrowing.cpp +++ b/test/x4/narrowing.cpp @@ -60,49 +60,49 @@ IRIS_X4_DEFINE_CONSTEXPR(double_rule) // Trait-level static checks // =================================================================== -TEST_CASE("narrowing: is_assignable_without_narrowing trait") +TEST_CASE("narrowing: is_assignable_without_lossy_conversion trait") { - using x4::detail::is_assignable_without_narrowing; + using x4::detail::is_assignable_without_lossy_conversion; // Non-narrowing: same type - STATIC_CHECK(is_assignable_without_narrowing::value); - STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); // Non-narrowing: widening - STATIC_CHECK(is_assignable_without_narrowing::value); - STATIC_CHECK(is_assignable_without_narrowing::value); - STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); // Narrowing: lossy conversions - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); // Signed/unsigned mismatch - STATIC_CHECK(!is_assignable_without_narrowing::value); - STATIC_CHECK(!is_assignable_without_narrowing::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); // Non-arithmetic dest: narrowing not checked - STATIC_CHECK(is_assignable_without_narrowing::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); } -TEST_CASE("narrowing: is_tuple_assignable_without_narrowing trait") +TEST_CASE("narrowing: is_tuple_assignable_without_lossy_conversion trait") { - using x4::detail::is_tuple_assignable_without_narrowing; + using x4::detail::is_tuple_assignable_without_lossy_conversion; - STATIC_CHECK((is_tuple_assignable_without_narrowing< + STATIC_CHECK((is_tuple_assignable_without_lossy_conversion< std::tuple, std::tuple >::value)); - STATIC_CHECK((!is_tuple_assignable_without_narrowing< + STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< std::tuple, std::tuple >::value)); - STATIC_CHECK((!is_tuple_assignable_without_narrowing< + STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< std::tuple, std::tuple >::value)); From 511efa61531394b4022f3cc8586c52fa092d43b6 Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 09:16:43 +0900 Subject: [PATCH 49/52] Introduce `is_lossy_assignment` customization point and rename narrowing to lossy --- include/iris/x4/core/move_to.hpp | 18 +- include/iris/x4/rule.hpp | 20 +- include/iris/x4/traits/can_hold.hpp | 6 +- .../{narrowing.hpp => lossy_conversion.hpp} | 32 +- modules/iris | 2 +- test/x4/CMakeLists.txt | 2 +- test/x4/lossy_conversion.cpp | 331 ++++++++++++++++++ test/x4/narrowing.cpp | 275 --------------- 8 files changed, 383 insertions(+), 303 deletions(-) rename include/iris/x4/traits/{narrowing.hpp => lossy_conversion.hpp} (59%) create mode 100644 test/x4/lossy_conversion.cpp delete mode 100644 test/x4/narrowing.cpp diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 6ba87933a..2f93ceb3c 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -125,7 +125,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( detail::is_assignable_without_lossy_conversion(alloy::get<0>(std::forward(src))))>::value, - "Narrowing conversion detected in move_to (single-element tuple-like to plain)" + "Lossy conversion detected in move_to (single-element tuple-like to plain)" ); dest = std::forward_like(alloy::get<0>(std::forward(src))); } @@ -139,7 +139,7 @@ move_to(Source&& src, Dest& dest) static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert( detail::is_assignable_without_lossy_conversion::value, - "Narrowing conversion detected in move_to (source to plain)" + "Lossy conversion detected in move_to (source to plain)" ); dest = std::forward(src); } @@ -156,7 +156,7 @@ move_to(Source&& src, Dest& dest) static_assert( detail::is_tuple_assignable_without_lossy_conversion>::value, - "Narrowing conversion detected in move_to (tuple element-wise assignment)" + "Lossy conversion detected in move_to (tuple element-wise assignment)" ); alloy::tuple_assign(std::forward(src), dest); @@ -203,7 +203,7 @@ move_to(Source&& src, Dest& dest) static_assert(std::is_assignable_v); static_assert( detail::is_assignable_without_lossy_conversion::value, - "Narrowing conversion detected in move_to (source to optional)" + "Lossy conversion detected in move_to (source to optional)" ); dest = std::forward(src); } @@ -221,6 +221,14 @@ move_to(It first, Se last, Dest& dest) static_assert(!std::same_as, unused_type>); static_assert(!std::same_as, unused_container_type>); + static_assert( + detail::is_assignable_without_lossy_conversion< + typename traits::container_value::type&, + std::iter_reference_t + >::value, + "Lossy conversion detected in move_to (container element-wise)" + ); + if constexpr (!is_ttp_specialization_of_v) { if (!traits::is_empty(dest)) { traits::clear(dest); diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 6f2c0b040..dc0661754 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -357,13 +357,13 @@ struct rule_definition : parser -concept RuleAttrConvertible = +concept RuleAttrAssignable = X4Attribute && std::is_assignable_v>&, RuleAttr>; template -concept RuleAttrConvertibleWithoutNarrowing = - RuleAttrConvertible && +concept RuleAttrAssignableWithoutLoss = + RuleAttrAssignable && is_assignable_without_lossy_conversion< unwrap_container_appender_t>&, RuleAttr @@ -377,8 +377,8 @@ concept RuleAttrTransformable = X4Attribute> && X4Attribute && std::default_initializable && - RuleAttrConvertible && - RuleAttrConvertibleWithoutNarrowing< + RuleAttrAssignable && + RuleAttrAssignableWithoutLoss< unwrap_container_appender_t>, RuleAttr >; @@ -462,7 +462,7 @@ struct rule : parser> } else { static_assert( detail::is_assignable_without_lossy_conversion::value, - "Narrowing conversion detected in rule (rule attribute to exposed attribute)" + "Lossy conversion detected in rule (rule attribute to exposed attribute)" ); exposed_attr = std::move(rule_attr); } @@ -474,10 +474,10 @@ struct rule : parser> requires (!std::same_as, unused_type>) && (!detail::RuleAttrCompatible) && - detail::RuleAttrConvertible && - (!detail::RuleAttrConvertibleWithoutNarrowing) + detail::RuleAttrAssignable && + (!detail::RuleAttrAssignableWithoutLoss) [[nodiscard]] constexpr bool - parse(It&, Se const&, Context const&, Exposed&) const = delete; // Rule attribute needs narrowing conversion + parse(It&, Se const&, Context const&, Exposed&) const = delete; // Rule attribute needs lossy conversion template Se, class Context> diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index 163103069..e5ca76649 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -44,9 +44,9 @@ struct value_type_can_hold {}; // This "implementation" exists for short-circuiting `can_hold` for certain trivial combinations. -// Note: narrowing conversions are NOT rejected here. Instead, the actual conversion -// sites (e.g. move_to overloads) static_assert against narrowing, so the user gets -// a clear error message at the point of use. +// Note: lossy conversions are NOT rejected here. Instead, the actual conversion +// sites (e.g. move_to overloads) static_assert against lossy conversions, so the +// user gets a clear error message at the point of use. template struct can_hold_impl : std::is_assignable {}; diff --git a/include/iris/x4/traits/narrowing.hpp b/include/iris/x4/traits/lossy_conversion.hpp similarity index 59% rename from include/iris/x4/traits/narrowing.hpp rename to include/iris/x4/traits/lossy_conversion.hpp index a404912f0..b03e1bf31 100644 --- a/include/iris/x4/traits/narrowing.hpp +++ b/include/iris/x4/traits/lossy_conversion.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_TRAITS_NARROWING_HPP -#define IRIS_ZZ_X4_TRAITS_NARROWING_HPP +#ifndef IRIS_ZZ_X4_TRAITS_LOSSY_CONVERSION_HPP +#define IRIS_ZZ_X4_TRAITS_LOSSY_CONVERSION_HPP /*============================================================================= Copyright (c) 2026 The Iris Project Contributors @@ -18,18 +18,34 @@ namespace iris::x4::detail { +// is_lossy_assignment +// +// Customization point: determines whether `Dest = Source` is a lossy assignment. +// The default implementation detects arithmetic narrowing conversions ([dcl.init.list]). +// Users can specialize this trait for their own types (e.g. big integer libraries) +// to flag additional lossy assignments. +// +// Dest and Source are unqualified types (no references, no cv). +template +struct is_lossy_assignment : std::false_type {}; + +template + requires std::is_arithmetic_v +struct is_lossy_assignment + : std::bool_constant< + !iris::is_convertible_without_narrowing_v + > +{}; + // is_assignable_without_lossy_conversion // -// True when `Dest = Source` is valid AND does not involve a lossy conversion. -// Currently this only rejects arithmetic narrowing ([dcl.init.list]), but -// the intent is broader: any conversion that silently loses information -// should eventually be caught here. +// True when `Dest = Source` is valid AND does not involve a lossy conversion +// as determined by `is_lossy_assignment`. template struct is_assignable_without_lossy_conversion : std::bool_constant< std::is_assignable_v && - (!std::is_arithmetic_v> || - iris::is_convertible_without_narrowing_v, std::remove_reference_t>) + !is_lossy_assignment, std::remove_cvref_t>::value > {}; diff --git a/modules/iris b/modules/iris index 8a4f0528f..daab5d19c 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 8a4f0528f463497fd7fb2a75f11e9ebe63ac3db2 +Subproject commit daab5d19ccf743e35093df3f1930f5d8ab937593 diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 33fd80d25..7cc80e7dc 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -67,7 +67,7 @@ x4_define_tests( lit matches move_to - narrowing + lossy_conversion not_predicate no_case no_skip diff --git a/test/x4/lossy_conversion.cpp b/test/x4/lossy_conversion.cpp new file mode 100644 index 000000000..54dea7f0e --- /dev/null +++ b/test/x4/lossy_conversion.cpp @@ -0,0 +1,331 @@ +#include "iris_x4_test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +template +using SES = x4_test::single_element_struct; + + +// =================================================================== +// Mock "small int" type for is_lossy_assignment customization point test +// =================================================================== + +struct small_int { + std::int8_t value{}; + + small_int() = default; + explicit small_int(std::int8_t v) : value(v) {} + + small_int& operator=(int v) { value = static_cast(v); return *this; } // convenient but may narrow +}; + +// Specialization: assigning int to small_int is lossy because +// small_int can only hold 8-bit values. +template<> +struct iris::x4::detail::is_lossy_assignment : std::true_type {}; + +struct convertible_to_int { + operator int() const { return 42; } +}; + +struct convertible_to_long_long { + operator long long() const { return 42; } +}; + + +// =================================================================== +// Rule definitions for lossy conversion tests +// =================================================================== + +using IntRule = x4::rule; +using LongLongRule = x4::rule; +using ShortRule = x4::rule; +using FloatRule = x4::rule; +using DoubleRule = x4::rule; + +IRIS_X4_DECLARE_CONSTEXPR(IntRule) +IRIS_X4_DECLARE_CONSTEXPR(LongLongRule) +IRIS_X4_DECLARE_CONSTEXPR(ShortRule) +IRIS_X4_DECLARE_CONSTEXPR(FloatRule) +IRIS_X4_DECLARE_CONSTEXPR(DoubleRule) + +constexpr IntRule int_rule; +constexpr LongLongRule long_long_rule; +constexpr ShortRule short_rule; +constexpr FloatRule float_rule; +constexpr DoubleRule double_rule; + +constexpr auto int_rule_def = x4::int_; +constexpr auto long_long_rule_def = x4::long_long; +constexpr auto short_rule_def = x4::short_; +constexpr auto float_rule_def = x4::float_; +constexpr auto double_rule_def = x4::double_; + +IRIS_X4_DEFINE_CONSTEXPR(int_rule) +IRIS_X4_DEFINE_CONSTEXPR(long_long_rule) +IRIS_X4_DEFINE_CONSTEXPR(short_rule) +IRIS_X4_DEFINE_CONSTEXPR(float_rule) +IRIS_X4_DEFINE_CONSTEXPR(double_rule) + + +// =================================================================== +// Trait-level static checks +// =================================================================== + +TEST_CASE("lossy: is_assignable_without_lossy_conversion trait") +{ + using x4::detail::is_assignable_without_lossy_conversion; + + // Non-lossy: same type + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + + // Non-lossy: widening + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + + // Lossy conversions + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + + // Signed/unsigned mismatch + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + + // Non-arithmetic dest: lossy conversion not checked + STATIC_CHECK(is_assignable_without_lossy_conversion::value); +} + +TEST_CASE("lossy: is_tuple_assignable_without_lossy_conversion trait") +{ + using x4::detail::is_tuple_assignable_without_lossy_conversion; + + STATIC_CHECK((is_tuple_assignable_without_lossy_conversion< + std::tuple, + std::tuple + >::value)); + + STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< + std::tuple, + std::tuple + >::value)); + + STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< + std::tuple, + std::tuple + >::value)); +} + +TEST_CASE("lossy: is_lossy_assignment trait") +{ + using x4::detail::is_lossy_assignment; + + // Default: arithmetic narrowing + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + + // Non-arithmetic Dest: not checked by default + STATIC_CHECK(!is_lossy_assignment::value); + + // Non-arithmetic Source with conversion operator to arithmetic Dest + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + + // Customization point: small_int = int is flagged as lossy (user-specialized) + STATIC_CHECK(is_lossy_assignment::value); + + // small_int = int via is_assignable_without_lossy_conversion + // small_int::operator=(int) exists, but the specialization rejects it + STATIC_CHECK(!x4::detail::is_assignable_without_lossy_conversion::value); +} + + +// =================================================================== +// Parse-level tests (non-lossy conversions that must compile & run) +// =================================================================== + +TEST_CASE("lossy: parse int to long long (widening)") +{ + long long a{}; + REQUIRE(parse("42", x4::int_, a)); + CHECK(a == 42); +} + +TEST_CASE("lossy: parse short to int (widening)") +{ + int a{}; + REQUIRE(parse("7", x4::short_, a)); + CHECK(a == 7); +} + +TEST_CASE("lossy: parse float to double (widening)") +{ + double a{}; + REQUIRE(parse("3.14", x4::float_, a)); + CHECK(a > 3.13); + CHECK(a < 3.15); +} + +TEST_CASE("lossy: parse int to long long via SES (single-element tuple-like)") +{ + SES a{}; + REQUIRE(parse("99", x4::int_, a)); + CHECK(a.value == 99); +} + +TEST_CASE("lossy: parse int to optional (widening)") +{ + std::optional a; + REQUIRE(parse("55", x4::int_, a)); + REQUIRE(a.has_value()); + CHECK(*a == 55); +} + +TEST_CASE("lossy: parse tuple element-wise (all widening)") +{ + std::tuple a; + REQUIRE(parse("1,3.0", x4::int_ >> ',' >> x4::float_, a)); + CHECK(std::get<0>(a) == 1); + CHECK(std::get<1>(a) > 2.99); +} + +TEST_CASE("lossy: parse same type (int to int)") +{ + int a{}; + REQUIRE(parse("123", x4::int_, a)); + CHECK(a == 123); +} + +TEST_CASE("lossy: parse same type (char to char)") +{ + char a{}; + REQUIRE(parse("x", x4::char_, a)); + CHECK(a == 'x'); +} + + +// =================================================================== +// Rule-level tests +// +// When the exposed attribute type differs from the rule's attribute +// type, the rule materializes its own attribute internally and then +// assigns it to the exposed attribute. The lossy conversion check applies +// to that assignment. +// =================================================================== + +TEST_CASE("lossy: RuleAttrAssignableWithoutLoss concept") +{ + using x4::detail::RuleAttrAssignableWithoutLoss; + + // Same type: always OK + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + + // Widening: OK + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + + // Lossy: rejected + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + + // Signed/unsigned mismatch + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); +} + +TEST_CASE("lossy: rule with same exposed type (int rule to int)") +{ + int a{}; + REQUIRE(parse("42", int_rule, a)); + CHECK(a == 42); +} + +TEST_CASE("lossy: rule widening (int rule to long long)") +{ + long long a{}; + REQUIRE(parse("42", int_rule, a)); + CHECK(a == 42); +} + +TEST_CASE("lossy: rule widening (short rule to int)") +{ + int a{}; + REQUIRE(parse("7", short_rule, a)); + CHECK(a == 7); +} + +TEST_CASE("lossy: rule widening (float rule to double)") +{ + double a{}; + REQUIRE(parse("3.14", float_rule, a)); + CHECK(a > 3.13); + CHECK(a < 3.15); +} + +TEST_CASE("lossy: immediate rule widening (int to long long)") +{ + long long a{}; + REQUIRE(parse("42", x4::rule{} = x4::int_, a)); + CHECK(a == 42); +} + + +// =================================================================== +// Lossy conversion static_assert tests +// +// The following test cases are commented out because they are expected +// to produce static_assert failures. Uncomment any single case to +// verify the assertion fires. +// =================================================================== + +// --- move_to: source to plain (lossy) --- +// TEST_CASE("lossy: int to char") { char a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("lossy: int to short") { short a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("lossy: long long to int") { int a{}; REQUIRE(parse("42", x4::long_long, a)); } +// TEST_CASE("lossy: double to float") { float a{}; REQUIRE(parse("3.14", x4::double_, a)); } + +// --- move_to: single-element tuple-like to plain (lossy) --- +// TEST_CASE("lossy: SES int to char") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: source to optional (lossy) --- +// TEST_CASE("lossy: int to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple element-wise (lossy) --- +// TEST_CASE("lossy: tuple char,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("lossy: tuple int,char") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } + +// --- rule: rule attribute to exposed attribute (lossy) --- +// TEST_CASE("lossy: int rule to char") { char a{}; REQUIRE(parse("42", int_rule, a)); } +// TEST_CASE("lossy: int rule to short") { short a{}; REQUIRE(parse("42", int_rule, a)); } +// TEST_CASE("lossy: long long rule to int") { int a{}; REQUIRE(parse("42", long_long_rule, a)); } +// TEST_CASE("lossy: double rule to float") { float a{}; REQUIRE(parse("3.14", double_rule, a)); } +// TEST_CASE("lossy: immediate rule lossy") { char a{}; REQUIRE(parse("42", x4::rule{} = x4::int_, a)); } diff --git a/test/x4/narrowing.cpp b/test/x4/narrowing.cpp deleted file mode 100644 index 8ea5df42a..000000000 --- a/test/x4/narrowing.cpp +++ /dev/null @@ -1,275 +0,0 @@ -#include "iris_x4_test.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -template -using SES = x4_test::single_element_struct; - - -// =================================================================== -// Rule definitions for narrowing tests -// =================================================================== - -using IntRule = x4::rule; -using LongLongRule = x4::rule; -using ShortRule = x4::rule; -using FloatRule = x4::rule; -using DoubleRule = x4::rule; - -IRIS_X4_DECLARE_CONSTEXPR(IntRule) -IRIS_X4_DECLARE_CONSTEXPR(LongLongRule) -IRIS_X4_DECLARE_CONSTEXPR(ShortRule) -IRIS_X4_DECLARE_CONSTEXPR(FloatRule) -IRIS_X4_DECLARE_CONSTEXPR(DoubleRule) - -constexpr IntRule int_rule; -constexpr LongLongRule long_long_rule; -constexpr ShortRule short_rule; -constexpr FloatRule float_rule; -constexpr DoubleRule double_rule; - -constexpr auto int_rule_def = x4::int_; -constexpr auto long_long_rule_def = x4::long_long; -constexpr auto short_rule_def = x4::short_; -constexpr auto float_rule_def = x4::float_; -constexpr auto double_rule_def = x4::double_; - -IRIS_X4_DEFINE_CONSTEXPR(int_rule) -IRIS_X4_DEFINE_CONSTEXPR(long_long_rule) -IRIS_X4_DEFINE_CONSTEXPR(short_rule) -IRIS_X4_DEFINE_CONSTEXPR(float_rule) -IRIS_X4_DEFINE_CONSTEXPR(double_rule) - - -// =================================================================== -// Trait-level static checks -// =================================================================== - -TEST_CASE("narrowing: is_assignable_without_lossy_conversion trait") -{ - using x4::detail::is_assignable_without_lossy_conversion; - - // Non-narrowing: same type - STATIC_CHECK(is_assignable_without_lossy_conversion::value); - STATIC_CHECK(is_assignable_without_lossy_conversion::value); - - // Non-narrowing: widening - STATIC_CHECK(is_assignable_without_lossy_conversion::value); - STATIC_CHECK(is_assignable_without_lossy_conversion::value); - STATIC_CHECK(is_assignable_without_lossy_conversion::value); - - // Narrowing: lossy conversions - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - - // Signed/unsigned mismatch - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - STATIC_CHECK(!is_assignable_without_lossy_conversion::value); - - // Non-arithmetic dest: narrowing not checked - STATIC_CHECK(is_assignable_without_lossy_conversion::value); -} - -TEST_CASE("narrowing: is_tuple_assignable_without_lossy_conversion trait") -{ - using x4::detail::is_tuple_assignable_without_lossy_conversion; - - STATIC_CHECK((is_tuple_assignable_without_lossy_conversion< - std::tuple, - std::tuple - >::value)); - - STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< - std::tuple, - std::tuple - >::value)); - - STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< - std::tuple, - std::tuple - >::value)); -} - - -// =================================================================== -// Parse-level tests (non-narrowing conversions that must compile & run) -// =================================================================== - -TEST_CASE("narrowing: parse int to long long (widening)") -{ - long long a{}; - REQUIRE(parse("42", x4::int_, a)); - CHECK(a == 42); -} - -TEST_CASE("narrowing: parse short to int (widening)") -{ - int a{}; - REQUIRE(parse("7", x4::short_, a)); - CHECK(a == 7); -} - -TEST_CASE("narrowing: parse float to double (widening)") -{ - double a{}; - REQUIRE(parse("3.14", x4::float_, a)); - CHECK(a > 3.13); - CHECK(a < 3.15); -} - -TEST_CASE("narrowing: parse int to long long via SES (single-element tuple-like)") -{ - SES a{}; - REQUIRE(parse("99", x4::int_, a)); - CHECK(a.value == 99); -} - -TEST_CASE("narrowing: parse int to optional (widening)") -{ - std::optional a; - REQUIRE(parse("55", x4::int_, a)); - REQUIRE(a.has_value()); - CHECK(*a == 55); -} - -TEST_CASE("narrowing: parse tuple element-wise (all widening)") -{ - std::tuple a; - REQUIRE(parse("1,3.0", x4::int_ >> ',' >> x4::float_, a)); - CHECK(std::get<0>(a) == 1); - CHECK(std::get<1>(a) > 2.99); -} - -TEST_CASE("narrowing: parse same type (int to int)") -{ - int a{}; - REQUIRE(parse("123", x4::int_, a)); - CHECK(a == 123); -} - -TEST_CASE("narrowing: parse same type (char to char)") -{ - char a{}; - REQUIRE(parse("x", x4::char_, a)); - CHECK(a == 'x'); -} - - -// =================================================================== -// Rule-level tests -// -// When the exposed attribute type differs from the rule's attribute -// type, the rule materializes its own attribute internally and then -// assigns it to the exposed attribute. The narrowing check applies -// to that assignment. -// =================================================================== - -TEST_CASE("narrowing: RuleAttrConvertibleWithoutNarrowing concept") -{ - using x4::detail::RuleAttrConvertibleWithoutNarrowing; - - // Same type: always OK - STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); - - // Widening: OK - STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(RuleAttrConvertibleWithoutNarrowing); - - // Narrowing: rejected - STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); - - // Signed/unsigned mismatch - STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); - STATIC_CHECK(!RuleAttrConvertibleWithoutNarrowing); -} - -TEST_CASE("narrowing: rule with same exposed type (int rule to int)") -{ - int a{}; - REQUIRE(parse("42", int_rule, a)); - CHECK(a == 42); -} - -TEST_CASE("narrowing: rule widening (int rule to long long)") -{ - long long a{}; - REQUIRE(parse("42", int_rule, a)); - CHECK(a == 42); -} - -TEST_CASE("narrowing: rule widening (short rule to int)") -{ - int a{}; - REQUIRE(parse("7", short_rule, a)); - CHECK(a == 7); -} - -TEST_CASE("narrowing: rule widening (float rule to double)") -{ - double a{}; - REQUIRE(parse("3.14", float_rule, a)); - CHECK(a > 3.13); - CHECK(a < 3.15); -} - -TEST_CASE("narrowing: immediate rule widening (int to long long)") -{ - long long a{}; - REQUIRE(parse("42", x4::rule{} = x4::int_, a)); - CHECK(a == 42); -} - - -// =================================================================== -// Narrowing conversion static_assert tests -// -// The following test cases are commented out because they are expected -// to produce static_assert failures. Uncomment any single case to -// verify the assertion fires. -// =================================================================== - -// --- move_to: source to plain (narrowing) --- -// TEST_CASE("narrowing: int to char") { char a{}; REQUIRE(parse("42", x4::int_, a)); } -// TEST_CASE("narrowing: int to short") { short a{}; REQUIRE(parse("42", x4::int_, a)); } -// TEST_CASE("narrowing: long long to int") { int a{}; REQUIRE(parse("42", x4::long_long, a)); } -// TEST_CASE("narrowing: double to float") { float a{}; REQUIRE(parse("3.14", x4::double_, a)); } - -// --- move_to: single-element tuple-like to plain (narrowing) --- -// TEST_CASE("narrowing: SES int to char") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } - -// --- move_to: source to optional (narrowing) --- -// TEST_CASE("narrowing: int to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } - -// --- move_to: tuple element-wise (narrowing) --- -// TEST_CASE("narrowing: tuple char,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } -// TEST_CASE("narrowing: tuple int,char") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } - -// --- rule: rule attribute to exposed attribute (narrowing) --- -// TEST_CASE("narrowing: int rule to char") { char a{}; REQUIRE(parse("42", int_rule, a)); } -// TEST_CASE("narrowing: int rule to short") { short a{}; REQUIRE(parse("42", int_rule, a)); } -// TEST_CASE("narrowing: long long rule to int") { int a{}; REQUIRE(parse("42", long_long_rule, a)); } -// TEST_CASE("narrowing: double rule to float") { float a{}; REQUIRE(parse("3.14", double_rule, a)); } -// TEST_CASE("narrowing: immediate rule narrowing") { char a{}; REQUIRE(parse("42", x4::rule{} = x4::int_, a)); } From 8d4c51db69acb7df59f8e9d3c0a527e8d8ad592e Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 10:04:10 +0900 Subject: [PATCH 50/52] Adjust comment styling --- test/x4/lossy_conversion.cpp | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/x4/lossy_conversion.cpp b/test/x4/lossy_conversion.cpp index 54dea7f0e..113f9f147 100644 --- a/test/x4/lossy_conversion.cpp +++ b/test/x4/lossy_conversion.cpp @@ -22,9 +22,9 @@ template using SES = x4_test::single_element_struct; -// =================================================================== +// ======================================================================= // Mock "small int" type for is_lossy_assignment customization point test -// =================================================================== +// ======================================================================= struct small_int { std::int8_t value{}; @@ -49,9 +49,9 @@ struct convertible_to_long_long { }; -// =================================================================== +// ======================================================================= // Rule definitions for lossy conversion tests -// =================================================================== +// ======================================================================= using IntRule = x4::rule; using LongLongRule = x4::rule; @@ -84,9 +84,9 @@ IRIS_X4_DEFINE_CONSTEXPR(float_rule) IRIS_X4_DEFINE_CONSTEXPR(double_rule) -// =================================================================== +// ======================================================================= // Trait-level static checks -// =================================================================== +// ======================================================================= TEST_CASE("lossy: is_assignable_without_lossy_conversion trait") { @@ -165,9 +165,9 @@ TEST_CASE("lossy: is_lossy_assignment trait") } -// =================================================================== +// ======================================================================= // Parse-level tests (non-lossy conversions that must compile & run) -// =================================================================== +// ======================================================================= TEST_CASE("lossy: parse int to long long (widening)") { @@ -229,14 +229,14 @@ TEST_CASE("lossy: parse same type (char to char)") } -// =================================================================== +// ======================================================================= // Rule-level tests // -// When the exposed attribute type differs from the rule's attribute -// type, the rule materializes its own attribute internally and then -// assigns it to the exposed attribute. The lossy conversion check applies -// to that assignment. -// =================================================================== +// When the exposed attribute type differs from the rule's attribute type, +// the rule materializes its own attribute internally and then assigns it +// to the exposed attribute. The lossy conversion check applies to that +// assignment. +// ======================================================================= TEST_CASE("lossy: RuleAttrAssignableWithoutLoss concept") { @@ -299,13 +299,13 @@ TEST_CASE("lossy: immediate rule widening (int to long long)") } -// =================================================================== +// ======================================================================= // Lossy conversion static_assert tests // -// The following test cases are commented out because they are expected -// to produce static_assert failures. Uncomment any single case to -// verify the assertion fires. -// =================================================================== +// The following test cases are commented out because they are expected to +// produce static_assert failures. Uncomment any single case to verify the +// assertion fires. +// ======================================================================= // --- move_to: source to plain (lossy) --- // TEST_CASE("lossy: int to char") { char a{}; REQUIRE(parse("42", x4::int_, a)); } From ba098db1af99465c7c0e83e795417a058d9f187d Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 11:07:48 +0900 Subject: [PATCH 51/52] Reorder and add tests --- test/x4/lossy_conversion.cpp | 69 +++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/test/x4/lossy_conversion.cpp b/test/x4/lossy_conversion.cpp index 113f9f147..43847253d 100644 --- a/test/x4/lossy_conversion.cpp +++ b/test/x4/lossy_conversion.cpp @@ -88,6 +88,30 @@ IRIS_X4_DEFINE_CONSTEXPR(double_rule) // Trait-level static checks // ======================================================================= +TEST_CASE("lossy: is_lossy_assignment trait") +{ + using x4::detail::is_lossy_assignment; + + // Default: arithmetic narrowing + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + + // Non-arithmetic Dest: not checked by default + STATIC_CHECK(!is_lossy_assignment::value); + + // Non-arithmetic Source with conversion operator to arithmetic Dest + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + + // Customization point: small_int = int is flagged as lossy (user-specialized) + STATIC_CHECK(is_lossy_assignment::value); +} + TEST_CASE("lossy: is_assignable_without_lossy_conversion trait") { using x4::detail::is_assignable_without_lossy_conversion; @@ -114,6 +138,9 @@ TEST_CASE("lossy: is_assignable_without_lossy_conversion trait") // Non-arithmetic dest: lossy conversion not checked STATIC_CHECK(is_assignable_without_lossy_conversion::value); + + // Customization point: small_int::operator=(int) exists, but the specialization rejects it + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); } TEST_CASE("lossy: is_tuple_assignable_without_lossy_conversion trait") @@ -136,34 +163,6 @@ TEST_CASE("lossy: is_tuple_assignable_without_lossy_conversion trait") >::value)); } -TEST_CASE("lossy: is_lossy_assignment trait") -{ - using x4::detail::is_lossy_assignment; - - // Default: arithmetic narrowing - STATIC_CHECK(!is_lossy_assignment::value); - STATIC_CHECK(!is_lossy_assignment::value); - STATIC_CHECK(!is_lossy_assignment::value); - STATIC_CHECK(is_lossy_assignment::value); - STATIC_CHECK(is_lossy_assignment::value); - STATIC_CHECK(is_lossy_assignment::value); - - // Non-arithmetic Dest: not checked by default - STATIC_CHECK(!is_lossy_assignment::value); - - // Non-arithmetic Source with conversion operator to arithmetic Dest - STATIC_CHECK(!is_lossy_assignment::value); - STATIC_CHECK(is_lossy_assignment::value); - STATIC_CHECK(!is_lossy_assignment::value); - - // Customization point: small_int = int is flagged as lossy (user-specialized) - STATIC_CHECK(is_lossy_assignment::value); - - // small_int = int via is_assignable_without_lossy_conversion - // small_int::operator=(int) exists, but the specialization rejects it - STATIC_CHECK(!x4::detail::is_assignable_without_lossy_conversion::value); -} - // ======================================================================= // Parse-level tests (non-lossy conversions that must compile & run) @@ -323,9 +322,23 @@ TEST_CASE("lossy: immediate rule widening (int to long long)") // TEST_CASE("lossy: tuple char,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } // TEST_CASE("lossy: tuple int,char") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// --- move_to: source to non-arithmetic dest (lossy via customization point) --- +// TEST_CASE("lossy: int to small_int") { small_int a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: single-element tuple-like with non-arithmetic dest (lossy) --- +// TEST_CASE("lossy: SES small_int from int") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: optional with non-arithmetic dest (lossy) --- +// TEST_CASE("lossy: int to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple with non-arithmetic dest (lossy) --- +// TEST_CASE("lossy: tuple small_int,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("lossy: tuple int,small_int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } + // --- rule: rule attribute to exposed attribute (lossy) --- // TEST_CASE("lossy: int rule to char") { char a{}; REQUIRE(parse("42", int_rule, a)); } // TEST_CASE("lossy: int rule to short") { short a{}; REQUIRE(parse("42", int_rule, a)); } // TEST_CASE("lossy: long long rule to int") { int a{}; REQUIRE(parse("42", long_long_rule, a)); } // TEST_CASE("lossy: double rule to float") { float a{}; REQUIRE(parse("3.14", double_rule, a)); } // TEST_CASE("lossy: immediate rule lossy") { char a{}; REQUIRE(parse("42", x4::rule{} = x4::int_, a)); } +// TEST_CASE("lossy: int rule to small_int") { small_int a; REQUIRE(parse("42", int_rule, a)); } From f70cb1202b62d4350abd7d2df3c6dcb7bc8b3d7b Mon Sep 17 00:00:00 2001 From: yaito3014 Date: Fri, 13 Mar 2026 11:11:56 +0900 Subject: [PATCH 52/52] Remove redundant static_assert --- include/iris/x4/rule.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index dc0661754..6cbaaf464 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -460,10 +460,6 @@ struct rule : parser> std::make_move_iterator(traits::end(rule_attr)) ); } else { - static_assert( - detail::is_assignable_without_lossy_conversion::value, - "Lossy conversion detected in rule (rule attribute to exposed attribute)" - ); exposed_attr = std::move(rule_attr); } return true;