-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[syntax-errors] Tuple unpacking in return and yield before Python 3.8
#16485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
914d0a6
f2aa25a
99a866e
0d93a56
67dd41c
c5485dc
7987697
d2b68f7
bc240ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # parse_options: {"target-version": "3.7"} | ||
| rest = (4, 5, 6) | ||
| def f(): return 1, 2, 3, *rest |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # parse_options: {"target-version": "3.7"} | ||
| rest = (4, 5, 6) | ||
| def g(): yield 1, 2, 3, *rest | ||
| def h(): yield 1, (yield 2, *rest), 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # parse_options: {"target-version": "3.7"} | ||
| rest = (4, 5, 6) | ||
| def f(): return (1, 2, 3, *rest) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # parse_options: {"target-version": "3.8"} | ||
| rest = (4, 5, 6) | ||
| def f(): return 1, 2, 3, *rest |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # parse_options: {"target-version": "3.7"} | ||
| rest = (4, 5, 6) | ||
| def g(): yield (1, 2, 3, *rest) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # parse_options: {"target-version": "3.8"} | ||
| rest = (4, 5, 6) | ||
| def g(): yield 1, 2, 3, *rest | ||
| def h(): yield 1, (yield 2, *rest), 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ use ruff_python_ast::{ | |
| }; | ||
| use ruff_text_size::{Ranged, TextRange, TextSize}; | ||
|
|
||
| use crate::error::StarTupleKind; | ||
| use crate::parser::expression::{ParsedExpr, EXPR_SET}; | ||
| use crate::parser::progress::ParserProgress; | ||
| use crate::parser::{ | ||
|
|
@@ -389,10 +390,25 @@ impl<'src> Parser<'src> { | |
| // return x := 1 | ||
| // return *x and y | ||
| let value = self.at_expr().then(|| { | ||
| Box::new( | ||
| self.parse_expression_list(ExpressionContext::starred_bitwise_or()) | ||
| .expr, | ||
| ) | ||
| let parsed_expr = self.parse_expression_list(ExpressionContext::starred_bitwise_or()); | ||
|
|
||
| // test_ok iter_unpack_return_py37 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add test cases using single unpack element like
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for these! The single cases are actually still invalid on my system Python (3.13): >>> def foo(): return *rest
File "<python-input-0>", line 1
def foo(): return *rest
^^^^^
SyntaxError: can't use starred expression here
>>> def foo(): yield *rest
File "<python-input-1>", line 1
def foo(): yield *rest
^^^^^
SyntaxError: can't use starred expression hereBut these aren't flagged as The second case is very interesting. It's actually accepted on both 3.7 and 3.8, but not on my 3.13 system Python ( I added your third case as an example of a yield expression and moved the check into
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's because the grammar says it's allowed. I think it's caught by the CPython compiler and not the parser. You can see that $ python -m ast _.py
Module(
body=[
FunctionDef(
name='foo',
args=arguments(
posonlyargs=[],
args=[
arg(arg='args')],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Return(
value=Starred(
value=Name(id='args', ctx=Load()),
ctx=Load()))],
decorator_list=[],
type_params=[])],
type_ignores=[])
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, missed the issue #16520 |
||
| // # parse_options: {"target-version": "3.7"} | ||
| // rest = (4, 5, 6) | ||
| // def f(): return (1, 2, 3, *rest) | ||
|
|
||
| // test_ok iter_unpack_return_py38 | ||
| // # parse_options: {"target-version": "3.8"} | ||
| // rest = (4, 5, 6) | ||
| // def f(): return 1, 2, 3, *rest | ||
|
|
||
| // test_err iter_unpack_return_py37 | ||
| // # parse_options: {"target-version": "3.7"} | ||
| // rest = (4, 5, 6) | ||
| // def f(): return 1, 2, 3, *rest | ||
| self.check_tuple_unpacking(&parsed_expr, StarTupleKind::Return); | ||
|
|
||
| Box::new(parsed_expr.expr) | ||
| }); | ||
|
|
||
| ast::StmtReturn { | ||
|
|
@@ -401,6 +417,33 @@ impl<'src> Parser<'src> { | |
| } | ||
| } | ||
|
|
||
| /// Report [`UnsupportedSyntaxError`]s for each starred element in `expr` if it is an | ||
| /// unparenthesized tuple. | ||
| /// | ||
| /// This method can be used to check for tuple unpacking in return and yield statements, which | ||
| /// are only allowed in Python 3.8 and later: <https://github.com/python/cpython/issues/76298>. | ||
| pub(crate) fn check_tuple_unpacking(&mut self, expr: &Expr, kind: StarTupleKind) { | ||
| let kind = UnsupportedSyntaxErrorKind::StarTuple(kind); | ||
| if self.options.target_version >= kind.minimum_version() { | ||
| return; | ||
| } | ||
|
|
||
| let Expr::Tuple(ast::ExprTuple { | ||
| elts, | ||
| parenthesized: false, | ||
| .. | ||
| }) = expr | ||
| else { | ||
| return; | ||
| }; | ||
|
|
||
| for elt in elts { | ||
| if elt.is_starred_expr() { | ||
| self.add_unsupported_syntax_error(kind, elt.range()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Parses a `raise` statement. | ||
| /// | ||
| /// # Panics | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| --- | ||
| source: crates/ruff_python_parser/tests/fixtures.rs | ||
| input_file: crates/ruff_python_parser/resources/inline/err/iter_unpack_return_py37.py | ||
| --- | ||
| ## AST | ||
|
|
||
| ``` | ||
| Module( | ||
| ModModule { | ||
| range: 0..91, | ||
| body: [ | ||
| Assign( | ||
| StmtAssign { | ||
| range: 43..59, | ||
| targets: [ | ||
| Name( | ||
| ExprName { | ||
| range: 43..47, | ||
| id: Name("rest"), | ||
| ctx: Store, | ||
| }, | ||
| ), | ||
| ], | ||
| value: Tuple( | ||
| ExprTuple { | ||
| range: 50..59, | ||
| elts: [ | ||
| NumberLiteral( | ||
| ExprNumberLiteral { | ||
| range: 51..52, | ||
| value: Int( | ||
| 4, | ||
| ), | ||
| }, | ||
| ), | ||
| NumberLiteral( | ||
| ExprNumberLiteral { | ||
| range: 54..55, | ||
| value: Int( | ||
| 5, | ||
| ), | ||
| }, | ||
| ), | ||
| NumberLiteral( | ||
| ExprNumberLiteral { | ||
| range: 57..58, | ||
| value: Int( | ||
| 6, | ||
| ), | ||
| }, | ||
| ), | ||
| ], | ||
| ctx: Load, | ||
| parenthesized: true, | ||
| }, | ||
| ), | ||
| }, | ||
| ), | ||
| FunctionDef( | ||
| StmtFunctionDef { | ||
| range: 60..90, | ||
| is_async: false, | ||
| decorator_list: [], | ||
| name: Identifier { | ||
| id: Name("f"), | ||
| range: 64..65, | ||
| }, | ||
| type_params: None, | ||
| parameters: Parameters { | ||
| range: 65..67, | ||
| posonlyargs: [], | ||
| args: [], | ||
| vararg: None, | ||
| kwonlyargs: [], | ||
| kwarg: None, | ||
| }, | ||
| returns: None, | ||
| body: [ | ||
| Return( | ||
| StmtReturn { | ||
| range: 69..90, | ||
| value: Some( | ||
| Tuple( | ||
| ExprTuple { | ||
| range: 76..90, | ||
| elts: [ | ||
| NumberLiteral( | ||
| ExprNumberLiteral { | ||
| range: 76..77, | ||
| value: Int( | ||
| 1, | ||
| ), | ||
| }, | ||
| ), | ||
| NumberLiteral( | ||
| ExprNumberLiteral { | ||
| range: 79..80, | ||
| value: Int( | ||
| 2, | ||
| ), | ||
| }, | ||
| ), | ||
| NumberLiteral( | ||
| ExprNumberLiteral { | ||
| range: 82..83, | ||
| value: Int( | ||
| 3, | ||
| ), | ||
| }, | ||
| ), | ||
| Starred( | ||
| ExprStarred { | ||
| range: 85..90, | ||
| value: Name( | ||
| ExprName { | ||
| range: 86..90, | ||
| id: Name("rest"), | ||
| ctx: Load, | ||
| }, | ||
| ), | ||
| ctx: Load, | ||
| }, | ||
| ), | ||
| ], | ||
| ctx: Load, | ||
| parenthesized: false, | ||
| }, | ||
| ), | ||
| ), | ||
| }, | ||
| ), | ||
| ], | ||
| }, | ||
| ), | ||
| ], | ||
| }, | ||
| ) | ||
| ``` | ||
| ## Unsupported Syntax Errors | ||
|
|
||
| | | ||
| 1 | # parse_options: {"target-version": "3.7"} | ||
| 2 | rest = (4, 5, 6) | ||
| 3 | def f(): return 1, 2, 3, *rest | ||
| | ^^^^^ Syntax Error: Cannot use iterable unpacking in return statements on Python 3.7 (syntax was added in Python 3.8) | ||
| | |
Uh oh!
There was an error while loading. Please reload this page.