Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
a653da4
Rename and drop aliases
yaito3014 Mar 7, 2026
6b0c90f
Add test
yaito3014 Mar 7, 2026
f4430ee
Move single element tuple like traits
yaito3014 Mar 7, 2026
cfe7cb6
Unwrap single element tuple like in `string_parse`
yaito3014 Mar 7, 2026
6a17b00
Add test
yaito3014 Mar 7, 2026
a75a454
Fix test
yaito3014 Mar 7, 2026
2aa10ab
Unwrap single element tuple recursively
yaito3014 Mar 7, 2026
9e240ee
Add special handling for rule
yaito3014 Mar 7, 2026
bc08648
Fix
yaito3014 Mar 7, 2026
1461752
Revise single element tuple like type handling
yaito3014 Mar 8, 2026
765fe91
Add tests
yaito3014 Mar 8, 2026
d333ca9
Suppress MSVC warning
yaito3014 Mar 8, 2026
41e0f0c
Split test case
yaito3014 Mar 8, 2026
c022926
Reduce stack usage
yaito3014 Mar 8, 2026
f05a9e6
Add newline at end
yaito3014 Mar 8, 2026
d1bcdaf
Unwrap single element tuple like once in rule parser
yaito3014 Mar 8, 2026
114486d
Fix indentation
yaito3014 Mar 8, 2026
59c5661
Use remove_cvref_t consistently
yaito3014 Mar 8, 2026
7ff873e
Remove deprecated overload of `move_to`
yaito3014 Mar 8, 2026
acd02df
Add notes for AI generated content
saki7 Mar 9, 2026
fa0cb78
Remove unneeded branch from `pass_sequence_attribute`
yaito3014 Mar 10, 2026
38fb7fc
Add comment and adjust noexcept
yaito3014 Mar 10, 2026
f8401c3
Reorganize includes
yaito3014 Mar 11, 2026
0e7dc8c
Styling fix
yaito3014 Mar 11, 2026
e11c5a7
Remove unused forward declaration
yaito3014 Mar 11, 2026
14eea5f
Remove `pass` from `partition_attribute`
yaito3014 Mar 11, 2026
e05a682
Remove unneeded metafunctions
yaito3014 Mar 11, 2026
308f9d8
Use metafunction to simplify constexpr if
yaito3014 Mar 11, 2026
2a11631
Remove unused include
yaito3014 Mar 11, 2026
f15176e
Rename `SET` to `SES`
yaito3014 Mar 11, 2026
ebc71c2
Rename to clearer name and resolve noexcept issue
yaito3014 Mar 11, 2026
42627b0
Fix comment
yaito3014 Mar 11, 2026
5a3b28e
Add variant case
yaito3014 Mar 11, 2026
2b5d184
Use `is_convertible_without_narrowing`
yaito3014 Mar 11, 2026
bf81ed2
Resolve several issues
yaito3014 Mar 11, 2026
3f80fe5
Pass original attribute to `on_success`
yaito3014 Mar 11, 2026
ff1886a
Add static_assertion that checks narrowing assignment
yaito3014 Mar 11, 2026
15d3f80
Merge branch 'main' into fix-single-element-tuple-like-handling
yaito3014 Mar 12, 2026
ae16031
Refine static_assert of `partition_attribute`
yaito3014 Mar 12, 2026
34261db
Minor styling fix
yaito3014 Mar 12, 2026
ab7b0ef
Use more accurate type to detect narrowing
yaito3014 Mar 12, 2026
7567791
Add narrowing check test
yaito3014 Mar 12, 2026
2480d49
Change `can_hold` primary definition to `is_assignable`
yaito3014 Mar 12, 2026
483439d
Update iris
yaito3014 Mar 12, 2026
00c88bc
Update iris
yaito3014 Mar 12, 2026
80ce6f0
Move `is_assignable_without_narrowing` into submodule
yaito3014 Mar 12, 2026
a3dcecc
Revert "Move `is_assignable_without_narrowing` into submodule"
yaito3014 Mar 12, 2026
4b5c385
Update iris
yaito3014 Mar 12, 2026
6fc643f
Rename
yaito3014 Mar 12, 2026
511efa6
Introduce `is_lossy_assignment` customization point and rename narrow…
yaito3014 Mar 13, 2026
8d4c51d
Adjust comment styling
yaito3014 Mar 13, 2026
ba098db
Reorder and add tests
yaito3014 Mar 13, 2026
f70cb12
Remove redundant static_assert
yaito3014 Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/iris/x4/core/detail/parse_alternative.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ struct pass_non_variant_attribute

