fix: parse errors must not leak ruff AST Debug formatting#390
fix: parse errors must not leak ruff AST Debug formatting#390smigolsmigol wants to merge 3 commits intopydantic:mainfrom
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
samuelcolvin
left a comment
There was a problem hiding this comment.
overall LGTM. a few minor points, more generally I would use insta for tests rather than a custom test function.
| let kind = describe_expr_kind(&other); | ||
| Err(ParseError::syntax( | ||
| format!("Expected name, got {kind}"), |
There was a problem hiding this comment.
| let kind = describe_expr_kind(&other); | |
| Err(ParseError::syntax( | |
| format!("Expected name, got {kind}"), | |
| Err(ParseError::syntax( | |
| format!("Expected name, got {}", describe_expr_kind(&other);), |
| let kind = describe_expr_kind(&other); | ||
| Err(ParseError::syntax( | ||
| format!("invalid unpacking target: {kind}"), |
There was a problem hiding this comment.
| let kind = describe_expr_kind(&other); | |
| Err(ParseError::syntax( | |
| format!("invalid unpacking target: {kind}"), | |
| Err(ParseError::syntax( | |
| format!("invalid unpacking target: {}", describe_expr_kind(&other)), |
|
|
||
| /// Asserts an error message is present, contains the expected prose, | ||
| /// and does not leak any telltale `ruff_python_ast` Debug tokens. | ||
| fn assert_no_debug_leak(msg: Option<&str>, expected_substr: &str) { |
There was a problem hiding this comment.
I would remove this and instead use insta to check the full error text.
|
Addressed all three points : inlined the Thanks for the review. |
parse_identifierandparse_unpack_target_implformat the offending AST node through RustDebugwhen rejecting a target. The resulting error message is the full Debug representation of theruff_python_astnode - struct name,node_index,range, nestedvalue,ctx, etc. Anything that surfacesMontyException::message()reads that text verbatim.What the error looks like today
Same shape for
*x.y = 1,*x[0] = 1,for x.y in [1]: pass. Each dumps the full nested AST struct instead of a human description.Fix
Adds
describe_expr_kind(&AstExpr) -> &'static str: a match over all 33AstExprvariants returning a short human description ("starred expression","attribute","subscript","list comprehension", and so on). The two call sites format this string instead of the Debug of the node.Exhaustive match, no catch-all: if ruff adds a new
Exprvariant, the compiler forces an update instead of silently falling back to Debug.Tests
New in
crates/monty/tests/parse_errors.rs, one per trigger shape:starred_name_target_has_clean_message-*a = [1, 2]starred_attribute_target_has_clean_message-*x.y = 1starred_subscript_target_has_clean_message-*x[0] = 1for_loop_attribute_target_has_clean_message-for x.y in [1]: passEach snapshots the full error message via
insta::assert_snapshot!so any regression that reintroduces Debug formatting of AST nodes (struct names,node_index,range,ctx: Store, etc.) fails the snapshot diff loudly.Not covered
for x.y in [1]: passis valid Python. CPython 3.11 through 3.14 accept it. Monty rejects it atparse_unpack_target_implbecause attribute / subscript targets are not implemented for for-loops. That rejection is a separate issue; this PR only cleans up the error message emitted along the existing path. Separate ticket.CPython's own messages (
"starred assignment target must be in a list or tuple", etc.) are not adopted verbatim here to keep the diff minimal. Switching to CPython prose verbatim is a separate follow-up.