Skip to content

Commit ab1011c

Browse files
authored
[syntax-errors] Single starred assignment target (#17024)
Summary -- Detects starred assignment targets outside of tuples and lists like `*a = (1,)`. This PR only considers assignment statements. I also checked annotated assigment statements, but these give a separate error that we already catch, so I think they're okay not to consider: ```pycon >>> *a: list[int] = [] File "<python-input-72>", line 1 *a: list[int] = [] ^ SyntaxError: invalid syntax ``` Fixes #13759 Test Plan -- New inline tests, plus a new `SemanticSyntaxError` for an existing parser test. I also removed a now-invalid case from an otherwise-valid test fixture. The new semantic error leads to two errors for the case below: ```python *foo() = 42 ``` but this matches [pyright] too. [pyright]: https://pyright-play.net/?code=FQMw9mAUCUAEC8sAsAmAUEA
1 parent a0819f0 commit ab1011c

9 files changed

+343
-116
lines changed

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

+5-9
Original file line numberDiff line numberDiff line change
@@ -551,16 +551,12 @@ impl SemanticSyntaxContext for Checker<'_> {
551551
| SemanticSyntaxErrorKind::DuplicateTypeParameter
552552
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
553553
| SemanticSyntaxErrorKind::IrrefutableCasePattern(_)
554-
| SemanticSyntaxErrorKind::WriteToDebug(_)
555-
if self.settings.preview.is_enabled() =>
556-
{
557-
self.semantic_errors.borrow_mut().push(error);
554+
| SemanticSyntaxErrorKind::SingleStarredAssignment
555+
| SemanticSyntaxErrorKind::WriteToDebug(_) => {
556+
if self.settings.preview.is_enabled() {
557+
self.semantic_errors.borrow_mut().push(error);
558+
}
558559
}
559-
SemanticSyntaxErrorKind::ReboundComprehensionVariable
560-
| SemanticSyntaxErrorKind::DuplicateTypeParameter
561-
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
562-
| SemanticSyntaxErrorKind::IrrefutableCasePattern(_)
563-
| SemanticSyntaxErrorKind::WriteToDebug(_) => {}
564560
}
565561
}
566562
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*a = (1,)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(*a,) = (1,)
2+
*a, = (1,)
3+
[*a] = (1,)

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

-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
# This last group of tests checks that assignments we expect to be parsed
1515
# (including some interesting ones) continue to be parsed successfully.
1616

17-
*foo = 42
18-
1917
[x, y, z] = [1, 2, 3]
2018

2119
(x, y, z) = (1, 2, 3)

crates/ruff_python_parser/src/semantic_errors.rs

+36
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ impl SemanticSyntaxChecker {
7373
Self::duplicate_type_parameter_name(type_params, ctx);
7474
}
7575
}
76+
Stmt::Assign(ast::StmtAssign { targets, .. }) => {
77+
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
78+
// test_ok single_starred_assignment_target
79+
// (*a,) = (1,)
80+
// *a, = (1,)
81+
// [*a] = (1,)
82+
83+
// test_err single_starred_assignment_target
84+
// *a = (1,)
85+
Self::add_error(
86+
ctx,
87+
SemanticSyntaxErrorKind::SingleStarredAssignment,
88+
*range,
89+
);
90+
}
91+
}
7692
_ => {}
7793
}
7894

@@ -437,6 +453,9 @@ impl Display for SemanticSyntaxError {
437453
f.write_str("wildcard makes remaining patterns unreachable")
438454
}
439455
},
456+
SemanticSyntaxErrorKind::SingleStarredAssignment => {
457+
f.write_str("starred assignment target must be in a list or tuple")
458+
}
440459
SemanticSyntaxErrorKind::WriteToDebug(kind) => match kind {
441460
WriteToDebugKind::Store => f.write_str("cannot assign to `__debug__`"),
442461
WriteToDebugKind::Delete(python_version) => {
@@ -522,6 +541,23 @@ pub enum SemanticSyntaxErrorKind {
522541
/// [Python reference]: https://docs.python.org/3/reference/compound_stmts.html#irrefutable-case-blocks
523542
IrrefutableCasePattern(IrrefutablePatternKind),
524543

544+
/// Represents a single starred assignment target outside of a tuple or list.
545+
///
546+
/// ## Examples
547+
///
548+
/// ```python
549+
/// *a = (1,) # SyntaxError
550+
/// ```
551+
///
552+
/// A starred assignment target can only occur within a tuple or list:
553+
///
554+
/// ```python
555+
/// b, *a = 1, 2, 3
556+
/// (*a,) = 1, 2, 3
557+
/// [*a] = 1, 2, 3
558+
/// ```
559+
SingleStarredAssignment,
560+
525561
/// Represents a write to `__debug__`. This includes simple assignments and deletions as well
526562
/// other kinds of statements that can introduce bindings, such as type parameters in functions,
527563
/// classes, and aliases, `match` arms, and imports, among others.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/err/single_starred_assignment_target.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..10,
11+
body: [
12+
Assign(
13+
StmtAssign {
14+
range: 0..9,
15+
targets: [
16+
Starred(
17+
ExprStarred {
18+
range: 0..2,
19+
value: Name(
20+
ExprName {
21+
range: 1..2,
22+
id: Name("a"),
23+
ctx: Store,
24+
},
25+
),
26+
ctx: Store,
27+
},
28+
),
29+
],
30+
value: Tuple(
31+
ExprTuple {
32+
range: 5..9,
33+
elts: [
34+
NumberLiteral(
35+
ExprNumberLiteral {
36+
range: 6..7,
37+
value: Int(
38+
1,
39+
),
40+
},
41+
),
42+
],
43+
ctx: Load,
44+
parenthesized: true,
45+
},
46+
),
47+
},
48+
),
49+
],
50+
},
51+
)
52+
```
53+
## Semantic Syntax Errors
54+
55+
|
56+
1 | *a = (1,)
57+
| ^^ Syntax Error: starred assignment target must be in a list or tuple
58+
|

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

+12
Original file line numberDiff line numberDiff line change
@@ -1690,3 +1690,15 @@ Module(
16901690
42 | (x, foo(), y) = (42, 42, 42)
16911691
| ^^^^^ Syntax Error: Invalid assignment target
16921692
|
1693+
1694+
1695+
## Semantic Syntax Errors
1696+
1697+
|
1698+
37 | None = 42
1699+
38 | ... = 42
1700+
39 | *foo() = 42
1701+
| ^^^^^^ Syntax Error: starred assignment target must be in a list or tuple
1702+
40 | [x, foo(), y] = [42, 42, 42]
1703+
41 | [[a, b], [[42]], d] = [[1, 2], [[3]], 4]
1704+
|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/ok/single_starred_assignment_target.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..36,
11+
body: [
12+
Assign(
13+
StmtAssign {
14+
range: 0..12,
15+
targets: [
16+
Tuple(
17+
ExprTuple {
18+
range: 0..5,
19+
elts: [
20+
Starred(
21+
ExprStarred {
22+
range: 1..3,
23+
value: Name(
24+
ExprName {
25+
range: 2..3,
26+
id: Name("a"),
27+
ctx: Store,
28+
},
29+
),
30+
ctx: Store,
31+
},
32+
),
33+
],
34+
ctx: Store,
35+
parenthesized: true,
36+
},
37+
),
38+
],
39+
value: Tuple(
40+
ExprTuple {
41+
range: 8..12,
42+
elts: [
43+
NumberLiteral(
44+
ExprNumberLiteral {
45+
range: 9..10,
46+
value: Int(
47+
1,
48+
),
49+
},
50+
),
51+
],
52+
ctx: Load,
53+
parenthesized: true,
54+
},
55+
),
56+
},
57+
),
58+
Assign(
59+
StmtAssign {
60+
range: 13..23,
61+
targets: [
62+
Tuple(
63+
ExprTuple {
64+
range: 13..16,
65+
elts: [
66+
Starred(
67+
ExprStarred {
68+
range: 13..15,
69+
value: Name(
70+
ExprName {
71+
range: 14..15,
72+
id: Name("a"),
73+
ctx: Store,
74+
},
75+
),
76+
ctx: Store,
77+
},
78+
),
79+
],
80+
ctx: Store,
81+
parenthesized: false,
82+
},
83+
),
84+
],
85+
value: Tuple(
86+
ExprTuple {
87+
range: 19..23,
88+
elts: [
89+
NumberLiteral(
90+
ExprNumberLiteral {
91+
range: 20..21,
92+
value: Int(
93+
1,
94+
),
95+
},
96+
),
97+
],
98+
ctx: Load,
99+
parenthesized: true,
100+
},
101+
),
102+
},
103+
),
104+
Assign(
105+
StmtAssign {
106+
range: 24..35,
107+
targets: [
108+
List(
109+
ExprList {
110+
range: 24..28,
111+
elts: [
112+
Starred(
113+
ExprStarred {
114+
range: 25..27,
115+
value: Name(
116+
ExprName {
117+
range: 26..27,
118+
id: Name("a"),
119+
ctx: Store,
120+
},
121+
),
122+
ctx: Store,
123+
},
124+
),
125+
],
126+
ctx: Store,
127+
},
128+
),
129+
],
130+
value: Tuple(
131+
ExprTuple {
132+
range: 31..35,
133+
elts: [
134+
NumberLiteral(
135+
ExprNumberLiteral {
136+
range: 32..33,
137+
value: Int(
138+
1,
139+
),
140+
},
141+
),
142+
],
143+
ctx: Load,
144+
parenthesized: true,
145+
},
146+
),
147+
},
148+
),
149+
],
150+
},
151+
)
152+
```

0 commit comments

Comments
 (0)