// Unwrap single element sequences
template<class Parser, X4Attribute Attr>
requires traits::is_size_one_sequence_v<Attr>
requires traits::is_single_element_tuple_like<Attr>::value
struct pass_non_variant_attribute<Parser, Attr>
{
using attr_type = typename std::remove_reference_t<
Expand Down
2 changes: 1 addition & 1 deletion include/iris/x4/core/detail/parse_into_container.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ struct parse_into_container_impl_default
}

} else {
if constexpr (traits::is_size_one_sequence_v<unwrapped_attribute_type>) {
if constexpr (traits::is_single_element_tuple_like<unwrapped_attribute_type>::value) {
// attribute is single element tuple-like; unwrap and try again
return parse_into_container_impl_default<Parser>::call(parser, first, last, ctx, alloy::get<0>(unwrapped_attr));
} else {
Expand Down
133 changes: 76 additions & 57 deletions include/iris/x4/core/detail/parse_sequence.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@

namespace iris::x4 {

template<class RuleID, X4Attribute RuleAttr, bool ForceAttr>
struct rule;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this forward declaration, the corresponding code was removed in fa0cb78.

And please be more careful when making changes spotted by review. If you're going to remove something according to a review, you should double-check the surrounding context and apply corresponding changes.

Copy link
Copy Markdown
Member

@saki7 saki7 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: in the previous review, I wrote:

  • This probably should assert narrowing conversion.

If the current code is working with the rule-specific handling being removed, that means our current code magically unwraps incompatible single-element tuple-like for class rule and its child parsers. That should trigger narrowing conversion check, and emit assertion error on such cases.

Maybe it should be handled here. #71 (comment)

We actually need some code that actually triggers the static_assert. Since we currently don't have a test system to assert hard errors (compilation failures), I'd like to have those cases written in a commented-out code inside single_element_tuple_like.cpp.

I will manually uncomment the test case in my local environment and check if the assertion actually works or not.

template<class Left, class Right>
struct sequence;

Expand Down Expand Up @@ -77,7 +80,7 @@ struct pass_through_sequence_attribute

template<class Parser, class Attr>
struct pass_sequence_attribute : std::conditional_t<
traits::is_size_one_view_v<Attr>,
traits::is_single_element_tuple_like_view<Attr>::value,
pass_sequence_attribute_size_one_view<Attr>,
pass_through_sequence_attribute<Attr>
>
Expand All @@ -88,6 +91,24 @@ struct pass_sequence_attribute<sequence<LParser, RParser>, Attr>
: pass_through_sequence_attribute<Attr>
{};

template<class RuleID, X4Attribute RuleAttr, bool ForceAttr, class Attr>
requires
traits::is_single_element_tuple_like<Attr>::value &&
(!traits::can_hold<RuleAttr, Attr>::value)
struct pass_sequence_attribute<rule<RuleID, RuleAttr, ForceAttr>, Attr>
{
using base_pass_sequence_attribute = pass_through_sequence_attribute<Attr>;

using type = alloy::tuple_element_t<0, Attr>&;

template<class Attr_>
[[nodiscard]] static constexpr type
call(Attr_& attribute) noexcept(noexcept(alloy::get<0>(attribute)))
{
return alloy::get<0>(attribute);
}
};

template<class Parser, class Attr>
requires requires {
typename Parser::proxy_backend_type;
Expand Down Expand Up @@ -117,16 +138,16 @@ struct partition_attribute<LParser, RParser, Attr>
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<Attr>;
using splitted = alloy::tuple_split_t<view, l_size, r_size>;
using l_part = alloy::tuple_element_t<0, splitted>;
using r_part = alloy::tuple_element_t<1, splitted>;
using split = alloy::tuple_split_t<view, l_size, r_size>;
using l_part = alloy::tuple_element_t<0, split>;
using r_part = alloy::tuple_element_t<1, split>;
using l_pass = pass_sequence_attribute<LParser, l_part>;
using r_pass = pass_sequence_attribute<RParser, r_part>;

Expand Down Expand Up @@ -203,43 +224,10 @@ struct partition_attribute<LParser, RParser, Attr>
}
};

// Default overload, no constraints on attribute category
template<class Parser, std::forward_iterator It, std::sentinel_for<It> 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<Attr>);

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<class Parser, std::forward_iterator It, std::sentinel_for<It> Se, class Context, X4Attribute Attr>
requires (parser_traits<Parser>::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<Parser, It, Se, Context, Attr>)
{
// static_assert(Parsable<Parser, It, Se, Context, Attr>);
Expand All @@ -249,32 +237,63 @@ parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context con
template<class Parser, std::forward_iterator It, std::sentinel_for<It> Se, class Context, X4Attribute Attr>
requires (parser_traits<Parser>::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<It> Se, class Context,
traits::CategorizedAttr<traits::container_attr> ContainerAttr
>
template<class Parser, std::forward_iterator It, std::sentinel_for<It> 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<It> &&
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)
static_assert(X4Attribute<Attr>);

if constexpr (traits::is_container_v<Attr>) {
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<Attr>::value &&
has_attribute_v<typename Parser::left_type> &&
has_attribute_v<typename Parser::right_type>
) {
first = std::move(local_it);
return true;
// 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,
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<class Left, class Right>
Expand Down
35 changes: 3 additions & 32 deletions include/iris/x4/core/list_like_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@
#include <iris/rvariant/rvariant.hpp>
#include <iris/rvariant/variant_helper.hpp>

#include <iris/alloy/tuple.hpp>
#include <iris/alloy/traits.hpp>

#include <type_traits>

namespace iris::x4 {

namespace list_like_parser {
Expand All @@ -29,7 +26,7 @@ template<X4NonUnusedAttribute ParserAttr, X4NonUnusedAttribute ExposedAttr>
// 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<ExposedAttr>::type
>
Expand All @@ -52,32 +49,6 @@ struct chunk_buffer_impl
static_assert(traits::X4Container<typename unwrap_container_candidate<ParserAttr, ExposedAttr>::type>);
};

template<class T>
[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept
{
return std::forward<T>(value);
}

template<class T>
requires traits::is_size_one_sequence_v<std::remove_cvref_t<T>>
[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept
{
return std::forward_like<T>(alloy::get<0>(std::forward<T>(value)));
}

template<class T>
struct unwrap_single_element_plain
{
using type = std::remove_cvref_t<T>;
};

template<class T>
requires traits::is_size_one_sequence_v<std::remove_cvref_t<T>>
struct unwrap_single_element_plain<T>
{
using type = std::remove_cvref_t<alloy::tuple_element_t<0, T>>;
};

} // detail


Expand All @@ -88,10 +59,10 @@ using chunk_buffer = detail::chunk_buffer_impl<ParserAttr, ExposedAttr>::type;
template<X4NonUnusedAttribute ParserAttr, X4NonUnusedAttribute ExposedAttr>
[[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<ExposedAttr>
>::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<unwrapped_attr_type>) {
using container_alternative = traits::variant_find_holdable_type<
Expand Down
17 changes: 9 additions & 8 deletions include/iris/x4/core/move_to.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ constexpr void move_to(It const&, Se const&, unused_type const&&) = delete; // t
// Category specific --------------------------------------

template<traits::NonUnusedAttr Source, traits::CategorizedAttr<traits::plain_attr> Dest>
requires traits::is_size_one_sequence_v<Source>
requires traits::is_single_element_tuple_like<std::remove_cvref_t<Source>>::value
constexpr void
move_to(Source&& src, Dest& dest)
noexcept(noexcept(dest = std::forward_like<Source>(alloy::get<0>(std::forward<Source>(src)))))
Expand All @@ -127,7 +127,7 @@ move_to(Source&& src, Dest& dest)
}

template<traits::NonUnusedAttr Source, traits::CategorizedAttr<traits::plain_attr> Dest>
requires (!traits::is_size_one_sequence_v<Source>)
requires (!traits::is_single_element_tuple_like<std::remove_cvref_t<Source>>::value)
constexpr void
move_to(Source&& src, Dest& dest)
noexcept(std::is_nothrow_assignable_v<Dest&, Source&&>)
Expand All @@ -139,8 +139,8 @@ move_to(Source&& src, Dest& dest)

template<traits::NonUnusedAttr Source, traits::CategorizedAttr<traits::tuple_attr> Dest>
requires
traits::is_same_size_sequence_v<Dest, Source> &&
(!traits::is_size_one_sequence_v<Dest>)
traits::is_same_size_tuple_like<Dest, std::remove_cvref_t<Source>>::value &&
(!traits::is_single_element_tuple_like<Dest>::value)
constexpr void
move_to(Source&& src, Dest& dest)
noexcept(noexcept(alloy::tuple_assign(std::forward<Source>(src), dest)))
Expand All @@ -166,7 +166,7 @@ move_to(Source&& src, Dest& dest)
}

template<traits::NonUnusedAttr Source, traits::CategorizedAttr<traits::variant_attr> Dest>
requires (!std::is_assignable_v<Dest&, Source&&>) && traits::is_size_one_sequence_v<Source>
requires (!std::is_assignable_v<Dest&, Source&&>) && traits::is_single_element_tuple_like<Source>::value
constexpr void
move_to(Source&& src, Dest& dest)
noexcept(noexcept(dest = std::forward_like<Source>(alloy::get<0>(std::forward<Source>(src)))))
Expand Down Expand Up @@ -221,11 +221,12 @@ move_to(It first, Se last, Dest& dest)
}

template<std::forward_iterator It, std::sentinel_for<It> Se, traits::CategorizedAttr<traits::tuple_attr> Dest>
requires traits::is_size_one_sequence_v<Dest>
requires traits::is_single_element_tuple_like<Dest>::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));
}

Expand Down Expand Up @@ -268,7 +269,7 @@ move_to(Source&& src, Dest& dest)

// Size-one tuple-like forwarding
template<traits::NonUnusedAttr Source, traits::CategorizedAttr<traits::tuple_attr> Dest>
requires traits::is_size_one_sequence_v<Dest>
requires traits::is_single_element_tuple_like<Dest>::value
constexpr void
move_to(Source&& src, Dest& dest)
noexcept(noexcept(x4::move_to(std::forward<Source>(src), alloy::get<0>(dest))))
Expand Down
19 changes: 16 additions & 3 deletions include/iris/x4/rule.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ struct rule_impl

It start = first; // backup

auto&& unwrapped_attr = [&]() -> decltype(auto) {
if constexpr (traits::is_single_element_tuple_like<RHSAttr>::value) {
if constexpr (traits::can_hold<typename parser_traits<RHS>::attribute_type, alloy::tuple_element_t<0, RHSAttr>>::value) {
return alloy::get<0>(rhs_attr);
} else {
return rhs_attr;
}
} else {
return rhs_attr;
}
}();

Comment on lines +192 to +199
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to emit static_assert to prohibit narrowing conversion case. We want to make such case hard error as per "X3 rule problem".

//
// NOTE: The branches below are intentionally written verbosely to make sure
// we have the minimal call stack. DON'T extract these procedures into a
Expand All @@ -187,21 +199,21 @@ 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,
// we'll make a context for this rule tagged by its `RuleID`
// so we can extract the rule later on in the default
// `parse_rule` overload.
auto const rule_id_context = x4::make_context<RuleID>(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<It, RContext, RHSAttr>) {
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;
}

Expand Down Expand Up @@ -390,6 +402,7 @@ struct rule : parser<rule<RuleID, RuleAttr, ForceAttr>>
static_assert(X4Attribute<RuleAttr>);
static_assert(X4UnusedAttribute<RuleAttr> || !std::is_const_v<RuleAttr>, "Rule attribute cannot be const qualified");
static_assert(!std::is_same_v<std::remove_const_t<RuleAttr>, unused_container_type>, "`rule` with `unused_container_type` is not supported");
static_assert(!is_ttp_specialization_of<RuleAttr, alloy::tuple>::value, "alloy::tuple is intended for internal use only");

using id = RuleID;
using attribute_type = RuleAttr;
Expand Down
Loading
Loading