-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[parser] Fix panic when parsing IPython escape command expressions
#21480
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
Conversation
|
| }; | ||
|
|
||
| if !matches!(kind, IpyEscapeKind::Magic | IpyEscapeKind::Shell) { | ||
| // This should never occur as the lexer won't allow it. |
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.
Can you explain to me why the lexer emits the Magic and Shell tokens contrary to what the comment says? Should we fix the lexer instead?
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.
That makes more sense, yeah. I'm pushing a change to have it fixed at the lexer level by adding an in_expression_context parameter to lex_ipython_escape_command and preventing Help kind conversion when true (e.g., %foo? stays Magic in expression contexts).
I think it makes sense to keep the parser check as a defensive measure for edge cases like with a,?b, where Help tokens can still be lexed at the start of logical lines. Does this approach look right?
|
Thanks for putting up this PR! I think the problem is in the way the parser is recovering. Considering your example: with a, ?b
?As there's a newline before the final What do you think? This is what I'm referring to: diff --git a/crates/ruff_python_parser/src/parser/mod.rs b/crates/ruff_python_parser/src/parser/mod.rs
index 5f35b84b04..59c1857152 100644
--- a/crates/ruff_python_parser/src/parser/mod.rs
+++ b/crates/ruff_python_parser/src/parser/mod.rs
@@ -1115,7 +1115,12 @@ impl RecoveryContextKind {
TokenKind::Colon => Some(ListTerminatorKind::ErrorRecovery),
_ => None,
},
- WithItemKind::Unparenthesized | WithItemKind::ParenthesizedExpression => p
+ WithItemKind::Unparenthesized => matches!(
+ p.current_token_kind(),
+ TokenKind::Colon | TokenKind::Newline
+ )
+ .then_some(ListTerminatorKind::Regular),
+ WithItemKind::ParenthesizedExpression => p
.at(TokenKind::Colon)
.then_some(ListTerminatorKind::Regular),
},Edit: Oh, I think it should maybe be restricted to |
Ok, I tried this and it seems to be working well and I think this is more correct approach. I've tried it on various code snippets to use parentheses in various places: with (a, ?b)
?with (a, ?b
?with a, ?b
? |
| #[test] | ||
| fn test_ipython_escape_command_in_with_statement() { | ||
| // Regression test for https://github.com/astral-sh/ruff/issues/21465 | ||
| // This should not panic when encountering a `?` escape command in a with statement | ||
| // The original issue was that parsing `with a,?b\n?` would panic when trying to | ||
| // parse `?b` as an expression. Now it should handle it gracefully with an error. | ||
| let source = "with a,?b\n?"; | ||
|
|
||
| // This should not panic - it may return an error, but that's fine | ||
| let parsed = parse(source, ParseOptions::from(Mode::Ipython)); | ||
|
|
||
| // Verify it doesn't panic - check if it parsed or has errors | ||
| match parsed { | ||
| Ok(result) => { | ||
| // If it parsed successfully, snapshot the result | ||
| insta::assert_debug_snapshot!(result.syntax()); | ||
| } | ||
| Err(error) => { | ||
| // If it has errors, that's also fine - the important thing is it didn't panic | ||
| // Snapshot the error to verify it's the expected error type | ||
| insta::assert_debug_snapshot!(error); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_ipython_help_escape_command_as_expression() { | ||
| // Regression test for https://github.com/astral-sh/ruff/issues/21465 | ||
| // Test that parsing a `?` escape command as an expression doesn't panic. | ||
| // Only `%` and `!` escape commands are allowed as expressions. | ||
| let source = "?foo"; | ||
|
|
||
| // Try to parse as an expression - this should not panic | ||
| let parsed = parse_expression(source); | ||
|
|
||
| // Should return an error, not panic | ||
| let error = parsed.unwrap_err(); | ||
| insta::assert_debug_snapshot!(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.
Can you convert those tests to inline tests instead
ruff/crates/ruff_python_parser/CONTRIBUTING.md
Lines 26 to 28 in c424007
| // test_err this_is_the_test_name | |
| // [1, 2 | |
| println!("some rust code"); |
| // This should not panic when encountering a `?` escape command in a with statement | ||
| // The original issue was that parsing `with a,?b\n?` would panic when trying to | ||
| // parse `?b` as an expression. Now it should handle it gracefully with an error. | ||
| let source = "with a,?b\n?"; |
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.
Can you add some case arms or statements coming after with so that it also demonstrates that the parser recovers properly?
dhruvmanila
left a comment
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.
Can you revert the original solution now that the parser recovers correctly? I'd also recommend to add some more test cases especially the ones that contains parentheses.
dhruvmanila
left a comment
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.
Thank you!
|
Hmm, actually the test cases now don't really test the bug which, I think, requires the |
* origin/main: (27 commits) [ty] Add hint about resolved Python version when a user attempts to import a member added on a newer version (#21615) Use release commit for actions/checkout (#21610) [ty] Add failing mdtest for known `Protocol` panic (#21594) [`parser`] Fix panic when parsing IPython escape command expressions (#21480) Fix cargo shear in CI (#21609) Update actions/checkout digest to c2d88d3 (#21601) Update dependency ruff to v0.14.6 (#21603) Update astral-sh/setup-uv action to v7.1.4 (#21602) Update Rust crate clap to v4.5.53 (#21604) Update taiki-e/install-action action to v2.62.56 (#21608) Update Rust crate hashbrown to v0.16.1 (#21605) Update Rust crate indexmap to v2.12.1 (#21606) Update Rust crate syn to v2.0.111 (#21607) [ty] Check method definitions on subclasses for Liskov violations (#21436) [ty] Fix panic for unclosed string literal in type annotation position (#21592) [ty] Fix rendering of unused suppression diagnostic (#21580) [ty] Improve lsp handling of hover/goto on imports (#21572) [ty] Improve diagnostics when a submodule is not available as an attribute on a module-literal type (#21561) [ty] Improve concise diagnostics for invalid exceptions when a user catches a tuple of objects (#21578) [ty] upgrade salsa (#21575) ...
…d-typevar * origin/main: (30 commits) [ty] Double click to insert inlay hint (#21600) [ty] Switch the error code from `unresolved-attribute` to `possibly-missing-attribute` for submodules that may not be available (#21618) [ty] Substitute for `typing.Self` when checking protocol members (#21569) [ty] Don't suggest things that aren't subclasses of `BaseException` after `raise` [ty] Add hint about resolved Python version when a user attempts to import a member added on a newer version (#21615) Use release commit for actions/checkout (#21610) [ty] Add failing mdtest for known `Protocol` panic (#21594) [`parser`] Fix panic when parsing IPython escape command expressions (#21480) Fix cargo shear in CI (#21609) Update actions/checkout digest to c2d88d3 (#21601) Update dependency ruff to v0.14.6 (#21603) Update astral-sh/setup-uv action to v7.1.4 (#21602) Update Rust crate clap to v4.5.53 (#21604) Update taiki-e/install-action action to v2.62.56 (#21608) Update Rust crate hashbrown to v0.16.1 (#21605) Update Rust crate indexmap to v2.12.1 (#21606) Update Rust crate syn to v2.0.111 (#21607) [ty] Check method definitions on subclasses for Liskov violations (#21436) [ty] Fix panic for unclosed string literal in type annotation position (#21592) [ty] Fix rendering of unused suppression diagnostic (#21580) ...
Summary
Fixes a panic when parsing IPython escape commands with
Helpkind (?) in expression contexts. The parser now reports an error instead of panicking.Fixes #21465.
Problem
The parser panicked with
unreachable!()inparse_ipython_escape_command_expressionwhen encountering escape commands withHelpkind (?) in expression contexts, where onlyMagic(%) andShell(!) are allowed.Approach
Replaced the
unreachable!()panic with error handling that adds aParseErrorType::OtherErrorand continues parsing, returning a valid AST node with the error attached.Test Plan
Added
test_ipython_escape_command_in_with_statementandtest_ipython_help_escape_command_as_expressionto verify the fix.