[syntax-errors] Tuple unpacking in for statement iterator clause before Python 3.9 (#16558)

Summary
--

This PR reuses a slightly modified version of the
`check_tuple_unpacking` method added for detecting unpacking in `return`
and `yield` statements to detect the same issue in the iterator clause
of `for` loops.

I ran into the same issue with a bare `for x in *rest: ...` example
(invalid even on Python 3.13) and added it as a comment on
https://github.com/astral-sh/ruff/issues/16520.

I considered just making this an additional `StarTupleKind` variant as
well, but this change was in a different version of Python, so I kept it
separate.

Test Plan
--

New inline tests.
This commit is contained in:
Brent Westbrook 2025-03-13 15:55:17 -04:00 committed by GitHub
parent 27e9d1fe3e
commit 2382fe1f25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 673 additions and 6 deletions

View file

@ -0,0 +1,220 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/for_iter_unpack_py38.py
---
## AST
```
Module(
ModModule {
range: 0..106,
body: [
For(
StmtFor {
range: 43..63,
is_async: false,
target: Name(
ExprName {
range: 47..48,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 52..58,
elts: [
Starred(
ExprStarred {
range: 52..54,
value: Name(
ExprName {
range: 53..54,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 57..58,
id: Name("b"),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
body: [
Expr(
StmtExpr {
range: 60..63,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 60..63,
},
),
},
),
],
orelse: [],
},
),
For(
StmtFor {
range: 64..84,
is_async: false,
target: Name(
ExprName {
range: 68..69,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 74..79,
elts: [
Name(
ExprName {
range: 74..75,
id: Name("a"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 77..79,
value: Name(
ExprName {
range: 78..79,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
body: [
Expr(
StmtExpr {
range: 81..84,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 81..84,
},
),
},
),
],
orelse: [],
},
),
For(
StmtFor {
range: 85..105,
is_async: false,
target: Name(
ExprName {
range: 89..90,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 94..100,
elts: [
Starred(
ExprStarred {
range: 94..96,
value: Name(
ExprName {
range: 95..96,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 98..100,
value: Name(
ExprName {
range: 99..100,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
body: [
Expr(
StmtExpr {
range: 102..105,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 102..105,
},
),
},
),
],
orelse: [],
},
),
],
},
)
```
## Unsupported Syntax Errors
|
1 | # parse_options: {"target-version": "3.8"}
2 | for x in *a, b: ...
| ^^ Syntax Error: Cannot use iterable unpacking in `for` statements on Python 3.8 (syntax was added in Python 3.9)
3 | for x in a, *b: ...
4 | for x in *a, *b: ...
|
|
1 | # parse_options: {"target-version": "3.8"}
2 | for x in *a, b: ...
3 | for x in a, *b: ...
| ^^ Syntax Error: Cannot use iterable unpacking in `for` statements on Python 3.8 (syntax was added in Python 3.9)
4 | for x in *a, *b: ...
|
|
2 | for x in *a, b: ...
3 | for x in a, *b: ...
4 | for x in *a, *b: ...
| ^^ Syntax Error: Cannot use iterable unpacking in `for` statements on Python 3.8 (syntax was added in Python 3.9)
|
|
2 | for x in *a, b: ...
3 | for x in a, *b: ...
4 | for x in *a, *b: ...
| ^^ Syntax Error: Cannot use iterable unpacking in `for` statements on Python 3.8 (syntax was added in Python 3.9)
|

View file

@ -0,0 +1,186 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/for_iter_unpack_py38.py
---
## AST
```
Module(
ModModule {
range: 0..112,
body: [
For(
StmtFor {
range: 43..65,
is_async: false,
target: Name(
ExprName {
range: 47..48,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 52..60,
elts: [
Starred(
ExprStarred {
range: 53..55,
value: Name(
ExprName {
range: 54..55,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 58..59,
id: Name("b"),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
body: [
Expr(
StmtExpr {
range: 62..65,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 62..65,
},
),
},
),
],
orelse: [],
},
),
For(
StmtFor {
range: 66..88,
is_async: false,
target: Name(
ExprName {
range: 70..71,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 75..83,
elts: [
Name(
ExprName {
range: 77..78,
id: Name("a"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 80..82,
value: Name(
ExprName {
range: 81..82,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
body: [
Expr(
StmtExpr {
range: 85..88,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 85..88,
},
),
},
),
],
orelse: [],
},
),
For(
StmtFor {
range: 89..111,
is_async: false,
target: Name(
ExprName {
range: 93..94,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 98..106,
elts: [
Starred(
ExprStarred {
range: 99..101,
value: Name(
ExprName {
range: 100..101,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 103..105,
value: Name(
ExprName {
range: 104..105,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
body: [
Expr(
StmtExpr {
range: 108..111,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 108..111,
},
),
},
),
],
orelse: [],
},
),
],
},
)
```

View file

@ -0,0 +1,186 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/for_iter_unpack_py39.py
---
## AST
```
Module(
ModModule {
range: 0..106,
body: [
For(
StmtFor {
range: 43..63,
is_async: false,
target: Name(
ExprName {
range: 47..48,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 52..58,
elts: [
Starred(
ExprStarred {
range: 52..54,
value: Name(
ExprName {
range: 53..54,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 57..58,
id: Name("b"),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
body: [
Expr(
StmtExpr {
range: 60..63,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 60..63,
},
),
},
),
],
orelse: [],
},
),
For(
StmtFor {
range: 64..84,
is_async: false,
target: Name(
ExprName {
range: 68..69,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 74..79,
elts: [
Name(
ExprName {
range: 74..75,
id: Name("a"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 77..79,
value: Name(
ExprName {
range: 78..79,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
body: [
Expr(
StmtExpr {
range: 81..84,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 81..84,
},
),
},
),
],
orelse: [],
},
),
For(
StmtFor {
range: 85..105,
is_async: false,
target: Name(
ExprName {
range: 89..90,
id: Name("x"),
ctx: Store,
},
),
iter: Tuple(
ExprTuple {
range: 94..100,
elts: [
Starred(
ExprStarred {
range: 94..96,
value: Name(
ExprName {
range: 95..96,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 98..100,
value: Name(
ExprName {
range: 99..100,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
body: [
Expr(
StmtExpr {
range: 102..105,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 102..105,
},
),
},
),
],
orelse: [],
},
),
],
},
)
```