1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
//! Easy way to duplicate a new function with `self: Box<Self>` signature.
//!
//! Sometimes you need both functions `fn consume(self)` and `fn consume_boxed(self: Box<Self>)`. This macro generates the second one for you.
//!
//! # Examples
//! ```
//! use box_self::box_self;
//!
//! trait Animal {
//! fn consume(self);
//! fn consume_boxed(self: Box<Self>);
//! }
//!
//! struct Dog{}
//! impl Animal for Dog{
//! #[box_self(_boxed)]
//! fn consume(self) {
//! println!("Bark");
//! }
//! }
//!
//! struct Cat{}
//! impl Animal for Cat{
//! #[box_self(_boxed)]
//! fn consume(self) {
//! println!("Jump");
//! }
//! }
//!
//! fn main(){
//! let animals:Vec<Box<dyn Animal>>=
//! vec![Box::new(Dog{}), Box::new(Cat{})];
//!
//! for anim in animals{
//! anim.consume_boxed();
//! }
//! }
//! ```
//!
//! <br><br>
//! ### Motivation:
//! - [`How to call a method that consumes self on a boxed trait object?`]
//!
//! [`How to call a method that consumes self on a boxed trait object?`]: https://stackoverflow.com/questions/46620790/how-to-call-a-method-that-consumes-self-on-a-boxed-trait-object
//! <br>
//!
//!
//! ### License
//! Licensed under either of [LICENSE-APACHE](LICENSE-APACHE) or [LICENSE-MIT](LICENSE-MIT) at your option.
//!
//! <br>
//!
//! Unless you explicitly state otherwise, any contribution intentionally submitted
//! for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
//! be dual licensed as above, without any additional terms or conditions.
use proc_macro::{TokenStream};
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote,ToTokens};
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn box_self(attr_postfix: TokenStream, item: TokenStream) -> TokenStream {
let orig_func = parse_macro_input!(item as ItemFn);
let func_vis = &orig_func.vis;
let func_sig = orig_func.sig.clone();
let func_generics = &func_sig.generics;
let func_output = &func_sig.output;
// original function signature
let orgi_fn_name=&func_sig.ident;
let s_fn_name=orgi_fn_name.to_string()+attr_postfix.to_string().as_str();
let func_params = func_sig.inputs.clone();
// convert to new function signature
let new_func_name: proc_macro2::Ident = proc_macro2::Ident::new(s_fn_name.as_str(),proc_macro2::Span::call_site()); // function name
let delcaring_params=replace_params_declaration(func_params.to_token_stream().into());
let calling_params=extract_params_without_type(func_params.into_token_stream());
// generate the new function body, using ` (*self).consume(calling_params) `
let new_func:TokenStream = quote!{
#[inline] #func_vis fn #new_func_name #func_generics(#delcaring_params) #func_output {
(*self).#orgi_fn_name(#calling_params)
}
}.into();
TokenStream::from_iter( [orig_func.to_token_stream().into() ,new_func])
}
//replace 'self' with 'self:Box<Self>' in the parameters declaration
fn replace_params_declaration(input: TokenStream2) -> TokenStream2 {
use proc_macro2::TokenTree;
let mut old_params=input.into_iter();
let mut new_params=Vec::new();
while let Some(tt) =old_params.next(){
match tt {
proc_macro2::TokenTree::Ident(i) if i.to_string()=="self" =>{
new_params.push(TokenTree::Ident(i));
let extra_boxed:TokenStream2=quote!{:Box<Self>}.into(); //add ':Box<Self>'
for extra_boxed_param in extra_boxed.into_iter(){
new_params.push(extra_boxed_param);
}
},
// All other tokens are just forwarded
other =>{
new_params.push(other)
}
}
}
new_params.into_iter().collect()
}
// extract parameters for calling the original function
fn extract_params_without_type(params:TokenStream2) -> TokenStream2 {
let mut params_without_type=Vec::new();
let mut it=params.into_iter();
let mut last_ident:Option::<proc_macro2::Ident>=None;
// only take idents before ':'
while let Some(tt) =it.next(){
match tt {
proc_macro2::TokenTree::Ident(i) =>{
last_ident=Some(i.clone());
},
proc_macro2::TokenTree::Punct(p) if p.as_char()==':' =>{
if let Some(i)=&last_ident{
if i.to_string()!="self"{ // ignore 'self'
params_without_type.push(proc_macro2::TokenTree::Ident(last_ident.take().unwrap()));
let p=proc_macro2::Punct::new(',',proc_macro2::Spacing::Alone);
params_without_type.push(proc_macro2::TokenTree::Punct(p));
}
}
},
_=>{}
}
}
params_without_type.into_iter().collect()
}