Skip to content

Commit e174ec6

Browse files
committed
initial commit
0 parents  commit e174ec6

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
Cargo.lock
3+
/.idea

Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "proxy-enum"
3+
version = "0.1.0"
4+
authors = ["Moritz Bischof <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
syn = { version = "1", features = [ "full", "visit", "fold" ] }
12+
quote = "1"
13+
proc-macro2 = "1"

src/lib.rs

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#![feature(box_patterns)]
2+
extern crate proc_macro2;
3+
4+
use proc_macro::{Ident, TokenStream};
5+
use std::collections::HashMap;
6+
7+
use syn::{Attribute, Fields, FnArg, ImplItem, Item, ItemEnum, ItemImpl, ItemMod, ItemTrait, parenthesized, parse2, parse_macro_input, Pat, Path, PathArguments, PathSegment, PatType, Signature, TraitItem, TraitItemMethod, Type, Variant, TraitItemConst};
8+
use syn::export::TokenStream2;
9+
use syn::fold::Fold;
10+
use syn::parse::{Parse, ParseStream, Result as ParseResult};
11+
12+
use quote::quote;
13+
14+
const IMPL_ATTR: &str = "implement";
15+
const EXT_ATTR: &str = "external";
16+
const PROXY_ATTR: &str = "proxy";
17+
18+
fn attr_idx(attrs: &[Attribute], ident: &str) -> Option<usize> {
19+
(0..attrs.len())
20+
.into_iter()
21+
.find(|idx| attrs[*idx].path.is_ident(ident))
22+
}
23+
24+
fn pop_attr(attrs: &mut Vec<Attribute>, ident: &str) -> Option<Attribute> {
25+
attr_idx(attrs, ident)
26+
.map(|idx| attrs.remove(idx))
27+
}
28+
29+
fn find_attr<'a>(attrs: &'a [Attribute], ident: &str) -> Option<&'a Attribute> {
30+
attr_idx(&attrs, ident)
31+
.map(|idx| &attrs[idx])
32+
}
33+
34+
fn gen_static_method_call(
35+
receiver_ty: &Type,
36+
signature: &Signature,
37+
) -> TokenStream2 {
38+
let method_ident = &signature.ident;
39+
40+
let args = signature
41+
.inputs
42+
.iter()
43+
.skip(1) // `self`
44+
.map(|a| match a {
45+
FnArg::Typed(PatType {
46+
pat: box Pat::Ident(ident),
47+
..
48+
}) => &ident.ident,
49+
_ => panic!("parameter binding must be an identifier"),
50+
});
51+
52+
quote! { <#receiver_ty>::#method_ident(__self #(, #args)*) }
53+
}
54+
55+
struct WrapperVariant {
56+
variant: Variant,
57+
wrapped: Type,
58+
}
59+
60+
impl From<Variant> for WrapperVariant {
61+
fn from(variant: Variant) -> Self {
62+
match &variant.fields {
63+
Fields::Unnamed(a) if a.unnamed.len() == 1 => WrapperVariant {
64+
variant: variant.clone(),
65+
wrapped: a.unnamed.first().unwrap().ty.clone(),
66+
},
67+
_ => panic!("expected a variant with a single unnamed value"),
68+
}
69+
}
70+
}
71+
72+
fn gen_match_block(
73+
variants: &[WrapperVariant],
74+
action: impl Fn(&WrapperVariant) -> TokenStream2,
75+
) -> TokenStream2 {
76+
let branches = variants
77+
.iter()
78+
.map(
79+
|variant| {
80+
let action = action(&variant);
81+
let ident = &variant.variant.ident;
82+
quote! { Self::#ident(__self) => #action }
83+
},
84+
)
85+
.collect::<Vec<_>>();
86+
87+
quote! {
88+
match self {
89+
#(#branches),*
90+
}
91+
}
92+
}
93+
94+
fn has_self_param(sig: &Signature) -> bool {
95+
sig.inputs
96+
.first()
97+
.map(|param| match param {
98+
FnArg::Receiver(..) => true,
99+
FnArg::Typed(PatType {
100+
pat: box Pat::Ident(ident),
101+
..
102+
}) => &ident.ident.to_string() == "self",
103+
_ => false,
104+
})
105+
.unwrap_or(false)
106+
}
107+
108+
/// populate an empty `#[implement] impl Trait for ProxyEnum {}` block
109+
fn implement_trait(
110+
trait_decl: &ItemTrait,
111+
variants: &[WrapperVariant],
112+
pseudo_impl: &mut ItemImpl,
113+
) {
114+
assert!(pseudo_impl.items.is_empty());
115+
116+
let receiver = &pseudo_impl.trait_.as_ref().unwrap().1;
117+
let trait_ty = parse2::<Type>(quote! { #receiver }).unwrap();
118+
119+
let proxy_methods = trait_decl
120+
.items
121+
.iter()
122+
.map(|i| match i {
123+
TraitItem::Method(i) => {
124+
let sig = &i.sig;
125+
if !has_self_param(sig) {
126+
match &i.default {
127+
Some(..) => return parse2(quote! { #i }).unwrap(),
128+
None => panic!(
129+
"`{}` has no self parameter or default implementation",
130+
quote! { #sig }
131+
),
132+
}
133+
}
134+
135+
let match_block = gen_match_block(
136+
variants,
137+
|variant| gen_static_method_call(&trait_ty, sig)
138+
);
139+
let tokens = quote! { #sig { #match_block } };
140+
parse2::<ImplItem>(tokens).unwrap()
141+
},
142+
other => panic!("impl block annotated with `#[{}]` may only contain methods", IMPL_ATTR),
143+
});
144+
145+
pseudo_impl.items = proxy_methods.collect();
146+
}
147+
148+
/// populate methods in a `impl ProxyEnum { #[implement] fn method(&self) {} }` block
149+
fn implement_raw(variants: &[WrapperVariant], pseudo_impl: &mut ItemImpl) {
150+
pseudo_impl
151+
.items
152+
.iter_mut()
153+
.flat_map(|i| match i {
154+
ImplItem::Method(method) => pop_attr(&mut method.attrs, IMPL_ATTR).map(|_| method),
155+
_ => None,
156+
})
157+
.for_each(|mut method| {
158+
if !method.block.stmts.is_empty() {
159+
panic!("method annotated with `#[{}]` must be empty", IMPL_ATTR)
160+
}
161+
162+
163+
let match_block = gen_match_block(
164+
variants,
165+
|variant| gen_static_method_call(&variant.wrapped, &method.sig)
166+
);
167+
let body = quote! { { #match_block } };
168+
method.block = syn::parse2(body).unwrap();
169+
});
170+
}
171+
172+
struct GenerateProxyImpl {
173+
variants: Option<Vec<WrapperVariant>>,
174+
trait_declarations: HashMap<String, ItemTrait>,
175+
}
176+
177+
impl GenerateProxyImpl {
178+
fn get_variants(&self) -> &[WrapperVariant] {
179+
self
180+
.variants
181+
.as_ref()
182+
.unwrap_or_else(|| panic!("`#[{}]` enum must be defined first", PROXY_ATTR))
183+
.as_slice()
184+
}
185+
186+
fn store_trait_decl(&mut self, attr: Option<Path>, decl: ItemTrait) {
187+
let mut path = match attr {
188+
Some(path) => quote! { #path },
189+
None => {
190+
let ident = &decl.ident;
191+
quote! { #ident }
192+
}
193+
}.to_string();
194+
path.retain(|c| !c.is_whitespace());
195+
self.trait_declarations.insert(path, decl);
196+
}
197+
198+
fn get_trait_decl(&self, mut path: Path) -> &ItemTrait {
199+
path.segments.iter_mut().for_each(|seg| seg.arguments = PathArguments::None);
200+
let mut path = quote! { #path }.to_string();
201+
path.retain(|c| !c.is_whitespace());
202+
203+
self.trait_declarations.get(&path)
204+
.unwrap_or_else(|| panic!("missing declaration of trait `{}`", path))
205+
}
206+
}
207+
208+
impl Default for GenerateProxyImpl {
209+
fn default() -> Self {
210+
GenerateProxyImpl {
211+
variants: None,
212+
trait_declarations: HashMap::new(),
213+
}
214+
}
215+
}
216+
217+
impl Fold for GenerateProxyImpl {
218+
// store variants of our enum
219+
fn fold_item_enum(&mut self, mut i: ItemEnum) -> ItemEnum {
220+
if pop_attr(&mut i.attrs, PROXY_ATTR).is_some() {
221+
if self.variants.is_some() {
222+
panic!("only one enum can be annotated with `#[{}]`", PROXY_ATTR)
223+
}
224+
225+
self.variants = Some(
226+
i.variants
227+
.iter()
228+
.cloned()
229+
.map(WrapperVariant::from)
230+
.collect(),
231+
);
232+
}
233+
i
234+
}
235+
236+
fn fold_item_impl(&mut self, mut i: ItemImpl) -> ItemImpl {
237+
match i.trait_.as_mut() {
238+
// `impl Type { #[implement] fn abc() {} }
239+
None => implement_raw(self.get_variants(), &mut i),
240+
// #[implement] `impl Trait for Type {}`
241+
Some((_, path, _)) => {
242+
if pop_attr(&mut i.attrs, IMPL_ATTR).is_none() {
243+
return i;
244+
}
245+
246+
implement_trait(
247+
self.get_trait_decl(path.clone()),
248+
self.get_variants(),
249+
&mut i,
250+
);
251+
}
252+
};
253+
i
254+
}
255+
256+
fn fold_item_mod(&mut self, i: ItemMod) -> ItemMod {
257+
let mut folded = syn::fold::fold_item_mod(self, i);
258+
// remove all items annotated with external
259+
folded.content.as_mut().unwrap().1.retain(|item| {
260+
if let Item::Trait(ItemTrait { attrs, .. }) = item {
261+
find_attr(&attrs, EXT_ATTR).is_none()
262+
} else {
263+
true
264+
}
265+
});
266+
folded
267+
}
268+
269+
// scan for trait declarations and store them
270+
fn fold_item_trait(&mut self, i: ItemTrait) -> ItemTrait {
271+
let ext_attr = find_attr(&i.attrs, EXT_ATTR)
272+
.map(|attr| attr.parse_args().unwrap());
273+
self.store_trait_decl(ext_attr, i.clone());
274+
i
275+
}
276+
}
277+
278+
#[proc_macro_attribute]
279+
pub fn module(_: TokenStream, item: TokenStream) -> TokenStream {
280+
let mut module = parse_macro_input!(item as ItemMod);
281+
282+
module = GenerateProxyImpl::default()
283+
.fold_item_mod(module);
284+
285+
TokenStream::from(quote! { #module })
286+
}

0 commit comments

Comments
 (0)