-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[syntax-errors] Unparenthesized assignment expressions in sets and indexes #16404
Conversation
|
5801f20
to
c833f0c
Compare
4fc3761
to
eb63c6e
Compare
c833f0c
to
e57b11e
Compare
eb63c6e
to
47da321
Compare
This PR is the first in a series derived from #16308, each of which add support for detecting one version-related syntax error from #6591. This one should be the largest because it also includes a couple of additional changes: 1. the `syntax_errors!` macro, which makes adding more variants a bit easier 2. the `Parser::add_unsupported_syntax_error` method Otherwise I think the general structure will be the same for each syntax error: * Detecting the error in the parser * Inline parser tests for the new error * New ruff CLI tests for the new error Because of the second point here, this PR is currently stacked on #16357. As noted above, there are new inline parser tests, as well as new ruff CLI tests. Once #16379 is resolved, there should also be new mdtests for red-knot, but this PR does not currently include those.
47da321
to
cb362f4
Compare
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'd prefer to wait for @AlexWaygood to confirm the exact details of this change in CPython (and when it was chagned). We otherwise risk flagging code that works perfectly fine on Python 3.9
@@ -457,6 +458,7 @@ impl Display for UnsupportedSyntaxError { | |||
UnsupportedSyntaxErrorKind::Match => "`match` statement", | |||
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)", | |||
UnsupportedSyntaxErrorKind::ExceptStar => "`except*`", | |||
UnsupportedSyntaxErrorKind::UnparWalrus => "unparenthesized assignment expression", |
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 use NamedExpr
for Walrus expressions internally.
UnsupportedSyntaxErrorKind::UnparWalrus => "unparenthesized assignment expression", | |
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExprInSubscript => "unparenthesized assignment expression", |
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.
Or should this allow all assignment expressions, e.g. even a[x=10]
?
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.
Unless you meant something else and this is a typo, that's an assignment statement which isn't allowed here.
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 think we should just rename it to UnparenthesizedNamedExpr
as it's relevant in other context as well other than subscript (set expression, set comprehension).
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.
Yeah sorry about the names, that seems like a running theme in these reviews. I was trying to make them shorter, but it does seem better just to use explanatory names. I will also add documentation on each of the variants, I should have been doing that as I went too.
|
||
// test_err walrus_slice | ||
// # even after 3.10, an unparenthesized walrus is not allowed in a slice | ||
// lst[x:=1:-1] |
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 we also add a test for when the named expression isn't at the lower
position
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 was a good idea. We actually don't have a nice error message for these like when it's in the first position:
1 | # even after 3.10, an unparenthesized walrus is not allowed in a slice
2 | lst[x:=1:-1]
| ^^^^ Syntax Error: Unparenthesized named expression cannot be used here
3 | lst[1:x:=1]
4 | lst[1:3:x:=1]
|
|
1 | # even after 3.10, an unparenthesized walrus is not allowed in a slice
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
| ^^ Syntax Error: Expected ']', found ':='
4 | lst[1:3:x:=1]
|
|
1 | # even after 3.10, an unparenthesized walrus is not allowed in a slice
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
| ^ Syntax Error: Expected a statement
4 | lst[1:3:x:=1]
|
|
1 | # even after 3.10, an unparenthesized walrus is not allowed in a slice
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
| ^ Syntax Error: Expected a statement
4 | lst[1:3:x:=1]
|
Those are regular ParseError
s though, so I might leave that for a follow-up.
if self.at_ts(NEWLINE_EOF_SET.union([TokenKind::Rsqb, TokenKind::Comma].into())) { | ||
// check this here because if we don't return, we will emit a ParseError 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.
I don't understand this comment. Or rather, I think I find it more confusing than helpful. Maybe we could change the comment. What I understand is that the parsed expression is a subscript if we reach 878?
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 think it might be related to the fact that parse_slice
is used for both sequence indexes (data[x := 1]
) and slices (data[x := 1: 2]
) but the unsupported syntax kind is only to be raised for the former. We have the same check for the latter case down below on 893 which raises a syntax 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.
Yes, @dhruvmanila is right, that's what I meant. I initially checked it right after let lower = ...
and then had to move it into this if
to avoid a duplicate with the ParseError
, so I thought it was worth noting, but I see how it could be confusing. I think I'll just delete this comment because it's pretty clear in hindsight what's going on.
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.
Or we could change it to saying that we have a sequence index here and add a comment to the other branch saying that it's a slice.
| | ||
1 | # parse_options: {"target-version": "3.9"} | ||
2 | {last := x for x in range(3)} | ||
| ^^^^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression on Python 3.9 (syntax was added in Python 3.10) |
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 think the message needs to be more explicit and include sequence index or similar because it otherwise suggests that if x:= 10:
wouldn't be allowed
@@ -457,6 +458,7 @@ impl Display for UnsupportedSyntaxError { | |||
UnsupportedSyntaxErrorKind::Match => "`match` statement", | |||
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)", | |||
UnsupportedSyntaxErrorKind::ExceptStar => "`except*`", | |||
UnsupportedSyntaxErrorKind::UnparWalrus => "unparenthesized assignment expression", |
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.
Unless you meant something else and this is a typo, that's an assignment statement which isn't allowed here.
@@ -457,6 +458,7 @@ impl Display for UnsupportedSyntaxError { | |||
UnsupportedSyntaxErrorKind::Match => "`match` statement", | |||
UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)", | |||
UnsupportedSyntaxErrorKind::ExceptStar => "`except*`", | |||
UnsupportedSyntaxErrorKind::UnparWalrus => "unparenthesized assignment expression", |
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 think we should just rename it to UnparenthesizedNamedExpr
as it's relevant in other context as well other than subscript (set expression, set comprehension).
// test_ok parenthesized_walrus_index_py39 | ||
// # parse_options: {"target-version": "3.9"} | ||
// lst[(x:=1)] | ||
|
||
// test_ok unparenthesized_walrus_index_py310 | ||
// # parse_options: {"target-version": "3.10"} | ||
// lst[x:=1] | ||
|
||
// test_err unparenthesized_walrus_index_py39 | ||
// # parse_options: {"target-version": "3.9"} | ||
// lst[x:=1] | ||
|
||
// test_err walrus_slice | ||
// # even after 3.10, an unparenthesized walrus is not allowed in a slice | ||
// lst[x:=1:-1] |
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 don't think there's anything to change here but just wanted to point out that we test some of these in the filesystem based tests:
- https://github.com/astral-sh/ruff/blob/cb362f4b0a9116867e60e3f08d185ea73c40e452/crates/ruff_python_parser/resources/valid/expressions/subscript.py
- https://github.com/astral-sh/ruff/tree/cb362f4b0a9116867e60e3f08d185ea73c40e452/crates/ruff_python_parser/resources/invalid/expressions/subscript
These are fine here as they're using different parser options.
// indexes", which I take to mean subscripts. See | ||
// <https://docs.python.org/3/whatsnew/3.10.html#other-language-changes>. | ||
|
||
// test_ok parenthesized_walrus_index_py39 |
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 we use "named_expr" instead of "walrus"? I think it'll be useful to maintain consistency in the terminology used internally. What do you think?
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 think that totally makes sense. I left the existing Walrus
variant alone for now but cleaned up all of the walruses in this PR. I'll do another cleanup pass for the already-merged variants separately.
if self.at_ts(NEWLINE_EOF_SET.union([TokenKind::Rsqb, TokenKind::Comma].into())) { | ||
// check this here because if we don't return, we will emit a ParseError 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.
I think it might be related to the fact that parse_slice
is used for both sequence indexes (data[x := 1]
) and slices (data[x := 1: 2]
) but the unsupported syntax kind is only to be raised for the former. We have the same check for the latter case down below on 893 which raises a syntax error.
I think it's python/cpython@87c87b5 that added it and it seems that the change was added in 3.9.1 so it doesn't exists in 3.9.0 (looking at the tags that commit is included in) which is why I guess it's not in the 3.9 release notes? So, it was fixed in python/cpython#23332 and backported to 3.9 in python/cpython#23333. |
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. We just need to make sure about the version cutoff but otherwise this looks good minor some nit comments.
I'm on PTO this week and don't have my laptop on me. But I hear that we have other CPython core devs on the team who might be able to help with this kind of question ;) In general, I think we should definitely follow CPython's implementation rather than CPython's documentation when it comes to the Python version that things like this became legal syntax. That seems like it should avoid false positives. In cases where the syntax was added unofficially for one or two versions prior to it being added to the language spec, it's very possible that people started using the new syntax accidentally even if it wasn't "officially" supported by the language |
I think this PR can wait till you're back ;) Enjoy your PTO |
This also required changing parse_set_expression to take a ParsedExpr instead of an Expr to call is_unparenthesized_named_expr
Thanks for the reviews! I think I have handled all of the comments, but I'm definitely happy to wait for Alex to come back to discuss the version. 3.9 sounds like a safer way to go than 3.10, but there's no rush. |
I handled the merge conflicts and changed the version cutoff to 3.9, so I think this should be good to go. |
* main: [red-knot] Use `try_call_dunder` for augmented assignment (#16717) [red-knot] Document current state of attribute assignment diagnostics (#16746) [red-knot] Case sensitive module resolver (#16521) [red-knot] Very minor simplification of the render tests (#16759) [syntax-errors] Unparenthesized assignment expressions in sets and indexes (#16404) ruff_db: add a new diagnostic renderer ruff_db: add `context` configuration red_knot: plumb through `DiagnosticFormat` to the CLI ruff_db: add concise diagnostic mode [syntax-errors] Star annotations before Python 3.11 (#16545) [syntax-errors] Star expression in index before Python 3.11 (#16544) Ruff 0.11.0 (#16723) [red-knot] Preliminary tests for typing.Final (#15917) [red-knot] fix: improve type inference for binary ops on tuples (#16725) [red-knot] Assignments to attributes (#16705) [`pygrep-hooks`]: Detect file-level suppressions comments without rul… (#16720) Fallback to requires-python in certain cases when target-version is not found (#16721)
Summary
This PR detects unparenthesized assignment expressions used in set literals and comprehensions and in sequence indexes. The link to the release notes in #6591 just has this entry:
with no other information, so hopefully the test cases I came up with cover all of the changes. I also tested these out in the Python REPL and they actually worked in Python 3.9 too. I'm guessing this may be another case that was "formally made part of the language spec in Python 3.10, but usable -- and commonly used -- in Python >=3.9" as @AlexWaygood added to the body of #6591 for context managers. So we may want to change the version cutoff, but I've gone along with the release notes for now.
Test Plan
New inline parser tests and linter CLI tests.