Skip to content

X3: Rules do not pass-through attributes #444

Description

@Xeverous

IMO this is an unwanted bug, as copies of various AST subparts can be expensive. It's also nowhere stated in the documentation that types need to be copyable.

I don't have an MCVE now (ask me if you want any, I will try to reduce my project as long as it reproduces). The error message (template backtrace) points to spirit implementation:

../src/parser/ast.hpp:243:8:   required from 'static bool boost::spirit::x3::detail::rule_parser<Attribute, ID>::call_rule_definition(const RHS&, const char*, Iterator&, const Iterator&, const Context&, ActualAttribute&, ExplicitAttrPropagation) [...]
[...]/boost/spirit/home/x3/nonterminal/rule.hpp:61:39:   required from 'bool boost::spirit::x3::rule_definition<ID, RHS, Attribute, force_attribute_>::parse(Iterator&, const Iterator&, const Context&, boost::spirit::x3::unused_type, Attribute_&) const [...]
../src/parser/grammar_def.hpp:256:1:   required from 'bool fs::parser::parse_rule(fs::parser::grammar_type, Iterator&, const Iterator&, const Context&, Attribute&) [...]
../src/parser/grammar.cpp:13:1:   required from here
[...]/mingw64/include/c++/8.2.0/bits/stl_construct.h:75:7: error: use of deleted function 'fs::parser::ast::action_expression::action_expression(const fs::parser::ast::action_expression&)'

digging the implementation (x3/nonterminal/rule.hpp, lines 56-66):

    template <typename Iterator, typename Context, typename Attribute_>
    bool parse(Iterator& first, Iterator const& last
      , Context const& context, unused_type, Attribute_& attr) const
    {
        return detail::rule_parser<attribute_type, ID>
            ::call_rule_definition(
                rhs, name, first, last
              , context
              , attr
              , mpl::bool_<force_attribute>());
    }

then (x3/nonterminal/detail/rule.hpp, lines 295-349)

    template <typename RHS, typename Iterator, typename Context
      , typename ActualAttribute, typename ExplicitAttrPropagation>
    static bool call_rule_definition(
        RHS const& rhs
      , char const* rule_name
      , Iterator& first, Iterator const& last
      , Context const& context, ActualAttribute& attr
      , ExplicitAttrPropagation)
    {
        boost::ignore_unused(rule_name);

        typedef traits::make_attribute<Attribute, ActualAttribute> make_attribute;

        // do down-stream transformation, provides attribute for
        // rhs parser
        typedef traits::transform_attribute<
            typename make_attribute::type, Attribute, parser_id>
        transform;

        typedef typename make_attribute::value_type value_type;
        typedef typename transform::type transform_attr;
        value_type made_attr = make_attribute::call(attr); // HERE IS THE COPY
        transform_attr attr_ = transform::pre(made_attr);

        bool ok_parse
          //Creates a place to hold the result of parse_rhs
          //called inside the following scope.
          ;
        {
         // Create a scope to cause the dbg variable below (within
         // the #if...#endif) to call it's DTOR before any
         // modifications are made to the attribute, attr_ passed
         // to parse_rhs (such as might be done in
         // traits::post_transform when, for example,
         // ActualAttribute is a recursive variant).
#if defined(BOOST_SPIRIT_X3_DEBUG)
            context_debug<Iterator, transform_attr>
            dbg(rule_name, first, last, attr_, ok_parse);
#endif
            ok_parse = parse_rhs(rhs, first, last, context, attr_, attr_
               , mpl::bool_
                 < (  RHS::has_action
                   && !ExplicitAttrPropagation::value
                   )
                 >()
              );
        }
        if (ok_parse)
        {
            // do up-stream transformation, this integrates the results
            // back into the original attribute value, if appropriate
            traits::post_transform(attr, std::forward<transform_attr>(attr_));
        }
        return ok_parse;
    }

x3/support/traits/make_attribute

template <typename Attribute>
struct make_attribute_base
{
    static Attribute call(unused_type)
    {
         // synthesize the attribute/parameter
        return Attribute();
    }

    template <typename T>
    static T& call(T& value)
    {
        return value; // just pass the one provided
    }
};

The core of the problem lies on these 2 lines

    value_type made_attr = make_attribute::call(attr);
    transform_attr attr_ = transform::pre(made_attr);

make_attribute::call() default-constructs the attribute if originally it was unused, but copies it instead if it was. At first I though that the library wants to preserve the state of the attribute (in case it's already partially filled) but ... no, the copy that is just made is then passed to transform::pre which doesn't do anything with the argument and returns a default-constructed object of type transform_attr. made_attr is never used anymore which makes it essentially a useless copy. At the end, attr (the orginal user object) is move-constructed from attr_.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions