Opt-in automatic collection of expected values on alt#415
Opt-in automatic collection of expected values on alt#415urso wants to merge 4 commits intowinnow-rs:mainfrom
alt#415Conversation
src/error.rs
Outdated
There was a problem hiding this comment.
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 theMergeContexttrait commit which will hopefully remove the roundtripping ofParseErrorgetting aCparameter and then dropping it - Adding of tests is unrelated to the merge context commit and should either be in the
LongestMatchcommit or its own commit
There was a problem hiding this comment.
Sure, will clean up the commits before undrafting the PR.
src/error/tests.rs
Outdated
There was a problem hiding this comment.
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.
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.
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.
Isn't Ord + core::cmp::Ord redundant?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
Why do we offer an empty error?
There was a problem hiding this comment.
And is this the only reason checkpoint is an Option?
There was a problem hiding this comment.
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.
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.
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) { |
|
|
||
| fn clear_context(self) -> Self { | ||
| Self { | ||
| checkpoint: None, |
There was a problem hiding this comment.
Why do we need to reset it when clearing?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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
MergeContexttrait, so we can callmerge_contextduringor.