-
Notifications
You must be signed in to change notification settings - Fork 17
Improve ConversionError messages with token details #41
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
base: master
Are you sure you want to change the base?
Conversation
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the Comment |
- Add NoMatchWithInfo variant with current_node, expected, and actual fields - Update Extraneous variant to include extraneous tokens description - Add is_no_match() helper method - Update Display implementations with detailed error messages - Update derive macro to pass token information to errors - Add tests for improved error messages Co-authored-by: tomtau <[email protected]>
Address code review feedback to use the is_no_match() helper method instead of pattern matching both NoMatch variants explicitly. Co-authored-by: tomtau <[email protected]>
|
@copilot Can you fix |
Add explicit lifetime parameter `<'_>` to Span type to fix mismatched_lifetime_syntaxes clippy warnings. Co-authored-by: tomtau <[email protected]>
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.
Pull request overview
This PR enhances error messages in the pest AST conversion framework by providing more detailed information about parsing failures. Instead of generic "no match" and "extraneous tokens" errors, the new error variants include specific details about which tokens were expected versus found, making debugging significantly easier.
Key Changes:
- Added
NoMatchWithInfovariant toConversionErrorthat reports the current AST node, expected rule, and actual rule found - Enhanced
Extraneousvariant to include details about which specific tokens are extraneous - Added
is_no_match()helper method to check for bothNoMatchandNoMatchWithInfovariants
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib.rs |
Added NoMatchWithInfo and enhanced Extraneous variants; removed Copy trait; added is_no_match() helper; updated Display implementation; updated Option<T> and Vec<T> implementations to use the helper |
derive/src/from_pest/mod.rs |
Updated struct and enum derivation to generate NoMatchWithInfo and enhanced Extraneous errors with token details |
derive/src/from_pest/field.rs |
Updated default field strategy to use is_no_match() helper for consistency |
tests/error_messages.rs |
Added comprehensive tests for new error message formats and is_no_match() helper |
examples/csv.rs |
Fixed clippy warning by adding explicit lifetime annotation to span_into_str function |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #[test] | ||
| fn test_extraneous_error_message() { | ||
| use from_pest::ConversionError; | ||
| use from_pest::Void; | ||
|
|
||
| let error: ConversionError<Void> = ConversionError::Extraneous { | ||
| current_node: "TestNode", | ||
| extraneous: "[extra_rule1, extra_rule2]".to_string(), | ||
| }; | ||
|
|
||
| let message = error.to_string(); | ||
| assert!(message.contains("TestNode")); | ||
| assert!(message.contains("extra_rule1")); | ||
| assert!(message.contains("extra_rule2")); | ||
| assert!(message.contains("extraneous tokens")); | ||
| } |
Copilot
AI
Dec 26, 2025
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 test creates error instances manually but doesn't verify that the derive macro actually generates these errors in real usage. Consider adding an integration test that uses the derive macro to generate code that produces an Extraneous error with the new extraneous field populated, to ensure the format string output is as expected (e.g., that it's human-readable and not just raw Debug output like [Rule1, Rule2]).
| Err(::from_pest::ConversionError::NoMatch) | ||
| Err(::from_pest::ConversionError::NoMatchWithInfo { | ||
| current_node: stringify!(#name), | ||
| expected: stringify!(#rule_variant), |
Copilot
AI
Dec 26, 2025
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 actual field uses format!("{:?}", pair.as_rule()) which will produce output like "pair" (including the Debug formatting). Consider whether this produces the desired user-facing error message format. It might be clearer to use pair.as_rule() directly if the Rule type implements Display, or to document that this intentionally uses Debug formatting.
| expected: stringify!(#rule_variant), | |
| expected: stringify!(#rule_variant), | |
| // Intentionally use `Debug` formatting here: pest rule types are only | |
| // required to implement `Debug`, and this representation matches other | |
| // diagnostics in the library. |
| Err(::from_pest::ConversionError::NoMatchWithInfo { | ||
| current_node: stringify!(#name), | ||
| expected: stringify!(#rule_variant), | ||
| actual: format!("{:?}", pair.as_rule()), |
Copilot
AI
Dec 26, 2025
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.
Same issue as in the struct case: format!("{:?}", pair.as_rule()) uses Debug formatting which may not produce the most user-friendly output. Consider whether this is the intended format or if a Display implementation would be more appropriate.
| actual: format!("{:?}", pair.as_rule()), | |
| actual: pair.as_str().to_string(), |
| } => write!( | ||
| f, | ||
| "when converting {current_node}, expected {expected}, but found {actual}" | ||
| ), |
Copilot
AI
Dec 26, 2025
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 Display implementation for NoMatchWithInfo uses string interpolation correctly, but the actual string will contain Debug-formatted output like "pair" (with quotes potentially). Consider if this produces the desired user experience. The error message might read: "when converting Value, expected value, but found pair" which is clear, but if Debug adds quotes it might be: "when converting Value, expected value, but found "pair"" which could be confusing.
| } => write!( | |
| f, | |
| "when converting {current_node}, expected {expected}, but found {actual}" | |
| ), | |
| } => { | |
| let actual_display = if actual.len() >= 2 | |
| && actual.starts_with('\"') | |
| && actual.ends_with('\"') | |
| { | |
| &actual[1..actual.len() - 1] | |
| } else { | |
| actual.as_str() | |
| }; | |
| write!( | |
| f, | |
| "when converting {current_node}, expected {expected}, but found {actual_display}" | |
| ) | |
| } |
| std::marker::PhantomData, | ||
| }; | ||
|
|
||
| /// An error that occurs during conversion. |
Copilot
AI
Dec 26, 2025
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 ConversionError enum no longer derives Copy. This is a breaking change that could affect existing code that relies on copying these error values. Since NoMatchWithInfo and Extraneous now contain String fields which are not Copy, this change is necessary but should be documented as a breaking change. Consider if this impacts the library's semver version.
| /// An error that occurs during conversion. | |
| /// An error that occurs during conversion. | |
| /// | |
| /// This type does **not** implement [`Copy`]. Variants such as | |
| /// [`ConversionError::NoMatchWithInfo`] and [`ConversionError::Extraneous`] | |
| /// contain owned [`String`] values, which cannot be `Copy`. | |
| /// | |
| /// If a previous version of this crate exposed `ConversionError` as `Copy`, | |
| /// migrating code may need to be updated to either: | |
| /// - borrow these values instead of copying them, or | |
| /// - clone them explicitly via [`Clone`]. |
| #extraneous | ||
| Err(::from_pest::ConversionError::Extraneous { | ||
| current_node: stringify!(#name), | ||
| extraneous: format!("{:?}", inner.clone().map(|p| p.as_rule()).collect::<Vec<_>>()), |
Copilot
AI
Dec 26, 2025
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 format!("{:?}", inner.clone().map(|p| p.as_rule()).collect::<Vec<_>>()) expression can be expensive when there are many extraneous tokens, as it clones the iterator and collects into a Vec. Consider using a more efficient approach, such as collecting the rules first and then formatting, or limiting the number of tokens displayed if there could be many extraneous tokens.
| #extraneous | ||
| Err(::from_pest::ConversionError::Extraneous { | ||
| current_node: stringify!(#variant_name), | ||
| extraneous: format!("{:?}", inner.clone().map(|p| p.as_rule()).collect::<Vec<_>>()), |
Copilot
AI
Dec 26, 2025
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 same performance concern as in the struct case: format!("{:?}", inner.clone().map(|p| p.as_rule()).collect::<Vec<_>>()) could be expensive when there are many extraneous tokens. Consider using a more efficient approach or limiting the number of displayed tokens.
| extraneous: format!("{:?}", inner.clone().map(|p| p.as_rule()).collect::<Vec<_>>()), | |
| extraneous: { | |
| let mut s = ::std::string::String::new(); | |
| use ::std::fmt::Write as _; | |
| s.push('['); | |
| for (i, rule) in inner.clone().map(|p| p.as_rule()).enumerate() { | |
| if i > 0 { | |
| s.push_str(", "); | |
| } | |
| if i >= 16 { | |
| s.push_str("..."); | |
| break; | |
| } | |
| let _ = write!(&mut s, "{:?}", rule); | |
| } | |
| s.push(']'); | |
| s | |
| }, |
ConversionError::NoMatchto include information about which token is present (added newNoMatchWithInfovariant)ConversionError::Extraneousto include information about which tokens are extraneousDisplayimplementation for better error messagesis_no_match()helper method to check for both NoMatch and NoMatchWithInfoOption<T>,Vec<T>, and default strategy implementations to useis_no_match()helperOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.