rust-analyzer/crates/syntax/src/ast/expr_ext.rs
2021-09-01 11:11:57 +02:00

462 lines
16 KiB
Rust

//! Various extension methods to ast Expr Nodes, which are hard to code-generate.
use rowan::WalkEvent;
use crate::{
ast::{
self,
operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
support, AstChildren, AstNode,
},
AstToken,
SyntaxKind::*,
SyntaxToken, T,
};
impl ast::AttrsOwner for ast::Expr {}
impl ast::Expr {
pub fn is_block_like(&self) -> bool {
matches!(
self,
ast::Expr::IfExpr(_)
| ast::Expr::LoopExpr(_)
| ast::Expr::ForExpr(_)
| ast::Expr::WhileExpr(_)
| ast::Expr::BlockExpr(_)
| ast::Expr::MatchExpr(_)
| ast::Expr::EffectExpr(_)
)
}
pub fn name_ref(&self) -> Option<ast::NameRef> {
if let ast::Expr::PathExpr(expr) = self {
let path = expr.path()?;
let segment = path.segment()?;
let name_ref = segment.name_ref()?;
if path.qualifier().is_none() {
return Some(name_ref);
}
}
None
}
/// Preorder walk all the expression's child expressions.
pub fn walk(&self, cb: &mut dyn FnMut(ast::Expr)) {
self.preorder(&mut |ev| {
if let WalkEvent::Enter(expr) = ev {
cb(expr);
}
false
})
}
/// Preorder walk all the expression's child expressions preserving events.
/// If the callback returns true on an [`WalkEvent::Enter`], the subtree of the expression will be skipped.
/// Note that the subtree may already be skipped due to the context analysis this function does.
pub fn preorder(&self, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
let mut preorder = self.syntax().preorder();
while let Some(event) = preorder.next() {
let node = match event {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(node) => {
if let Some(expr) = ast::Expr::cast(node) {
cb(WalkEvent::Leave(expr));
}
continue;
}
};
match ast::Stmt::cast(node.clone()) {
// recursively walk the initializer, skipping potential const pat expressions
// let statements aren't usually nested too deeply so this is fine to recurse on
Some(ast::Stmt::LetStmt(l)) => {
if let Some(expr) = l.initializer() {
expr.preorder(cb);
}
preorder.skip_subtree();
}
// Don't skip subtree since we want to process the expression child next
Some(ast::Stmt::ExprStmt(_)) => (),
// This might be an expression
Some(ast::Stmt::Item(ast::Item::MacroCall(mcall))) => {
cb(WalkEvent::Enter(ast::Expr::MacroCall(mcall)));
preorder.skip_subtree();
}
// skip inner items which might have their own expressions
Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
None => {
// skip const args, those expressions are a different context
if ast::GenericArg::can_cast(node.kind()) {
preorder.skip_subtree();
} else if let Some(expr) = ast::Expr::cast(node) {
let is_different_context = match &expr {
ast::Expr::EffectExpr(effect) => {
matches!(
effect.effect(),
ast::Effect::Async(_)
| ast::Effect::Try(_)
| ast::Effect::Const(_)
)
}
ast::Expr::ClosureExpr(_) => true,
_ => false,
};
let skip = cb(WalkEvent::Enter(expr));
if skip || is_different_context {
preorder.skip_subtree();
}
}
}
}
}
}
/// Preorder walk all the expression's child patterns.
pub fn walk_patterns(&self, cb: &mut dyn FnMut(ast::Pat)) {
let mut preorder = self.syntax().preorder();
while let Some(event) = preorder.next() {
let node = match event {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(_) => continue,
};
match ast::Stmt::cast(node.clone()) {
Some(ast::Stmt::LetStmt(l)) => {
if let Some(pat) = l.pat() {
pat.walk(cb);
}
if let Some(expr) = l.initializer() {
expr.walk_patterns(cb);
}
preorder.skip_subtree();
}
// Don't skip subtree since we want to process the expression child next
Some(ast::Stmt::ExprStmt(_)) => (),
// skip inner items which might have their own patterns
Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
None => {
// skip const args, those are a different context
if ast::GenericArg::can_cast(node.kind()) {
preorder.skip_subtree();
} else if let Some(expr) = ast::Expr::cast(node.clone()) {
let is_different_context = match &expr {
ast::Expr::EffectExpr(effect) => {
matches!(
effect.effect(),
ast::Effect::Async(_)
| ast::Effect::Try(_)
| ast::Effect::Const(_)
)
}
ast::Expr::ClosureExpr(_) => true,
_ => false,
};
if is_different_context {
preorder.skip_subtree();
}
} else if let Some(pat) = ast::Pat::cast(node) {
preorder.skip_subtree();
pat.walk(cb);
}
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ElseBranch {
Block(ast::BlockExpr),
IfExpr(ast::IfExpr),
}
impl From<ast::BlockExpr> for ElseBranch {
fn from(block_expr: ast::BlockExpr) -> Self {
Self::Block(block_expr)
}
}
impl From<ast::IfExpr> for ElseBranch {
fn from(if_expr: ast::IfExpr) -> Self {
Self::IfExpr(if_expr)
}
}
impl ast::IfExpr {
pub fn then_branch(&self) -> Option<ast::BlockExpr> {
self.blocks().next()
}
pub fn else_branch(&self) -> Option<ElseBranch> {
let res = match self.blocks().nth(1) {
Some(block) => ElseBranch::Block(block),
None => {
let elif: ast::IfExpr = support::child(self.syntax())?;
ElseBranch::IfExpr(elif)
}
};
Some(res)
}
pub fn blocks(&self) -> AstChildren<ast::BlockExpr> {
support::children(self.syntax())
}
}
impl ast::PrefixExpr {
pub fn op_kind(&self) -> Option<UnaryOp> {
let res = match self.op_token()?.kind() {
T![*] => UnaryOp::Deref,
T![!] => UnaryOp::Not,
T![-] => UnaryOp::Neg,
_ => return None,
};
Some(res)
}
pub fn op_token(&self) -> Option<SyntaxToken> {
self.syntax().first_child_or_token()?.into_token()
}
}
impl ast::BinExpr {
pub fn op_details(&self) -> Option<(SyntaxToken, BinaryOp)> {
self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| {
#[rustfmt::skip]
let bin_op = match c.kind() {
T![||] => BinaryOp::LogicOp(LogicOp::Or),
T![&&] => BinaryOp::LogicOp(LogicOp::And),
T![==] => BinaryOp::CmpOp(CmpOp::Eq { negated: false }),
T![!=] => BinaryOp::CmpOp(CmpOp::Eq { negated: true }),
T![<=] => BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Less, strict: false }),
T![>=] => BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Greater, strict: false }),
T![<] => BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Less, strict: true }),
T![>] => BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Greater, strict: true }),
T![+] => BinaryOp::ArithOp(ArithOp::Add),
T![*] => BinaryOp::ArithOp(ArithOp::Mul),
T![-] => BinaryOp::ArithOp(ArithOp::Sub),
T![/] => BinaryOp::ArithOp(ArithOp::Div),
T![%] => BinaryOp::ArithOp(ArithOp::Rem),
T![<<] => BinaryOp::ArithOp(ArithOp::Shl),
T![>>] => BinaryOp::ArithOp(ArithOp::Shr),
T![^] => BinaryOp::ArithOp(ArithOp::BitXor),
T![|] => BinaryOp::ArithOp(ArithOp::BitOr),
T![&] => BinaryOp::ArithOp(ArithOp::BitAnd),
T![=] => BinaryOp::Assignment { op: None },
T![+=] => BinaryOp::Assignment { op: Some(ArithOp::Add) },
T![*=] => BinaryOp::Assignment { op: Some(ArithOp::Mul) },
T![-=] => BinaryOp::Assignment { op: Some(ArithOp::Sub) },
T![/=] => BinaryOp::Assignment { op: Some(ArithOp::Div) },
T![%=] => BinaryOp::Assignment { op: Some(ArithOp::Rem) },
T![<<=] => BinaryOp::Assignment { op: Some(ArithOp::Shl) },
T![>>=] => BinaryOp::Assignment { op: Some(ArithOp::Shr) },
T![^=] => BinaryOp::Assignment { op: Some(ArithOp::BitXor) },
T![|=] => BinaryOp::Assignment { op: Some(ArithOp::BitOr) },
T![&=] => BinaryOp::Assignment { op: Some(ArithOp::BitAnd) },
_ => return None,
};
Some((c, bin_op))
})
}
pub fn op_kind(&self) -> Option<BinaryOp> {
self.op_details().map(|t| t.1)
}
pub fn op_token(&self) -> Option<SyntaxToken> {
self.op_details().map(|t| t.0)
}
pub fn lhs(&self) -> Option<ast::Expr> {
support::children(self.syntax()).next()
}
pub fn rhs(&self) -> Option<ast::Expr> {
support::children(self.syntax()).nth(1)
}
pub fn sub_exprs(&self) -> (Option<ast::Expr>, Option<ast::Expr>) {
let mut children = support::children(self.syntax());
let first = children.next();
let second = children.next();
(first, second)
}
}
impl ast::RangeExpr {
fn op_details(&self) -> Option<(usize, SyntaxToken, RangeOp)> {
self.syntax().children_with_tokens().enumerate().find_map(|(ix, child)| {
let token = child.into_token()?;
let bin_op = match token.kind() {
T![..] => RangeOp::Exclusive,
T![..=] => RangeOp::Inclusive,
_ => return None,
};
Some((ix, token, bin_op))
})
}
pub fn op_kind(&self) -> Option<RangeOp> {
self.op_details().map(|t| t.2)
}
pub fn op_token(&self) -> Option<SyntaxToken> {
self.op_details().map(|t| t.1)
}
pub fn start(&self) -> Option<ast::Expr> {
let op_ix = self.op_details()?.0;
self.syntax()
.children_with_tokens()
.take(op_ix)
.find_map(|it| ast::Expr::cast(it.into_node()?))
}
pub fn end(&self) -> Option<ast::Expr> {
let op_ix = self.op_details()?.0;
self.syntax()
.children_with_tokens()
.skip(op_ix + 1)
.find_map(|it| ast::Expr::cast(it.into_node()?))
}
}
impl ast::IndexExpr {
pub fn base(&self) -> Option<ast::Expr> {
support::children(self.syntax()).next()
}
pub fn index(&self) -> Option<ast::Expr> {
support::children(self.syntax()).nth(1)
}
}
pub enum ArrayExprKind {
Repeat { initializer: Option<ast::Expr>, repeat: Option<ast::Expr> },
ElementList(AstChildren<ast::Expr>),
}
impl ast::ArrayExpr {
pub fn kind(&self) -> ArrayExprKind {
if self.is_repeat() {
ArrayExprKind::Repeat {
initializer: support::children(self.syntax()).next(),
repeat: support::children(self.syntax()).nth(1),
}
} else {
ArrayExprKind::ElementList(support::children(self.syntax()))
}
}
fn is_repeat(&self) -> bool {
self.syntax().children_with_tokens().any(|it| it.kind() == T![;])
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LiteralKind {
String(ast::String),
ByteString(ast::ByteString),
IntNumber(ast::IntNumber),
FloatNumber(ast::FloatNumber),
Char,
Byte,
Bool(bool),
}
impl ast::Literal {
pub fn token(&self) -> SyntaxToken {
self.syntax()
.children_with_tokens()
.find(|e| e.kind() != ATTR && !e.kind().is_trivia())
.and_then(|e| e.into_token())
.unwrap()
}
pub fn kind(&self) -> LiteralKind {
let token = self.token();
if let Some(t) = ast::IntNumber::cast(token.clone()) {
return LiteralKind::IntNumber(t);
}
if let Some(t) = ast::FloatNumber::cast(token.clone()) {
return LiteralKind::FloatNumber(t);
}
if let Some(t) = ast::String::cast(token.clone()) {
return LiteralKind::String(t);
}
if let Some(t) = ast::ByteString::cast(token.clone()) {
return LiteralKind::ByteString(t);
}
match token.kind() {
T![true] => LiteralKind::Bool(true),
T![false] => LiteralKind::Bool(false),
CHAR => LiteralKind::Char,
BYTE => LiteralKind::Byte,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Effect {
Async(SyntaxToken),
Unsafe(SyntaxToken),
Try(SyntaxToken),
Const(SyntaxToken),
// Very much not an effect, but we stuff it into this node anyway
Label(ast::Label),
}
impl ast::EffectExpr {
pub fn effect(&self) -> Effect {
if let Some(token) = self.async_token() {
return Effect::Async(token);
}
if let Some(token) = self.unsafe_token() {
return Effect::Unsafe(token);
}
if let Some(token) = self.try_token() {
return Effect::Try(token);
}
if let Some(token) = self.const_token() {
return Effect::Const(token);
}
if let Some(label) = self.label() {
return Effect::Label(label);
}
unreachable!("ast::EffectExpr without Effect")
}
}
impl ast::BlockExpr {
/// false if the block is an intrinsic part of the syntax and can't be
/// replaced with arbitrary expression.
///
/// ```not_rust
/// fn foo() { not_stand_alone }
/// const FOO: () = { stand_alone };
/// ```
pub fn is_standalone(&self) -> bool {
let parent = match self.syntax().parent() {
Some(it) => it,
None => return true,
};
!matches!(parent.kind(), FN | IF_EXPR | WHILE_EXPR | LOOP_EXPR | EFFECT_EXPR)
}
}
#[test]
fn test_literal_with_attr() {
let parse = ast::SourceFile::parse(r#"const _: &str = { #[attr] "Hello" };"#);
let lit = parse.tree().syntax().descendants().find_map(ast::Literal::cast).unwrap();
assert_eq!(lit.token().text(), r#""Hello""#);
}
impl ast::RecordExprField {
pub fn parent_record_lit(&self) -> ast::RecordExpr {
self.syntax().ancestors().find_map(ast::RecordExpr::cast).unwrap()
}
}