| use proc_macro2::{Span, TokenStream}; |
| use quote::ToTokens; |
| use syn::{ |
| visit_mut::{self, VisitMut}, |
| *, |
| }; |
| |
| use crate::utils::{ |
| determine_lifetime_name, insert_lifetime, parse_as_empty, Immutable, Mutability, Mutable, |
| Owned, SliceExt, VecExt, |
| }; |
| |
| pub(crate) fn attribute(args: &TokenStream, input: Stmt, mutability: Mutability) -> TokenStream { |
| parse_as_empty(args) |
| .and_then(|()| parse(input, mutability)) |
| .unwrap_or_else(|e| e.to_compile_error()) |
| } |
| |
| fn replace_expr(expr: &mut Expr, mutability: Mutability) { |
| match expr { |
| Expr::Match(expr) => { |
| Context::new(mutability).replace_expr_match(expr); |
| } |
| Expr::If(expr_if) => { |
| let mut expr_if = expr_if; |
| while let Expr::Let(ref mut expr) = &mut *expr_if.cond { |
| Context::new(mutability).replace_expr_let(expr); |
| if let Some((_, ref mut expr)) = expr_if.else_branch { |
| if let Expr::If(new_expr_if) = &mut **expr { |
| expr_if = new_expr_if; |
| continue; |
| } |
| } |
| break; |
| } |
| } |
| _ => {} |
| } |
| } |
| |
| fn parse(mut stmt: Stmt, mutability: Mutability) -> Result<TokenStream> { |
| match &mut stmt { |
| Stmt::Expr(expr) | Stmt::Semi(expr, _) => replace_expr(expr, mutability), |
| Stmt::Local(local) => Context::new(mutability).replace_local(local)?, |
| Stmt::Item(Item::Fn(item)) => replace_item_fn(item, mutability)?, |
| Stmt::Item(Item::Impl(item)) => replace_item_impl(item, mutability)?, |
| Stmt::Item(Item::Use(item)) => replace_item_use(item, mutability)?, |
| _ => {} |
| } |
| |
| Ok(stmt.into_token_stream()) |
| } |
| |
| struct Context { |
| register: Option<(Ident, usize)>, |
| replaced: bool, |
| mutability: Mutability, |
| } |
| |
| impl Context { |
| fn new(mutability: Mutability) -> Self { |
| Self { register: None, replaced: false, mutability } |
| } |
| |
| fn update(&mut self, ident: &Ident, len: usize) { |
| if self.register.is_none() { |
| self.register = Some((ident.clone(), len)); |
| } |
| } |
| |
| fn compare_paths(&self, ident: &Ident, len: usize) -> bool { |
| match &self.register { |
| Some((i, l)) => *l == len && i == ident, |
| None => false, |
| } |
| } |
| |
| fn replace_local(&mut self, local: &mut Local) -> Result<()> { |
| if let Some(attr) = local.attrs.find(self.mutability.method_name()) { |
| return Err(error!(attr, "duplicate #[{}] attribute", self.mutability.method_name())); |
| } |
| |
| if let Some(Expr::Match(expr)) = local.init.as_mut().map(|(_, expr)| &mut **expr) { |
| self.replace_expr_match(expr); |
| } |
| |
| if self.replaced { |
| if is_replaceable(&local.pat, false) { |
| return Err(error!( |
| local.pat, |
| "Both initializer expression and pattern are replaceable, \ |
| you need to split the initializer expression into separate let bindings \ |
| to avoid ambiguity" |
| )); |
| } |
| } else { |
| self.replace_pat(&mut local.pat, false); |
| } |
| |
| Ok(()) |
| } |
| |
| fn replace_expr_let(&mut self, expr: &mut ExprLet) { |
| self.replace_pat(&mut expr.pat, true) |
| } |
| |
| fn replace_expr_match(&mut self, expr: &mut ExprMatch) { |
| expr.arms.iter_mut().for_each(|arm| self.replace_pat(&mut arm.pat, true)) |
| } |
| |
| fn replace_pat(&mut self, pat: &mut Pat, allow_pat_path: bool) { |
| match pat { |
| Pat::Ident(PatIdent { subpat: Some((_, pat)), .. }) |
| | Pat::Reference(PatReference { pat, .. }) |
| | Pat::Box(PatBox { pat, .. }) |
| | Pat::Type(PatType { pat, .. }) => self.replace_pat(pat, allow_pat_path), |
| |
| Pat::Or(PatOr { cases, .. }) => { |
| cases.iter_mut().for_each(|pat| self.replace_pat(pat, allow_pat_path)) |
| } |
| |
| Pat::Struct(PatStruct { path, .. }) | Pat::TupleStruct(PatTupleStruct { path, .. }) => { |
| self.replace_path(path) |
| } |
| Pat::Path(PatPath { qself: None, path, .. }) if allow_pat_path => { |
| self.replace_path(path) |
| } |
| _ => {} |
| } |
| } |
| |
| fn replace_path(&mut self, path: &mut Path) { |
| let len = match path.segments.len() { |
| // 1: struct |
| // 2: enum |
| len @ 1 | len @ 2 => len, |
| // other path |
| _ => return, |
| }; |
| |
| if self.register.is_none() || self.compare_paths(&path.segments[0].ident, len) { |
| self.update(&path.segments[0].ident, len); |
| self.replaced = true; |
| replace_ident(&mut path.segments[0].ident, self.mutability); |
| } |
| } |
| } |
| |
| fn is_replaceable(pat: &Pat, allow_pat_path: bool) -> bool { |
| match pat { |
| Pat::Ident(PatIdent { subpat: Some((_, pat)), .. }) |
| | Pat::Reference(PatReference { pat, .. }) |
| | Pat::Box(PatBox { pat, .. }) |
| | Pat::Type(PatType { pat, .. }) => is_replaceable(pat, allow_pat_path), |
| |
| Pat::Or(PatOr { cases, .. }) => cases.iter().any(|pat| is_replaceable(pat, allow_pat_path)), |
| |
| Pat::Struct(_) | Pat::TupleStruct(_) => true, |
| Pat::Path(PatPath { qself: None, .. }) => allow_pat_path, |
| _ => false, |
| } |
| } |
| |
| fn replace_ident(ident: &mut Ident, mutability: Mutability) { |
| *ident = mutability.proj_ident(ident); |
| } |
| |
| fn replace_item_impl(item: &mut ItemImpl, mutability: Mutability) -> Result<()> { |
| if let Some(attr) = item.attrs.find(mutability.method_name()) { |
| return Err(error!(attr, "duplicate #[{}] attribute", mutability.method_name())); |
| } |
| |
| let PathSegment { ident, arguments } = match &mut *item.self_ty { |
| Type::Path(TypePath { qself: None, path }) => path.segments.last_mut().unwrap(), |
| _ => return Ok(()), |
| }; |
| |
| replace_ident(ident, mutability); |
| |
| let mut lifetime_name = String::from("'pin"); |
| determine_lifetime_name(&mut lifetime_name, &mut item.generics); |
| item.items |
| .iter_mut() |
| .filter_map(|i| if let ImplItem::Method(i) = i { Some(i) } else { None }) |
| .for_each(|item| determine_lifetime_name(&mut lifetime_name, &mut item.sig.generics)); |
| let lifetime = Lifetime::new(&lifetime_name, Span::call_site()); |
| |
| insert_lifetime(&mut item.generics, lifetime.clone()); |
| |
| match arguments { |
| PathArguments::None => { |
| *arguments = PathArguments::AngleBracketed(syn::parse_quote!(<#lifetime>)); |
| } |
| PathArguments::AngleBracketed(args) => { |
| args.args.insert(0, syn::parse_quote!(#lifetime)); |
| } |
| PathArguments::Parenthesized(_) => unreachable!(), |
| } |
| Ok(()) |
| } |
| |
| fn replace_item_fn(item: &mut ItemFn, mutability: Mutability) -> Result<()> { |
| struct FnVisitor(Result<()>); |
| |
| impl FnVisitor { |
| fn visit_stmt(&mut self, node: &mut Stmt) -> Result<()> { |
| match node { |
| Stmt::Expr(expr) | Stmt::Semi(expr, _) => self.visit_expr(expr), |
| Stmt::Local(local) => { |
| visit_mut::visit_local_mut(self, local); |
| |
| let mut prev = None; |
| for &mutability in &[Immutable, Mutable, Owned] { |
| if let Some(attr) = local.attrs.find_remove(mutability.method_name())? { |
| if let Some(prev) = prev.replace(mutability) { |
| return Err(error!( |
| attr, |
| "attributes `{}` and `{}` are mutually exclusive", |
| prev.method_name(), |
| mutability.method_name(), |
| )); |
| } |
| Context::new(mutability).replace_local(local)?; |
| } |
| } |
| |
| Ok(()) |
| } |
| // Do not recurse into nested items. |
| Stmt::Item(_) => Ok(()), |
| } |
| } |
| |
| fn visit_expr(&mut self, node: &mut Expr) -> Result<()> { |
| visit_mut::visit_expr_mut(self, node); |
| match node { |
| Expr::Match(expr) => { |
| let mut prev = None; |
| for &mutability in &[Immutable, Mutable, Owned] { |
| if let Some(attr) = expr.attrs.find_remove(mutability.method_name())? { |
| if let Some(prev) = prev.replace(mutability) { |
| return Err(error!( |
| attr, |
| "attributes `{}` and `{}` are mutually exclusive", |
| prev.method_name(), |
| mutability.method_name(), |
| )); |
| } |
| } |
| } |
| if let Some(mutability) = prev { |
| replace_expr(node, mutability); |
| } |
| } |
| Expr::If(expr_if) => { |
| if let Expr::Let(_) = &*expr_if.cond { |
| let mut prev = None; |
| for &mutability in &[Immutable, Mutable, Owned] { |
| if let Some(attr) = |
| expr_if.attrs.find_remove(mutability.method_name())? |
| { |
| if let Some(prev) = prev.replace(mutability) { |
| return Err(error!( |
| attr, |
| "attributes `{}` and `{}` are mutually exclusive", |
| prev.method_name(), |
| mutability.method_name(), |
| )); |
| } |
| } |
| } |
| if let Some(mutability) = prev { |
| replace_expr(node, mutability); |
| } |
| } |
| } |
| _ => {} |
| } |
| Ok(()) |
| } |
| } |
| |
| impl VisitMut for FnVisitor { |
| fn visit_stmt_mut(&mut self, node: &mut Stmt) { |
| if self.0.is_err() { |
| return; |
| } |
| if let Err(e) = self.visit_stmt(node) { |
| self.0 = Err(e) |
| } |
| } |
| |
| fn visit_expr_mut(&mut self, node: &mut Expr) { |
| if self.0.is_err() { |
| return; |
| } |
| if let Err(e) = self.visit_expr(node) { |
| self.0 = Err(e) |
| } |
| } |
| |
| fn visit_item_mut(&mut self, _: &mut Item) { |
| // Do not recurse into nested items. |
| } |
| } |
| |
| if let Some(attr) = item.attrs.find(mutability.method_name()) { |
| return Err(error!(attr, "duplicate #[{}] attribute", mutability.method_name())); |
| } |
| |
| let mut visitor = FnVisitor(Ok(())); |
| visitor.visit_block_mut(&mut item.block); |
| visitor.0 |
| } |
| |
| fn replace_item_use(item: &mut ItemUse, mutability: Mutability) -> Result<()> { |
| struct UseTreeVisitor { |
| res: Result<()>, |
| mutability: Mutability, |
| } |
| |
| impl VisitMut for UseTreeVisitor { |
| fn visit_use_tree_mut(&mut self, node: &mut UseTree) { |
| if self.res.is_err() { |
| return; |
| } |
| |
| match node { |
| // Desugar `use tree::<name>` into `tree::__<name>Projection`. |
| UseTree::Name(name) => replace_ident(&mut name.ident, self.mutability), |
| UseTree::Glob(glob) => { |
| self.res = Err(error!( |
| glob, |
| "#[{}] attribute may not be used on glob imports", |
| self.mutability.method_name() |
| )); |
| } |
| UseTree::Rename(rename) => { |
| self.res = Err(error!( |
| rename, |
| "#[{}] attribute may not be used on renamed imports", |
| self.mutability.method_name() |
| )); |
| } |
| UseTree::Path(_) | UseTree::Group(_) => visit_mut::visit_use_tree_mut(self, node), |
| } |
| } |
| } |
| |
| if let Some(attr) = item.attrs.find(mutability.method_name()) { |
| return Err(error!(attr, "duplicate #[{}] attribute", mutability.method_name())); |
| } |
| |
| let mut visitor = UseTreeVisitor { res: Ok(()), mutability }; |
| visitor.visit_item_use_mut(item); |
| visitor.res |
| } |