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
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::spanned::Spanned;
use syn::{
parse_macro_input, FnArg, Ident, ImplItem, ImplItemConst, ImplItemMethod, ImplItemType,
ItemImpl, Pat, PatBox, PatIdent, PatReference, PatTuple, PatType, Signature, Visibility,
};
#[proc_macro_attribute]
pub fn extension_trait(args: TokenStream, input: TokenStream) -> TokenStream {
let visibility = parse_macro_input!(args as Visibility);
let input_cloned = input.clone();
let ItemImpl {
impl_token,
attrs,
unsafety,
trait_,
items,
..
} = parse_macro_input!(input_cloned as ItemImpl);
let items = items.into_iter().map(|item| match item {
ImplItem::Const(ImplItemConst {
attrs, ident, ty, ..
}) => quote! { #(#attrs)* const #ident: #ty; },
ImplItem::Method(ImplItemMethod { attrs, sig: Signature {
constness, asyncness, unsafety, abi, ident, generics, inputs, variadic, output, ..
}, .. }) => {
let inputs = inputs.into_iter().map(|arg| {
let span = arg.span();
match arg {
FnArg::Typed(PatType { attrs, pat, ty, .. }) => {
let ident = extract_ident(*pat).unwrap_or_else(|| Ident::new("_", span));
quote! { #(#attrs)* #ident: #ty }
},
FnArg::Receiver(_) => quote! { #arg }
}
});
let where_clause = &generics.where_clause;
quote! {
#(#attrs)*
#constness #asyncness #unsafety #abi fn #ident #generics (#(#inputs,)* #variadic) #output #where_clause;
}
},
ImplItem::Type(ImplItemType {
attrs,
ident,
generics,
..
}) => quote! { #(#attrs)* type #ident #generics; },
_ => return syn::Error::new(item.span(), "unsupported item type").to_compile_error().into(),
});
if let Some((None, path, _)) = trait_ {
let input = proc_macro2::TokenStream::from(input);
(quote! {
#(#attrs)*
#visibility #unsafety trait #path {
#(#items)*
}
#input
})
.into()
} else {
syn::Error::new(impl_token.span(), "extension trait name was not provided")
.to_compile_error()
.into()
}
}
fn extract_ident(pat: Pat) -> Option<Ident> {
match pat {
Pat::Box(PatBox { pat, .. }) | Pat::Reference(PatReference { pat, .. }) => {
extract_ident(*pat)
}
Pat::Ident(PatIdent { ident, .. }) => Some(ident),
Pat::Tuple(PatTuple { elems, .. }) => {
if elems.len() <= 1 {
extract_ident(elems.into_iter().next()?)
} else {
let span = elems.span();
let elems = elems
.into_iter()
.map(extract_ident)
.map(|o| o.map(|ident| ident.unraw().to_string()))
.collect::<Option<Vec<String>>>()?;
let joined = elems.join("_");
Some(Ident::new(&joined, span))
}
}
_ => None,
}
}