diff --git a/crates/ruff_python_parser/resources/inline/err/missing_power_lhs.py b/crates/ruff_python_parser/resources/inline/err/missing_power_lhs.py new file mode 100644 index 0000000000..982d4994c1 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/missing_power_lhs.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.8"} +{x: **y for x, y in z} diff --git a/crates/ruff_python_parser/resources/inline/ok/function_kwargs_double_star.py b/crates/ruff_python_parser/resources/inline/ok/function_kwargs_double_star.py new file mode 100644 index 0000000000..b57364359d --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/function_kwargs_double_star.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.8"} +foo(**kwargs) diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index f0e930461a..4434ac1071 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -364,6 +364,41 @@ impl<'src> Parser<'src> { } match self.current_token_kind() { + TokenKind::DoubleStar => { + let current_start = self.node_start(); + self.bump(TokenKind::DoubleStar); + let right = + self.parse_binary_expression_or_higher(OperatorPrecedence::Exponent, context); + + let expr = Expr::BinOp(ast::ExprBinOp { + left: Box::new(Expr::Name(ast::ExprName { + range: TextRange::empty(current_start), + id: Name::empty(), + ctx: ExprContext::Invalid, + node_index: AtomicNodeIndex::NONE, + })), + op: Operator::Pow, + right: Box::new(right.expr), + range: self.node_range(start), + node_index: AtomicNodeIndex::NONE, + }); + + // test_err missing_power_lhs + // # parse_options: {"target-version": "3.8"} + // {x: **y for x, y in z} + + // test_ok function_kwargs_double_star + // # parse_options: {"target-version": "3.8"} + // foo(**kwargs) + + self.add_error( + ParseErrorType::OtherError( + "Power operator '**' requires a left operand".to_string(), + ), + &expr, + ); + return expr.into(); + } TokenKind::Star => { let starred_expr = self.parse_starred_expression(context); diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@missing_power_lhs.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@missing_power_lhs.py.snap new file mode 100644 index 0000000000..cd7f1397b3 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@missing_power_lhs.py.snap @@ -0,0 +1,108 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/missing_power_lhs.py +--- +## AST + +``` +Module( + ModModule { + node_index: NodeIndex(None), + range: 0..66, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 43..65, + value: DictComp( + ExprDictComp { + node_index: NodeIndex(None), + range: 43..65, + key: Name( + ExprName { + node_index: NodeIndex(None), + range: 44..45, + id: Name("x"), + ctx: Load, + }, + ), + value: BinOp( + ExprBinOp { + node_index: NodeIndex(None), + range: 47..50, + left: Name( + ExprName { + node_index: NodeIndex(None), + range: 47..47, + id: Name(""), + ctx: Invalid, + }, + ), + op: Pow, + right: Name( + ExprName { + node_index: NodeIndex(None), + range: 49..50, + id: Name("y"), + ctx: Load, + }, + ), + }, + ), + generators: [ + Comprehension { + range: 51..64, + node_index: NodeIndex(None), + target: Tuple( + ExprTuple { + node_index: NodeIndex(None), + range: 55..59, + elts: [ + Name( + ExprName { + node_index: NodeIndex(None), + range: 55..56, + id: Name("x"), + ctx: Store, + }, + ), + Name( + ExprName { + node_index: NodeIndex(None), + range: 58..59, + id: Name("y"), + ctx: Store, + }, + ), + ], + ctx: Store, + parenthesized: false, + }, + ), + iter: Name( + ExprName { + node_index: NodeIndex(None), + range: 63..64, + id: Name("z"), + ctx: Load, + }, + ), + ifs: [], + is_async: false, + }, + ], + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.8"} +2 | {x: **y for x, y in z} + | ^^^ Syntax Error: Power operator '**' requires a left operand + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_kwargs_double_star.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_kwargs_double_star.py.snap new file mode 100644 index 0000000000..627bb3a907 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_kwargs_double_star.py.snap @@ -0,0 +1,56 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/function_kwargs_double_star.py +--- +## AST + +``` +Module( + ModModule { + node_index: NodeIndex(None), + range: 0..57, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 43..56, + value: Call( + ExprCall { + node_index: NodeIndex(None), + range: 43..56, + func: Name( + ExprName { + node_index: NodeIndex(None), + range: 43..46, + id: Name("foo"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 46..56, + node_index: NodeIndex(None), + args: [], + keywords: [ + Keyword { + range: 47..55, + node_index: NodeIndex(None), + arg: None, + value: Name( + ExprName { + node_index: NodeIndex(None), + range: 49..55, + id: Name("kwargs"), + ctx: Load, + }, + ), + }, + ], + }, + }, + ), + }, + ), + ], + }, +) +```