1extern crate proc_macro;
2
3use proc_macro::{TokenStream};
4use syn::{parse_macro_input, DeriveInput, Ident};
5use quote::{quote, quote_spanned, ToTokens};
6use syn::spanned::Spanned;
7
8#[proc_macro_derive(Atomizable)]
9pub fn derive_atomizable(input: TokenStream) -> TokenStream {
10 let input = parse_macro_input!(input as DeriveInput);
11
12 let name = &input.ident;
13
14 let (field_type, pack_line, unpack_line) = if let syn::Data::Struct(ref data) = input.data {
15 let field = if let syn::Fields::Named(ref fields) = data.fields {
16 if fields.named.len() != 1 {
17 return TokenStream::from(quote_spanned! { fields.span() =>
18 compile_error!("Atomizable can only be derived for structs with a single field.");
19 });
20 }
21 fields.named.get(0).unwrap()
22 } else if let syn::Fields::Unnamed(ref fields) = data.fields {
23 if fields.unnamed.len() != 1 {
24 return TokenStream::from(quote_spanned! { fields.span() =>
25 compile_error!("Atomizable can only be derived for structs with a single field.");
26 });
27 }
28 fields.unnamed.get(0).unwrap()
29 } else {
30 return TokenStream::from(quote! {
31 compile_error!("Atomizable can only be derived for structs with a single field.");
32 });
33 };
34
35 let field_name = field
36 .ident
37 .as_ref();
38
39 if let Some(ref field_name) = field_name {
40 (field.ty.to_token_stream(), quote! { self.#field_name }, quote! { #name { #field_name: atom } })
41 } else {
42 (field.ty.to_token_stream(), quote! { self.0 }, quote! { #name(atom) })
43 }
44 } else if let syn::Data::Enum(ref data) = input.data {
45 let repr_attr = input.attrs.iter().find(|attr| {
46 attr.meta.path().is_ident("repr")
47 });
48
49 if repr_attr.is_none() {
50 return TokenStream::from(quote! {
51 compile_error!(
52 "Atomizable can only be derived for enums with an explicit repr attribute."
53 );
54 });
55 }
56
57 let repr_ident: Ident = repr_attr.unwrap().parse_args().unwrap();
58
59 let fielded_variants = data.variants.iter().filter(|variant| !variant.fields.is_empty())
60 .map(|variant| {
61 quote_spanned! {variant.fields.span()=>
62 compile_error!(
63 "Atomizable can only be derived for enums with only unit variants."
64 );
65 }
66 })
67 .collect::<Vec<_>>();
68 if !fielded_variants.is_empty() {
69 return TokenStream::from(quote! {
70 #(#fielded_variants)*
71 });
72 }
73
74 (
75 repr_ident.to_token_stream(),
76 quote!(self as #repr_ident),
77 quote!(unsafe { core::mem::transmute(atom) }),
78 )
79 } else {
80 return TokenStream::from(quote! {
81 compile_error!("Atomizable can only be derived for structs and enums.");
82 });
83 };
84
85 let expanded = quote! {
86 impl ::atomiq::Atomizable for #name {
87 type Atom = #field_type;
88
89 fn pack(self) -> Self::Atom {
90 #pack_line
91 }
92
93 fn unpack(atom: Self::Atom) -> Self {
94 #unpack_line
95 }
96 }
97 };
98
99 TokenStream::from(expanded)
100}
101
102#[proc_macro_derive(BitAtomizable)]
103pub fn derive_bit_atomizable(input: TokenStream) -> TokenStream {
104 let input = parse_macro_input!(input as DeriveInput);
105
106 let name = &input.ident;
107
108 let expanded = quote! {
109 impl ::atomiq::BitAtomizable for #name {}
110 };
111
112 TokenStream::from(expanded)
113}
114
115#[proc_macro_derive(IntAtomizable)]
116pub fn derive_int_atomizable(input: TokenStream) -> TokenStream {
117 let input = parse_macro_input!(input as DeriveInput);
118
119 let name = &input.ident;
120
121 let expanded = quote! {
122 impl ::atomiq::IntAtomizable for #name {}
123 };
124
125 TokenStream::from(expanded)
126}
127
128#[cfg(test)]
129mod tests {
130 #[test]
131 fn test_atomiq_derive() {
132 let t = trybuild::TestCases::new();
133 t.pass("tests/pass_*.rs");
134 t.compile_fail("tests/fail_*.rs");
135 }
136}