Raise syntax error for unparenthesized generator expr in multi-argument call (#12445)

## Summary

This PR fixes a bug to raise a syntax error when an unparenthesized
generator expression is used as an argument to a call when there are
more than one argument.

For reference, the grammar is:
```
primary:
    | ...
    | primary genexp 
    | primary '(' [arguments] ')' 
    | ...

genexp:
    | '(' ( assignment_expression | expression !':=') for_if_clauses ')' 
```

The `genexp` requires the parenthesis as mentioned in the grammar. So,
the grammar for a call expression is either a name followed by a
generator expression or a name followed by a list of argument. In the
former case, the parenthesis are excluded because the generator
expression provides them while in the later case, the parenthesis are
explicitly provided for a list of arguments which means that the
generator expression requires it's own parenthesis.

This was discovered in https://github.com/astral-sh/ruff/issues/12420.

## Test Plan

Add test cases for valid and invalid syntax.

Make sure that the parser from CPython also raises this at the parsing
step:
```console
$ python3.13 -m ast parser/_.py
  File "parser/_.py", line 1
    total(1, 2, x for x in range(5), 6)
                ^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized

$ python3.13 -m ast parser/_.py
  File "parser/_.py", line 1
    sum(x for x in range(10), 10)
        ^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
```
This commit is contained in:
Dhruv Manilawala 2024-07-22 14:44:20 +05:30 committed by GitHub
parent f8735e1ee8
commit 978909fcf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 336 additions and 4 deletions

View file

@ -2272,9 +2272,10 @@ impl<'src> Parser<'src> {
command
}
/// Validate that the given arguments doesn't have any duplicate keyword argument.
///
/// Report errors for all the duplicate names found.
/// Performs the following validations on the function call arguments:
/// 1. There aren't any duplicate keyword argument
/// 2. If there are more than one argument (positional or keyword), all generator expressions
/// present should be parenthesized.
fn validate_arguments(&mut self, arguments: &ast::Arguments) {
let mut all_arg_names =
FxHashSet::with_capacity_and_hasher(arguments.keywords.len(), FxBuildHasher);
@ -2292,6 +2293,25 @@ impl<'src> Parser<'src> {
);
}
}
if arguments.len() > 1 {
for arg in arguments.args.iter() {
if let Some(ast::ExprGenerator {
range,
parenthesized: false,
..
}) = arg.as_generator_expr()
{
// test_ok args_unparenthesized_generator
// sum(x for x in range(10))
// test_err args_unparenthesized_generator
// sum(x for x in range(10), 5)
// total(1, 2, x for x in range(5), 6)
self.add_error(ParseErrorType::UnparenthesizedGeneratorExpression, range);
}
}
}
}
}