diff --git a/crates/ruff_python_parser/resources/inline/ok/ambiguous_lpar_with_items_binary_expr.py b/crates/ruff_python_parser/resources/inline/ok/ambiguous_lpar_with_items_binary_expr.py new file mode 100644 index 0000000000000..e55c72c55660e --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/ambiguous_lpar_with_items_binary_expr.py @@ -0,0 +1,10 @@ +# It doesn't matter what's inside the parentheses, these tests need to make sure +# all binary expressions parses correctly. +with (a) and b: ... +with (a) is not b: ... +# Make sure precedence works +with (a) or b and c: ... +with (a) and b or c: ... +with (a | b) << c | d: ... +# Postfix should still be parsed first +with (a)[0] + b * c: ... diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index 0a7196b1750e4..8683c27643f97 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -347,8 +347,18 @@ impl<'src> Parser<'src> { /// [Pratt parsing algorithm]: https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html fn parse_expression_with_precedence(&mut self, previous_precedence: Precedence) -> ParsedExpr { let start = self.node_start(); - let mut lhs = self.parse_lhs_expression(previous_precedence); + let lhs = self.parse_lhs_expression(previous_precedence); + self.parse_expression_with_precedence_recursive(lhs, previous_precedence, start) + } + /// Parses an expression with binding power of at least `previous_precedence` given the + /// left-hand side expression. + pub(super) fn parse_expression_with_precedence_recursive( + &mut self, + mut lhs: ParsedExpr, + previous_precedence: Precedence, + start: TextSize, + ) -> ParsedExpr { let mut progress = ParserProgress::default(); loop { @@ -2498,7 +2508,7 @@ enum Associativity { /// /// See: #[derive(Debug, Ord, Eq, PartialEq, PartialOrd, Copy, Clone)] -enum Precedence { +pub(super) enum Precedence { /// Precedence for an unknown operator. Unknown, /// The initital precedence when parsing an expression. diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index e397321b9d595..f98dc33253444 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -17,7 +17,7 @@ use crate::parser::{ use crate::token_set::TokenSet; use crate::{Mode, ParseErrorType, Tok, TokenKind}; -use super::expression::{AllowNamedExpression, AllowStarredExpression}; +use super::expression::{AllowNamedExpression, AllowStarredExpression, Precedence}; use super::Parenthesized; /// Tokens that represent compound statements. @@ -2147,15 +2147,31 @@ impl<'src> Parser<'src> { // expressions. lhs = self.parse_postfix_expression(lhs, start); - // test_ok ambiguous_lpar_with_items_if_expr - // with (x) if True else y: ... - // with (x for x in iter) if True else y: ... - // with (x async for x in iter) if True else y: ... - // with (x)[0] if True else y: ... let context_expr = if self.at(TokenKind::If) { + // test_ok ambiguous_lpar_with_items_if_expr + // with (x) if True else y: ... + // with (x for x in iter) if True else y: ... + // with (x async for x in iter) if True else y: ... + // with (x)[0] if True else y: ... Expr::If(self.parse_if_expression(lhs, start)) } else { - lhs + // test_ok ambiguous_lpar_with_items_binary_expr + // # It doesn't matter what's inside the parentheses, these tests need to make sure + // # all binary expressions parses correctly. + // with (a) and b: ... + // with (a) is not b: ... + // # Make sure precedence works + // with (a) or b and c: ... + // with (a) and b or c: ... + // with (a | b) << c | d: ... + // # Postfix should still be parsed first + // with (a)[0] + b * c: ... + self.parse_expression_with_precedence_recursive( + lhs.into(), + Precedence::Initial, + start, + ) + .expr }; let optional_vars = self diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap new file mode 100644 index 0000000000000..500e275e86d26 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@ambiguous_lpar_with_items_binary_expr.py.snap @@ -0,0 +1,372 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/ambiguous_lpar_with_items_binary_expr.py +--- +## AST + +``` +Module( + ModModule { + range: 0..337, + body: [ + With( + StmtWith { + range: 124..143, + is_async: false, + items: [ + WithItem { + range: 129..138, + context_expr: BoolOp( + ExprBoolOp { + range: 129..138, + op: And, + values: [ + Name( + ExprName { + range: 130..131, + id: "a", + ctx: Load, + }, + ), + Name( + ExprName { + range: 137..138, + id: "b", + ctx: Load, + }, + ), + ], + }, + ), + optional_vars: None, + }, + ], + body: [ + Expr( + StmtExpr { + range: 140..143, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 140..143, + }, + ), + }, + ), + ], + }, + ), + With( + StmtWith { + range: 144..166, + is_async: false, + items: [ + WithItem { + range: 149..161, + context_expr: Compare( + ExprCompare { + range: 149..161, + left: Name( + ExprName { + range: 150..151, + id: "a", + ctx: Load, + }, + ), + ops: [ + IsNot, + ], + comparators: [ + Name( + ExprName { + range: 160..161, + id: "b", + ctx: Load, + }, + ), + ], + }, + ), + optional_vars: None, + }, + ], + body: [ + Expr( + StmtExpr { + range: 163..166, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 163..166, + }, + ), + }, + ), + ], + }, + ), + With( + StmtWith { + range: 196..220, + is_async: false, + items: [ + WithItem { + range: 201..215, + context_expr: BoolOp( + ExprBoolOp { + range: 201..215, + op: Or, + values: [ + Name( + ExprName { + range: 202..203, + id: "a", + ctx: Load, + }, + ), + BoolOp( + ExprBoolOp { + range: 208..215, + op: And, + values: [ + Name( + ExprName { + range: 208..209, + id: "b", + ctx: Load, + }, + ), + Name( + ExprName { + range: 214..215, + id: "c", + ctx: Load, + }, + ), + ], + }, + ), + ], + }, + ), + optional_vars: None, + }, + ], + body: [ + Expr( + StmtExpr { + range: 217..220, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 217..220, + }, + ), + }, + ), + ], + }, + ), + With( + StmtWith { + range: 221..245, + is_async: false, + items: [ + WithItem { + range: 226..240, + context_expr: BoolOp( + ExprBoolOp { + range: 226..240, + op: Or, + values: [ + BoolOp( + ExprBoolOp { + range: 226..235, + op: And, + values: [ + Name( + ExprName { + range: 227..228, + id: "a", + ctx: Load, + }, + ), + Name( + ExprName { + range: 234..235, + id: "b", + ctx: Load, + }, + ), + ], + }, + ), + Name( + ExprName { + range: 239..240, + id: "c", + ctx: Load, + }, + ), + ], + }, + ), + optional_vars: None, + }, + ], + body: [ + Expr( + StmtExpr { + range: 242..245, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 242..245, + }, + ), + }, + ), + ], + }, + ), + With( + StmtWith { + range: 246..272, + is_async: false, + items: [ + WithItem { + range: 251..267, + context_expr: BinOp( + ExprBinOp { + range: 251..267, + left: BinOp( + ExprBinOp { + range: 251..263, + left: BinOp( + ExprBinOp { + range: 252..257, + left: Name( + ExprName { + range: 252..253, + id: "a", + ctx: Load, + }, + ), + op: BitOr, + right: Name( + ExprName { + range: 256..257, + id: "b", + ctx: Load, + }, + ), + }, + ), + op: LShift, + right: Name( + ExprName { + range: 262..263, + id: "c", + ctx: Load, + }, + ), + }, + ), + op: BitOr, + right: Name( + ExprName { + range: 266..267, + id: "d", + ctx: Load, + }, + ), + }, + ), + optional_vars: None, + }, + ], + body: [ + Expr( + StmtExpr { + range: 269..272, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 269..272, + }, + ), + }, + ), + ], + }, + ), + With( + StmtWith { + range: 312..336, + is_async: false, + items: [ + WithItem { + range: 317..331, + context_expr: BinOp( + ExprBinOp { + range: 317..331, + left: Subscript( + ExprSubscript { + range: 317..323, + value: Name( + ExprName { + range: 318..319, + id: "a", + ctx: Load, + }, + ), + slice: NumberLiteral( + ExprNumberLiteral { + range: 321..322, + value: Int( + 0, + ), + }, + ), + ctx: Load, + }, + ), + op: Add, + right: BinOp( + ExprBinOp { + range: 326..331, + left: Name( + ExprName { + range: 326..327, + id: "b", + ctx: Load, + }, + ), + op: Mult, + right: Name( + ExprName { + range: 330..331, + id: "c", + ctx: Load, + }, + ), + }, + ), + }, + ), + optional_vars: None, + }, + ], + body: [ + Expr( + StmtExpr { + range: 333..336, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 333..336, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +```