Skip to content

Commit

Permalink
Move handling of #[nested] to Visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Oct 7, 2019
1 parent 5010225 commit 518e1b6
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 316 deletions.
8 changes: 7 additions & 1 deletion core/src/auto_enum/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use syn::{

use crate::utils::{expr_call, path, replace_expr, unit, VisitedNode};

use super::visitor::{Dummy, FindTry, Visitor};
use super::visitor::{Dummy, FindNested, FindTry, Visitor};

// =================================================================================================
// Context
Expand Down Expand Up @@ -230,6 +230,12 @@ impl Context {
node.visited(&mut Dummy::new(self));
}

pub(super) fn find_nested(&mut self, node: &mut impl VisitedNode) -> bool {
let mut visitor = FindNested::new();
node.visited(&mut visitor);
visitor.has
}

pub(super) fn find_try(&mut self, node: &mut impl VisitedNode) {
let mut visitor = FindTry::new(self);
node.visited(&mut visitor);
Expand Down
277 changes: 101 additions & 176 deletions core/src/auto_enum/expr.rs
Original file line number Diff line number Diff line change
@@ -1,237 +1,162 @@
use std::ops::{Deref, DerefMut};

use syn::{
visit_mut::{self, VisitMut},
*,
};

use crate::utils::{expr_block, replace_block, replace_expr};
use crate::utils::{expr_block, replace_block, replace_expr, Attrs};

use super::{Attrs, NAME, NESTED, NEVER};
use super::{Context, NAME, NESTED, NEVER};

/// Visits last expression.
///
/// Note that do not use this after `cx.visitor()`.
pub(super) fn child_expr(expr: &mut Expr, cx: &mut super::Context) -> Result<()> {
fn child_expr_inner(expr: &mut Expr, cx: &mut Context<'_>) -> Result<()> {
cx.last_expr_mut(
expr,
(),
|expr, cx| {
if expr.any_empty_attr(NESTED) {
cx.nested = true;
}
!cx.is_unreachable(expr)
},
|expr, cx| match expr {
Expr::Match(expr) => cx.visit_last_expr_match(expr),
Expr::If(expr) => cx.visit_last_expr_if(expr),
Expr::Loop(expr) => cx.visit_last_expr_loop(expr),

// Search recursively
Expr::MethodCall(ExprMethodCall { receiver: expr, .. })
| Expr::Paren(ExprParen { expr, .. })
| Expr::Type(ExprType { expr, .. }) => child_expr_inner(expr, cx),

_ => Ok(()),
},
)
pub(super) fn child_expr(expr: &mut Expr, cx: &mut Context) -> Result<()> {
if !cx.visit_last() || is_unreachable(cx, expr) {
return Ok(());
}

if cx.visit_last() { child_expr_inner(expr, &mut Context::from(cx)) } else { Ok(()) }
}

// =================================================================================================
// Context
match expr {
Expr::Block(ExprBlock { block, .. }) | Expr::Unsafe(ExprUnsafe { block, .. }) => {
match block.stmts.last_mut() {
Some(Stmt::Expr(expr)) => return child_expr(expr, cx),
Some(Stmt::Item(_)) => unreachable!(),
_ => {}
}
}
_ => {}
}

struct Context<'a> {
cx: &'a mut super::Context,
nested: bool,
}
match expr {
Expr::Match(expr) => visit_last_expr_match(cx, expr),
Expr::If(expr) => visit_last_expr_if(cx, expr),
Expr::Loop(expr) => visit_last_expr_loop(cx, expr),

// To avoid `cx.cx`
impl Deref for Context<'_> {
type Target = super::Context;
// Search recursively
Expr::MethodCall(ExprMethodCall { receiver: expr, .. })
| Expr::Paren(ExprParen { expr, .. })
| Expr::Type(ExprType { expr, .. }) => child_expr(expr, cx),

fn deref(&self) -> &Self::Target {
self.cx
_ => Ok(()),
}
}

impl DerefMut for Context<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.cx
}
}
pub(super) fn is_unreachable(cx: &Context, expr: &Expr) -> bool {
const UNREACHABLE_MACROS: &[&str] = &["unreachable", "panic"];

impl<'a> From<&'a mut super::Context> for Context<'a> {
fn from(cx: &'a mut super::Context) -> Self {
Self { cx, nested: false }
if expr.any_empty_attr(NEVER) || expr.any_attr(NAME) {
return true;
}
}

impl Context<'_> {
fn last_expr_mut<T>(
&mut self,
expr: &mut Expr,
success: T,
mut filter: impl FnMut(&Expr, &mut Self) -> bool,
f: impl FnOnce(&mut Expr, &mut Self) -> Result<T>,
) -> Result<T> {
if !filter(expr, self) {
return Ok(success);
}

match expr {
Expr::Block(ExprBlock { block, .. }) | Expr::Unsafe(ExprUnsafe { block, .. }) => {
match block.stmts.last_mut() {
Some(Stmt::Expr(expr)) => return self.last_expr_mut(expr, success, filter, f),
Some(Stmt::Semi(expr, _)) => {
if !filter(expr, self) {
return Ok(success);
}
}
Some(_) => return Ok(success),
None => {}
}
}
_ => {}
match expr {
Expr::Block(ExprBlock { block, .. }) | Expr::Unsafe(ExprUnsafe { block, .. }) => {
is_unreachable_stmt(cx, block.stmts.last())
}

f(expr, self)
}

fn is_unreachable(&self, expr: &Expr) -> bool {
const UNREACHABLE_MACROS: &[&str] = &["unreachable", "panic"];

fn filter(expr: &Expr) -> bool {
!expr.any_empty_attr(NEVER) && !expr.any_attr(NAME)
}
Expr::Break(_) | Expr::Continue(_) | Expr::Return(_) => true,

if !filter(expr) {
return true;
// `unreachable!`, `panic!` or an expression level marker (`marker!` macro).
Expr::Macro(ExprMacro { mac, .. }) => {
UNREACHABLE_MACROS.iter().any(|i| mac.path.is_ident(i)) || cx.is_marker_macro(mac)
}

match expr {
Expr::Block(ExprBlock { block, .. }) | Expr::Unsafe(ExprUnsafe { block, .. }) => {
match block.stmts.last() {
Some(Stmt::Expr(expr)) => return self.is_unreachable(expr),
Some(Stmt::Semi(expr, _)) if !filter(expr) => return true,
Some(_) => return true,
None => {}
}
}
_ => {}
Expr::Match(ExprMatch { arms, .. }) => {
arms.iter().all(|arm| arm.any_empty_attr(NEVER) || is_unreachable(cx, &*arm.body))
}

match expr {
Expr::Break(_) | Expr::Continue(_) | Expr::Return(_) => true,

// `unreachable!`, `panic!` or an expression level marker (`marker!` macro).
Expr::Macro(ExprMacro { mac, .. }) => {
UNREACHABLE_MACROS.iter().any(|i| mac.path.is_ident(i)) || self.is_marker_macro(mac)
}

Expr::Match(ExprMatch { arms, .. }) => {
arms.iter().all(|arm| arm.any_empty_attr(NEVER) || self.is_unreachable(&*arm.body))
}

// `Err(expr)?` or `None?`.
Expr::Try(ExprTry { expr, .. }) => match &**expr {
Expr::Path(ExprPath { path, qself: None, .. }) => path.is_ident("None"),
Expr::Call(ExprCall { args, func, .. }) if args.len() == 1 => match &**func {
Expr::Path(ExprPath { path, qself: None, .. }) => path.is_ident("Err"),
_ => false,
},
// `Err(expr)?` or `None?`.
Expr::Try(ExprTry { expr, .. }) => match &**expr {
Expr::Path(ExprPath { path, qself: None, .. }) => path.is_ident("None"),
Expr::Call(ExprCall { args, func, .. }) if args.len() == 1 => match &**func {
Expr::Path(ExprPath { path, qself: None, .. }) => path.is_ident("Err"),
_ => false,
},
_ => false,
}
}
},

fn visit_last_expr(&mut self, expr: &mut Expr) -> Result<bool> {
self.last_expr_mut(
expr,
true,
|expr, cx| !cx.is_unreachable(expr),
|expr, cx| match expr {
Expr::Match(expr) => cx.visit_last_expr_match(expr).map(|()| true),
Expr::If(expr) => cx.visit_last_expr_if(expr).map(|()| true),
Expr::Loop(expr) => cx.visit_last_expr_loop(expr).map(|()| true),
_ => Ok(false),
},
)
// Search recursively
Expr::MethodCall(ExprMethodCall { receiver: expr, .. })
| Expr::Paren(ExprParen { expr, .. })
| Expr::Type(ExprType { expr, .. }) => is_unreachable(cx, expr),

_ => false,
}
}

fn visit_last_expr_match(&mut self, expr: &mut ExprMatch) -> Result<()> {
fn skip(arm: &mut Arm, cx: &mut Context<'_>) -> Result<bool> {
Ok(arm.any_empty_attr(NEVER)
|| cx.is_unreachable(&*arm.body)
|| ((arm.any_empty_attr(NESTED) || cx.nested)
&& cx.visit_last_expr(&mut arm.body)?))
fn is_unreachable_stmt(cx: &Context, stmt: Option<&Stmt>) -> bool {
match stmt {
Some(Stmt::Expr(expr)) | Some(Stmt::Semi(expr, _)) => is_unreachable(cx, expr),
Some(Stmt::Local(local)) => {
local.init.as_ref().map_or(false, |(_, expr)| is_unreachable(cx, expr))
}
Some(Stmt::Item(_)) => true,
None => false,
}
}

expr.arms.iter_mut().try_for_each(|arm| {
if !skip(arm, self)? {
arm.comma = Some(token::Comma::default());
replace_expr(&mut *arm.body, |x| self.next_expr(x));
}
Ok(())
})
fn visit_last_expr_match(cx: &mut Context, expr: &mut ExprMatch) -> Result<()> {
fn skip(arm: &mut Arm, cx: &mut Context) -> bool {
arm.any_empty_attr(NEVER)
|| arm.any_empty_attr(NESTED)
|| is_unreachable(cx, &*arm.body)
|| cx.find_nested(arm)
}

fn visit_last_expr_if(&mut self, expr: &mut ExprIf) -> Result<()> {
#[allow(clippy::needless_pass_by_value)]
fn skip(last: Option<&mut Stmt>, cx: &mut Context<'_>) -> Result<bool> {
Ok(match &last {
Some(Stmt::Expr(expr)) | Some(Stmt::Semi(expr, _)) => cx.is_unreachable(expr),
_ => true,
} || match last {
Some(Stmt::Expr(expr)) => {
(expr.any_empty_attr(NESTED) || cx.nested) && cx.visit_last_expr(expr)?
}
_ => true,
})
expr.arms.iter_mut().try_for_each(|arm| {
if !skip(arm, cx) {
arm.comma = Some(token::Comma::default());
replace_expr(&mut *arm.body, |x| cx.next_expr(x));
}
Ok(())
})
}

if !skip(expr.then_branch.stmts.last_mut(), self)? {
replace_block(&mut expr.then_branch, |b| self.next_expr(expr_block(b)));
fn visit_last_expr_if(cx: &mut Context, expr: &mut ExprIf) -> Result<()> {
fn skip(block: &mut Block, cx: &mut Context) -> bool {
match block.stmts.last_mut() {
Some(Stmt::Expr(expr)) => {
expr.any_empty_attr(NESTED) || is_unreachable(cx, expr) || cx.find_nested(block)
}
_ => is_unreachable_stmt(cx, block.stmts.last()),
}
}

match expr.else_branch.as_mut().map(|(_, expr)| &mut **expr) {
Some(Expr::Block(expr)) => {
if !skip(expr.block.stmts.last_mut(), self)? {
replace_block(&mut expr.block, |b| self.next_expr(expr_block(b)));
}
Ok(())
if !skip(&mut expr.then_branch, cx) {
replace_block(&mut expr.then_branch, |b| cx.next_expr(expr_block(b)));
}

match expr.else_branch.as_mut().map(|(_, expr)| &mut **expr) {
Some(Expr::Block(expr)) => {
if !skip(&mut expr.block, cx) {
replace_block(&mut expr.block, |b| cx.next_expr(expr_block(b)));
}
Some(Expr::If(expr)) => self.visit_last_expr_if(expr),
Ok(())
}
Some(Expr::If(expr)) => visit_last_expr_if(cx, expr),

// TODO: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.Span.html#method.join
// `expr.span().join(expr.then_branch.span()).unwrap_or_else(|| expr.span())``
None => Err(error!(expr.if_token, "`if` expression missing an else clause")),
// TODO: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.Span.html#method.join
// `expr.span().join(expr.then_branch.span()).unwrap_or_else(|| expr.span())``
None => Err(error!(expr.if_token, "`if` expression missing an else clause")),

Some(_) => unreachable!("wrong_if"),
}
Some(_) => unreachable!("wrong_if"),
}
}

fn visit_last_expr_loop(&mut self, expr: &mut ExprLoop) -> Result<()> {
LoopVisitor::new(expr, self).visit_block_mut(&mut expr.body);
Ok(())
}
fn visit_last_expr_loop(cx: &mut Context, expr: &mut ExprLoop) -> Result<()> {
LoopVisitor::new(expr, cx).visit_block_mut(&mut expr.body);
Ok(())
}

// =================================================================================================
// LoopVisitor

struct LoopVisitor<'a> {
cx: &'a mut super::Context,
cx: &'a mut Context,
label: Option<Lifetime>,
nested: bool,
}

impl<'a> LoopVisitor<'a> {
fn new(expr: &ExprLoop, cx: &'a mut super::Context) -> Self {
fn new(expr: &ExprLoop, cx: &'a mut Context) -> Self {
Self { cx, label: expr.label.as_ref().map(|l| l.name.clone()), nested: false }
}

Expand Down
2 changes: 0 additions & 2 deletions core/src/auto_enum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ const NAME: &str = "auto_enum";
const NESTED: &str = "nested";
/// The annotation for skipping branch.
const NEVER: &str = "never";
/// The annotations used by `#[auto_enum]`.
const EMPTY_ATTRS: &[&str] = &[NEVER, NESTED];

/// The old annotation replaced by `#[nested]`.
const NESTED_OLD: &str = "rec";
Expand Down
Loading

0 comments on commit 518e1b6

Please sign in to comment.