-
Notifications
You must be signed in to change notification settings - Fork 52
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
Opt-in automatic collection of expected values on alt
#415
base: main
Are you sure you want to change the base?
Conversation
src/error.rs
Outdated
@@ -1,4 +1,4 @@ | |||
//! # Error management | |||
//! # Errormanagement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please clean up the commit history
- This commit shouldn't have this change
- Most other content from this commit should likely be squashed into a previous commit
- Replace the
AddContext::...
commits with theMergeContext
trait commit which will hopefully remove the roundtripping ofParseError
getting aC
parameter and then dropping it - Adding of tests is unrelated to the merge context commit and should either be in the
LongestMatch
commit or its own commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will clean up the commits before undrafting the PR.
src/error/tests.rs
Outdated
type Error<'a> = LongestMatch<Input<'a>, ContextError<&'static str>>; | ||
type PResult<'a, O> = crate::error::PResult<O, Error<'a>>; | ||
|
||
fn tag<'a>(t: &'static str) -> impl Parser<Input<'a>, &'a str, Error<'a>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we adding a hand-written tag
rather than just using the fact that &'static str
implements Parser
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using tag or &;static str
then winnow uses a specialized compare to check if the inputs prefix matches that string.. That is on backtracking the checkpoints all point to the same location. Alternatively I maybe could have used ('a', 'b', 'c')
as test input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Yeah, I'd prefer something else like a tuple or array so we don't have to deal with the correctness of custom parser implementations.
Pull Request Test Coverage Report for Build 7402348403
💛 - Coveralls |
Made some changes like introducing the MergeContext trait and added a few more tests. Also update the commit history. |
/// Used to compare checkpoints | ||
pub trait AsOrd { | ||
/// The type used to compare checkpoint positions | ||
type Ord: Ord + Clone + core::cmp::Ord + crate::lib::std::fmt::Debug; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't Ord + core::cmp::Ord
redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, some oversight when combining the commits into one.
impl<T: PartialEq> PartialEq for Checkpoint<T> { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.0.eq(&other.0) | ||
} | ||
} | ||
|
||
impl<T: Eq> Eq for Checkpoint<T> {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are currently used by the tests. We can update the tests to compare the err.into_inner()
or compare via format!(...)
.
let (mut context, other) = if self.context.capacity() >= other.context.capacity() { | ||
(self.context, other.context) | ||
} else { | ||
(other.context, self.context) | ||
}; | ||
context.extend(other); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this could lead to confusing ordering.
Should we instead check if one is empty and instead pick the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't really care about order if there are multiple parsers with the same longest match, but I see how this might be confusing to developers or mess eventually mess with tests when they change parsers..
Should we instead check if one is empty and instead pick the other?
👍
/// Used to compare checkpoints | ||
pub trait AsOrd { | ||
/// The type used to compare checkpoint positions | ||
type Ord: Ord + Clone + core::cmp::Ord + crate::lib::std::fmt::Debug; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Clone
and Debug
, if this is because LongestMatch
impls those traits, wouldn't that be dependent on whether the Ord
does so, meaning we don't need these bounds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove them. Clone
was required because I initially stored the ord
in LongestMatch
. I updated LongestMatch to store the actual checkpoint now.
impl<I, E> LongestMatch<I, E> | ||
where | ||
I: Stream, | ||
<I as Stream>::Checkpoint: AsOrd, | ||
E: MergeContext + Default, | ||
{ | ||
/// Create an empty error | ||
pub fn new() -> Self { | ||
Self { | ||
checkpoint: None, | ||
inner: Default::default(), | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we offer an empty error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And is this the only reason checkpoint
is an Option
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for checkpoint
to be Option
is the LongestMatch
currently implements the MergeContext
trait as well. When calling clear_context
we must set checkpoint: None
, such that the empty context is always <
then any non-empty context. Otherwise merging 2 context would give us some inconsistent output.
I'm removing impl MergeContext for LongestMatch
, then we should get rid of the Option
for checkpoint
.
// For tests | ||
impl<I, E> core::cmp::PartialEq for LongestMatch<I, E> | ||
where | ||
I: Stream, | ||
<I as Stream>::Checkpoint: AsOrd + core::cmp::PartialEq, | ||
E: MergeContext + core::cmp::PartialEq, | ||
{ | ||
fn eq(&self, other: &Self) -> bool { | ||
self.checkpoint == other.checkpoint && self.inner == other.inner | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this? ContextError
needs it because its the default and so we need this for testing parsers. Here, we likely shouldn't be doing equality check tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are used by the tests.
As LongestMatch
is no error on itself itself, but a decorator most likely used with ContextError
I did feel like being able to compare LongestMatch<ContextError>
would be the right thing to have.
fn append(mut self, input: &I, kind: ErrorKind) -> Self { | ||
let checkpoint = input.checkpoint(); | ||
match self.cmp_with_checkpoint(&checkpoint) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is this needed?
|
||
fn clear_context(self) -> Self { | ||
Self { | ||
checkpoint: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to reset it when clearing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment here #415 (comment)
I'm removing impl MergeContext
or LongestMatch
. This also removes the need or checkpoint
to be Option
.
let checkpoint1 = &&input[2..]; | ||
let checkpoint2 = &&input[3..]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we keep these tests, we should name these checkpoints to make reading the asserts clearer
@@ -0,0 +1,190 @@ | |||
use super::*; | |||
|
|||
mod longest_match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My primary concern with these tests is they focus on all of the details but they don't make sure the details are actually correct for which we'd need tests that render error messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "rendered error message" you mean using format!(...)
?
I didn't do this because LongestMatch
does not directly implement any rendering itself. It will just delegates rendering to the inner error. That is the test output would depend on the current implementation of ContextError
.
As LongestMatch acts as a 'guard' on the wrapped error when merging/adding context details I wanted to make sure with the tests that this 'guard' works as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed that ErrMode
uses Debug
internally to render the error.
Is this what you have had in mind?
#[test]
fn multi_longest_match() {
let mut parser = alt((
pattern(('d', 'e', 'f'), "don't want"),
pattern(('a', 'b', 'c', 'd'), "wanted 1"),
pattern(('a', 'b', 'c'), "wanted 2"),
pattern(('d', 'e', 'f', 'g'), "don't want"),
));
let mut input = "abd";
let checkpoint = &&input[2..]; // 2 characters consumed by longest match
assert_eq!(
format!("{}", parser.parse_next(&mut input).unwrap_err(),),
r#"Parsing Error: LongestMatch { checkpoint: Checkpoint("d"), inner: ContextError { context: ["wanted 1", "wanted 2"], cause: None } }"#,
);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't do this because LongestMatch does not directly implement any rendering itself. It will just delegates rendering to the inner error. That is the test output would depend on the current implementation of ContextError.
For me, without a Display
test (not Debug
or the current state of context), we aren't showing that we've been able to achieve what this is trying to accomplish.
Any updates on this? |
Continuation of #413 and #410
This PR introduces the
MergeContext
trait, so we can callmerge_context
duringor
.