syntax/ast/
prec.rs

1//! Precedence representation.
2
3use stdx::always;
4
5use crate::{
6    AstNode, Direction, SyntaxNode, T,
7    algo::skip_trivia_token,
8    ast::{self, BinaryOp, Expr, HasArgList, RangeItem},
9    match_ast,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
13pub enum ExprPrecedence {
14    // return, break, continue, yield, yeet, become (with or without value)
15    Jump,
16    // = += -= *= /= %= &= |= ^= <<= >>=
17    Assign,
18    // .. ..=
19    Range,
20    // ||
21    LOr,
22    // &&
23    LAnd,
24    // == != < > <= >=
25    Compare,
26    // |
27    BitOr,
28    // ^
29    BitXor,
30    // &
31    BitAnd,
32    // << >>
33    Shift,
34    // + -
35    Sum,
36    // * / %
37    Product,
38    // as
39    Cast,
40    // unary - * ! & &mut
41    Prefix,
42    // function calls, array indexing, field expressions, method calls
43    Postfix,
44    // paths, loops,
45    Unambiguous,
46}
47
48impl ExprPrecedence {
49    pub fn needs_parentheses_in(self, other: ExprPrecedence) -> bool {
50        match other {
51            ExprPrecedence::Unambiguous => false,
52            // postfix ops have higher precedence than any other operator, so we need to wrap
53            // any inner expression that is below
54            ExprPrecedence::Postfix => self < ExprPrecedence::Postfix,
55            // We need to wrap all binary like things, thats everything below prefix except for
56            // jumps (as those are prefix operations as well)
57            ExprPrecedence::Prefix => ExprPrecedence::Jump < self && self < ExprPrecedence::Prefix,
58            parent => self <= parent,
59        }
60    }
61}
62
63#[derive(PartialEq, Debug)]
64pub enum Fixity {
65    /// The operator is left-associative
66    Left,
67    /// The operator is right-associative
68    Right,
69    /// The operator is not associative
70    None,
71}
72
73pub fn precedence(expr: &ast::Expr) -> ExprPrecedence {
74    match expr {
75        Expr::ClosureExpr(closure) => match closure.ret_type() {
76            None => ExprPrecedence::Jump,
77            Some(_) => ExprPrecedence::Unambiguous,
78        },
79
80        Expr::BreakExpr(_)
81        | Expr::BecomeExpr(_)
82        | Expr::ReturnExpr(_)
83        | Expr::YeetExpr(_)
84        | Expr::YieldExpr(_)
85        | Expr::ContinueExpr(_) => ExprPrecedence::Jump,
86
87        Expr::RangeExpr(..) => ExprPrecedence::Range,
88
89        Expr::BinExpr(bin_expr) => match bin_expr.op_kind() {
90            Some(it) => match it {
91                BinaryOp::LogicOp(logic_op) => match logic_op {
92                    ast::LogicOp::And => ExprPrecedence::LAnd,
93                    ast::LogicOp::Or => ExprPrecedence::LOr,
94                },
95                BinaryOp::ArithOp(arith_op) => match arith_op {
96                    ast::ArithOp::Add | ast::ArithOp::Sub => ExprPrecedence::Sum,
97                    ast::ArithOp::Div | ast::ArithOp::Rem | ast::ArithOp::Mul => {
98                        ExprPrecedence::Product
99                    }
100                    ast::ArithOp::Shl | ast::ArithOp::Shr => ExprPrecedence::Shift,
101                    ast::ArithOp::BitXor => ExprPrecedence::BitXor,
102                    ast::ArithOp::BitOr => ExprPrecedence::BitOr,
103                    ast::ArithOp::BitAnd => ExprPrecedence::BitAnd,
104                },
105                BinaryOp::CmpOp(_) => ExprPrecedence::Compare,
106                BinaryOp::Assignment { .. } => ExprPrecedence::Assign,
107            },
108            None => ExprPrecedence::Unambiguous,
109        },
110        Expr::CastExpr(_) => ExprPrecedence::Cast,
111
112        Expr::LetExpr(_) | Expr::PrefixExpr(_) | Expr::RefExpr(_) => ExprPrecedence::Prefix,
113
114        Expr::AwaitExpr(_)
115        | Expr::CallExpr(_)
116        | Expr::FieldExpr(_)
117        | Expr::IndexExpr(_)
118        | Expr::MethodCallExpr(_)
119        | Expr::TryExpr(_) => ExprPrecedence::Postfix,
120
121        Expr::ArrayExpr(_)
122        | Expr::AsmExpr(_)
123        | Expr::BlockExpr(_)
124        | Expr::ForExpr(_)
125        | Expr::FormatArgsExpr(_)
126        | Expr::IfExpr(_)
127        | Expr::Literal(_)
128        | Expr::LoopExpr(_)
129        | Expr::MacroExpr(_)
130        | Expr::MatchExpr(_)
131        | Expr::OffsetOfExpr(_)
132        | Expr::ParenExpr(_)
133        | Expr::PathExpr(_)
134        | Expr::RecordExpr(_)
135        | Expr::TupleExpr(_)
136        | Expr::UnderscoreExpr(_)
137        | Expr::WhileExpr(_) => ExprPrecedence::Unambiguous,
138    }
139}
140
141fn check_ancestry(ancestor: &SyntaxNode, descendent: &SyntaxNode) -> bool {
142    let bail = || always!(false, "{} is not an ancestor of {}", ancestor, descendent);
143
144    if !ancestor.text_range().contains_range(descendent.text_range()) {
145        return bail();
146    }
147
148    for anc in descendent.ancestors() {
149        if anc == *ancestor {
150            return true;
151        }
152    }
153
154    bail()
155}
156
157impl Expr {
158    pub fn precedence(&self) -> ExprPrecedence {
159        precedence(self)
160    }
161
162    // Implementation is based on
163    // - https://doc.rust-lang.org/reference/expressions.html#expression-precedence
164    // - https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
165    // - rustc source, including, but not limited to
166    //   - https://github.com/rust-lang/rust/blob/b6852428a8ea9728369b64b9964cad8e258403d3/compiler/rustc_ast/src/util/parser.rs#L296
167
168    /// Returns `true` if `self` would need to be wrapped in parentheses given that its parent is `parent`.
169    pub fn needs_parens_in(&self, parent: &SyntaxNode) -> bool {
170        self.needs_parens_in_place_of(parent, self.syntax())
171    }
172
173    /// Returns `true` if `self` would need to be wrapped in parentheses if it replaces `place_of`
174    /// given that `place_of`'s parent is `parent`.
175    pub fn needs_parens_in_place_of(&self, parent: &SyntaxNode, place_of: &SyntaxNode) -> bool {
176        if !check_ancestry(parent, place_of) {
177            return false;
178        }
179
180        match_ast! {
181            match parent {
182                ast::Expr(e) => self.needs_parens_in_expr(&e, place_of),
183                ast::Stmt(e) => self.needs_parens_in_stmt(Some(&e)),
184                ast::StmtList(_) => self.needs_parens_in_stmt(None),
185                ast::ArgList(_) => false,
186                ast::MatchArm(_) => false,
187                _ => false,
188            }
189        }
190    }
191
192    fn needs_parens_in_expr(&self, parent: &Expr, place_of: &SyntaxNode) -> bool {
193        // Parentheses are necessary when calling a function-like pointer that is a member of a struct or union
194        // (e.g. `(a.f)()`).
195        let is_parent_call_expr = matches!(parent, ast::Expr::CallExpr(_));
196        let is_field_expr = matches!(self, ast::Expr::FieldExpr(_));
197        if is_parent_call_expr && is_field_expr {
198            return true;
199        }
200
201        // Special-case block weirdness
202        if parent.child_is_followed_by_a_block() {
203            use Expr::*;
204            match self {
205                // Cases like `if return {}` (need parens or else `{}` is returned, instead of being `if`'s body)
206                ReturnExpr(e) if e.expr().is_none() => return true,
207                BreakExpr(e) if e.expr().is_none() => return true,
208                YieldExpr(e) if e.expr().is_none() => return true,
209
210                // Same but with `..{}`
211                RangeExpr(e) if matches!(e.end(), Some(BlockExpr(..))) => return true,
212
213                // Similarly with struct literals, e.g. `if S{} == 1 {}`
214                _ if self.contains_exterior_struct_lit() => return true,
215                _ => {}
216            }
217        }
218
219        // Special-case `return.f()`
220        if self.is_ret_like_with_no_value() && parent.is_postfix() {
221            return false;
222        }
223
224        // Keep parens when a ret-like expr is followed by `||` or `&&`.
225        // For `||`, removing parens could reparse as `<ret-like> || <closure>`.
226        // For `&&`, we avoid introducing `<ret-like> && <expr>` into a binary chain.
227
228        if self.precedence() == ExprPrecedence::Jump
229            && let Some(node) =
230                place_of.ancestors().find(|it| it.parent().is_none_or(|p| &p == parent.syntax()))
231            && let Some(next) =
232                node.last_token().and_then(|t| skip_trivia_token(t.next_token()?, Direction::Next))
233            && matches!(next.kind(), T![||] | T![&&])
234        {
235            return true;
236        }
237
238        if self.is_paren_like()
239            || parent.is_paren_like()
240            || self.is_prefix()
241                && (parent.is_prefix()
242                    || !self.is_ordered_before_parent_in_place_of(parent, place_of))
243            || self.is_postfix()
244                && (parent.is_postfix()
245                    || self.is_ordered_before_parent_in_place_of(parent, place_of))
246        {
247            return false;
248        }
249
250        let (left, right, inv) = match self.is_ordered_before_parent_in_place_of(parent, place_of) {
251            true => (self, parent, false),
252            false => (parent, self, true),
253        };
254
255        let (_, left_right_bp) = left.binding_power();
256        let (right_left_bp, _) = right.binding_power();
257
258        (left_right_bp < right_left_bp) ^ inv
259    }
260
261    fn needs_parens_in_stmt(&self, stmt: Option<&ast::Stmt>) -> bool {
262        use Expr::*;
263
264        // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`,
265        // `{ { 0 } + 1 }` won't parse -- `{ 0 }` would be parsed as a self-contained stmt,
266        // leaving `+ 1` as a parse error.
267        let mut innermost = self.clone();
268        loop {
269            let next = match &innermost {
270                BinExpr(e) => e.lhs(),
271                CallExpr(e) => e.expr(),
272                CastExpr(e) => e.expr(),
273                IndexExpr(e) => e.base(),
274                _ => break,
275            };
276
277            if let Some(next) = next {
278                innermost = next;
279                if !innermost.requires_semi_to_be_stmt() {
280                    return true;
281                }
282            } else {
283                break;
284            }
285        }
286
287        // Not every expression can be followed by `else` in the `let-else`
288        if let Some(ast::Stmt::LetStmt(e)) = stmt
289            && e.let_else().is_some()
290        {
291            match self {
292                BinExpr(e)
293                    if e.op_kind()
294                        .map(|op| matches!(op, BinaryOp::LogicOp(_)))
295                        .unwrap_or(false) =>
296                {
297                    return true;
298                }
299                _ if self.clone().trailing_brace().is_some() => return true,
300                _ => {}
301            }
302        }
303
304        false
305    }
306
307    /// Returns left and right so-called "binding powers" of this expression.
308    fn binding_power(&self) -> (u8, u8) {
309        use ast::{ArithOp::*, BinaryOp::*, Expr::*, LogicOp::*};
310
311        match self {
312            // (0, 0)   -- paren-like/nullary
313            // (0, N)   -- prefix
314            // (N, 0)   -- postfix
315            // (N, N)   -- infix, requires parens
316            // (N, N+1) -- infix, left to right associative
317            // (N+1, N) -- infix, right to left associative
318            // N is odd
319            //
320            ContinueExpr(_) => (0, 0),
321
322            ClosureExpr(_) | ReturnExpr(_) | BecomeExpr(_) | YieldExpr(_) | YeetExpr(_)
323            | BreakExpr(_) | OffsetOfExpr(_) | FormatArgsExpr(_) | AsmExpr(_) => (0, 1),
324
325            RangeExpr(_) => (5, 5),
326
327            BinExpr(e) => {
328                // Return a dummy value if we don't know the op
329                let Some(op) = e.op_kind() else { return (0, 0) };
330                match op {
331                    Assignment { .. } => (4, 3),
332                    //
333                    // Ranges are here in order :)
334                    //
335                    LogicOp(op) => match op {
336                        Or => (7, 8),
337                        And => (9, 10),
338                    },
339                    CmpOp(_) => (11, 11),
340                    ArithOp(op) => match op {
341                        BitOr => (13, 14),
342                        BitXor => (15, 16),
343                        BitAnd => (17, 18),
344                        Shl | Shr => (19, 20),
345                        Add | Sub => (21, 22),
346                        Mul | Div | Rem => (23, 24),
347                    },
348                }
349            }
350
351            CastExpr(_) => (25, 26),
352
353            RefExpr(_) | LetExpr(_) | PrefixExpr(_) => (0, 27),
354
355            AwaitExpr(_) | CallExpr(_) | MethodCallExpr(_) | IndexExpr(_) | TryExpr(_)
356            | MacroExpr(_) => (29, 0),
357
358            FieldExpr(_) => (31, 32),
359
360            ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_)
361            | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_)
362            | RecordExpr(_) | UnderscoreExpr(_) => (0, 0),
363        }
364    }
365
366    fn is_paren_like(&self) -> bool {
367        matches!(self.binding_power(), (0, 0))
368    }
369
370    fn is_prefix(&self) -> bool {
371        matches!(self.binding_power(), (0, 1..))
372    }
373
374    fn is_postfix(&self) -> bool {
375        matches!(self.binding_power(), (1.., 0))
376    }
377
378    /// Returns `true` if this expression can't be a standalone statement.
379    fn requires_semi_to_be_stmt(&self) -> bool {
380        use Expr::*;
381        !matches!(
382            self,
383            IfExpr(..) | MatchExpr(..) | BlockExpr(..) | WhileExpr(..) | LoopExpr(..) | ForExpr(..)
384        )
385    }
386
387    /// If an expression ends with `}`, returns the innermost expression ending in this `}`.
388    fn trailing_brace(mut self) -> Option<Expr> {
389        use Expr::*;
390
391        loop {
392            let rhs = match self {
393                RefExpr(e) => e.expr(),
394                BinExpr(e) => e.rhs(),
395                BreakExpr(e) => e.expr(),
396                LetExpr(e) => e.expr(),
397                RangeExpr(e) => e.end(),
398                ReturnExpr(e) => e.expr(),
399                PrefixExpr(e) => e.expr(),
400                YieldExpr(e) => e.expr(),
401                ClosureExpr(e) => e.body(),
402
403                BlockExpr(..) | ForExpr(..) | IfExpr(..) | LoopExpr(..) | MatchExpr(..)
404                | RecordExpr(..) | WhileExpr(..) => break Some(self),
405                _ => break None,
406            };
407
408            self = rhs?;
409        }
410    }
411
412    /// Expressions that syntactically contain an "exterior" struct literal i.e., not surrounded by any
413    /// parens or other delimiters, e.g., `X { y: 1 }`, `X { y: 1 }.method()`, `foo == X { y: 1 }` and
414    /// `X { y: 1 } == foo` all do, but `(X { y: 1 }) == foo` does not.
415    fn contains_exterior_struct_lit(&self) -> bool {
416        return contains_exterior_struct_lit_inner(self).is_some();
417
418        fn contains_exterior_struct_lit_inner(expr: &Expr) -> Option<()> {
419            use Expr::*;
420
421            match expr {
422                RecordExpr(..) => Some(()),
423
424                // X { y: 1 } + X { y: 2 }
425                BinExpr(e) => e
426                    .lhs()
427                    .as_ref()
428                    .and_then(contains_exterior_struct_lit_inner)
429                    .or_else(|| e.rhs().as_ref().and_then(contains_exterior_struct_lit_inner)),
430
431                // `&X { y: 1 }`, `X { y: 1 }.y`, `X { y: 1 }.bar(...)`, etc
432                IndexExpr(e) => contains_exterior_struct_lit_inner(&e.base()?),
433                AwaitExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
434                PrefixExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
435                CastExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
436                FieldExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
437                MethodCallExpr(e) => contains_exterior_struct_lit_inner(&e.receiver()?),
438
439                _ => None,
440            }
441        }
442    }
443
444    /// Returns true if self is one of `return`, `break`, `continue` or `yield` with **no associated value**.
445    pub fn is_ret_like_with_no_value(&self) -> bool {
446        use Expr::*;
447
448        match self {
449            ReturnExpr(e) => e.expr().is_none(),
450            BreakExpr(e) => e.expr().is_none(),
451            ContinueExpr(_) => true,
452            YieldExpr(e) => e.expr().is_none(),
453            BecomeExpr(e) => e.expr().is_none(),
454            _ => false,
455        }
456    }
457
458    fn is_ordered_before_parent_in_place_of(&self, parent: &Expr, place_of: &SyntaxNode) -> bool {
459        use Expr::*;
460        use rowan::TextSize;
461
462        let self_range = self.syntax().text_range();
463        let place_of_range = place_of.text_range();
464
465        let self_order_adjusted = order(self) - self_range.start() + place_of_range.start();
466
467        let parent_order = order(parent);
468        let parent_order_adjusted = if parent_order <= place_of_range.start() {
469            parent_order
470        } else if parent_order >= place_of_range.end() {
471            parent_order - place_of_range.len() + self_range.len()
472        } else {
473            return false;
474        };
475
476        return self_order_adjusted < parent_order_adjusted;
477
478        /// Returns text range that can be used to compare two expression for order (which goes first).
479        fn order(this: &Expr) -> TextSize {
480            // For non-paren-like operators: get the operator itself
481            let token = match this {
482                RangeExpr(e) => e.op_token(),
483                BinExpr(e) => e.op_token(),
484                CastExpr(e) => e.as_token(),
485                FieldExpr(e) => e.dot_token(),
486                AwaitExpr(e) => e.dot_token(),
487                BreakExpr(e) => e.break_token(),
488                CallExpr(e) => e.arg_list().and_then(|args| args.l_paren_token()),
489                ClosureExpr(e) => e.param_list().and_then(|params| params.l_paren_token()),
490                ContinueExpr(e) => e.continue_token(),
491                IndexExpr(e) => e.l_brack_token(),
492                MethodCallExpr(e) => e.dot_token(),
493                PrefixExpr(e) => e.op_token(),
494                RefExpr(e) => e.amp_token(),
495                ReturnExpr(e) => e.return_token(),
496                BecomeExpr(e) => e.become_token(),
497                TryExpr(e) => e.question_mark_token(),
498                YieldExpr(e) => e.yield_token(),
499                YeetExpr(e) => e.do_token(),
500                LetExpr(e) => e.let_token(),
501                OffsetOfExpr(e) => e.builtin_token(),
502                FormatArgsExpr(e) => e.builtin_token(),
503                AsmExpr(e) => e.builtin_token(),
504                ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_)
505                | IfExpr(_) | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_)
506                | BlockExpr(_) | RecordExpr(_) | UnderscoreExpr(_) | MacroExpr(_) => None,
507            };
508
509            token.map(|t| t.text_range()).unwrap_or_else(|| this.syntax().text_range()).start()
510        }
511    }
512
513    fn child_is_followed_by_a_block(&self) -> bool {
514        use Expr::*;
515
516        match self {
517            ArrayExpr(_) | AwaitExpr(_) | BlockExpr(_) | CallExpr(_) | CastExpr(_)
518            | ClosureExpr(_) | FieldExpr(_) | IndexExpr(_) | Literal(_) | LoopExpr(_)
519            | MacroExpr(_) | MethodCallExpr(_) | ParenExpr(_) | PathExpr(_) | RecordExpr(_)
520            | TryExpr(_) | TupleExpr(_) | UnderscoreExpr(_) | OffsetOfExpr(_)
521            | FormatArgsExpr(_) | AsmExpr(_) => false,
522
523            // For BinExpr and RangeExpr this is technically wrong -- the child can be on the left...
524            BinExpr(_) | RangeExpr(_) | BreakExpr(_) | ContinueExpr(_) | PrefixExpr(_)
525            | RefExpr(_) | ReturnExpr(_) | BecomeExpr(_) | YieldExpr(_) | YeetExpr(_)
526            | LetExpr(_) => self
527                .syntax()
528                .parent()
529                .and_then(Expr::cast)
530                .map(|e| e.child_is_followed_by_a_block())
531                .unwrap_or(false),
532
533            ForExpr(_) | IfExpr(_) | MatchExpr(_) | WhileExpr(_) => true,
534        }
535    }
536}