From fd11ae2d9b05ca43837764685bf8e703acdb5b07 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 21 Jul 2024 14:17:21 +0530 Subject: [PATCH] Raise syntax error for unparenthesized generator expr --- .../err/args_unparenthesized_generator.py | 2 + .../ok/args_unparenthesized_generator.py | 1 + crates/ruff_python_parser/src/error.rs | 5 + .../src/parser/expression.rs | 26 ++- ...tax@args_unparenthesized_generator.py.snap | 213 ++++++++++++++++++ ...tax@args_unparenthesized_generator.py.snap | 91 ++++++++ 6 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 crates/ruff_python_parser/resources/inline/err/args_unparenthesized_generator.py create mode 100644 crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_generator.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap diff --git a/crates/ruff_python_parser/resources/inline/err/args_unparenthesized_generator.py b/crates/ruff_python_parser/resources/inline/err/args_unparenthesized_generator.py new file mode 100644 index 00000000000000..45f01b2ea20d02 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/args_unparenthesized_generator.py @@ -0,0 +1,2 @@ +sum(x for x in range(10), 5) +total(1, 2, x for x in range(5), 6) diff --git a/crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_generator.py b/crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_generator.py new file mode 100644 index 00000000000000..ecadabd4e33f17 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_generator.py @@ -0,0 +1 @@ +sum(x for x in range(10)) diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 143c50e86f725f..98efdf52e2e487 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -106,6 +106,8 @@ pub enum ParseErrorType { UnparenthesizedNamedExpression, /// An unparenthesized tuple expression was found where it is not allowed. UnparenthesizedTupleExpression, + /// An unparenthesized generator expression was found where it is not allowed. + UnparenthesizedGeneratorExpression, /// An invalid usage of a lambda expression was found. InvalidLambdaExpressionUsage, @@ -216,6 +218,9 @@ impl std::fmt::Display for ParseErrorType { ParseErrorType::UnparenthesizedTupleExpression => { f.write_str("Unparenthesized tuple expression cannot be used here") } + ParseErrorType::UnparenthesizedGeneratorExpression => { + f.write_str("Unparenthesized generator expression cannot be used here") + } ParseErrorType::InvalidYieldExpressionUsage => { f.write_str("Yield expression cannot be used here") } diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 61060f9e34ce56..2b16c2d4c825ea 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -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); + } + } + } } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap new file mode 100644 index 00000000000000..653de136451b20 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@args_unparenthesized_generator.py.snap @@ -0,0 +1,213 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/args_unparenthesized_generator.py +--- +## AST + +``` +Module( + ModModule { + range: 0..65, + body: [ + Expr( + StmtExpr { + range: 0..28, + value: Call( + ExprCall { + range: 0..28, + func: Name( + ExprName { + range: 0..3, + id: Name("sum"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 3..28, + args: [ + Generator( + ExprGenerator { + range: 4..24, + elt: Name( + ExprName { + range: 4..5, + id: Name("x"), + ctx: Load, + }, + ), + generators: [ + Comprehension { + range: 6..24, + target: Name( + ExprName { + range: 10..11, + id: Name("x"), + ctx: Store, + }, + ), + iter: Call( + ExprCall { + range: 15..24, + func: Name( + ExprName { + range: 15..20, + id: Name("range"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 20..24, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 21..23, + value: Int( + 10, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ifs: [], + is_async: false, + }, + ], + parenthesized: false, + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 26..27, + value: Int( + 5, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 29..64, + value: Call( + ExprCall { + range: 29..64, + func: Name( + ExprName { + range: 29..34, + id: Name("total"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 34..64, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 35..36, + value: Int( + 1, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 38..39, + value: Int( + 2, + ), + }, + ), + Generator( + ExprGenerator { + range: 41..60, + elt: Name( + ExprName { + range: 41..42, + id: Name("x"), + ctx: Load, + }, + ), + generators: [ + Comprehension { + range: 43..60, + target: Name( + ExprName { + range: 47..48, + id: Name("x"), + ctx: Store, + }, + ), + iter: Call( + ExprCall { + range: 52..60, + func: Name( + ExprName { + range: 52..57, + id: Name("range"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 57..60, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 58..59, + value: Int( + 5, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ifs: [], + is_async: false, + }, + ], + parenthesized: false, + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 62..63, + value: Int( + 6, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | sum(x for x in range(10), 5) + | ^^^^^^^^^^^^^^^^^^^^ Syntax Error: Unparenthesized generator expression cannot be used here +2 | total(1, 2, x for x in range(5), 6) + | + + + | +1 | sum(x for x in range(10), 5) +2 | total(1, 2, x for x in range(5), 6) + | ^^^^^^^^^^^^^^^^^^^ Syntax Error: Unparenthesized generator expression cannot be used here + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap new file mode 100644 index 00000000000000..1be3f89dd11187 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@args_unparenthesized_generator.py.snap @@ -0,0 +1,91 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/args_unparenthesized_generator.py +--- +## AST + +``` +Module( + ModModule { + range: 0..26, + body: [ + Expr( + StmtExpr { + range: 0..25, + value: Call( + ExprCall { + range: 0..25, + func: Name( + ExprName { + range: 0..3, + id: Name("sum"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 3..25, + args: [ + Generator( + ExprGenerator { + range: 4..24, + elt: Name( + ExprName { + range: 4..5, + id: Name("x"), + ctx: Load, + }, + ), + generators: [ + Comprehension { + range: 6..24, + target: Name( + ExprName { + range: 10..11, + id: Name("x"), + ctx: Store, + }, + ), + iter: Call( + ExprCall { + range: 15..24, + func: Name( + ExprName { + range: 15..20, + id: Name("range"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 20..24, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 21..23, + value: Int( + 10, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ifs: [], + is_async: false, + }, + ], + parenthesized: false, + }, + ), + ], + keywords: [], + }, + }, + ), + }, + ), + ], + }, +) +```