[syntax-errors] Star annotations before Python 3.11 (#16545)

Summary
--

This is closely related to (and stacked on)
https://github.com/astral-sh/ruff/pull/16544 and detects star
annotations in function definitions.

I initially called the variant `StarExpressionInAnnotation` to mirror
`StarExpressionInIndex`, but I realized it's not really a "star
expression" in this position and renamed it. `StarAnnotation` seems in
line with the PEP.

Test Plan
--

Two new inline tests. It looked like there was pretty good existing
coverage of this syntax, so I just added simple examples to test the
version cutoff.
This commit is contained in:
Brent Westbrook 2025-03-14 11:20:44 -04:00 committed by GitHub
parent 4f2851982d
commit 6311412373
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 199 additions and 2 deletions

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.10"}
def foo(*args: *Ts): ...

View file

@ -0,0 +1,2 @@
# parse_options: {"target-version": "3.11"}
def foo(*args: *Ts): ...

View file

@ -644,6 +644,34 @@ pub enum UnsupportedSyntaxErrorKind {
/// ///
/// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes /// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes
StarExpressionInIndex, 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 /// Represents the use of tuple unpacking in a `for` statement iterator clause before Python
/// 3.9. /// 3.9.
/// ///
@ -699,6 +727,7 @@ impl Display for UnsupportedSyntaxError {
UnsupportedSyntaxErrorKind::StarExpressionInIndex => { UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
"Cannot use star expression in index" "Cannot use star expression in index"
} }
UnsupportedSyntaxErrorKind::StarAnnotation => "Cannot use star annotation",
UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => { UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
"Cannot use iterable unpacking in `for` statements" "Cannot use iterable unpacking in `for` statements"
} }
@ -750,6 +779,7 @@ impl UnsupportedSyntaxErrorKind {
UnsupportedSyntaxErrorKind::StarExpressionInIndex => { UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
Change::Added(PythonVersion::PY311) Change::Added(PythonVersion::PY311)
} }
UnsupportedSyntaxErrorKind::StarAnnotation => Change::Added(PythonVersion::PY311),
UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => { UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
Change::Added(PythonVersion::PY39) Change::Added(PythonVersion::PY39)
} }

View file

@ -2882,9 +2882,23 @@ impl<'src> Parser<'src> {
// def foo(*args: *int or str): ... // def foo(*args: *int or str): ...
// def foo(*args: *yield x): ... // def foo(*args: *yield x): ...
// # def foo(*args: **int): ... // # 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(), 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 => { AllowStarAnnotation::No => {
// test_ok param_with_annotation // test_ok param_with_annotation

View file

@ -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)
|

View file

@ -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,
},
),
},
),
],
},
),
],
},
)
```