| use crate::ast::{self, BinOpKind}; |
| use crate::token::{self, BinOpToken, Token}; |
| use rustc_span::symbol::kw; |
| |
| /// Associative operator with precedence. |
| /// |
| /// This is the enum which specifies operator precedence and fixity to the parser. |
| #[derive(Copy, Clone, PartialEq, Debug)] |
| pub enum AssocOp { |
| /// `+` |
| Add, |
| /// `-` |
| Subtract, |
| /// `*` |
| Multiply, |
| /// `/` |
| Divide, |
| /// `%` |
| Modulus, |
| /// `&&` |
| LAnd, |
| /// `||` |
| LOr, |
| /// `^` |
| BitXor, |
| /// `&` |
| BitAnd, |
| /// `|` |
| BitOr, |
| /// `<<` |
| ShiftLeft, |
| /// `>>` |
| ShiftRight, |
| /// `==` |
| Equal, |
| /// `<` |
| Less, |
| /// `<=` |
| LessEqual, |
| /// `!=` |
| NotEqual, |
| /// `>` |
| Greater, |
| /// `>=` |
| GreaterEqual, |
| /// `=` |
| Assign, |
| /// `?=` where ? is one of the BinOpToken |
| AssignOp(BinOpToken), |
| /// `as` |
| As, |
| /// `..` range |
| DotDot, |
| /// `..=` range |
| DotDotEq, |
| /// `:` |
| Colon, |
| } |
| |
| #[derive(PartialEq, Debug)] |
| pub enum Fixity { |
| /// The operator is left-associative |
| Left, |
| /// The operator is right-associative |
| Right, |
| /// The operator is not associative |
| None, |
| } |
| |
| impl AssocOp { |
| /// Creates a new AssocOP from a token |
| pub fn from_token(t: &Token) -> Option<AssocOp> { |
| use AssocOp::*; |
| match t.kind { |
| token::BinOpEq(k) => Some(AssignOp(k)), |
| token::Eq => Some(Assign), |
| token::BinOp(BinOpToken::Star) => Some(Multiply), |
| token::BinOp(BinOpToken::Slash) => Some(Divide), |
| token::BinOp(BinOpToken::Percent) => Some(Modulus), |
| token::BinOp(BinOpToken::Plus) => Some(Add), |
| token::BinOp(BinOpToken::Minus) => Some(Subtract), |
| token::BinOp(BinOpToken::Shl) => Some(ShiftLeft), |
| token::BinOp(BinOpToken::Shr) => Some(ShiftRight), |
| token::BinOp(BinOpToken::And) => Some(BitAnd), |
| token::BinOp(BinOpToken::Caret) => Some(BitXor), |
| token::BinOp(BinOpToken::Or) => Some(BitOr), |
| token::Lt => Some(Less), |
| token::Le => Some(LessEqual), |
| token::Ge => Some(GreaterEqual), |
| token::Gt => Some(Greater), |
| token::EqEq => Some(Equal), |
| token::Ne => Some(NotEqual), |
| token::AndAnd => Some(LAnd), |
| token::OrOr => Some(LOr), |
| token::DotDot => Some(DotDot), |
| token::DotDotEq => Some(DotDotEq), |
| // DotDotDot is no longer supported, but we need some way to display the error |
| token::DotDotDot => Some(DotDotEq), |
| token::Colon => Some(Colon), |
| // `<-` should probably be `< -` |
| token::LArrow => Some(Less), |
| _ if t.is_keyword(kw::As) => Some(As), |
| _ => None, |
| } |
| } |
| |
| /// Creates a new AssocOp from ast::BinOpKind. |
| pub fn from_ast_binop(op: BinOpKind) -> Self { |
| use AssocOp::*; |
| match op { |
| BinOpKind::Lt => Less, |
| BinOpKind::Gt => Greater, |
| BinOpKind::Le => LessEqual, |
| BinOpKind::Ge => GreaterEqual, |
| BinOpKind::Eq => Equal, |
| BinOpKind::Ne => NotEqual, |
| BinOpKind::Mul => Multiply, |
| BinOpKind::Div => Divide, |
| BinOpKind::Rem => Modulus, |
| BinOpKind::Add => Add, |
| BinOpKind::Sub => Subtract, |
| BinOpKind::Shl => ShiftLeft, |
| BinOpKind::Shr => ShiftRight, |
| BinOpKind::BitAnd => BitAnd, |
| BinOpKind::BitXor => BitXor, |
| BinOpKind::BitOr => BitOr, |
| BinOpKind::And => LAnd, |
| BinOpKind::Or => LOr, |
| } |
| } |
| |
| /// Gets the precedence of this operator |
| pub fn precedence(&self) -> usize { |
| use AssocOp::*; |
| match *self { |
| As | Colon => 14, |
| Multiply | Divide | Modulus => 13, |
| Add | Subtract => 12, |
| ShiftLeft | ShiftRight => 11, |
| BitAnd => 10, |
| BitXor => 9, |
| BitOr => 8, |
| Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => 7, |
| LAnd => 6, |
| LOr => 5, |
| DotDot | DotDotEq => 4, |
| Assign | AssignOp(_) => 2, |
| } |
| } |
| |
| /// Gets the fixity of this operator |
| pub fn fixity(&self) -> Fixity { |
| use AssocOp::*; |
| // NOTE: it is a bug to have an operators that has same precedence but different fixities! |
| match *self { |
| Assign | AssignOp(_) => Fixity::Right, |
| As | Multiply | Divide | Modulus | Add | Subtract | ShiftLeft | ShiftRight | BitAnd |
| | BitXor | BitOr | Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual |
| | LAnd | LOr | Colon => Fixity::Left, |
| DotDot | DotDotEq => Fixity::None, |
| } |
| } |
| |
| pub fn is_comparison(&self) -> bool { |
| use AssocOp::*; |
| match *self { |
| Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => true, |
| Assign | AssignOp(_) | As | Multiply | Divide | Modulus | Add | Subtract |
| | ShiftLeft | ShiftRight | BitAnd | BitXor | BitOr | LAnd | LOr | DotDot | DotDotEq |
| | Colon => false, |
| } |
| } |
| |
| pub fn is_assign_like(&self) -> bool { |
| use AssocOp::*; |
| match *self { |
| Assign | AssignOp(_) => true, |
| Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual | As | Multiply |
| | Divide | Modulus | Add | Subtract | ShiftLeft | ShiftRight | BitAnd | BitXor |
| | BitOr | LAnd | LOr | DotDot | DotDotEq | Colon => false, |
| } |
| } |
| |
| pub fn to_ast_binop(&self) -> Option<BinOpKind> { |
| use AssocOp::*; |
| match *self { |
| Less => Some(BinOpKind::Lt), |
| Greater => Some(BinOpKind::Gt), |
| LessEqual => Some(BinOpKind::Le), |
| GreaterEqual => Some(BinOpKind::Ge), |
| Equal => Some(BinOpKind::Eq), |
| NotEqual => Some(BinOpKind::Ne), |
| Multiply => Some(BinOpKind::Mul), |
| Divide => Some(BinOpKind::Div), |
| Modulus => Some(BinOpKind::Rem), |
| Add => Some(BinOpKind::Add), |
| Subtract => Some(BinOpKind::Sub), |
| ShiftLeft => Some(BinOpKind::Shl), |
| ShiftRight => Some(BinOpKind::Shr), |
| BitAnd => Some(BinOpKind::BitAnd), |
| BitXor => Some(BinOpKind::BitXor), |
| BitOr => Some(BinOpKind::BitOr), |
| LAnd => Some(BinOpKind::And), |
| LOr => Some(BinOpKind::Or), |
| Assign | AssignOp(_) | As | DotDot | DotDotEq | Colon => None, |
| } |
| } |
| |
| /// This operator could be used to follow a block unambiguously. |
| /// |
| /// This is used for error recovery at the moment, providing a suggestion to wrap blocks with |
| /// parentheses while having a high degree of confidence on the correctness of the suggestion. |
| pub fn can_continue_expr_unambiguously(&self) -> bool { |
| use AssocOp::*; |
| matches!( |
| self, |
| BitXor | // `{ 42 } ^ 3` |
| Assign | // `{ 42 } = { 42 }` |
| Divide | // `{ 42 } / 42` |
| Modulus | // `{ 42 } % 2` |
| ShiftRight | // `{ 42 } >> 2` |
| LessEqual | // `{ 42 } <= 3` |
| Greater | // `{ 42 } > 3` |
| GreaterEqual | // `{ 42 } >= 3` |
| AssignOp(_) | // `{ 42 } +=` |
| As | // `{ 42 } as usize` |
| // Equal | // `{ 42 } == { 42 }` Accepting these here would regress incorrect |
| // NotEqual | // `{ 42 } != { 42 } struct literals parser recovery. |
| Colon, // `{ 42 }: usize` |
| ) |
| } |
| } |
| |
| pub const PREC_CLOSURE: i8 = -40; |
| pub const PREC_JUMP: i8 = -30; |
| pub const PREC_RANGE: i8 = -10; |
| // The range 2..=14 is reserved for AssocOp binary operator precedences. |
| pub const PREC_PREFIX: i8 = 50; |
| pub const PREC_POSTFIX: i8 = 60; |
| pub const PREC_PAREN: i8 = 99; |
| pub const PREC_FORCE_PAREN: i8 = 100; |
| |
| #[derive(Debug, Clone, Copy)] |
| pub enum ExprPrecedence { |
| Closure, |
| Break, |
| Continue, |
| Ret, |
| Yield, |
| Yeet, |
| |
| Range, |
| |
| Binary(BinOpKind), |
| |
| Cast, |
| Type, |
| |
| Assign, |
| AssignOp, |
| |
| Box, |
| AddrOf, |
| Let, |
| Unary, |
| |
| Call, |
| MethodCall, |
| Field, |
| Index, |
| Try, |
| InlineAsm, |
| Mac, |
| |
| Array, |
| Repeat, |
| Tup, |
| Lit, |
| Path, |
| Paren, |
| If, |
| While, |
| ForLoop, |
| Loop, |
| Match, |
| ConstBlock, |
| Block, |
| TryBlock, |
| Struct, |
| Async, |
| Await, |
| Err, |
| } |
| |
| impl ExprPrecedence { |
| pub fn order(self) -> i8 { |
| match self { |
| ExprPrecedence::Closure => PREC_CLOSURE, |
| |
| ExprPrecedence::Break |
| | ExprPrecedence::Continue |
| | ExprPrecedence::Ret |
| | ExprPrecedence::Yield |
| | ExprPrecedence::Yeet => PREC_JUMP, |
| |
| // `Range` claims to have higher precedence than `Assign`, but `x .. x = x` fails to |
| // parse, instead of parsing as `(x .. x) = x`. Giving `Range` a lower precedence |
| // ensures that `pprust` will add parentheses in the right places to get the desired |
| // parse. |
| ExprPrecedence::Range => PREC_RANGE, |
| |
| // Binop-like expr kinds, handled by `AssocOp`. |
| ExprPrecedence::Binary(op) => AssocOp::from_ast_binop(op).precedence() as i8, |
| ExprPrecedence::Cast => AssocOp::As.precedence() as i8, |
| ExprPrecedence::Type => AssocOp::Colon.precedence() as i8, |
| |
| ExprPrecedence::Assign | |
| ExprPrecedence::AssignOp => AssocOp::Assign.precedence() as i8, |
| |
| // Unary, prefix |
| ExprPrecedence::Box |
| | ExprPrecedence::AddrOf |
| // Here `let pats = expr` has `let pats =` as a "unary" prefix of `expr`. |
| // However, this is not exactly right. When `let _ = a` is the LHS of a binop we |
| // need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b` |
| // but we need to print `(let _ = a) < b` as-is with parens. |
| | ExprPrecedence::Let |
| | ExprPrecedence::Unary => PREC_PREFIX, |
| |
| // Unary, postfix |
| ExprPrecedence::Await |
| | ExprPrecedence::Call |
| | ExprPrecedence::MethodCall |
| | ExprPrecedence::Field |
| | ExprPrecedence::Index |
| | ExprPrecedence::Try |
| | ExprPrecedence::InlineAsm |
| | ExprPrecedence::Mac => PREC_POSTFIX, |
| |
| // Never need parens |
| ExprPrecedence::Array |
| | ExprPrecedence::Repeat |
| | ExprPrecedence::Tup |
| | ExprPrecedence::Lit |
| | ExprPrecedence::Path |
| | ExprPrecedence::Paren |
| | ExprPrecedence::If |
| | ExprPrecedence::While |
| | ExprPrecedence::ForLoop |
| | ExprPrecedence::Loop |
| | ExprPrecedence::Match |
| | ExprPrecedence::ConstBlock |
| | ExprPrecedence::Block |
| | ExprPrecedence::TryBlock |
| | ExprPrecedence::Async |
| | ExprPrecedence::Struct |
| | ExprPrecedence::Err => PREC_PAREN, |
| } |
| } |
| } |
| |
| /// In `let p = e`, operators with precedence `<=` this one requires parentheses in `e`. |
| pub fn prec_let_scrutinee_needs_par() -> usize { |
| AssocOp::LAnd.precedence() |
| } |
| |
| /// Suppose we have `let _ = e` and the `order` of `e`. |
| /// Is the `order` such that `e` in `let _ = e` needs parentheses when it is on the RHS? |
| /// |
| /// Conversely, suppose that we have `(let _ = a) OP b` and `order` is that of `OP`. |
| /// Can we print this as `let _ = a OP b`? |
| pub fn needs_par_as_let_scrutinee(order: i8) -> bool { |
| order <= prec_let_scrutinee_needs_par() as i8 |
| } |
| |
| /// Expressions that syntactically contain an "exterior" struct literal i.e., not surrounded by any |
| /// parens or other delimiters, e.g., `X { y: 1 }`, `X { y: 1 }.method()`, `foo == X { y: 1 }` and |
| /// `X { y: 1 } == foo` all do, but `(X { y: 1 }) == foo` does not. |
| pub fn contains_exterior_struct_lit(value: &ast::Expr) -> bool { |
| match value.kind { |
| ast::ExprKind::Struct(..) => true, |
| |
| ast::ExprKind::Assign(ref lhs, ref rhs, _) |
| | ast::ExprKind::AssignOp(_, ref lhs, ref rhs) |
| | ast::ExprKind::Binary(_, ref lhs, ref rhs) => { |
| // X { y: 1 } + X { y: 2 } |
| contains_exterior_struct_lit(&lhs) || contains_exterior_struct_lit(&rhs) |
| } |
| ast::ExprKind::Await(ref x) |
| | ast::ExprKind::Unary(_, ref x) |
| | ast::ExprKind::Cast(ref x, _) |
| | ast::ExprKind::Type(ref x, _) |
| | ast::ExprKind::Field(ref x, _) |
| | ast::ExprKind::Index(ref x, _) => { |
| // &X { y: 1 }, X { y: 1 }.y |
| contains_exterior_struct_lit(&x) |
| } |
| |
| ast::ExprKind::MethodCall(_, ref receiver, _, _) => { |
| // X { y: 1 }.bar(...) |
| contains_exterior_struct_lit(&receiver) |
| } |
| |
| _ => false, |
| } |
| } |