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}