use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
parse_quote, token,
visit_mut::{self, VisitMut},
Arm, Attribute, Expr, ExprMacro, ExprMatch, ExprReturn, ExprTry, Item, Local, LocalInit,
MetaList, Stmt, Token,
};
use super::{Context, VisitMode, DEFAULT_MARKER, NAME, NESTED, NEVER};
use crate::utils::{replace_expr, Attrs, Node};
#[derive(Clone, Copy, Default)]
struct Scope {
closure: bool,
try_block: bool,
foreign: bool,
}
impl Scope {
fn check_expr(&mut self, expr: &Expr) {
match expr {
Expr::Closure(_) => self.closure = true,
Expr::TryBlock(_) => self.try_block = true,
_ => {}
}
}
}
pub(super) struct Visitor<'a> {
cx: &'a mut Context,
scope: Scope,
}
impl<'a> Visitor<'a> {
pub(super) fn new(cx: &'a mut Context) -> Self {
Self { cx, scope: Scope::default() }
}
fn find_remove_attrs(&mut self, attrs: &mut impl Attrs) {
if !self.scope.foreign {
if let Some(attr) = attrs.find_remove_attr(NEVER) {
if let Err(e) = attr.meta.require_path_only() {
self.cx.error(e);
}
}
if let Some(old) = attrs.find_remove_attr("rec") {
self.cx.error(format_err!(
old,
"#[rec] has been removed and replaced with #[{}]",
NESTED
));
}
}
}
fn visit_return(&mut self, node: &mut Expr, count: usize) {
debug_assert!(self.cx.visit_mode == VisitMode::Return(count));
if !self.scope.closure && !node.any_empty_attr(NEVER) {
if let Expr::Return(ExprReturn { expr, .. }) = node {
if expr.as_ref().map_or(true, |expr| !self.cx.is_marker_expr(expr)) {
self.cx.replace_boxed_expr(expr);
}
}
}
}
fn visit_try(&mut self, node: &mut Expr) {
debug_assert!(self.cx.visit_mode == VisitMode::Try);
if !self.scope.try_block && !self.scope.closure && !node.any_empty_attr(NEVER) {
match &node {
Expr::Try(ExprTry { expr, .. }) if !self.cx.is_marker_expr(expr) => {
replace_expr(node, |expr| {
let ExprTry { attrs, expr, .. } =
if let Expr::Try(expr) = expr { expr } else { unreachable!() };
let mut arms = Vec::with_capacity(2);
arms.push(parse_quote! {
::core::result::Result::Ok(val) => val,
});
let err = self.cx.next_expr(parse_quote!(err));
arms.push(parse_quote! {
::core::result::Result::Err(err) => {
return ::core::result::Result::Err(#err);
}
});
Expr::Match(ExprMatch {
attrs,
match_token: <Token![match]>::default(),
expr,
brace_token: token::Brace::default(),
arms,
})
});
}
_ => {}
}
}
}
fn visit_nested(&mut self, node: &mut Expr, attr: &Attribute) {
debug_assert!(!self.scope.foreign);
if let Err(e) = attr.meta.require_path_only() {
self.cx.error(e);
} else {
super::expr::child_expr(self.cx, node);
}
}
fn visit_marker_macro(&mut self, node: &mut Expr) {
debug_assert!(!self.scope.foreign || self.cx.current_marker != DEFAULT_MARKER);
match node {
Expr::Macro(ExprMacro { mac, .. }) if self.cx.is_marker_macro_exact(mac) => {
replace_expr(node, |expr| {
let expr = if let Expr::Macro(expr) = expr { expr } else { unreachable!() };
let args = syn::parse2(expr.mac.tokens).unwrap_or_else(|e| {
self.cx.error(e);
parse_quote!(compile_error!(
"#[auto_enum] failed to generate error message"
))
});
if self.cx.has_error() {
args
} else {
self.cx.next_expr_with_attrs(expr.attrs, args)
}
});
}
_ => {}
}
}
fn visit_expr(&mut self, node: &mut Expr, has_semi: bool) {
debug_assert!(!self.cx.has_error());
let tmp = self.scope;
if node.any_attr(NAME) {
self.scope.foreign = true;
self.cx.has_child = true;
}
self.scope.check_expr(node);
match self.cx.visit_mode {
VisitMode::Return(count) => self.visit_return(node, count),
VisitMode::Try => self.visit_try(node),
VisitMode::Default => {}
}
if !self.scope.foreign {
if let Some(attr) = node.find_remove_attr(NESTED) {
self.visit_nested(node, &attr);
}
}
VisitStmt::visit_expr(self, node, has_semi);
if !self.scope.foreign || self.cx.current_marker != DEFAULT_MARKER {
self.visit_marker_macro(node);
self.find_remove_attrs(node);
}
self.scope = tmp;
}
}
impl VisitMut for Visitor<'_> {
fn visit_expr_mut(&mut self, node: &mut Expr) {
if !self.cx.has_error() {
self.visit_expr(node, false);
}
}
fn visit_arm_mut(&mut self, node: &mut Arm) {
if !self.cx.has_error() {
if !self.scope.foreign {
if let Some(attr) = node.find_remove_attr(NESTED) {
self.visit_nested(&mut node.body, &attr);
}
}
visit_mut::visit_arm_mut(self, node);
self.find_remove_attrs(node);
}
}
fn visit_local_mut(&mut self, node: &mut Local) {
if !self.cx.has_error() {
if !self.scope.foreign {
if let Some(attr) = node.find_remove_attr(NESTED) {
if let Some(LocalInit { expr, .. }) = &mut node.init {
self.visit_nested(expr, &attr);
}
}
}
visit_mut::visit_local_mut(self, node);
self.find_remove_attrs(node);
}
}
fn visit_stmt_mut(&mut self, node: &mut Stmt) {
if !self.cx.has_error() {
if let Stmt::Expr(expr, semi) = node {
self.visit_expr(expr, semi.is_some());
} else {
let tmp = self.scope;
if node.any_attr(NAME) {
self.scope.foreign = true;
self.cx.has_child = true;
}
VisitStmt::visit_stmt(self, node);
self.scope = tmp;
}
}
}
fn visit_item_mut(&mut self, _: &mut Item) {
}
}
impl VisitStmt for Visitor<'_> {
fn cx(&mut self) -> &mut Context {
self.cx
}
}
pub(super) struct Dummy<'a> {
cx: &'a mut Context,
}
impl<'a> Dummy<'a> {
pub(super) fn new(cx: &'a mut Context) -> Self {
Self { cx }
}
}
impl VisitMut for Dummy<'_> {
fn visit_stmt_mut(&mut self, node: &mut Stmt) {
if !self.cx.has_error() {
if node.any_attr(NAME) {
self.cx.has_child = true;
}
VisitStmt::visit_stmt(self, node);
}
}
fn visit_expr_mut(&mut self, node: &mut Expr) {
if !self.cx.has_error() {
if node.any_attr(NAME) {
self.cx.has_child = true;
}
VisitStmt::visit_expr(self, node, false);
}
}
fn visit_item_mut(&mut self, _: &mut Item) {
}
}
impl VisitStmt for Dummy<'_> {
fn cx(&mut self) -> &mut Context {
self.cx
}
}
trait VisitStmt: VisitMut {
fn cx(&mut self) -> &mut Context;
fn visit_expr(visitor: &mut Self, node: &mut Expr, has_semi: bool) {
let attr = node.find_remove_attr(NAME);
let res = attr.map(|attr| {
attr.meta.require_list().and_then(|MetaList { tokens, .. }| {
visitor.cx().make_child(node.to_token_stream(), tokens.clone())
})
});
visit_mut::visit_expr_mut(visitor, node);
match res {
Some(Err(e)) => visitor.cx().error(e),
Some(Ok(mut cx)) => {
super::expand_parent_expr(&mut cx, node, has_semi);
visitor.cx().join_child(cx);
}
None => {}
}
}
fn visit_stmt(visitor: &mut Self, node: &mut Stmt) {
let attr = match node {
Stmt::Expr(expr, semi) => {
Self::visit_expr(visitor, expr, semi.is_some());
return;
}
Stmt::Local(local) => local.find_remove_attr(NAME),
Stmt::Macro(_) => None,
Stmt::Item(_) => return,
};
let res = attr.map(|attr| {
let args = match attr.meta {
syn::Meta::Path(_) => TokenStream::new(),
syn::Meta::List(list) => list.tokens,
syn::Meta::NameValue(nv) => bail!(nv.eq_token, "expected list"),
};
visitor.cx().make_child(node.to_token_stream(), args)
});
visit_mut::visit_stmt_mut(visitor, node);
match res {
Some(Err(e)) => visitor.cx().error(e),
Some(Ok(mut cx)) => {
super::expand_parent_stmt(&mut cx, node);
visitor.cx().join_child(cx);
}
None => {}
}
}
}
pub(super) fn find_nested(node: &mut impl Node) -> bool {
struct FindNested {
has: bool,
}
impl VisitMut for FindNested {
fn visit_expr_mut(&mut self, node: &mut Expr) {
if !node.any_attr(NAME) {
if node.any_empty_attr(NESTED) {
self.has = true;
} else {
visit_mut::visit_expr_mut(self, node);
}
}
}
fn visit_arm_mut(&mut self, node: &mut Arm) {
if node.any_empty_attr(NESTED) {
self.has = true;
} else {
visit_mut::visit_arm_mut(self, node);
}
}
fn visit_local_mut(&mut self, node: &mut Local) {
if !node.any_attr(NAME) {
if node.any_empty_attr(NESTED) {
self.has = true;
} else {
visit_mut::visit_local_mut(self, node);
}
}
}
fn visit_item_mut(&mut self, _: &mut Item) {
}
}
let mut visitor = FindNested { has: false };
node.visited(&mut visitor);
visitor.has
}
#[derive(Default)]
pub(super) struct FnCount {
pub(super) try_: usize,
pub(super) return_: usize,
}
pub(super) fn visit_fn(cx: &Context, node: &mut impl Node) -> FnCount {
struct FnVisitor<'a> {
cx: &'a Context,
scope: Scope,
count: FnCount,
}
impl VisitMut for FnVisitor<'_> {
fn visit_expr_mut(&mut self, node: &mut Expr) {
let tmp = self.scope;
self.scope.check_expr(node);
if !self.scope.closure && !node.any_empty_attr(NEVER) {
match node {
Expr::Try(ExprTry { expr, .. }) => {
if !self.cx.is_marker_expr(expr) {
self.count.try_ += 1;
}
}
Expr::Return(ExprReturn { expr, .. }) => {
if expr.as_ref().map_or(true, |expr| !self.cx.is_marker_expr(expr)) {
self.count.return_ += 1;
}
}
_ => {}
}
}
if node.any_attr(NAME) {
self.scope.foreign = true;
}
visit_mut::visit_expr_mut(self, node);
self.scope = tmp;
}
fn visit_stmt_mut(&mut self, node: &mut Stmt) {
let tmp = self.scope;
if node.any_attr(NAME) {
self.scope.foreign = true;
}
visit_mut::visit_stmt_mut(self, node);
self.scope = tmp;
}
fn visit_item_mut(&mut self, _: &mut Item) {
}
}
let mut visitor = FnVisitor { cx, scope: Scope::default(), count: FnCount::default() };
node.visited(&mut visitor);
visitor.count
}