[syntax-errors] Detect single starred expression assignment x = *y (#17624)

## Summary

Part of #17412

Starred expressions cannot be used as values in assignment expressions.
Add a new semantic syntax error to catch such instances.
Note that we already have
`ParseErrorType::InvalidStarredExpressionUsage` to catch some starred
expression errors during parsing, but that does not cover top level
assignment expressions.

## Test Plan

- Added new inline tests for the new rule
- Found some examples marked as "valid" in existing tests (`_ = *data`),
which are not really valid (per this new rule) and updated them
- There was an existing inline test - `assign_stmt_invalid_value_expr`
which had instances of `*` expression which would be deemed invalid by
this new rule. Converted these to tuples, so that they do not trigger
this new rule.
This commit is contained in:
Abhijeet Prasad Bodas 2025-05-01 00:34:00 +05:30 committed by GitHub
parent f31b1c695c
commit 0eeb02c0c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 661 additions and 217 deletions

View file

@ -1,5 +1,5 @@
x = *a and b x = (*a and b,)
x = *yield x x = (42, *yield x)
x = *yield from x x = (42, *yield from x)
x = *lambda x: x x = (*lambda x: x,)
x = x := 1 x = x := 1

View file

@ -0,0 +1,4 @@
_ = *[42]
_ = *{42}
_ = *list()
_ = *(p + q)

View file

@ -0,0 +1,4 @@
_ = 4
_ = [4]
_ = (*[1],)
_ = *[1],

View file

@ -37,7 +37,7 @@ foo.bar = 42
foo = 42 foo = 42
[] = *data [] = (*data,)
() = *data () = (*data,)
a, b = ab a, b = ab
a = b = c a = b = c

View file

@ -1127,10 +1127,10 @@ impl<'src> Parser<'src> {
// a + b // a + b
// test_err assign_stmt_invalid_value_expr // test_err assign_stmt_invalid_value_expr
// x = *a and b // x = (*a and b,)
// x = *yield x // x = (42, *yield x)
// x = *yield from x // x = (42, *yield from x)
// x = *lambda x: x // x = (*lambda x: x,)
// x = x := 1 // x = x := 1
let mut value = let mut value =

View file

@ -90,7 +90,7 @@ impl SemanticSyntaxChecker {
Self::duplicate_type_parameter_name(type_params, ctx); Self::duplicate_type_parameter_name(type_params, ctx);
} }
} }
Stmt::Assign(ast::StmtAssign { targets, .. }) => { Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() { if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
// test_ok single_starred_assignment_target // test_ok single_starred_assignment_target
// (*a,) = (1,) // (*a,) = (1,)
@ -105,6 +105,19 @@ impl SemanticSyntaxChecker {
*range, *range,
); );
} }
// test_ok assign_stmt_starred_expr_value
// _ = 4
// _ = [4]
// _ = (*[1],)
// _ = *[1],
// test_err assign_stmt_starred_expr_value
// _ = *[42]
// _ = *{42}
// _ = *list()
// _ = *(p + q)
Self::invalid_star_expression(value, ctx);
} }
Stmt::Return(ast::StmtReturn { value, range }) => { Stmt::Return(ast::StmtReturn { value, range }) => {
if let Some(value) = value { if let Some(value) = value {

View file

@ -1,18 +1,17 @@
--- ---
source: crates/ruff_python_parser/tests/fixtures.rs source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_invalid_value_expr.py input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_invalid_value_expr.py
snapshot_kind: text
--- ---
## AST ## AST
``` ```
Module( Module(
ModModule { ModModule {
range: 0..72, range: 0..90,
body: [ body: [
Assign( Assign(
StmtAssign { StmtAssign {
range: 0..12, range: 0..15,
targets: [ targets: [
Name( Name(
ExprName { ExprName {
@ -22,164 +21,216 @@ Module(
}, },
), ),
], ],
value: Starred( value: Tuple(
ExprStarred { ExprTuple {
range: 4..12, range: 4..15,
value: BoolOp( elts: [
ExprBoolOp { Starred(
range: 5..12, ExprStarred {
op: And, range: 5..13,
values: [ value: BoolOp(
Name( ExprBoolOp {
ExprName { range: 6..13,
range: 5..6, op: And,
id: Name("a"), values: [
ctx: Load, Name(
}, ExprName {
), range: 6..7,
Name( id: Name("a"),
ExprName { ctx: Load,
range: 11..12,
id: Name("b"),
ctx: Load,
},
),
],
},
),
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 13..25,
targets: [
Name(
ExprName {
range: 13..14,
id: Name("x"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 17..25,
value: Yield(
ExprYield {
range: 18..25,
value: Some(
Name(
ExprName {
range: 24..25,
id: Name("x"),
ctx: Load,
},
),
),
},
),
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 26..43,
targets: [
Name(
ExprName {
range: 26..27,
id: Name("x"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 30..43,
value: YieldFrom(
ExprYieldFrom {
range: 31..43,
value: Name(
ExprName {
range: 42..43,
id: Name("x"),
ctx: Load,
},
),
},
),
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 44..60,
targets: [
Name(
ExprName {
range: 44..45,
id: Name("x"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 48..60,
value: Lambda(
ExprLambda {
range: 49..60,
parameters: Some(
Parameters {
range: 56..57,
posonlyargs: [],
args: [
ParameterWithDefault {
range: 56..57,
parameter: Parameter {
range: 56..57,
name: Identifier {
id: Name("x"),
range: 56..57,
}, },
annotation: None, ),
}, Name(
default: None, ExprName {
}, range: 12..13,
], id: Name("b"),
vararg: None, ctx: Load,
kwonlyargs: [], },
kwarg: None, ),
}, ],
), },
body: Name( ),
ExprName { ctx: Load,
range: 59..60, },
id: Name("x"), ),
ctx: Load, ],
},
),
},
),
ctx: Load, ctx: Load,
parenthesized: true,
}, },
), ),
}, },
), ),
Assign( Assign(
StmtAssign { StmtAssign {
range: 61..66, range: 16..34,
targets: [ targets: [
Name( Name(
ExprName { ExprName {
range: 61..62, range: 16..17,
id: Name("x"),
ctx: Store,
},
),
],
value: Tuple(
ExprTuple {
range: 20..34,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 21..23,
value: Int(
42,
),
},
),
Starred(
ExprStarred {
range: 25..33,
value: Yield(
ExprYield {
range: 26..33,
value: Some(
Name(
ExprName {
range: 32..33,
id: Name("x"),
ctx: Load,
},
),
),
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
},
),
Assign(
StmtAssign {
range: 35..58,
targets: [
Name(
ExprName {
range: 35..36,
id: Name("x"),
ctx: Store,
},
),
],
value: Tuple(
ExprTuple {
range: 39..58,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 40..42,
value: Int(
42,
),
},
),
Starred(
ExprStarred {
range: 44..57,
value: YieldFrom(
ExprYieldFrom {
range: 45..57,
value: Name(
ExprName {
range: 56..57,
id: Name("x"),
ctx: Load,
},
),
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
},
),
Assign(
StmtAssign {
range: 59..78,
targets: [
Name(
ExprName {
range: 59..60,
id: Name("x"),
ctx: Store,
},
),
],
value: Tuple(
ExprTuple {
range: 63..78,
elts: [
Starred(
ExprStarred {
range: 64..76,
value: Lambda(
ExprLambda {
range: 65..76,
parameters: Some(
Parameters {
range: 72..73,
posonlyargs: [],
args: [
ParameterWithDefault {
range: 72..73,
parameter: Parameter {
range: 72..73,
name: Identifier {
id: Name("x"),
range: 72..73,
},
annotation: None,
},
default: None,
},
],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
),
body: Name(
ExprName {
range: 75..76,
id: Name("x"),
ctx: Load,
},
),
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
},
),
Assign(
StmtAssign {
range: 79..84,
targets: [
Name(
ExprName {
range: 79..80,
id: Name("x"), id: Name("x"),
ctx: Store, ctx: Store,
}, },
@ -187,7 +238,7 @@ Module(
], ],
value: Name( value: Name(
ExprName { ExprName {
range: 65..66, range: 83..84,
id: Name("x"), id: Name("x"),
ctx: Load, ctx: Load,
}, },
@ -196,10 +247,10 @@ Module(
), ),
Expr( Expr(
StmtExpr { StmtExpr {
range: 70..71, range: 88..89,
value: NumberLiteral( value: NumberLiteral(
ExprNumberLiteral { ExprNumberLiteral {
range: 70..71, range: 88..89,
value: Int( value: Int(
1, 1,
), ),
@ -214,44 +265,44 @@ Module(
## Errors ## Errors
| |
1 | x = *a and b 1 | x = (*a and b,)
| ^^^^^^^ Syntax Error: Boolean expression cannot be used here | ^^^^^^^ Syntax Error: Boolean expression cannot be used here
2 | x = *yield x 2 | x = (42, *yield x)
3 | x = *yield from x 3 | x = (42, *yield from x)
| |
| |
1 | x = *a and b 1 | x = (*a and b,)
2 | x = *yield x 2 | x = (42, *yield x)
| ^^^^^^^ Syntax Error: Yield expression cannot be used here | ^^^^^^^ Syntax Error: Yield expression cannot be used here
3 | x = *yield from x 3 | x = (42, *yield from x)
4 | x = *lambda x: x 4 | x = (*lambda x: x,)
| |
| |
1 | x = *a and b 1 | x = (*a and b,)
2 | x = *yield x 2 | x = (42, *yield x)
3 | x = *yield from x 3 | x = (42, *yield from x)
| ^^^^^^^^^^^^ Syntax Error: Yield expression cannot be used here | ^^^^^^^^^^^^ Syntax Error: Yield expression cannot be used here
4 | x = *lambda x: x 4 | x = (*lambda x: x,)
5 | x = x := 1 5 | x = x := 1
| |
| |
2 | x = *yield x 2 | x = (42, *yield x)
3 | x = *yield from x 3 | x = (42, *yield from x)
4 | x = *lambda x: x 4 | x = (*lambda x: x,)
| ^^^^^^^^^^^ Syntax Error: Lambda expression cannot be used here | ^^^^^^^^^^^ Syntax Error: Lambda expression cannot be used here
5 | x = x := 1 5 | x = x := 1
| |
| |
3 | x = *yield from x 3 | x = (42, *yield from x)
4 | x = *lambda x: x 4 | x = (*lambda x: x,)
5 | x = x := 1 5 | x = x := 1
| ^^ Syntax Error: Expected a statement | ^^ Syntax Error: Expected a statement
| |

View file

@ -0,0 +1,197 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/assign_stmt_starred_expr_value.py
---
## AST
```
Module(
ModModule {
range: 0..45,
body: [
Assign(
StmtAssign {
range: 0..9,
targets: [
Name(
ExprName {
range: 0..1,
id: Name("_"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 4..9,
value: List(
ExprList {
range: 5..9,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 6..8,
value: Int(
42,
),
},
),
],
ctx: Load,
},
),
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 10..19,
targets: [
Name(
ExprName {
range: 10..11,
id: Name("_"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 14..19,
value: Set(
ExprSet {
range: 15..19,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 16..18,
value: Int(
42,
),
},
),
],
},
),
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 20..31,
targets: [
Name(
ExprName {
range: 20..21,
id: Name("_"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 24..31,
value: Call(
ExprCall {
range: 25..31,
func: Name(
ExprName {
range: 25..29,
id: Name("list"),
ctx: Load,
},
),
arguments: Arguments {
range: 29..31,
args: [],
keywords: [],
},
},
),
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 32..44,
targets: [
Name(
ExprName {
range: 32..33,
id: Name("_"),
ctx: Store,
},
),
],
value: Starred(
ExprStarred {
range: 36..44,
value: BinOp(
ExprBinOp {
range: 38..43,
left: Name(
ExprName {
range: 38..39,
id: Name("p"),
ctx: Load,
},
),
op: Add,
right: Name(
ExprName {
range: 42..43,
id: Name("q"),
ctx: Load,
},
),
},
),
ctx: Load,
},
),
},
),
],
},
)
```
## Semantic Syntax Errors
|
1 | _ = *[42]
| ^^^^^ Syntax Error: can't use starred expression here
2 | _ = *{42}
3 | _ = *list()
|
|
1 | _ = *[42]
2 | _ = *{42}
| ^^^^^ Syntax Error: can't use starred expression here
3 | _ = *list()
4 | _ = *(p + q)
|
|
1 | _ = *[42]
2 | _ = *{42}
3 | _ = *list()
| ^^^^^^^ Syntax Error: can't use starred expression here
4 | _ = *(p + q)
|
|
2 | _ = *{42}
3 | _ = *list()
4 | _ = *(p + q)
| ^^^^^^^^ Syntax Error: can't use starred expression here
|

View file

@ -0,0 +1,157 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/assign_stmt_starred_expr_value.py
---
## AST
```
Module(
ModModule {
range: 0..36,
body: [
Assign(
StmtAssign {
range: 0..5,
targets: [
Name(
ExprName {
range: 0..1,
id: Name("_"),
ctx: Store,
},
),
],
value: NumberLiteral(
ExprNumberLiteral {
range: 4..5,
value: Int(
4,
),
},
),
},
),
Assign(
StmtAssign {
range: 6..13,
targets: [
Name(
ExprName {
range: 6..7,
id: Name("_"),
ctx: Store,
},
),
],
value: List(
ExprList {
range: 10..13,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 11..12,
value: Int(
4,
),
},
),
],
ctx: Load,
},
),
},
),
Assign(
StmtAssign {
range: 14..25,
targets: [
Name(
ExprName {
range: 14..15,
id: Name("_"),
ctx: Store,
},
),
],
value: Tuple(
ExprTuple {
range: 18..25,
elts: [
Starred(
ExprStarred {
range: 19..23,
value: List(
ExprList {
range: 20..23,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 21..22,
value: Int(
1,
),
},
),
],
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: true,
},
),
},
),
Assign(
StmtAssign {
range: 26..35,
targets: [
Name(
ExprName {
range: 26..27,
id: Name("_"),
ctx: Store,
},
),
],
value: Tuple(
ExprTuple {
range: 30..35,
elts: [
Starred(
ExprStarred {
range: 30..34,
value: List(
ExprList {
range: 31..34,
elts: [
NumberLiteral(
ExprNumberLiteral {
range: 32..33,
value: Int(
1,
),
},
),
],
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
},
),
],
},
)
```

View file

@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/valid/statement/assignment.py
``` ```
Module( Module(
ModModule { ModModule {
range: 0..723, range: 0..729,
body: [ body: [
Assign( Assign(
StmtAssign { StmtAssign {
@ -802,7 +802,7 @@ Module(
), ),
Assign( Assign(
StmtAssign { StmtAssign {
range: 682..692, range: 682..695,
targets: [ targets: [
List( List(
ExprList { ExprList {
@ -812,67 +812,85 @@ Module(
}, },
), ),
], ],
value: Starred( value: Tuple(
ExprStarred { ExprTuple {
range: 687..692, range: 687..695,
value: Name( elts: [
ExprName { Starred(
range: 688..692, ExprStarred {
id: Name("data"), range: 688..693,
ctx: Load, value: Name(
}, ExprName {
), range: 689..693,
id: Name("data"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load, ctx: Load,
parenthesized: true,
}, },
), ),
}, },
), ),
Assign( Assign(
StmtAssign { StmtAssign {
range: 693..703, range: 696..709,
targets: [ targets: [
Tuple( Tuple(
ExprTuple { ExprTuple {
range: 693..695, range: 696..698,
elts: [], elts: [],
ctx: Store, ctx: Store,
parenthesized: true, parenthesized: true,
}, },
), ),
], ],
value: Starred( value: Tuple(
ExprStarred { ExprTuple {
range: 698..703, range: 701..709,
value: Name( elts: [
ExprName { Starred(
range: 699..703, ExprStarred {
id: Name("data"), range: 702..707,
ctx: Load, value: Name(
}, ExprName {
), range: 703..707,
id: Name("data"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load, ctx: Load,
parenthesized: true,
}, },
), ),
}, },
), ),
Assign( Assign(
StmtAssign { StmtAssign {
range: 704..713, range: 710..719,
targets: [ targets: [
Tuple( Tuple(
ExprTuple { ExprTuple {
range: 704..708, range: 710..714,
elts: [ elts: [
Name( Name(
ExprName { ExprName {
range: 704..705, range: 710..711,
id: Name("a"), id: Name("a"),
ctx: Store, ctx: Store,
}, },
), ),
Name( Name(
ExprName { ExprName {
range: 707..708, range: 713..714,
id: Name("b"), id: Name("b"),
ctx: Store, ctx: Store,
}, },
@ -885,7 +903,7 @@ Module(
], ],
value: Name( value: Name(
ExprName { ExprName {
range: 711..713, range: 717..719,
id: Name("ab"), id: Name("ab"),
ctx: Load, ctx: Load,
}, },
@ -894,18 +912,18 @@ Module(
), ),
Assign( Assign(
StmtAssign { StmtAssign {
range: 714..723, range: 720..729,
targets: [ targets: [
Name( Name(
ExprName { ExprName {
range: 714..715, range: 720..721,
id: Name("a"), id: Name("a"),
ctx: Store, ctx: Store,
}, },
), ),
Name( Name(
ExprName { ExprName {
range: 718..719, range: 724..725,
id: Name("b"), id: Name("b"),
ctx: Store, ctx: Store,
}, },
@ -913,7 +931,7 @@ Module(
], ],
value: Name( value: Name(
ExprName { ExprName {
range: 722..723, range: 728..729,
id: Name("c"), id: Name("c"),
ctx: Load, ctx: Load,
}, },