|
1 |
| -use std::collections::HashMap; |
2 |
| - |
| 1 | +//! A crate of |
3 | 2 | use proc_macro::TokenStream;
|
4 |
| -use quote::quote; |
5 |
| -use syn::{punctuated::Punctuated, spanned::Spanned, *}; |
6 |
| - |
7 |
| -#[derive(Copy, Clone)] |
8 |
| -// All conversions we support. Check references to this type for an idea how to add more. |
9 |
| -enum Conversion<'a> { |
10 |
| - Into(&'a Type), |
11 |
| - AsRef(&'a Type), |
12 |
| - AsMut(&'a Type), |
13 |
| -} |
14 |
| - |
15 |
| -impl<'a> Conversion<'a> { |
16 |
| - fn conversion_expr(&self, i: &Ident) -> Expr { |
17 |
| - match *self { |
18 |
| - Conversion::Into(_) => parse_quote!(#i.into()), |
19 |
| - Conversion::AsRef(_) => parse_quote!(#i.as_ref()), |
20 |
| - Conversion::AsMut(_) => parse_quote!(#i.as_mut()), |
21 |
| - } |
22 |
| - } |
23 |
| -} |
24 |
| - |
25 |
| -fn parse_bounded_type(ty: &Type) -> Option<Ident> { |
26 |
| - match &ty { |
27 |
| - Type::Path(TypePath { qself: None, path }) if path.segments.len() == 1 => Some(path.segments[0].ident.clone()), |
28 |
| - _ => None, |
29 |
| - } |
30 |
| -} |
31 |
| - |
32 |
| -fn parse_bounds(bounds: &Punctuated<TypeParamBound, Token![+]>) -> Option<Conversion> { |
33 |
| - if bounds.len() != 1 { |
34 |
| - return None; |
35 |
| - } |
36 |
| - if let TypeParamBound::Trait(ref tb) = bounds.first().unwrap() { |
37 |
| - if let Some(seg) = tb.path.segments.iter().last() { |
38 |
| - if let PathArguments::AngleBracketed(ref gen_args) = seg.arguments { |
39 |
| - if let GenericArgument::Type(ref arg_ty) = gen_args.args.first().unwrap() { |
40 |
| - if seg.ident == "Into" { |
41 |
| - return Some(Conversion::Into(arg_ty)); |
42 |
| - } else if seg.ident == "AsRef" { |
43 |
| - return Some(Conversion::AsRef(arg_ty)); |
44 |
| - } else if seg.ident == "AsMut" { |
45 |
| - return Some(Conversion::AsMut(arg_ty)); |
46 |
| - } |
47 |
| - } |
48 |
| - } |
49 |
| - } |
50 |
| - } |
51 |
| - None |
52 |
| -} |
53 |
| - |
54 |
| -// create a map from generic type to Conversion |
55 |
| -fn parse_generics(decl: &Signature) -> HashMap<Ident, Conversion<'_>> { |
56 |
| - let mut ty_conversions = HashMap::new(); |
57 |
| - for gp in decl.generics.params.iter() { |
58 |
| - if let GenericParam::Type(ref tp) = gp { |
59 |
| - if let Some(conversion) = parse_bounds(&tp.bounds) { |
60 |
| - ty_conversions.insert(tp.ident.clone(), conversion); |
61 |
| - } |
62 |
| - } |
63 |
| - } |
64 |
| - if let Some(ref wc) = decl.generics.where_clause { |
65 |
| - for wp in wc.predicates.iter() { |
66 |
| - if let WherePredicate::Type(ref pt) = wp { |
67 |
| - if let Some(ident) = parse_bounded_type(&pt.bounded_ty) { |
68 |
| - if let Some(conversion) = parse_bounds(&pt.bounds) { |
69 |
| - ty_conversions.insert(ident, conversion); |
70 |
| - } |
71 |
| - } |
72 |
| - } |
73 |
| - } |
74 |
| - } |
75 |
| - ty_conversions |
76 |
| -} |
77 |
| - |
78 |
| -fn convert<'a>( |
79 |
| - inputs: &'a Punctuated<FnArg, Token![,]>, |
80 |
| - ty_conversions: &HashMap<Ident, Conversion<'a>>, |
81 |
| -) -> (Punctuated<FnArg, Token![,]>, Punctuated<Expr, Token![,]>, bool) { |
82 |
| - let mut argtypes = Punctuated::new(); |
83 |
| - let mut argexprs = Punctuated::new(); |
84 |
| - let mut has_self = false; |
85 |
| - inputs.iter().enumerate().for_each(|(i, input)| match input.clone() { |
86 |
| - FnArg::Receiver(receiver) => { |
87 |
| - has_self = true; |
88 |
| - argtypes.push(FnArg::Receiver(receiver)); |
89 |
| - } |
90 |
| - FnArg::Typed(mut pat_type) => { |
91 |
| - let pat_ident = match &mut *pat_type.pat { |
92 |
| - Pat::Ident(pat_ident) if pat_ident.by_ref.is_none() && pat_ident.subpat.is_none() => pat_ident, |
93 |
| - _ => { |
94 |
| - pat_type.pat = Box::new(Pat::Ident(PatIdent { |
95 |
| - ident: Ident::new(&format!("arg_{i}_gen_by_momo_"), proc_macro2::Span::call_site()), |
96 |
| - attrs: Default::default(), |
97 |
| - by_ref: None, |
98 |
| - mutability: None, |
99 |
| - subpat: None, |
100 |
| - })); |
101 |
| - |
102 |
| - if let Pat::Ident(pat_ident) = &mut *pat_type.pat { |
103 |
| - pat_ident |
104 |
| - } else { |
105 |
| - panic!() |
106 |
| - } |
107 |
| - } |
108 |
| - }; |
109 |
| - // Outer function type argument pat does not need mut unless its |
110 |
| - // type is `impl AsMut`. |
111 |
| - pat_ident.mutability = None; |
112 |
| - |
113 |
| - let ident = &pat_ident.ident; |
114 |
| - |
115 |
| - let to_expr = || parse_quote!(#ident); |
116 |
| - |
117 |
| - match *pat_type.ty { |
118 |
| - Type::ImplTrait(TypeImplTrait { ref bounds, .. }) => { |
119 |
| - if let Some(conv) = parse_bounds(bounds) { |
120 |
| - argexprs.push(conv.conversion_expr(ident)); |
121 |
| - if let Conversion::AsMut(_) = conv { |
122 |
| - pat_ident.mutability = Some(Default::default()); |
123 |
| - } |
124 |
| - } else { |
125 |
| - argexprs.push(to_expr()); |
126 |
| - } |
127 |
| - } |
128 |
| - Type::Path(..) => { |
129 |
| - if let Some(conv) = parse_bounded_type(&pat_type.ty).and_then(|ident| ty_conversions.get(&ident)) { |
130 |
| - argexprs.push(conv.conversion_expr(ident)); |
131 |
| - if let Conversion::AsMut(_) = conv { |
132 |
| - pat_ident.mutability = Some(Default::default()); |
133 |
| - } |
134 |
| - } else { |
135 |
| - argexprs.push(to_expr()); |
136 |
| - } |
137 |
| - } |
138 |
| - _ => { |
139 |
| - argexprs.push(to_expr()); |
140 |
| - } |
141 |
| - } |
142 |
| - |
143 |
| - // Now that mutability is decided, push the type into argtypes |
144 |
| - argtypes.push(FnArg::Typed(pat_type)); |
145 |
| - } |
146 |
| - }); |
147 |
| - (argtypes, argexprs, has_self) |
148 |
| -} |
149 |
| - |
150 |
| -fn contains_self_type_path(path: &Path) -> bool { |
151 |
| - path.segments.iter().any(|segment| { |
152 |
| - segment.ident == "Self" |
153 |
| - || match &segment.arguments { |
154 |
| - PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) => { |
155 |
| - args.iter().any(|generic_arg| match generic_arg { |
156 |
| - GenericArgument::Type(ty) => contains_self_type(ty), |
157 |
| - GenericArgument::Const(expr) => contains_self_type_expr(expr), |
158 |
| - _ => false, |
159 |
| - }) |
160 |
| - } |
161 |
| - PathArguments::Parenthesized(ParenthesizedGenericArguments { inputs, output, .. }) => { |
162 |
| - inputs.iter().any(contains_self_type) |
163 |
| - || matches!(output, ReturnType::Type(_, ty) if contains_self_type(ty)) |
164 |
| - } |
165 |
| - _ => false, |
166 |
| - } |
167 |
| - }) |
168 |
| -} |
169 |
| - |
170 |
| -fn contains_self_type_expr(expr: &Expr) -> bool { |
171 |
| - match expr { |
172 |
| - Expr::Path(ExprPath { qself: Some(_), .. }) => true, |
173 |
| - Expr::Path(ExprPath { path, .. }) => contains_self_type_path(path), |
174 |
| - _ => false, |
175 |
| - } |
176 |
| -} |
177 |
| - |
178 |
| -fn contains_self_type(input: &Type) -> bool { |
179 |
| - match input { |
180 |
| - Type::Array(TypeArray { elem, len, .. }) => { |
181 |
| - // Call `matches!` first so that we can do tail call here |
182 |
| - // as an optimization. |
183 |
| - contains_self_type_expr(len) || contains_self_type(elem) |
184 |
| - } |
185 |
| - Type::Group(TypeGroup { elem, .. }) => contains_self_type(elem), |
186 |
| - Type::Paren(TypeParen { elem, .. }) => contains_self_type(elem), |
187 |
| - Type::Ptr(TypePtr { elem, .. }) => contains_self_type(elem), |
188 |
| - Type::Reference(TypeReference { elem, .. }) => contains_self_type(elem), |
189 |
| - Type::Slice(TypeSlice { elem, .. }) => contains_self_type(elem), |
190 |
| - |
191 |
| - Type::Tuple(TypeTuple { elems, .. }) => elems.iter().any(contains_self_type), |
192 |
| - |
193 |
| - Type::Path(TypePath { qself: Some(_), .. }) => true, |
194 |
| - Type::Path(TypePath { path, .. }) => contains_self_type_path(path), |
195 |
| - |
196 |
| - _ => false, |
197 |
| - } |
198 |
| -} |
199 |
| - |
200 |
| -fn has_self_type(input: &FnArg) -> bool { |
201 |
| - match input { |
202 |
| - FnArg::Receiver(_) => true, |
203 |
| - FnArg::Typed(PatType { ty, .. }) => contains_self_type(ty), |
204 |
| - } |
205 |
| -} |
206 | 3 |
|
207 | 4 | /// Generate lightweight monomorphized wrapper around main implementation.
|
208 | 5 | /// May be applied to functions and methods.
|
209 | 6 | #[proc_macro_attribute]
|
210 | 7 | pub fn momo(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
211 | 8 | //TODO: alternatively parse ImplItem::Method
|
212 |
| - momo_inner(input.into()).into() |
| 9 | + momo::inner(input.into()).into() |
213 | 10 | }
|
214 | 11 |
|
215 |
| -fn momo_inner(code: proc_macro2::TokenStream) -> proc_macro2::TokenStream { |
216 |
| - let fn_item: Item = match syn::parse2(code.clone()) { |
217 |
| - Ok(input) => input, |
218 |
| - Err(err) => return err.to_compile_error(), |
219 |
| - }; |
220 |
| - |
221 |
| - if let Item::Fn(item_fn) = fn_item { |
222 |
| - let ty_conversions = parse_generics(&item_fn.sig); |
223 |
| - let (argtypes, argexprs, has_self) = convert(&item_fn.sig.inputs, &ty_conversions); |
224 |
| - |
225 |
| - let uses_self = has_self |
226 |
| - || item_fn.sig.inputs.iter().any(has_self_type) |
227 |
| - || matches!(&item_fn.sig.output, ReturnType::Type(_, ty) if contains_self_type(ty)); |
228 |
| - |
229 |
| - let inner_ident = Ident::new( |
230 |
| - // Use long qualifier to avoid name colision. |
231 |
| - &format!("_{}_inner_generated_by_gix_macro_momo", item_fn.sig.ident), |
232 |
| - proc_macro2::Span::call_site(), |
233 |
| - ); |
234 |
| - |
235 |
| - let outer_sig = Signature { |
236 |
| - inputs: argtypes, |
237 |
| - ..item_fn.sig.clone() |
238 |
| - }; |
239 |
| - |
240 |
| - let new_inner_item = Item::Fn(ItemFn { |
241 |
| - // Remove doc comment since they will increase compile-time and |
242 |
| - // also generates duplicate warning/error messages for the doc, |
243 |
| - // especially if it contains doc-tests. |
244 |
| - attrs: { |
245 |
| - let mut attrs = item_fn.attrs.clone(); |
246 |
| - attrs.retain(|attr| { |
247 |
| - let segments = &attr.path().segments; |
248 |
| - !(segments.len() == 1 && segments[0].ident == "doc") |
249 |
| - }); |
250 |
| - attrs |
251 |
| - }, |
252 |
| - vis: Visibility::Inherited, |
253 |
| - sig: Signature { |
254 |
| - ident: inner_ident.clone(), |
255 |
| - ..item_fn.sig |
256 |
| - }, |
257 |
| - block: item_fn.block, |
258 |
| - }); |
259 |
| - |
260 |
| - if uses_self { |
261 |
| - // We can use `self` or `Self` inside function defined within |
262 |
| - // the impl-fn, so instead declare two separate functions. |
263 |
| - // |
264 |
| - // Since it's an impl block, it's unlikely to have name conflict, |
265 |
| - // though this won't work for impl-trait. |
266 |
| - // |
267 |
| - // This approach also make sure we can call the right function |
268 |
| - // using `Self` qualifier. |
269 |
| - let new_item = Item::Fn(ItemFn { |
270 |
| - attrs: item_fn.attrs, |
271 |
| - vis: item_fn.vis, |
272 |
| - sig: outer_sig, |
273 |
| - block: if has_self { |
274 |
| - parse_quote!({ self.#inner_ident(#argexprs) }) |
275 |
| - } else { |
276 |
| - parse_quote!({ Self::#inner_ident(#argexprs) }) |
277 |
| - }, |
278 |
| - }); |
279 |
| - quote!(#new_item #new_inner_item) |
280 |
| - } else { |
281 |
| - // Put the new inner function within the function block |
282 |
| - // to avoid duplicate function name and support associated |
283 |
| - // function that doesn't use `self` or `Self`. |
284 |
| - let new_item = Item::Fn(ItemFn { |
285 |
| - attrs: item_fn.attrs, |
286 |
| - vis: item_fn.vis, |
287 |
| - sig: outer_sig, |
288 |
| - block: parse_quote!({ |
289 |
| - #new_inner_item |
290 |
| - |
291 |
| - #inner_ident(#argexprs) |
292 |
| - }), |
293 |
| - }); |
294 |
| - quote!(#new_item) |
295 |
| - } |
296 |
| - } else { |
297 |
| - Error::new(fn_item.span(), "expect a function").to_compile_error() |
298 |
| - } |
299 |
| -} |
| 12 | +mod momo; |
0 commit comments