diff --git a/crates/ruff_python_parser/resources/inline/err/star_index_py310.py b/crates/ruff_python_parser/resources/inline/err/star_index_py310.py new file mode 100644 index 0000000000..df2725db74 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/star_index_py310.py @@ -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 diff --git a/crates/ruff_python_parser/resources/inline/err/star_slices.py b/crates/ruff_python_parser/resources/inline/err/star_slices.py new file mode 100644 index 0000000000..67c7a354f1 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/star_slices.py @@ -0,0 +1 @@ +array[*start:*end] diff --git a/crates/ruff_python_parser/resources/inline/ok/star_index_py311.py b/crates/ruff_python_parser/resources/inline/ok/star_index_py311.py new file mode 100644 index 0000000000..2374c77f61 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/star_index_py311.py @@ -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 diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 2df7ce03a0..d8267fd311 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -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) } diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index f1e370e68d..f9f764e2ff 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -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), diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap new file mode 100644 index 0000000000..0f06b4ecff --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_index_py310.py.snap @@ -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) + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap new file mode 100644 index 0000000000..de033a8afe --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@star_slices.py.snap @@ -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 + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap new file mode 100644 index 0000000000..241edaeadd --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@star_index_py311.py.snap @@ -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, + }, + ), + }, + ), + ], + }, +) +```