Skip to content

Commit d8a185e

Browse files
ntBremaxmynter
authored andcommitted
[syntax-errors] Starred expressions in return, yield, and for (astral-sh#17134)
Summary -- Fixes astral-sh#16520 by flagging single, starred expressions in `return`, `yield`, and `for` statements. I thought `yield from` would also be included here, but that error is emitted by the CPython parser: ```pycon >>> ast.parse("def f(): yield from *x") Traceback (most recent call last): File "<python-input-214>", line 1, in <module> ast.parse("def f(): yield from *x") ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/ast.py", line 54, in parse return compile(source, filename, mode, flags, _feature_version=feature_version, optimize=optimize) File "<unknown>", line 1 def f(): yield from *x ^ SyntaxError: invalid syntax ``` And we also already catch it in our parser. Test Plan -- New inline tests and updates to existing tests.
1 parent d09d364 commit d8a185e

20 files changed

+679
-435
lines changed

crates/ruff_linter/src/checkers/ast/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,8 @@ impl SemanticSyntaxContext for Checker<'_> {
552552
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
553553
| SemanticSyntaxErrorKind::IrrefutableCasePattern(_)
554554
| SemanticSyntaxErrorKind::SingleStarredAssignment
555-
| SemanticSyntaxErrorKind::WriteToDebug(_) => {
555+
| SemanticSyntaxErrorKind::WriteToDebug(_)
556+
| SemanticSyntaxErrorKind::InvalidStarExpression => {
556557
if self.settings.preview.is_enabled() {
557558
self.semantic_errors.borrow_mut().push(error);
558559
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
for _ in *x: ...
2+
for *x in xs: ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def f(): return *x
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def f(): yield *x
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def f(): yield (*x,)
2+
def f(): return (*x,)
3+
for _ in (*x,): ...
4+
for (*x,) in xs: ...

crates/ruff_python_parser/resources/valid/expressions/yield.py

-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,5 @@
1010
yield (x, y)
1111
yield x == y
1212
yield (x := 1)
13-
yield *y
1413
yield x, *y
1514
yield *x,
16-
yield *x | y

crates/ruff_python_parser/resources/valid/statement/for.py

-10
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,9 @@
2222
for (a, b) in iter:
2323
pass
2424

25-
for target in *x.attr:
26-
pass
27-
2825
for target in [1, 2]:
2926
pass
3027

31-
for *target in a, b, c,:
32-
pass
33-
else:
34-
pass
35-
36-
for target in *x | y: ...
37-
for target in *await x: ...
3828
for target in await x: ...
3929
for target in lambda x: x: ...
4030
for target in x if True else y: ...

crates/ruff_python_parser/resources/valid/statement/return.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
return
22
return x
3-
return *x
4-
return *x | y
53
return *x, *y
64
return (x := 1)
75
return None

crates/ruff_python_parser/src/semantic_errors.rs

+53
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,42 @@ impl SemanticSyntaxChecker {
8989
);
9090
}
9191
}
92+
Stmt::Return(ast::StmtReturn {
93+
value: Some(value), ..
94+
}) => {
95+
// test_err single_star_return
96+
// def f(): return *x
97+
Self::invalid_star_expression(value, ctx);
98+
}
99+
Stmt::For(ast::StmtFor { target, iter, .. }) => {
100+
// test_err single_star_for
101+
// for _ in *x: ...
102+
// for *x in xs: ...
103+
Self::invalid_star_expression(target, ctx);
104+
Self::invalid_star_expression(iter, ctx);
105+
}
92106
_ => {}
93107
}
94108

95109
Self::debug_shadowing(stmt, ctx);
96110
}
97111

112+
/// Emit a [`SemanticSyntaxErrorKind::InvalidStarExpression`] if `expr` is starred.
113+
fn invalid_star_expression<Ctx: SemanticSyntaxContext>(expr: &Expr, ctx: &Ctx) {
114+
// test_ok single_star_in_tuple
115+
// def f(): yield (*x,)
116+
// def f(): return (*x,)
117+
// for _ in (*x,): ...
118+
// for (*x,) in xs: ...
119+
if expr.is_starred_expr() {
120+
Self::add_error(
121+
ctx,
122+
SemanticSyntaxErrorKind::InvalidStarExpression,
123+
expr.range(),
124+
);
125+
}
126+
}
127+
98128
/// Check for [`SemanticSyntaxErrorKind::WriteToDebug`] in `stmt`.
99129
fn debug_shadowing<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
100130
match stmt {
@@ -368,6 +398,13 @@ impl SemanticSyntaxChecker {
368398
};
369399
}
370400
}
401+
Expr::Yield(ast::ExprYield {
402+
value: Some(value), ..
403+
}) => {
404+
// test_err single_star_yield
405+
// def f(): yield *x
406+
Self::invalid_star_expression(value, ctx);
407+
}
371408
_ => {}
372409
}
373410
}
@@ -462,6 +499,9 @@ impl Display for SemanticSyntaxError {
462499
write!(f, "cannot delete `__debug__` on Python {python_version} (syntax was removed in 3.9)")
463500
}
464501
},
502+
SemanticSyntaxErrorKind::InvalidStarExpression => {
503+
f.write_str("can't use starred expression here")
504+
}
465505
}
466506
}
467507
}
@@ -575,6 +615,19 @@ pub enum SemanticSyntaxErrorKind {
575615
///
576616
/// [BPO 45000]: https://github.com/python/cpython/issues/89163
577617
WriteToDebug(WriteToDebugKind),
618+
619+
/// Represents the use of a starred expression in an invalid location, such as a `return` or
620+
/// `yield` statement.
621+
///
622+
/// ## Examples
623+
///
624+
/// ```python
625+
/// def f(): return *x
626+
/// def f(): yield *x
627+
/// for _ in *x: ...
628+
/// for *x in xs: ...
629+
/// ```
630+
InvalidStarExpression,
578631
}
579632

580633
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__yield__star_expression.py.snap

+11
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,14 @@ Module(
111111
4 | yield *x and y, z
112112
| ^^^^^^^ Syntax Error: Boolean expression cannot be used here
113113
|
114+
115+
116+
## Semantic Syntax Errors
117+
118+
|
119+
1 | # Cannot use starred expression here
120+
2 | yield (*x)
121+
| ^^ Syntax Error: can't use starred expression here
122+
3 |
123+
4 | yield *x and y, z
124+
|

crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_iter_expr.py.snap

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
source: crates/ruff_python_parser/tests/fixtures.rs
33
input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_invalid_iter_expr.py
4-
snapshot_kind: text
54
---
65
## AST
76

@@ -182,3 +181,13 @@ Module(
182181
3 | for target in x := 1: ...
183182
| ^ Syntax Error: Invalid annotated assignment target
184183
|
184+
185+
186+
## Semantic Syntax Errors
187+
188+
|
189+
1 | for x in *a and b: ...
190+
| ^^^^^^^^ Syntax Error: can't use starred expression here
191+
2 | for x in yield a: ...
192+
3 | for target in x := 1: ...
193+
|

crates/ruff_python_parser/tests/snapshots/invalid_syntax@for_stmt_invalid_target.py.snap

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
source: crates/ruff_python_parser/tests/fixtures.rs
33
input_file: crates/ruff_python_parser/resources/inline/err/for_stmt_invalid_target.py
4-
snapshot_kind: text
54
---
65
## AST
76

@@ -462,3 +461,25 @@ Module(
462461
7 | for [x, 1, y, *["a"]] in z: ...
463462
| ^^^ Syntax Error: Invalid assignment target
464463
|
464+
465+
466+
## Semantic Syntax Errors
467+
468+
|
469+
1 | for 1 in x: ...
470+
2 | for "a" in x: ...
471+
3 | for *x and y in z: ...
472+
| ^^^^^^^^ Syntax Error: can't use starred expression here
473+
4 | for *x | y in z: ...
474+
5 | for await x in z: ...
475+
|
476+
477+
478+
|
479+
2 | for "a" in x: ...
480+
3 | for *x and y in z: ...
481+
4 | for *x | y in z: ...
482+
| ^^^^^^ Syntax Error: can't use starred expression here
483+
5 | for await x in z: ...
484+
6 | for yield x in y: ...
485+
|

crates/ruff_python_parser/tests/snapshots/invalid_syntax@return_stmt_invalid_expr.py.snap

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
source: crates/ruff_python_parser/tests/fixtures.rs
33
input_file: crates/ruff_python_parser/resources/inline/err/return_stmt_invalid_expr.py
4-
snapshot_kind: text
54
---
65
## AST
76

@@ -181,3 +180,21 @@ Module(
181180
5 | return *x and y
182181
| ^^^^^^^ Syntax Error: Boolean expression cannot be used here
183182
|
183+
184+
185+
## Semantic Syntax Errors
186+
187+
|
188+
1 | return *
189+
| ^ Syntax Error: can't use starred expression here
190+
2 | return yield x
191+
3 | return yield from x
192+
|
193+
194+
195+
|
196+
3 | return yield from x
197+
4 | return x := 1
198+
5 | return *x and y
199+
| ^^^^^^^^ Syntax Error: can't use starred expression here
200+
|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/err/single_star_for.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..35,
11+
body: [
12+
For(
13+
StmtFor {
14+
range: 0..16,
15+
is_async: false,
16+
target: Name(
17+
ExprName {
18+
range: 4..5,
19+
id: Name("_"),
20+
ctx: Store,
21+
},
22+
),
23+
iter: Starred(
24+
ExprStarred {
25+
range: 9..11,
26+
value: Name(
27+
ExprName {
28+
range: 10..11,
29+
id: Name("x"),
30+
ctx: Load,
31+
},
32+
),
33+
ctx: Load,
34+
},
35+
),
36+
body: [
37+
Expr(
38+
StmtExpr {
39+
range: 13..16,
40+
value: EllipsisLiteral(
41+
ExprEllipsisLiteral {
42+
range: 13..16,
43+
},
44+
),
45+
},
46+
),
47+
],
48+
orelse: [],
49+
},
50+
),
51+
For(
52+
StmtFor {
53+
range: 17..34,
54+
is_async: false,
55+
target: Starred(
56+
ExprStarred {
57+
range: 21..23,
58+
value: Name(
59+
ExprName {
60+
range: 22..23,
61+
id: Name("x"),
62+
ctx: Store,
63+
},
64+
),
65+
ctx: Store,
66+
},
67+
),
68+
iter: Name(
69+
ExprName {
70+
range: 27..29,
71+
id: Name("xs"),
72+
ctx: Load,
73+
},
74+
),
75+
body: [
76+
Expr(
77+
StmtExpr {
78+
range: 31..34,
79+
value: EllipsisLiteral(
80+
ExprEllipsisLiteral {
81+
range: 31..34,
82+
},
83+
),
84+
},
85+
),
86+
],
87+
orelse: [],
88+
},
89+
),
90+
],
91+
},
92+
)
93+
```
94+
## Semantic Syntax Errors
95+
96+
|
97+
1 | for _ in *x: ...
98+
| ^^ Syntax Error: can't use starred expression here
99+
2 | for *x in xs: ...
100+
|
101+
102+
103+
|
104+
1 | for _ in *x: ...
105+
2 | for *x in xs: ...
106+
| ^^ Syntax Error: can't use starred expression here
107+
|

0 commit comments

Comments
 (0)