diff --git a/crates/ruff_python_parser/resources/inline/err/param_with_star_annotation_py310.py b/crates/ruff_python_parser/resources/inline/err/param_with_star_annotation_py310.py new file mode 100644 index 0000000000..39cf903ce6 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/param_with_star_annotation_py310.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.10"} +def foo(*args: *Ts): ... diff --git a/crates/ruff_python_parser/resources/inline/ok/param_with_star_annotation_py311.py b/crates/ruff_python_parser/resources/inline/ok/param_with_star_annotation_py311.py new file mode 100644 index 0000000000..4ba49a293a --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/param_with_star_annotation_py311.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.11"} +def foo(*args: *Ts): ... diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index d8267fd311..59a91c5da9 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -644,6 +644,34 @@ pub enum UnsupportedSyntaxErrorKind { /// /// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes StarExpressionInIndex, + + /// Represents the use of a [PEP 646] star annotations in a function definition. + /// + /// ## Examples + /// + /// Before Python 3.11, star annotations were not allowed in function definitions. This + /// restriction was lifted in [PEP 646] to allow type annotations for `typing.TypeVarTuple`, + /// also added in Python 3.11: + /// + /// ```python + /// from typing import TypeVarTuple + /// + /// Ts = TypeVarTuple('Ts') + /// + /// def foo(*args: *Ts): ... + /// ``` + /// + /// Unlike [`UnsupportedSyntaxErrorKind::StarExpressionInIndex`], this does not include any + /// other annotation positions: + /// + /// ```python + /// x: *Ts # Syntax error + /// def foo(x: *Ts): ... # Syntax error + /// ``` + /// + /// [PEP 646]: https://peps.python.org/pep-0646/#change-2-args-as-a-typevartuple + StarAnnotation, + /// Represents the use of tuple unpacking in a `for` statement iterator clause before Python /// 3.9. /// @@ -699,6 +727,7 @@ impl Display for UnsupportedSyntaxError { UnsupportedSyntaxErrorKind::StarExpressionInIndex => { "Cannot use star expression in index" } + UnsupportedSyntaxErrorKind::StarAnnotation => "Cannot use star annotation", UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => { "Cannot use iterable unpacking in `for` statements" } @@ -750,6 +779,7 @@ impl UnsupportedSyntaxErrorKind { UnsupportedSyntaxErrorKind::StarExpressionInIndex => { Change::Added(PythonVersion::PY311) } + UnsupportedSyntaxErrorKind::StarAnnotation => Change::Added(PythonVersion::PY311), UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => { Change::Added(PythonVersion::PY39) } diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index e234562c83..2450a3834c 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -2882,9 +2882,23 @@ impl<'src> Parser<'src> { // def foo(*args: *int or str): ... // def foo(*args: *yield x): ... // # def foo(*args: **int): ... - self.parse_conditional_expression_or_higher_impl( + let parsed_expr = self.parse_conditional_expression_or_higher_impl( ExpressionContext::starred_bitwise_or(), - ) + ); + + // test_ok param_with_star_annotation_py311 + // # parse_options: {"target-version": "3.11"} + // def foo(*args: *Ts): ... + + // test_err param_with_star_annotation_py310 + // # parse_options: {"target-version": "3.10"} + // def foo(*args: *Ts): ... + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::StarAnnotation, + parsed_expr.range(), + ); + + parsed_expr } AllowStarAnnotation::No => { // test_ok param_with_annotation diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap new file mode 100644 index 0000000000..ef87ca461f --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@param_with_star_annotation_py310.py.snap @@ -0,0 +1,78 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/param_with_star_annotation_py310.py +--- +## AST + +``` +Module( + ModModule { + range: 0..69, + body: [ + FunctionDef( + StmtFunctionDef { + range: 44..68, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 48..51, + }, + type_params: None, + parameters: Parameters { + range: 51..63, + posonlyargs: [], + args: [], + vararg: Some( + Parameter { + range: 52..62, + name: Identifier { + id: Name("args"), + range: 53..57, + }, + annotation: Some( + Starred( + ExprStarred { + range: 59..62, + value: Name( + ExprName { + range: 60..62, + id: Name("Ts"), + ctx: Load, + }, + ), + ctx: Load, + }, + ), + ), + }, + ), + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 65..68, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 65..68, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.10"} +2 | def foo(*args: *Ts): ... + | ^^^ Syntax Error: Cannot use star annotation on Python 3.10 (syntax was added in Python 3.11) + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap new file mode 100644 index 0000000000..7156b308c5 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@param_with_star_annotation_py311.py.snap @@ -0,0 +1,71 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/param_with_star_annotation_py311.py +--- +## AST + +``` +Module( + ModModule { + range: 0..69, + body: [ + FunctionDef( + StmtFunctionDef { + range: 44..68, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 48..51, + }, + type_params: None, + parameters: Parameters { + range: 51..63, + posonlyargs: [], + args: [], + vararg: Some( + Parameter { + range: 52..62, + name: Identifier { + id: Name("args"), + range: 53..57, + }, + annotation: Some( + Starred( + ExprStarred { + range: 59..62, + value: Name( + ExprName { + range: 60..62, + id: Name("Ts"), + ctx: Load, + }, + ), + ctx: Load, + }, + ), + ), + }, + ), + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 65..68, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 65..68, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +```