atomiq_derive/
lib.rs

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}