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
//! 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 mut animals:Vec<Box<dyn Animal>>=Vec::new();
//!        animals.push(Box::new(Dog{}));
//!        animals.push(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>
//! 
//! ### Another solution on nightly Rust `unsized_fn_params`:
//! - [`How to pass a boxed trait object by value in Rust?`]
//! 
//! [`How to pass a boxed trait object by value in Rust?`]: https://stackoverflow.com/questions/65261399/how-to-pass-a-boxed-trait-object-by-value-in-rust
//! <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, TokenTree,  Ident};
use quote::{quote,ToTokens};
use syn::{parse_macro_input, ItemFn};

#[doc(hidden)]
#[proc_macro]
pub fn replace_params(input: TokenStream) -> TokenStream {
    let mut old_params=input.into_iter();
    let mut new_params=Vec::new();
    while let Some(tt) =old_params.next(){
        match tt {
            TokenTree::Ident(i) if i.to_string()=="self" =>{   
                new_params.push(TokenTree::Ident(i));
                let extra_boxed:TokenStream=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()
}

/// Duplicate `consume self` function with boxed signature and postfix
#[proc_macro_attribute]
pub fn box_self(attr_postfix: TokenStream, item: TokenStream) -> TokenStream {
    // get the function this attribute is attached to
    let orig_func = parse_macro_input!(item as ItemFn);
    let orig_func_stream:TokenStream=orig_func.to_token_stream().into();

    let mut next_group_is_params=false;
    let mut next_iden_is_fn_name=false;
    let it=orig_func_stream.into_iter();
    let boxed_self_func:TokenStream=it.map(|tt| {
        match tt {
            TokenTree::Ident(ref i) if i.to_string()=="fn"=>{
                next_iden_is_fn_name=true;
                TokenTree::Ident(i.clone())
            },
            TokenTree::Ident(ref i) if next_iden_is_fn_name => {
                next_iden_is_fn_name=false;
                next_group_is_params=true;
                TokenTree::Ident(Ident::new((i.to_string()+attr_postfix.to_string().as_str()).as_str(), i.span()))
            },
            TokenTree::Group(params) if next_group_is_params =>{
                next_group_is_params=false;
                let new_stream=replace_params(params.stream());
                let g2=proc_macro::Group::new(params.delimiter(),new_stream);
                TokenTree::Group(g2)
            },
            // All other tokens are just forwarded
            other => other,
        }
    }).collect();

    TokenStream::from_iter( [orig_func.to_token_stream().into() ,boxed_self_func])
}