box_self/
lib.rs

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