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()
}