[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

@ -0,0 +1,4 @@
# even after 3.9, an unparenthesized named expression is not allowed in a slice
lst[x:=1:-1]
lst[1:x:=1]
lst[1:3:x:=1]

View file

@ -0,0 +1,3 @@
# parse_options: {"target-version": "3.8"}
# before 3.9, only emit the parse error, not the unsupported syntax error
lst[x:=1:-1]

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.8"}
lst[x:=1]

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.8"}
{last := x for x in range(3)}

View file

@ -0,0 +1,4 @@
# parse_options: {"target-version": "3.8"}
{x := 1, 2, 3}
{1, x := 2, 3}
{1, 2, x := 3}

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.8"}
lst[(x:=1)]

View file

@ -0,0 +1,3 @@
# parse_options: {"target-version": "3.8"}
{(x := 1), 2, 3}
{(last := x) for x in range(3)}

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.9"}
lst[x:=1]

View file

@ -0,0 +1,3 @@
# parse_options: {"target-version": "3.9"}
{x := 1, 2, 3}
{last := x for x in range(3)}

View file

@ -452,11 +452,55 @@ pub enum StarTupleKind {
Yield, Yield,
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum UnparenthesizedNamedExprKind {
SequenceIndex,
SetLiteral,
SetComprehension,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum UnsupportedSyntaxErrorKind { pub enum UnsupportedSyntaxErrorKind {
Match, Match,
Walrus, Walrus,
ExceptStar, 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. /// 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::Match => "Cannot use `match` statement",
UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)", UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`", 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 => { UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
"Cannot use parenthesized keyword argument name" "Cannot use parenthesized keyword argument name"
} }
@ -765,6 +818,9 @@ impl UnsupportedSyntaxErrorKind {
UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310), UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310),
UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38), UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38),
UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311), UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311),
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(_) => {
Change::Added(PythonVersion::PY39)
}
UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38), UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38),
UnsupportedSyntaxErrorKind::RelaxedDecorator => Change::Added(PythonVersion::PY39), UnsupportedSyntaxErrorKind::RelaxedDecorator => Change::Added(PythonVersion::PY39),
UnsupportedSyntaxErrorKind::PositionalOnlyParameter => { UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {

View file

@ -11,7 +11,7 @@ use ruff_python_ast::{
}; };
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use crate::error::StarTupleKind; use crate::error::{StarTupleKind, UnparenthesizedNamedExprKind};
use crate::parser::progress::ParserProgress; use crate::parser::progress::ParserProgress;
use crate::parser::{helpers, FunctionKind, Parser}; use crate::parser::{helpers, FunctionKind, Parser};
use crate::string::{parse_fstring_literal_element, parse_string_literal, StringType}; use crate::string::{parse_fstring_literal_element, parse_string_literal, StringType};
@ -900,15 +900,48 @@ impl<'src> Parser<'src> {
const STEP_END_SET: TokenSet = const STEP_END_SET: TokenSet =
TokenSet::new([TokenKind::Comma, TokenKind::Rsqb]).union(NEWLINE_EOF_SET); TokenSet::new([TokenKind::Comma, TokenKind::Rsqb]).union(NEWLINE_EOF_SET);
// test_err named_expr_slice
// # even after 3.9, an unparenthesized named expression is not allowed in a slice
// lst[x:=1:-1]
// lst[1:x:=1]
// lst[1:3:x:=1]
// test_err named_expr_slice_parse_error
// # parse_options: {"target-version": "3.8"}
// # before 3.9, only emit the parse error, not the unsupported syntax error
// lst[x:=1:-1]
let start = self.node_start(); let start = self.node_start();
let lower = if self.at_expr() { let lower = if self.at_expr() {
let lower = let lower =
self.parse_named_expression_or_higher(ExpressionContext::starred_conditional()); self.parse_named_expression_or_higher(ExpressionContext::starred_conditional());
// This means we're in a subscript.
if self.at_ts(NEWLINE_EOF_SET.union([TokenKind::Rsqb, TokenKind::Comma].into())) { if self.at_ts(NEWLINE_EOF_SET.union([TokenKind::Rsqb, TokenKind::Comma].into())) {
// test_ok parenthesized_named_expr_index_py38
// # parse_options: {"target-version": "3.8"}
// lst[(x:=1)]
// test_ok unparenthesized_named_expr_index_py39
// # parse_options: {"target-version": "3.9"}
// lst[x:=1]
// test_err unparenthesized_named_expr_index_py38
// # parse_options: {"target-version": "3.8"}
// lst[x:=1]
if lower.is_unparenthesized_named_expr() {
self.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SequenceIndex,
),
lower.range(),
);
}
return lower.expr; return lower.expr;
} }
// Now we know we're in a slice.
if !lower.is_parenthesized { if !lower.is_parenthesized {
match lower.expr { match lower.expr {
Expr::Starred(_) => { Expr::Starred(_) => {
@ -1659,6 +1692,26 @@ impl<'src> Parser<'src> {
ParseErrorType::IterableUnpackingInComprehension, ParseErrorType::IterableUnpackingInComprehension,
&key_or_element, &key_or_element,
); );
} else if key_or_element.is_unparenthesized_named_expr() {
// test_ok parenthesized_named_expr_py38
// # parse_options: {"target-version": "3.8"}
// {(x := 1), 2, 3}
// {(last := x) for x in range(3)}
// test_ok unparenthesized_named_expr_py39
// # parse_options: {"target-version": "3.9"}
// {x := 1, 2, 3}
// {last := x for x in range(3)}
// test_err unparenthesized_named_expr_set_comp_py38
// # parse_options: {"target-version": "3.8"}
// {last := x for x in range(3)}
self.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SetComprehension,
),
key_or_element.range(),
);
} }
Expr::SetComp(self.parse_set_comprehension_expression(key_or_element.expr, start)) Expr::SetComp(self.parse_set_comprehension_expression(key_or_element.expr, start))
@ -1697,7 +1750,7 @@ impl<'src> Parser<'src> {
)) ))
} }
} }
_ => Expr::Set(self.parse_set_expression(key_or_element.expr, start)), _ => Expr::Set(self.parse_set_expression(key_or_element, start)),
} }
} }
@ -1847,19 +1900,42 @@ impl<'src> Parser<'src> {
/// Parses a set expression. /// Parses a set expression.
/// ///
/// See: <https://docs.python.org/3/reference/expressions.html#set-displays> /// See: <https://docs.python.org/3/reference/expressions.html#set-displays>
fn parse_set_expression(&mut self, first_element: Expr, start: TextSize) -> ast::ExprSet { fn parse_set_expression(&mut self, first_element: ParsedExpr, start: TextSize) -> ast::ExprSet {
if !self.at_sequence_end() { if !self.at_sequence_end() {
self.expect(TokenKind::Comma); self.expect(TokenKind::Comma);
} }
let mut elts = vec![first_element]; // test_err unparenthesized_named_expr_set_literal_py38
// # parse_options: {"target-version": "3.8"}
// {x := 1, 2, 3}
// {1, x := 2, 3}
// {1, 2, x := 3}
if first_element.is_unparenthesized_named_expr() {
self.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SetLiteral,
),
first_element.range(),
);
}
let mut elts = vec![first_element.expr];
self.parse_comma_separated_list(RecoveryContextKind::SetElements, |parser| { self.parse_comma_separated_list(RecoveryContextKind::SetElements, |parser| {
elts.push( let parsed_expr =
parser parser.parse_named_expression_or_higher(ExpressionContext::starred_bitwise_or());
.parse_named_expression_or_higher(ExpressionContext::starred_bitwise_or())
.expr, if parsed_expr.is_unparenthesized_named_expr() {
); parser.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
UnparenthesizedNamedExprKind::SetLiteral,
),
parsed_expr.range(),
);
}
elts.push(parsed_expr.expr);
}); });
self.expect(TokenKind::Rbrace); self.expect(TokenKind::Rbrace);
@ -2410,6 +2486,11 @@ impl ParsedExpr {
pub(super) const fn is_unparenthesized_starred_expr(&self) -> bool { pub(super) const fn is_unparenthesized_starred_expr(&self) -> bool {
!self.is_parenthesized && self.expr.is_starred_expr() !self.is_parenthesized && self.expr.is_starred_expr()
} }
#[inline]
pub(super) const fn is_unparenthesized_named_expr(&self) -> bool {
!self.is_parenthesized && self.expr.is_named_expr()
}
} }
impl From<Expr> for ParsedExpr { impl From<Expr> for ParsedExpr {

View file

@ -0,0 +1,258 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/named_expr_slice.py
---
## AST
```
Module(
ModModule {
range: 0..119,
body: [
Expr(
StmtExpr {
range: 80..92,
value: Subscript(
ExprSubscript {
range: 80..92,
value: Name(
ExprName {
range: 80..83,
id: Name("lst"),
ctx: Load,
},
),
slice: Slice(
ExprSlice {
range: 84..91,
lower: Some(
Named(
ExprNamed {
range: 84..88,
target: Name(
ExprName {
range: 84..85,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 87..88,
value: Int(
1,
),
},
),
},
),
),
upper: Some(
UnaryOp(
ExprUnaryOp {
range: 89..91,
op: USub,
operand: NumberLiteral(
ExprNumberLiteral {
range: 90..91,
value: Int(
1,
),
},
),
},
),
),
step: None,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 93..100,
value: Subscript(
ExprSubscript {
range: 93..100,
value: Name(
ExprName {
range: 93..96,
id: Name("lst"),
ctx: Load,
},
),
slice: Slice(
ExprSlice {
range: 97..100,
lower: Some(
NumberLiteral(
ExprNumberLiteral {
range: 97..98,
value: Int(
1,
),
},
),
),
upper: Some(
Name(
ExprName {
range: 99..100,
id: Name("x"),
ctx: Load,
},
),
),
step: None,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 102..103,
value: NumberLiteral(
ExprNumberLiteral {
range: 102..103,
value: Int(
1,
),
},
),
},
),
Expr(
StmtExpr {
range: 105..114,
value: Subscript(
ExprSubscript {
range: 105..114,
value: Name(
ExprName {
range: 105..108,
id: Name("lst"),
ctx: Load,
},
),
slice: Slice(
ExprSlice {
range: 109..114,
lower: Some(
NumberLiteral(
ExprNumberLiteral {
range: 109..110,
value: Int(
1,
),
},
),
),
upper: Some(
NumberLiteral(
ExprNumberLiteral {
range: 111..112,
value: Int(
3,
),
},
),
),
step: Some(
Name(
ExprName {
range: 113..114,
id: Name("x"),
ctx: Load,
},
),
),
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 116..117,
value: NumberLiteral(
ExprNumberLiteral {
range: 116..117,
value: Int(
1,
),
},
),
},
),
],
},
)
```
## Errors
|
1 | # even after 3.9, an unparenthesized named expression 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.9, an unparenthesized named expression 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.9, an unparenthesized named expression 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.9, an unparenthesized named expression 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]
|
|
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
4 | lst[1:3:x:=1]
| ^^ Syntax Error: Expected ']', found ':='
|
|
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
4 | lst[1:3:x:=1]
| ^ Syntax Error: Expected a statement
|
|
2 | lst[x:=1:-1]
3 | lst[1:x:=1]
4 | lst[1:3:x:=1]
| ^ Syntax Error: Expected a statement
|

View file

@ -0,0 +1,85 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/named_expr_slice_parse_error.py
---
## AST
```
Module(
ModModule {
range: 0..130,
body: [
Expr(
StmtExpr {
range: 117..129,
value: Subscript(
ExprSubscript {
range: 117..129,
value: Name(
ExprName {
range: 117..120,
id: Name("lst"),
ctx: Load,
},
),
slice: Slice(
ExprSlice {
range: 121..128,
lower: Some(
Named(
ExprNamed {
range: 121..125,
target: Name(
ExprName {
range: 121..122,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 124..125,
value: Int(
1,
),
},
),
},
),
),
upper: Some(
UnaryOp(
ExprUnaryOp {
range: 126..128,
op: USub,
operand: NumberLiteral(
ExprNumberLiteral {
range: 127..128,
value: Int(
1,
),
},
),
},
),
),
step: None,
},
),
ctx: Load,
},
),
},
),
],
},
)
```
## Errors
|
1 | # parse_options: {"target-version": "3.8"}
2 | # before 3.9, only emit the parse error, not the unsupported syntax error
3 | lst[x:=1:-1]
| ^^^^ Syntax Error: Unparenthesized named expression cannot be used here
|

View file

@ -0,0 +1,60 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_index_py38.py
---
## AST
```
Module(
ModModule {
range: 0..53,
body: [
Expr(
StmtExpr {
range: 43..52,
value: Subscript(
ExprSubscript {
range: 43..52,
value: Name(
ExprName {
range: 43..46,
id: Name("lst"),
ctx: Load,
},
),
slice: Named(
ExprNamed {
range: 47..51,
target: Name(
ExprName {
range: 47..48,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 50..51,
value: Int(
1,
),
},
),
},
),
ctx: Load,
},
),
},
),
],
},
)
```
## Unsupported Syntax Errors
|
1 | # parse_options: {"target-version": "3.8"}
2 | lst[x:=1]
| ^^^^ Syntax Error: Cannot use unparenthesized assignment expression in a sequence index on Python 3.8 (syntax was added in Python 3.9)
|

View file

@ -0,0 +1,91 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_comp_py38.py
---
## AST
```
Module(
ModModule {
range: 0..73,
body: [
Expr(
StmtExpr {
range: 43..72,
value: SetComp(
ExprSetComp {
range: 43..72,
elt: Named(
ExprNamed {
range: 44..53,
target: Name(
ExprName {
range: 44..48,
id: Name("last"),
ctx: Store,
},
),
value: Name(
ExprName {
range: 52..53,
id: Name("x"),
ctx: Load,
},
),
},
),
generators: [
Comprehension {
range: 54..71,
target: Name(
ExprName {
range: 58..59,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 63..71,
func: Name(
ExprName {
range: 63..68,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 68..71,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 69..70,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
},
),
],
},
)
```
## Unsupported Syntax Errors
|
1 | # parse_options: {"target-version": "3.8"}
2 | {last := x for x in range(3)}
| ^^^^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set comprehension on Python 3.8 (syntax was added in Python 3.9)
|

View file

@ -0,0 +1,185 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_literal_py38.py
---
## AST
```
Module(
ModModule {
range: 0..88,
body: [
Expr(
StmtExpr {
range: 43..57,
value: Set(
ExprSet {
range: 43..57,
elts: [
Named(
ExprNamed {
range: 44..50,
target: Name(
ExprName {
range: 44..45,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 49..50,
value: Int(
1,
),
},
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 52..53,
value: Int(
2,
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 55..56,
value: Int(
3,
),
},
),
],
},
),
},
),
Expr(
StmtExpr {
range: 58..72,
value: Set(
ExprSet {
range: 58..72,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 59..60,
value: Int(
1,
),
},
),
Named(
ExprNamed {
range: 62..68,
target: Name(
ExprName {
range: 62..63,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 67..68,
value: Int(
2,
),
},
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 70..71,
value: Int(
3,
),
},
),
],
},
),
},
),
Expr(
StmtExpr {
range: 73..87,
value: Set(
ExprSet {
range: 73..87,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 74..75,
value: Int(
1,
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 77..78,
value: Int(
2,
),
},
),
Named(
ExprNamed {
range: 80..86,
target: Name(
ExprName {
range: 80..81,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 85..86,
value: Int(
3,
),
},
),
},
),
],
},
),
},
),
],
},
)
```
## Unsupported Syntax Errors
|
1 | # parse_options: {"target-version": "3.8"}
2 | {x := 1, 2, 3}
| ^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set literal on Python 3.8 (syntax was added in Python 3.9)
3 | {1, x := 2, 3}
4 | {1, 2, x := 3}
|
|
1 | # parse_options: {"target-version": "3.8"}
2 | {x := 1, 2, 3}
3 | {1, x := 2, 3}
| ^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set literal on Python 3.8 (syntax was added in Python 3.9)
4 | {1, 2, x := 3}
|
|
2 | {x := 1, 2, 3}
3 | {1, x := 2, 3}
4 | {1, 2, x := 3}
| ^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set literal on Python 3.8 (syntax was added in Python 3.9)
|

View file

@ -0,0 +1,53 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_index_py38.py
---
## AST
```
Module(
ModModule {
range: 0..55,
body: [
Expr(
StmtExpr {
range: 43..54,
value: Subscript(
ExprSubscript {
range: 43..54,
value: Name(
ExprName {
range: 43..46,
id: Name("lst"),
ctx: Load,
},
),
slice: Named(
ExprNamed {
range: 48..52,
target: Name(
ExprName {
range: 48..49,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 51..52,
value: Int(
1,
),
},
),
},
),
ctx: Load,
},
),
},
),
],
},
)
```

View file

@ -0,0 +1,132 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_py38.py
---
## AST
```
Module(
ModModule {
range: 0..92,
body: [
Expr(
StmtExpr {
range: 43..59,
value: Set(
ExprSet {
range: 43..59,
elts: [
Named(
ExprNamed {
range: 45..51,
target: Name(
ExprName {
range: 45..46,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 50..51,
value: Int(
1,
),
},
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 54..55,
value: Int(
2,
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 57..58,
value: Int(
3,
),
},
),
],
},
),
},
),
Expr(
StmtExpr {
range: 60..91,
value: SetComp(
ExprSetComp {
range: 60..91,
elt: Named(
ExprNamed {
range: 62..71,
target: Name(
ExprName {
range: 62..66,
id: Name("last"),
ctx: Store,
},
),
value: Name(
ExprName {
range: 70..71,
id: Name("x"),
ctx: Load,
},
),
},
),
generators: [
Comprehension {
range: 73..90,
target: Name(
ExprName {
range: 77..78,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 82..90,
func: Name(
ExprName {
range: 82..87,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 87..90,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 88..89,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
},
),
],
},
)
```

View file

@ -0,0 +1,53 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_index_py39.py
---
## AST
```
Module(
ModModule {
range: 0..53,
body: [
Expr(
StmtExpr {
range: 43..52,
value: Subscript(
ExprSubscript {
range: 43..52,
value: Name(
ExprName {
range: 43..46,
id: Name("lst"),
ctx: Load,
},
),
slice: Named(
ExprNamed {
range: 47..51,
target: Name(
ExprName {
range: 47..48,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 50..51,
value: Int(
1,
),
},
),
},
),
ctx: Load,
},
),
},
),
],
},
)
```

View file

@ -0,0 +1,132 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_py39.py
---
## AST
```
Module(
ModModule {
range: 0..88,
body: [
Expr(
StmtExpr {
range: 43..57,
value: Set(
ExprSet {
range: 43..57,
elts: [
Named(
ExprNamed {
range: 44..50,
target: Name(
ExprName {
range: 44..45,
id: Name("x"),
ctx: Store,
},
),
value: NumberLiteral(
ExprNumberLiteral {
range: 49..50,
value: Int(
1,
),
},
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 52..53,
value: Int(
2,
),
},
),
NumberLiteral(
ExprNumberLiteral {
range: 55..56,
value: Int(
3,
),
},
),
],
},
),
},
),
Expr(
StmtExpr {
range: 58..87,
value: SetComp(
ExprSetComp {
range: 58..87,
elt: Named(
ExprNamed {
range: 59..68,
target: Name(
ExprName {
range: 59..63,
id: Name("last"),
ctx: Store,
},
),
value: Name(
ExprName {
range: 67..68,
id: Name("x"),
ctx: Load,
},
),
},
),
generators: [
Comprehension {
range: 69..86,
target: Name(
ExprName {
range: 73..74,
id: Name("x"),
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 78..86,
func: Name(
ExprName {
range: 78..83,
id: Name("range"),
ctx: Load,
},
),
arguments: Arguments {
range: 83..86,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 84..85,
value: Int(
3,
),
},
),
],
keywords: [],
},
},
),
ifs: [],
is_async: false,
},
],
},
),
},
),
],
},
)
```