[syntax-errors] Unparenthesized assignment expressions in sets and indexes (#16404)

## Summary
This PR detects unparenthesized assignment expressions used in set
literals and comprehensions and in sequence indexes. The link to the
release notes in https://github.com/astral-sh/ruff/issues/6591 just has
this entry:
> * Assignment expressions can now be used unparenthesized within set
literals and set comprehensions, as well as in sequence indexes (but not
slices).

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.
This commit is contained in:
Brent Westbrook 2025-03-14 15:06:42 -04:00 committed by GitHub
parent b9d7c36a23
commit 3a32e56445
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1220 additions and 9 deletions

View file

@ -452,11 +452,55 @@ pub enum StarTupleKind {
Yield,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum UnparenthesizedNamedExprKind {
SequenceIndex,
SetLiteral,
SetComprehension,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum UnsupportedSyntaxErrorKind {
Match,
Walrus,
ExceptStar,
/// Represents the use of an unparenthesized named expression (`:=`) in a set literal, set
/// comprehension, or sequence index before Python 3.10.
///
/// ## Examples
///
/// These are allowed on Python 3.10:
///
/// ```python
/// {x := 1, 2, 3} # set literal
/// {last := x for x in range(3)} # set comprehension
/// lst[x := 1] # sequence index
/// ```
///
/// But on Python 3.9 the named expression needs to be parenthesized:
///
/// ```python
/// {(x := 1), 2, 3} # set literal
/// {(last := x) for x in range(3)} # set comprehension
/// lst[(x := 1)] # sequence index
/// ```
///
/// However, unparenthesized named expressions are never allowed in slices:
///
/// ```python
/// lst[x:=1:-1] # syntax error
/// lst[1:x:=1] # syntax error
/// lst[1:3:x:=1] # syntax error
///
/// lst[(x:=1):-1] # ok
/// lst[1:(x:=1)] # ok
/// lst[1:3:(x:=1)] # ok
/// ```
///
/// ## References
///
/// - [Python 3.10 Other Language Changes](https://docs.python.org/3/whatsnew/3.10.html#other-language-changes)
UnparenthesizedNamedExpr(UnparenthesizedNamedExprKind),
/// Represents the use of a parenthesized keyword argument name after Python 3.8.
///
@ -706,6 +750,15 @@ impl Display for UnsupportedSyntaxError {
UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SequenceIndex,
) => "Cannot use unparenthesized assignment expression in a sequence index",
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SetLiteral,
) => "Cannot use unparenthesized assignment expression as an element in a set literal",
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SetComprehension,
) => "Cannot use unparenthesized assignment expression as an element in a set comprehension",
UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
"Cannot use parenthesized keyword argument name"
}
@ -765,6 +818,9 @@ impl UnsupportedSyntaxErrorKind {
UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310),
UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38),
UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311),
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(_) => {
Change::Added(PythonVersion::PY39)
}
UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38),
UnsupportedSyntaxErrorKind::RelaxedDecorator => Change::Added(PythonVersion::PY39),
UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {