[syntax-errors] Star expression in index before Python 3.11 (#16544)

Summary
--

This PR detects tuple unpacking expressions in index/subscript
expressions before Python 3.11.

Test Plan
--

New inline tests
This commit is contained in:
Brent Westbrook 2025-03-14 10:51:34 -04:00 committed by GitHub
parent 2cd25ef641
commit 4f2851982d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 933 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# parse_options: {"target-version": "3.10"}
lst[*index] # simple index
class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
lst[a, *b, c] # different positions
lst[a, b, *c] # different positions
lst[*a, *b] # multiple unpacks
array[3:5, *idxs] # mixed with slices

View file

@ -0,0 +1 @@
array[*start:*end]

View file

@ -0,0 +1,7 @@
# parse_options: {"target-version": "3.11"}
lst[*index] # simple index
class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
lst[a, *b, c] # different positions
lst[a, b, *c] # different positions
lst[*a, *b] # multiple unpacks
array[3:5, *idxs] # mixed with slices

View file

@ -617,6 +617,33 @@ pub enum UnsupportedSyntaxErrorKind {
TypeAliasStatement,
TypeParamDefault,
/// Represents the use of a [PEP 646] star expression in an index.
///
/// ## Examples
///
/// Before Python 3.11, star expressions were not allowed in index/subscript operations (within
/// square brackets). This restriction was lifted in [PEP 646] to allow for star-unpacking of
/// `typing.TypeVarTuple`s, also added in Python 3.11. As such, this is the primary motivating
/// example from the PEP:
///
/// ```python
/// from typing import TypeVar, TypeVarTuple
///
/// DType = TypeVar('DType')
/// Shape = TypeVarTuple('Shape')
///
/// class Array(Generic[DType, *Shape]): ...
/// ```
///
/// But it applies to simple indexing as well:
///
/// ```python
/// vector[*x]
/// array[a, *b]
/// ```
///
/// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes
StarExpressionInIndex,
/// Represents the use of tuple unpacking in a `for` statement iterator clause before Python
/// 3.9.
///
@ -669,6 +696,9 @@ impl Display for UnsupportedSyntaxError {
UnsupportedSyntaxErrorKind::TypeParamDefault => {
"Cannot set default type for a type parameter"
}
UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
"Cannot use star expression in index"
}
UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
"Cannot use iterable unpacking in `for` statements"
}
@ -717,6 +747,9 @@ impl UnsupportedSyntaxErrorKind {
UnsupportedSyntaxErrorKind::TypeParameterList => Change::Added(PythonVersion::PY312),
UnsupportedSyntaxErrorKind::TypeAliasStatement => Change::Added(PythonVersion::PY312),
UnsupportedSyntaxErrorKind::TypeParamDefault => Change::Added(PythonVersion::PY313),
UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
Change::Added(PythonVersion::PY311)
}
UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
Change::Added(PythonVersion::PY39)
}

View file

@ -853,6 +853,35 @@ impl<'src> Parser<'src> {
self.expect(TokenKind::Rsqb);
// test_ok star_index_py311
// # parse_options: {"target-version": "3.11"}
// lst[*index] # simple index
// class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
// lst[a, *b, c] # different positions
// lst[a, b, *c] # different positions
// lst[*a, *b] # multiple unpacks
// array[3:5, *idxs] # mixed with slices
// test_err star_index_py310
// # parse_options: {"target-version": "3.10"}
// lst[*index] # simple index
// class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
// lst[a, *b, c] # different positions
// lst[a, b, *c] # different positions
// lst[*a, *b] # multiple unpacks
// array[3:5, *idxs] # mixed with slices
// test_err star_slices
// array[*start:*end]
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &slice {
for elt in elts.iter().filter(|elt| elt.is_starred_expr()) {
self.add_unsupported_syntax_error(
UnsupportedSyntaxErrorKind::StarExpressionInIndex,
elt.range(),
);
}
};
ast::ExprSubscript {
value: Box::new(value),
slice: Box::new(slice),

View file

@ -0,0 +1,420 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/star_index_py310.py
---
## AST
```
Module(
ModModule {
range: 0..293,
body: [
Expr(
StmtExpr {
range: 44..55,
value: Subscript(
ExprSubscript {
range: 44..55,
value: Name(
ExprName {
range: 44..47,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 48..54,
elts: [
Starred(
ExprStarred {
range: 48..54,
value: Name(
ExprName {
range: 49..54,
id: Name("index"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
ClassDef(
StmtClassDef {
range: 72..112,
decorator_list: [],
name: Identifier {
id: Name("Array"),
range: 78..83,
},
type_params: None,
arguments: Some(
Arguments {
range: 83..107,
args: [
Subscript(
ExprSubscript {
range: 84..106,
value: Name(
ExprName {
range: 84..91,
id: Name("Generic"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 92..105,
elts: [
Name(
ExprName {
range: 92..97,
id: Name("DType"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 99..105,
value: Name(
ExprName {
range: 100..105,
id: Name("Shape"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
range: 109..112,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 109..112,
},
),
},
),
],
},
),
Expr(
StmtExpr {
range: 148..161,
value: Subscript(
ExprSubscript {
range: 148..161,
value: Name(
ExprName {
range: 148..151,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 152..160,
elts: [
Name(
ExprName {
range: 152..153,
id: Name("a"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 155..157,
value: Name(
ExprName {
range: 156..157,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 159..160,
id: Name("c"),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 185..198,
value: Subscript(
ExprSubscript {
range: 185..198,
value: Name(
ExprName {
range: 185..188,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 189..197,
elts: [
Name(
ExprName {
range: 189..190,
id: Name("a"),
ctx: Load,
},
),
Name(
ExprName {
range: 192..193,
id: Name("b"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 195..197,
value: Name(
ExprName {
range: 196..197,
id: Name("c"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 222..233,
value: Subscript(
ExprSubscript {
range: 222..233,
value: Name(
ExprName {
range: 222..225,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 226..232,
elts: [
Starred(
ExprStarred {
range: 226..228,
value: Name(
ExprName {
range: 227..228,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 230..232,
value: Name(
ExprName {
range: 231..232,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 254..271,
value: Subscript(
ExprSubscript {
range: 254..271,
value: Name(
ExprName {
range: 254..259,
id: Name("array"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 260..270,
elts: [
Slice(
ExprSlice {
range: 260..263,
lower: Some(
NumberLiteral(
ExprNumberLiteral {
range: 260..261,
value: Int(
3,
),
},
),
),
upper: Some(
NumberLiteral(
ExprNumberLiteral {
range: 262..263,
value: Int(
5,
),
},
),
),
step: None,
},
),
Starred(
ExprStarred {
range: 265..270,
value: Name(
ExprName {
range: 266..270,
id: Name("idxs"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
],
},
)
```
## Unsupported Syntax Errors
|
1 | # parse_options: {"target-version": "3.10"}
2 | lst[*index] # simple index
| ^^^^^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
3 | class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
4 | lst[a, *b, c] # different positions
|
|
1 | # parse_options: {"target-version": "3.10"}
2 | lst[*index] # simple index
3 | class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
| ^^^^^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
4 | lst[a, *b, c] # different positions
5 | lst[a, b, *c] # different positions
|
|
2 | lst[*index] # simple index
3 | class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
4 | lst[a, *b, c] # different positions
| ^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
5 | lst[a, b, *c] # different positions
6 | lst[*a, *b] # multiple unpacks
|
|
3 | class Array(Generic[DType, *Shape]): ... # motivating example from the PEP
4 | lst[a, *b, c] # different positions
5 | lst[a, b, *c] # different positions
| ^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
6 | lst[*a, *b] # multiple unpacks
7 | array[3:5, *idxs] # mixed with slices
|
|
4 | lst[a, *b, c] # different positions
5 | lst[a, b, *c] # different positions
6 | lst[*a, *b] # multiple unpacks
| ^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
7 | array[3:5, *idxs] # mixed with slices
|
|
4 | lst[a, *b, c] # different positions
5 | lst[a, b, *c] # different positions
6 | lst[*a, *b] # multiple unpacks
| ^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
7 | array[3:5, *idxs] # mixed with slices
|
|
5 | lst[a, b, *c] # different positions
6 | lst[*a, *b] # multiple unpacks
7 | array[3:5, *idxs] # mixed with slices
| ^^^^^ Syntax Error: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
|

View file

@ -0,0 +1,81 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/err/star_slices.py
---
## AST
```
Module(
ModModule {
range: 0..19,
body: [
Expr(
StmtExpr {
range: 0..18,
value: Subscript(
ExprSubscript {
range: 0..18,
value: Name(
ExprName {
range: 0..5,
id: Name("array"),
ctx: Load,
},
),
slice: Slice(
ExprSlice {
range: 6..17,
lower: Some(
Starred(
ExprStarred {
range: 6..12,
value: Name(
ExprName {
range: 7..12,
id: Name("start"),
ctx: Load,
},
),
ctx: Load,
},
),
),
upper: Some(
Starred(
ExprStarred {
range: 13..17,
value: Name(
ExprName {
range: 14..17,
id: Name("end"),
ctx: Load,
},
),
ctx: Load,
},
),
),
step: None,
},
),
ctx: Load,
},
),
},
),
],
},
)
```
## Errors
|
1 | array[*start:*end]
| ^^^^^^ Syntax Error: Starred expression cannot be used here
|
|
1 | array[*start:*end]
| ^^^^ Syntax Error: Starred expression cannot be used here
|

View file

@ -0,0 +1,355 @@
---
source: crates/ruff_python_parser/tests/fixtures.rs
input_file: crates/ruff_python_parser/resources/inline/ok/star_index_py311.py
---
## AST
```
Module(
ModModule {
range: 0..293,
body: [
Expr(
StmtExpr {
range: 44..55,
value: Subscript(
ExprSubscript {
range: 44..55,
value: Name(
ExprName {
range: 44..47,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 48..54,
elts: [
Starred(
ExprStarred {
range: 48..54,
value: Name(
ExprName {
range: 49..54,
id: Name("index"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
ClassDef(
StmtClassDef {
range: 72..112,
decorator_list: [],
name: Identifier {
id: Name("Array"),
range: 78..83,
},
type_params: None,
arguments: Some(
Arguments {
range: 83..107,
args: [
Subscript(
ExprSubscript {
range: 84..106,
value: Name(
ExprName {
range: 84..91,
id: Name("Generic"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 92..105,
elts: [
Name(
ExprName {
range: 92..97,
id: Name("DType"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 99..105,
value: Name(
ExprName {
range: 100..105,
id: Name("Shape"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
],
keywords: [],
},
),
body: [
Expr(
StmtExpr {
range: 109..112,
value: EllipsisLiteral(
ExprEllipsisLiteral {
range: 109..112,
},
),
},
),
],
},
),
Expr(
StmtExpr {
range: 148..161,
value: Subscript(
ExprSubscript {
range: 148..161,
value: Name(
ExprName {
range: 148..151,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 152..160,
elts: [
Name(
ExprName {
range: 152..153,
id: Name("a"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 155..157,
value: Name(
ExprName {
range: 156..157,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
Name(
ExprName {
range: 159..160,
id: Name("c"),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 185..198,
value: Subscript(
ExprSubscript {
range: 185..198,
value: Name(
ExprName {
range: 185..188,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 189..197,
elts: [
Name(
ExprName {
range: 189..190,
id: Name("a"),
ctx: Load,
},
),
Name(
ExprName {
range: 192..193,
id: Name("b"),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 195..197,
value: Name(
ExprName {
range: 196..197,
id: Name("c"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 222..233,
value: Subscript(
ExprSubscript {
range: 222..233,
value: Name(
ExprName {
range: 222..225,
id: Name("lst"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 226..232,
elts: [
Starred(
ExprStarred {
range: 226..228,
value: Name(
ExprName {
range: 227..228,
id: Name("a"),
ctx: Load,
},
),
ctx: Load,
},
),
Starred(
ExprStarred {
range: 230..232,
value: Name(
ExprName {
range: 231..232,
id: Name("b"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
Expr(
StmtExpr {
range: 254..271,
value: Subscript(
ExprSubscript {
range: 254..271,
value: Name(
ExprName {
range: 254..259,
id: Name("array"),
ctx: Load,
},
),
slice: Tuple(
ExprTuple {
range: 260..270,
elts: [
Slice(
ExprSlice {
range: 260..263,
lower: Some(
NumberLiteral(
ExprNumberLiteral {
range: 260..261,
value: Int(
3,
),
},
),
),
upper: Some(
NumberLiteral(
ExprNumberLiteral {
range: 262..263,
value: Int(
5,
),
},
),
),
step: None,
},
),
Starred(
ExprStarred {
range: 265..270,
value: Name(
ExprName {
range: 266..270,
id: Name("idxs"),
ctx: Load,
},
),
ctx: Load,
},
),
],
ctx: Load,
parenthesized: false,
},
),
ctx: Load,
},
),
},
),
],
},
)
```