Skip to content

Commit

Permalink
Merge pull request #1820 from dtolnay/rangeprecedence
Browse files Browse the repository at this point in the history
Fix range precedence edge cases
  • Loading branch information
dtolnay authored Dec 31, 2024
2 parents d2794d0 + f7b0d72 commit 8eb2407
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 11 deletions.
20 changes: 11 additions & 9 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1315,9 +1315,8 @@ pub(crate) mod parsing {
) -> Result<Expr> {
loop {
let ahead = input.fork();
if let Expr::Range(ExprRange { end: Some(_), .. }) = lhs {
// A range with an upper bound cannot be the left-hand side of
// another binary operator.
if let Expr::Range(_) = lhs {
// A range cannot be the left-hand side of another binary operator.
break;
} else if let Ok(op) = ahead.parse::<BinOp>() {
let precedence = Precedence::of_binop(&op);
Expand Down Expand Up @@ -2886,17 +2885,20 @@ pub(crate) mod parsing {
|| input.peek(Token![?])
|| input.peek(Token![=>])
|| !allow_struct.0 && input.peek(token::Brace)
|| input.peek(Token![=]) && !input.peek(Token![==])
|| input.peek(Token![+=])
|| input.peek(Token![=])
|| input.peek(Token![+])
|| input.peek(Token![/])
|| input.peek(Token![%])
|| input.peek(Token![^])
|| input.peek(Token![>])
|| input.peek(Token![<=])
|| input.peek(Token![!=])
|| input.peek(Token![-=])
|| input.peek(Token![*=])
|| input.peek(Token![/=])
|| input.peek(Token![%=])
|| input.peek(Token![^=])
|| input.peek(Token![&=])
|| input.peek(Token![|=])
|| input.peek(Token![<<=])
|| input.peek(Token![>>=]))
|| input.peek(Token![as]))
{
Ok(None)
} else {
Expand Down
31 changes: 29 additions & 2 deletions src/fixup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ pub(crate) struct FixupContext {
#[cfg(feature = "full")]
leftmost_subexpression_in_match_arm: bool,

// This is the difference between:
//
// (..) + x // parens because range cannot be lhs of a binop
//
// &.. + x // no parens because range is a right subexpression
//
#[cfg(feature = "full")]
rightmost_subexpression: bool,

// This is the difference between:
//
// if let _ = (Struct {}) {} // needs parens
Expand Down Expand Up @@ -133,6 +142,8 @@ impl FixupContext {
#[cfg(feature = "full")]
leftmost_subexpression_in_match_arm: false,
#[cfg(feature = "full")]
rightmost_subexpression: false,
#[cfg(feature = "full")]
parenthesize_exterior_struct_lit: false,
#[cfg(feature = "full")]
next_operator_can_begin_expr: false,
Expand Down Expand Up @@ -196,6 +207,8 @@ impl FixupContext {
leftmost_subexpression_in_match_arm: self.match_arm
|| self.leftmost_subexpression_in_match_arm,
#[cfg(feature = "full")]
rightmost_subexpression: false,
#[cfg(feature = "full")]
next_operator_can_begin_expr: false,
#[cfg(feature = "full")]
next_operator_can_continue_expr: true,
Expand All @@ -219,6 +232,8 @@ impl FixupContext {
#[cfg(feature = "full")]
leftmost_subexpression_in_match_arm: false,
#[cfg(feature = "full")]
rightmost_subexpression: false,
#[cfg(feature = "full")]
next_operator_can_begin_expr: false,
#[cfg(feature = "full")]
next_operator_can_continue_expr: true,
Expand Down Expand Up @@ -264,6 +279,8 @@ impl FixupContext {
match_arm: false,
#[cfg(feature = "full")]
leftmost_subexpression_in_match_arm: false,
#[cfg(feature = "full")]
rightmost_subexpression: true,
..self
}
}
Expand Down Expand Up @@ -309,6 +326,12 @@ impl FixupContext {
if let Expr::Break(_) | Expr::Return(_) | Expr::Yield(_) = expr {
return Precedence::Jump;
}
} else if self.rightmost_subexpression {
if let Expr::Range(range) = expr {
if range.start.is_none() && range.end.is_none() {
return Precedence::Unambiguous;
}
}
}
#[cfg(feature = "full")]
if !self.next_operator_can_continue_expr {
Expand Down Expand Up @@ -348,7 +371,7 @@ impl Clone for FixupContext {
#[cfg(feature = "full")]
#[test]
fn test_leftmost_rightmost_invariant() {
const BITS: usize = 8;
const BITS: usize = 9;

for bits in 0u16..1 << BITS {
let mut i = 0;
Expand All @@ -362,6 +385,7 @@ fn test_leftmost_rightmost_invariant() {
leftmost_subexpression_in_stmt: bit(),
match_arm: bit(),
leftmost_subexpression_in_match_arm: bit(),
rightmost_subexpression: bit(),
parenthesize_exterior_struct_lit: bit(),
next_operator_can_begin_expr: bit(),
next_operator_can_continue_expr: bit(),
Expand All @@ -370,7 +394,10 @@ fn test_leftmost_rightmost_invariant() {
assert_eq!(i, BITS);
assert_eq!(
fixup.leftmost_subexpression().rightmost_subexpression(),
fixup.rightmost_subexpression().leftmost_subexpression(),
FixupContext {
rightmost_subexpression: true,
..fixup.rightmost_subexpression().leftmost_subexpression()
},
);
}
}
13 changes: 13 additions & 0 deletions tests/test_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,19 @@ fn test_range_precedence() {
}
"#);

snapshot!("() = .. + ()" as Expr, @r"
Expr::Binary {
left: Expr::Assign {
left: Expr::Tuple,
right: Expr::Range {
limits: RangeLimits::HalfOpen,
},
},
op: BinOp::Add,
right: Expr::Tuple,
}
");

// A range with a lower bound cannot be the upper bound of another range,
// and a range with an upper bound cannot be the lower bound of another
// range.
Expand Down

0 comments on commit 8eb2407

Please sign in to comment.