Skip to content
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

[syntax-errors] Tuple unpacking in return and yield before Python 3.8 #16485

Merged
merged 9 commits into from
Mar 6, 2025

Conversation

ntBre
Copy link
Contributor

@ntBre ntBre commented Mar 3, 2025

Summary

Checks for tuple unpacking in return and yield statements before Python 3.8, as described here.

Test Plan

Inline tests.

@ntBre ntBre added parser Related to the parser preview Related to preview mode features labels Mar 3, 2025
Summary
--

Checks for tuple unpacking in `return` and `yield` statements before Python 3.8,
as described [here].

I split the `ErrorKind` into two variants for the sake of more precise error
messages. I thought that was nicer than having a single `StarTuple` variant
containing another enum with two options.

Test Plan
--
Inline tests.

[here]: python/cpython#76298
@ntBre ntBre force-pushed the brent/syn-unpack-return-yield branch from 052265d to 914d0a6 Compare March 3, 2025 23:47
Copy link
Contributor

github-actions bot commented Mar 3, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

"iterable unpacking in return statements"
}
UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Yield) => {
"iterable unpacking in yield statements"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python also knows yield expressions. Is this something we have to distinguish / consider in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to test this out. The CPython PR said statements, but maybe I took it too literally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this also applies to expressions:

Python 3.7:

>>> def i(): yield 1, (yield 2, *rest), 3
  File "<stdin>", line 1
    def i(): yield 1, (yield 2, *rest), 3
                                ^
SyntaxError: invalid syntax

Comment on lines 298 to 303
if let Expr::Yield(ast::ExprYield {
value: Some(expr), ..
}) = &parsed_expr.expr
{
self.check_tuple_unpacking(expr, StarTupleKind::Yield);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also handle nested yield expressions a = yield x
Could this code be moved into parse_yield_expression?

)
let parsed_expr = self.parse_expression_list(ExpressionContext::starred_bitwise_or());

// test_ok iter_unpack_return_py37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add test cases using single unpack element like return *rest or yield *rest?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about yield 1, (*rest), yield 1, (yield 2, *rest), 3 (yield can be used as an expression)? I'm just throwing out random examples :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 here

But these aren't flagged as ParseErrors by ruff.

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 (SyntaxError: cannot use starred expression here). That might be another change not currently tracked in #6591.

I added your third case as an example of a yield expression and moved the check into parse_yield_expression as @MichaReiser suggested.

Copy link
Member

@dhruvmanila dhruvmanila Mar 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But these aren't flagged as ParseErrors by ruff.

That's because the grammar says it's allowed.

return_stmt:
    | 'return' [star_expressions] 

star_expressions:
    | star_expression (',' star_expression )+ [','] 
    | star_expression ',' 
    | star_expression

star_expression:
    | '*' bitwise_or 
    | expression

I think it's caught by the CPython compiler and not the parser. You can see that python -m ast test.py wouldn't raise a syntax error here.

$ 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=[])

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, missed the issue #16520

@ntBre
Copy link
Contributor Author

ntBre commented Mar 5, 2025

I added more test cases (thanks for the suggestions!). Two of them became TODOs because I think they aren't exactly related to this PR:

  • return|yield *rest is invalid on every Python version I tried and should be flagged as a parse error, in my opinion
  • return|yield 1, (*rest) is valid on 3.7 and 3.8 but not 3.13, so I think this is a separate change from the 3.8 unpacking (and a second Change::Removed case to use the enum from the other PR)

I also checked on yield from and it looks like our parse_yield_from method already reports an error for any unparenthesized tuple expression, so I think that should be covered.

Finally, I changed the message for the yield statement to yield expression. I think in an ideal case we'd have a third variant of StarTupleKind and differentiate YieldExpr from YieldStmt but I didn't see an easy way to do that with the new detection in parse_yield_expression. Since I had to pick one, I thought "expression" was slightly preferable.

@MichaReiser
Copy link
Member

return|yield *rest is invalid on every Python version I tried and should be flagged as a parse error, in my opinion

Yes, that seems correct. Can you create an issue for it?

@ntBre ntBre merged commit 6c14225 into main Mar 6, 2025
21 checks passed
@ntBre ntBre deleted the brent/syn-unpack-return-yield branch March 6, 2025 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
parser Related to the parser preview Related to preview mode features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants