chumsky icon indicating copy to clipboard operation
chumsky copied to clipboard

`Repeated` deleting custom error and returning generic `Unexpected` error instead

Open Cookie04DE opened this issue 11 months ago • 3 comments

Consider the following code:

use std::fmt::{Debug, Display};

use chumsky::{prelude::*, util::Maybe};

#[derive(Debug, Clone, PartialEq, Eq)]
enum MyErr {
    Unexpected {
        expected: Vec<Option<char>>,
        found: Option<char>,
        span: SimpleSpan<usize>,
    },
    UnclosedComment {
        start: SimpleSpan<usize>,
    },
}

impl Display for MyErr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        <Self as Debug>::fmt(&self, f)
    }
}

impl std::error::Error for MyErr {}

impl<'a> chumsky::error::Error<'a, &'a str> for MyErr {
    fn expected_found<
        E: IntoIterator<Item = Option<chumsky::util::MaybeRef<'a, <&'a str as Input<'a>>::Token>>>,
    >(
        expected: E,
        found: Option<chumsky::util::MaybeRef<'a, <&'a str as Input<'a>>::Token>>,
        span: <&'a str as Input<'a>>::Span,
    ) -> Self {
        MyErr::Unexpected {
            expected: expected
                .into_iter()
                .map(|m| m.map(Maybe::into_inner))
                .collect(),
            found: found.map(Maybe::into_inner),
            span,
        }
    }

    fn merge(self, other: Self) -> Self {
        match (self, other) {
            (MyErr::Unexpected { .. }, a) => a,
            (a, MyErr::Unexpected { .. }) => a,
            (a, _) => a,
        }
    }
}

fn comment<'a>() -> impl Parser<'a, &'a str, (), extra::Err<MyErr>> {
    just("#")
        .to_span()
        .then(choice((just("#").to(Some(())), empty().to(None))))
        .try_map(|(start, end), _| match end {
            Some(()) => Ok(()),
            None => Err(MyErr::UnclosedComment { start }),
        })
}

#[test]
fn test_comment_ok() {
    comment().parse("##").into_result().unwrap()
}

#[test]
fn test_comment_ok_repeated() {
    let input = "##".repeat(3);
    comment().repeated().parse(&input).into_result().unwrap();
}

#[test]
fn test_comment_unclosed() {
    assert_eq!(
        comment().parse("#").into_result().unwrap_err(),
        vec![MyErr::UnclosedComment {
            start: (0..1).into()
        }]
    )
}

#[test]
fn test_comment_unclosed_repeated() {
    let input = "##".repeat(3) + "#";
    assert_eq!(
        comment()
            .repeated()
            .parse(&input)
            .into_result()
            .unwrap_err(),
        vec![MyErr::UnclosedComment {
            start: (6..7).into()
        }]
    );
}

All tests beside test_comment_unclosed_repeated run successfully.

This is the output of the failing test:

thread 'test_comment_unclosed_repeated' panicked at 'assertion failed: `(left == right)`
  left: `[Unexpected { expected: [], found: Some('#'), span: 6..7 }]`,
 right: `[UnclosedComment { start: 6..7 }]`', src/lib.rs:86:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

As you can see, instead of the expected custom UnclosedComment error, a generic Unexpected error is returned instead. I traced the issue to this line where my UnclosedComment error get's deleted.

Cookie04DE avatar Jul 18 '23 18:07 Cookie04DE