use proc_macro2::{Group, Span, TokenStream, TokenTree}; use std::iter::FromIterator; use syn::visit_mut::{self, VisitMut}; use syn::{ Block, ExprPath, Ident, Item, Macro, Pat, PatIdent, Path, Receiver, Signature, Token, TypePath, }; pub fn has_self_in_sig(sig: &mut Signature) -> bool { let mut visitor = HasSelf(false); visitor.visit_signature_mut(sig); visitor.0 } pub fn has_self_in_block(block: &mut Block) -> bool { let mut visitor = HasSelf(false); visitor.visit_block_mut(block); visitor.0 } fn has_self_in_token_stream(tokens: TokenStream) -> bool { tokens.into_iter().any(|tt| match tt { TokenTree::Ident(ident) => ident == "Self", TokenTree::Group(group) => has_self_in_token_stream(group.stream()), _ => false, }) } pub fn mut_pat(pat: &mut Pat) -> Option { let mut visitor = HasMutPat(None); visitor.visit_pat_mut(pat); visitor.0 } fn contains_fn(tokens: TokenStream) -> bool { tokens.into_iter().any(|tt| match tt { TokenTree::Ident(ident) => ident == "fn", TokenTree::Group(group) => contains_fn(group.stream()), _ => false, }) } struct HasMutPat(Option); impl VisitMut for HasMutPat { fn visit_pat_ident_mut(&mut self, i: &mut PatIdent) { if let Some(m) = i.mutability { self.0 = Some(m); } else { visit_mut::visit_pat_ident_mut(self, i); } } } struct HasSelf(bool); impl VisitMut for HasSelf { fn visit_expr_path_mut(&mut self, expr: &mut ExprPath) { self.0 |= expr.path.segments[0].ident == "Self"; visit_mut::visit_expr_path_mut(self, expr); } fn visit_type_path_mut(&mut self, ty: &mut TypePath) { self.0 |= ty.path.segments[0].ident == "Self"; visit_mut::visit_type_path_mut(self, ty); } fn visit_receiver_mut(&mut self, _arg: &mut Receiver) { self.0 = true; } fn visit_item_mut(&mut self, _: &mut Item) { // Do not recurse into nested items. } fn visit_macro_mut(&mut self, mac: &mut Macro) { if !contains_fn(mac.tokens.clone()) { self.0 |= has_self_in_token_stream(mac.tokens.clone()); } } } pub struct ReplaceSelf(pub Span); impl ReplaceSelf { #[cfg_attr(not(self_span_hack), allow(clippy::unused_self))] fn prepend_underscore_to_self(&self, ident: &mut Ident) -> bool { let modified = ident == "self"; if modified { *ident = Ident::new("__self", ident.span()); #[cfg(self_span_hack)] ident.set_span(self.0); } modified } fn visit_token_stream(&mut self, tokens: &mut TokenStream) -> bool { let mut out = Vec::new(); let mut modified = false; visit_token_stream_impl(self, tokens.clone(), &mut modified, &mut out); if modified { *tokens = TokenStream::from_iter(out); } return modified; fn visit_token_stream_impl( visitor: &mut ReplaceSelf, tokens: TokenStream, modified: &mut bool, out: &mut Vec, ) { for tt in tokens { match tt { TokenTree::Ident(mut ident) => { *modified |= visitor.prepend_underscore_to_self(&mut ident); out.push(TokenTree::Ident(ident)); } TokenTree::Group(group) => { let mut content = group.stream(); *modified |= visitor.visit_token_stream(&mut content); let mut new = Group::new(group.delimiter(), content); new.set_span(group.span()); out.push(TokenTree::Group(new)); } other => out.push(other), } } } } } impl VisitMut for ReplaceSelf { fn visit_ident_mut(&mut self, i: &mut Ident) { self.prepend_underscore_to_self(i); } fn visit_path_mut(&mut self, p: &mut Path) { if p.segments.len() == 1 { // Replace `self`, but not `self::function`. self.visit_ident_mut(&mut p.segments[0].ident); } for segment in &mut p.segments { self.visit_path_arguments_mut(&mut segment.arguments); } } fn visit_item_mut(&mut self, i: &mut Item) { // Visit `macro_rules!` because locally defined macros can refer to // `self`. // // Visit `futures::select` and similar select macros, which commonly // appear syntactically like an item despite expanding to an expression. // // Otherwise, do not recurse into nested items. if let Item::Macro(i) = i { if i.mac.path.is_ident("macro_rules") || i.mac.path.segments.last().unwrap().ident == "select" { self.visit_macro_mut(&mut i.mac); } } } fn visit_macro_mut(&mut self, mac: &mut Macro) { // We can't tell in general whether `self` inside a macro invocation // refers to the self in the argument list or a different self // introduced within the macro. Heuristic: if the macro input contains // `fn`, then `self` is more likely to refer to something other than the // outer function's self argument. if !contains_fn(mac.tokens.clone()) { self.visit_token_stream(&mut mac.tokens); } } }