diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_ast/src/util/parser.rs | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs new file mode 100644 index 000000000..74b7fe9e2 --- /dev/null +++ b/compiler/rustc_ast/src/util/parser.rs @@ -0,0 +1,406 @@ +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 exprs, _) => { + // X { y: 1 }.bar(...) + contains_exterior_struct_lit(&exprs[0]) + } + + _ => false, + } +} |