use stdx::always;
use crate::{
AstNode, SyntaxNode,
ast::{self, BinaryOp, Expr, HasArgList, RangeItem},
match_ast,
};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum ExprPrecedence {
Jump,
Assign,
Range,
LOr,
LAnd,
Compare,
BitOr,
BitXor,
BitAnd,
Shift,
Sum,
Product,
Cast,
Prefix,
Postfix,
Unambiguous,
}
impl ExprPrecedence {
pub fn needs_parentheses_in(self, other: ExprPrecedence) -> bool {
match other {
ExprPrecedence::Unambiguous => false,
ExprPrecedence::Postfix => self < ExprPrecedence::Postfix,
ExprPrecedence::Prefix => ExprPrecedence::Jump < self && self < ExprPrecedence::Prefix,
parent => self <= parent,
}
}
}
#[derive(PartialEq, Debug)]
pub enum Fixity {
Left,
Right,
None,
}
pub fn precedence(expr: &ast::Expr) -> ExprPrecedence {
match expr {
Expr::ClosureExpr(closure) => match closure.ret_type() {
None => ExprPrecedence::Jump,
Some(_) => ExprPrecedence::Unambiguous,
},
Expr::BreakExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
Expr::BecomeExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
Expr::ReturnExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
Expr::YeetExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
Expr::YieldExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
Expr::BreakExpr(_)
| Expr::BecomeExpr(_)
| Expr::ReturnExpr(_)
| Expr::YeetExpr(_)
| Expr::YieldExpr(_)
| Expr::ContinueExpr(_) => ExprPrecedence::Unambiguous,
Expr::RangeExpr(..) => ExprPrecedence::Range,
Expr::BinExpr(bin_expr) => match bin_expr.op_kind() {
Some(it) => match it {
BinaryOp::LogicOp(logic_op) => match logic_op {
ast::LogicOp::And => ExprPrecedence::LAnd,
ast::LogicOp::Or => ExprPrecedence::LOr,
},
BinaryOp::ArithOp(arith_op) => match arith_op {
ast::ArithOp::Add | ast::ArithOp::Sub => ExprPrecedence::Sum,
ast::ArithOp::Div | ast::ArithOp::Rem | ast::ArithOp::Mul => {
ExprPrecedence::Product
}
ast::ArithOp::Shl | ast::ArithOp::Shr => ExprPrecedence::Shift,
ast::ArithOp::BitXor => ExprPrecedence::BitXor,
ast::ArithOp::BitOr => ExprPrecedence::BitOr,
ast::ArithOp::BitAnd => ExprPrecedence::BitAnd,
},
BinaryOp::CmpOp(_) => ExprPrecedence::Compare,
BinaryOp::Assignment { .. } => ExprPrecedence::Assign,
},
None => ExprPrecedence::Unambiguous,
},
Expr::CastExpr(_) => ExprPrecedence::Cast,
Expr::LetExpr(_) | Expr::PrefixExpr(_) | Expr::RefExpr(_) => ExprPrecedence::Prefix,
Expr::AwaitExpr(_)
| Expr::CallExpr(_)
| Expr::FieldExpr(_)
| Expr::IndexExpr(_)
| Expr::MethodCallExpr(_)
| Expr::TryExpr(_) => ExprPrecedence::Postfix,
Expr::ArrayExpr(_)
| Expr::AsmExpr(_)
| Expr::BlockExpr(_)
| Expr::ForExpr(_)
| Expr::FormatArgsExpr(_)
| Expr::IfExpr(_)
| Expr::Literal(_)
| Expr::LoopExpr(_)
| Expr::MacroExpr(_)
| Expr::MatchExpr(_)
| Expr::OffsetOfExpr(_)
| Expr::ParenExpr(_)
| Expr::PathExpr(_)
| Expr::RecordExpr(_)
| Expr::TupleExpr(_)
| Expr::UnderscoreExpr(_)
| Expr::WhileExpr(_) => ExprPrecedence::Unambiguous,
}
}
fn check_ancestry(ancestor: &SyntaxNode, descendent: &SyntaxNode) -> bool {
let bail = || always!(false, "{} is not an ancestor of {}", ancestor, descendent);
if !ancestor.text_range().contains_range(descendent.text_range()) {
return bail();
}
for anc in descendent.ancestors() {
if anc == *ancestor {
return true;
}
}
bail()
}
impl Expr {
pub fn precedence(&self) -> ExprPrecedence {
precedence(self)
}
pub fn needs_parens_in(&self, parent: &SyntaxNode) -> bool {
self.needs_parens_in_place_of(parent, self.syntax())
}
pub fn needs_parens_in_place_of(&self, parent: &SyntaxNode, place_of: &SyntaxNode) -> bool {
if !check_ancestry(parent, place_of) {
return false;
}
match_ast! {
match parent {
ast::Expr(e) => self.needs_parens_in_expr(&e, place_of),
ast::Stmt(e) => self.needs_parens_in_stmt(Some(&e)),
ast::StmtList(_) => self.needs_parens_in_stmt(None),
ast::ArgList(_) => false,
ast::MatchArm(_) => false,
_ => false,
}
}
}
fn needs_parens_in_expr(&self, parent: &Expr, place_of: &SyntaxNode) -> bool {
let is_parent_call_expr = matches!(parent, ast::Expr::CallExpr(_));
let is_field_expr = matches!(self, ast::Expr::FieldExpr(_));
if is_parent_call_expr && is_field_expr {
return true;
}
if parent.child_is_followed_by_a_block() {
use Expr::*;
match self {
ReturnExpr(e) if e.expr().is_none() => return true,
BreakExpr(e) if e.expr().is_none() => return true,
YieldExpr(e) if e.expr().is_none() => return true,
RangeExpr(e) if matches!(e.end(), Some(BlockExpr(..))) => return true,
_ if self.contains_exterior_struct_lit() => return true,
_ => {}
}
}
if self.is_ret_like_with_no_value() && parent.is_postfix() {
return false;
}
if self.is_paren_like()
|| parent.is_paren_like()
|| self.is_prefix()
&& (parent.is_prefix()
|| !self.is_ordered_before_parent_in_place_of(parent, place_of))
|| self.is_postfix()
&& (parent.is_postfix()
|| self.is_ordered_before_parent_in_place_of(parent, place_of))
{
return false;
}
let (left, right, inv) = match self.is_ordered_before_parent_in_place_of(parent, place_of) {
true => (self, parent, false),
false => (parent, self, true),
};
let (_, left_right_bp) = left.binding_power();
let (right_left_bp, _) = right.binding_power();
(left_right_bp < right_left_bp) ^ inv
}
fn needs_parens_in_stmt(&self, stmt: Option<&ast::Stmt>) -> bool {
use Expr::*;
let mut innermost = self.clone();
loop {
let next = match &innermost {
BinExpr(e) => e.lhs(),
CallExpr(e) => e.expr(),
CastExpr(e) => e.expr(),
IndexExpr(e) => e.base(),
_ => break,
};
if let Some(next) = next {
innermost = next;
if !innermost.requires_semi_to_be_stmt() {
return true;
}
} else {
break;
}
}
if let Some(ast::Stmt::LetStmt(e)) = stmt {
if e.let_else().is_some() {
match self {
BinExpr(e)
if e.op_kind()
.map(|op| matches!(op, BinaryOp::LogicOp(_)))
.unwrap_or(false) =>
{
return true;
}
_ if self.clone().trailing_brace().is_some() => return true,
_ => {}
}
}
}
false
}
fn binding_power(&self) -> (u8, u8) {
use ast::{ArithOp::*, BinaryOp::*, Expr::*, LogicOp::*};
match self {
ContinueExpr(_) => (0, 0),
ClosureExpr(_) | ReturnExpr(_) | BecomeExpr(_) | YieldExpr(_) | YeetExpr(_)
| BreakExpr(_) | OffsetOfExpr(_) | FormatArgsExpr(_) | AsmExpr(_) => (0, 1),
RangeExpr(_) => (5, 5),
BinExpr(e) => {
let Some(op) = e.op_kind() else { return (0, 0) };
match op {
Assignment { .. } => (4, 3),
LogicOp(op) => match op {
Or => (7, 8),
And => (9, 10),
},
CmpOp(_) => (11, 11),
ArithOp(op) => match op {
BitOr => (13, 14),
BitXor => (15, 16),
BitAnd => (17, 18),
Shl | Shr => (19, 20),
Add | Sub => (21, 22),
Mul | Div | Rem => (23, 24),
},
}
}
CastExpr(_) => (25, 26),
RefExpr(_) | LetExpr(_) | PrefixExpr(_) => (0, 27),
AwaitExpr(_) | CallExpr(_) | MethodCallExpr(_) | IndexExpr(_) | TryExpr(_)
| MacroExpr(_) => (29, 0),
FieldExpr(_) => (31, 32),
ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_)
| WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_)
| RecordExpr(_) | UnderscoreExpr(_) => (0, 0),
}
}
fn is_paren_like(&self) -> bool {
matches!(self.binding_power(), (0, 0))
}
fn is_prefix(&self) -> bool {
matches!(self.binding_power(), (0, 1..))
}
fn is_postfix(&self) -> bool {
matches!(self.binding_power(), (1.., 0))
}
fn requires_semi_to_be_stmt(&self) -> bool {
use Expr::*;
!matches!(
self,
IfExpr(..) | MatchExpr(..) | BlockExpr(..) | WhileExpr(..) | LoopExpr(..) | ForExpr(..)
)
}
fn trailing_brace(mut self) -> Option<Expr> {
use Expr::*;
loop {
let rhs = match self {
RefExpr(e) => e.expr(),
BinExpr(e) => e.rhs(),
BreakExpr(e) => e.expr(),
LetExpr(e) => e.expr(),
RangeExpr(e) => e.end(),
ReturnExpr(e) => e.expr(),
PrefixExpr(e) => e.expr(),
YieldExpr(e) => e.expr(),
ClosureExpr(e) => e.body(),
BlockExpr(..) | ForExpr(..) | IfExpr(..) | LoopExpr(..) | MatchExpr(..)
| RecordExpr(..) | WhileExpr(..) => break Some(self),
_ => break None,
};
self = rhs?;
}
}
fn contains_exterior_struct_lit(&self) -> bool {
return contains_exterior_struct_lit_inner(self).is_some();
fn contains_exterior_struct_lit_inner(expr: &Expr) -> Option<()> {
use Expr::*;
match expr {
RecordExpr(..) => Some(()),
BinExpr(e) => e
.lhs()
.as_ref()
.and_then(contains_exterior_struct_lit_inner)
.or_else(|| e.rhs().as_ref().and_then(contains_exterior_struct_lit_inner)),
IndexExpr(e) => contains_exterior_struct_lit_inner(&e.base()?),
AwaitExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
PrefixExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
CastExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
FieldExpr(e) => contains_exterior_struct_lit_inner(&e.expr()?),
MethodCallExpr(e) => contains_exterior_struct_lit_inner(&e.receiver()?),
_ => None,
}
}
}
pub fn is_ret_like_with_no_value(&self) -> bool {
use Expr::*;
match self {
ReturnExpr(e) => e.expr().is_none(),
BreakExpr(e) => e.expr().is_none(),
ContinueExpr(_) => true,
YieldExpr(e) => e.expr().is_none(),
BecomeExpr(e) => e.expr().is_none(),
_ => false,
}
}
fn is_ordered_before_parent_in_place_of(&self, parent: &Expr, place_of: &SyntaxNode) -> bool {
use Expr::*;
use rowan::TextSize;
let self_range = self.syntax().text_range();
let place_of_range = place_of.text_range();
let self_order_adjusted = order(self) - self_range.start() + place_of_range.start();
let parent_order = order(parent);
let parent_order_adjusted = if parent_order <= place_of_range.start() {
parent_order
} else if parent_order >= place_of_range.end() {
parent_order - place_of_range.len() + self_range.len()
} else {
return false;
};
return self_order_adjusted < parent_order_adjusted;
fn order(this: &Expr) -> TextSize {
let token = match this {
RangeExpr(e) => e.op_token(),
BinExpr(e) => e.op_token(),
CastExpr(e) => e.as_token(),
FieldExpr(e) => e.dot_token(),
AwaitExpr(e) => e.dot_token(),
BreakExpr(e) => e.break_token(),
CallExpr(e) => e.arg_list().and_then(|args| args.l_paren_token()),
ClosureExpr(e) => e.param_list().and_then(|params| params.l_paren_token()),
ContinueExpr(e) => e.continue_token(),
IndexExpr(e) => e.l_brack_token(),
MethodCallExpr(e) => e.dot_token(),
PrefixExpr(e) => e.op_token(),
RefExpr(e) => e.amp_token(),
ReturnExpr(e) => e.return_token(),
BecomeExpr(e) => e.become_token(),
TryExpr(e) => e.question_mark_token(),
YieldExpr(e) => e.yield_token(),
YeetExpr(e) => e.do_token(),
LetExpr(e) => e.let_token(),
OffsetOfExpr(e) => e.builtin_token(),
FormatArgsExpr(e) => e.builtin_token(),
AsmExpr(e) => e.builtin_token(),
ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_)
| IfExpr(_) | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_)
| BlockExpr(_) | RecordExpr(_) | UnderscoreExpr(_) | MacroExpr(_) => None,
};
token.map(|t| t.text_range()).unwrap_or_else(|| this.syntax().text_range()).start()
}
}
fn child_is_followed_by_a_block(&self) -> bool {
use Expr::*;
match self {
ArrayExpr(_) | AwaitExpr(_) | BlockExpr(_) | CallExpr(_) | CastExpr(_)
| ClosureExpr(_) | FieldExpr(_) | IndexExpr(_) | Literal(_) | LoopExpr(_)
| MacroExpr(_) | MethodCallExpr(_) | ParenExpr(_) | PathExpr(_) | RecordExpr(_)
| TryExpr(_) | TupleExpr(_) | UnderscoreExpr(_) | OffsetOfExpr(_)
| FormatArgsExpr(_) | AsmExpr(_) => false,
BinExpr(_) | RangeExpr(_) | BreakExpr(_) | ContinueExpr(_) | PrefixExpr(_)
| RefExpr(_) | ReturnExpr(_) | BecomeExpr(_) | YieldExpr(_) | YeetExpr(_)
| LetExpr(_) => self
.syntax()
.parent()
.and_then(Expr::cast)
.map(|e| e.child_is_followed_by_a_block())
.unwrap_or(false),
ForExpr(_) | IfExpr(_) | MatchExpr(_) | WhileExpr(_) => true,
}
}
}