blob: d8bd28846d6ff436ad14d5d81971801aff068932 [file] [log] [blame]
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
}