Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

seq! for tuples errors when capturing locally-defined parsers. #502

Open
2 tasks done
aDotInTheVoid opened this issue Apr 6, 2024 · 1 comment
Open
2 tasks done
Labels
A-combinator Area: combinators C-bug Category: Things not working as expected M-breaking-change Meta: Implementing or merging this will introduce a breaking change.
Milestone

Comments

@aDotInTheVoid
Copy link

Please complete the following tasks

rust version

rustc 1.77.0 (aedd173a2 2024-03-17)

winnow version

0.6.5

Minimal reproducible code

use winnow::combinator::seq;
use winnow::prelude::*;

pub fn doesnt_work(input: &mut &str) -> PResult<(i32, i32)> {
    let mut foo = "foo".map(|_| 10);
    let mut bar = "bar".map(|_| 20);
    seq!(foo, bar).parse_next(input)
}

Steps to reproduce the bug with the above code

cargo check

Actual Behaviour

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> good-crates/parser/examples/mcve.rs:18:5
   |
18 |     seq!(foo, bar).parse_next(input)
   |     ^^^^^---^^^^^^
   |     |    |
   |     |    closure is `FnOnce` because it moves the variable `foo` out of its environment
   |     this closure implements `FnOnce`, not `FnMut`
   |     the requirement to implement `FnMut` derives from here
   |     required by a bound introduced by this call
   |
   = note: required for `{closure@/home/alona/.cargo/registry/src/index.crates.io-6f17d22bba15001f/winnow-0.6.5/src/macros/seq.rs:88:44: 88:64}` to implement `winnow::Parser<&str, (i32, i32), ContextError>`
note: required by a bound in `trace`
  --> /home/alona/.cargo/registry/src/index.crates.io-6f17d22bba15001f/winnow-0.6.5/src/combinator/debug/mod.rs:45:18
   |
43 | pub fn trace<I: Stream, O, E>(
   |        ----- required by a bound in this function
44 |     name: impl crate::lib::std::fmt::Display,
45 |     parser: impl Parser<I, O, E>,
   |                  ^^^^^^^^^^^^^^^ required by this bound in `trace`
   = note: this error originates in the macro `$crate::seq` which comes from the expansion of the macro `seq` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0525`.

Expected Behaviour

This should compile.

If seq! is used to make a struct (instead of a tuple) it does compile.

pub struct Res {
    pub a: i32,
    pub b: i32,
}

pub fn does_work(input: &mut &str) -> PResult<Res> {
    let mut foo = "foo".map(|_| 10);
    let mut bar = "bar".map(|_| 20);
    seq! { Res{ a: foo, b: bar } }.parse_next(input)
}

works fine.

Additional Context

Manually expanding the seq! macro (with RA) for the broken example gives:

pub fn doesnt_work(input: &mut &str) -> PResult<(i32, i32)> {
    let mut foo = "foo".map(|_| 10);
    let mut bar = "bar".map(|_| 20);
    {
        winnow::combinator::trace("tuple", move |input: &mut _| {
            use winnow::Parser;
            (foo, bar).map(|t| ((t.0), (t.1))).parse_next(input)
        })
    }
    .parse_next(input)
}

This gives a more descriptive error:


error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> good-crates/parser/examples/mcve.rs:19:44
   |
19 |           winnow::combinator::trace("tuple", move |input: &mut _| {
   |           -------------------------          -^^^^^^^^^^^^^^^^^^^
   |           |                                  |
   |  _________|__________________________________this closure implements `FnOnce`, not `FnMut`
   | |         |
   | |         required by a bound introduced by this call
20 | |             use winnow::Parser;
21 | |             (foo, bar).map(|t| ((t.0), (t.1))).parse_next(input)
   | |              --- closure is `FnOnce` because it moves the variable `foo` out of its environment
22 | |         })
   | |_________- the requirement to implement `FnMut` derives from here
   |
   = note: required for `{closure@good-crates/parser/examples/mcve.rs:19:44: 19:64}` to implement `winnow::Parser<&str, (i32, i32), ContextError>`
note: required by a bound in `trace`
  --> /home/alona/.cargo/registry/src/index.crates.io-6f17d22bba15001f/winnow-0.6.5/src/combinator/debug/mod.rs:45:18
   |
43 | pub fn trace<I: Stream, O, E>(
   |        ----- required by a bound in this function
44 |     name: impl crate::lib::std::fmt::Display,
45 |     parser: impl Parser<I, O, E>,
   |                  ^^^^^^^^^^^^^^^ required by this bound in `trace`

For more information about this error, try `rustc --explain E0525`.

Manually expanding the example that does work gives:

pub fn does_work(input: &mut &str) -> PResult<Res> {
    let mut foo = "foo".map(|_| 10);
    let mut bar = "bar".map(|_| 20);
    {
        winnow::combinator::trace("Res", move |input: &mut _| {
            use winnow::Parser;
            let a = foo.parse_next(input)?;
            let b = bar.parse_next(input)?;
            #[allow(clippy::redundant_field_names)]
            Ok(Res { a: a, b: b })
        })
    }
    .parse_next(input)
}

I think the reason this works is that foo and bar don't need to be captured by value here, as .parse_next takes &mut self.

Adding .by_ref() to the tuple example version may fix it.

@aDotInTheVoid aDotInTheVoid added the C-bug Category: Things not working as expected label Apr 6, 2024
@epage epage added the A-combinator Area: combinators label Apr 9, 2024
epage added a commit to epage/winnow that referenced this issue Apr 9, 2024
This still has a deficiency compared to seq structs in that you can only
use the parser once.

This is a part of winnow-rs#502
@epage epage added the M-breaking-change Meta: Implementing or merging this will introduce a breaking change. label Apr 9, 2024
@epage
Copy link
Collaborator

epage commented Apr 9, 2024

Thanks for reporting this!

I worked on a fix but it appears to be a breaking change. Before, we moved the parser and the caller could declare their parsers as immutable. With this change, they now need to make their parsers mutable.

I'm assuming this can be worked around on the callers side by adding .by_ref() on their side.

I'm thinking this is likely to not be enough to justify a breaking release right now but will need to wait until the motivation is strong enough to release one.

Side note: #502 only partially brings their behavior in alignment which is why I didn't mark this as a fix but a step. I suspect the way to fix this would be to stop using ().parse_next() completely and instead assign each parse result to a variable using the same pre-filled list of variable names that the macros for implementing Parser for tuples does. Hopefully this would let us reduce the scope of #237 affecting users of seq!.

@epage epage added this to the 0.7.0 milestone Apr 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-combinator Area: combinators C-bug Category: Things not working as expected M-breaking-change Meta: Implementing or merging this will introduce a breaking change.
Projects
None yet
Development

No branches or pull requests

2 participants