atomig_macro/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
5use quote::quote;
6use syn::{
7    parse_macro_input, spanned::Spanned, Data, DataEnum, DataStruct, DeriveInput, Error, Fields,
8};
9
10
11/// Custom derive for the `Atom` trait. Please see the trait's documentation
12/// for more information on this derive.
13#[proc_macro_derive(Atom)]
14pub fn derive_atom(input: TokenStream) -> TokenStream {
15    let input = parse_macro_input!(input as DeriveInput);
16    gen_atom_impl(&input)
17        .unwrap_or_else(|e| e.to_compile_error())
18        .into()
19}
20
21/// Custom derive for the `AtomLogic` trait. Please see the trait's
22/// documentation for more information on this derive.
23#[proc_macro_derive(AtomLogic)]
24pub fn derive_atom_logic(input: TokenStream) -> TokenStream {
25    let input = parse_macro_input!(input as DeriveInput);
26    gen_marker_trait_impl("AtomLogic", &input)
27        .unwrap_or_else(|e| e.to_compile_error())
28        .into()
29}
30
31/// Custom derive for the `AtomInteger` trait. Please see the trait's
32/// documentation for more information on this derive.
33#[proc_macro_derive(AtomInteger)]
34pub fn derive_atom_integer(input: TokenStream) -> TokenStream {
35    let input = parse_macro_input!(input as DeriveInput);
36    gen_marker_trait_impl("AtomInteger", &input)
37        .unwrap_or_else(|e| e.to_compile_error())
38        .into()
39}
40
41fn gen_marker_trait_impl(trait_name: &str, input: &DeriveInput) -> Result<TokenStream2, Error> {
42    match input.data {
43        Data::Struct(_) => {
44            let type_name = &input.ident;
45            let trait_name = Ident::new(trait_name, Span::call_site());
46            let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
47            Ok(quote! {
48                impl #impl_generics atomig::#trait_name
49                    for #type_name #ty_generics #where_clause {}
50            })
51        }
52        Data::Enum(_) => {
53            let msg = format!(
54                "`{}` cannot be derived for enums as this is almost always incorrect to do. \
55                    Please read the documentation of `{}` carefully. If you still think you \
56                    want to implement this trait, you have to do it manually.",
57                trait_name,
58                trait_name,
59            );
60            Err(Error::new(Span::call_site(), msg))
61        }
62        Data::Union(_) => {
63            let msg = format!("`{}` cannot be derived for unions", trait_name);
64            Err(Error::new(Span::call_site(), msg))
65        }
66    }
67}
68
69/// The actual implementation for `derive(Atom)`.
70fn gen_atom_impl(input: &DeriveInput) -> Result<TokenStream2, Error> {
71    // Generate the body of the impl block.
72    let impl_body = match &input.data {
73        Data::Struct(s) => atom_impl_for_struct(s),
74        Data::Enum(e) => atom_impl_for_enum(input, e),
75        Data::Union(_) => Err(Error::new(Span::call_site(), "unions cannot derive `Atom`")),
76    }?;
77
78    // Combine everything into a finshed impl block.
79    let type_name = &input.ident;
80    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
81    Ok(quote! {
82        impl #impl_generics atomig::Atom for #type_name #ty_generics #where_clause {
83            #impl_body
84        }
85    })
86}
87
88/// Generates the body of the `impl Atom` block for the given struct definition.
89fn atom_impl_for_struct(s: &DataStruct) -> Result<TokenStream2, Error> {
90    let mut it = s.fields.iter();
91
92    // Get first field
93    let field = it.next().ok_or_else(|| {
94        let msg = "struct has no fields, but `derive(Atom)` works only for \
95            structs with exactly one field";
96        Error::new(s.fields.span(), msg)
97    })?;
98
99    // Make sure there are no other fields
100    if it.next().is_some() {
101        let msg = "struct has more than one field, but `derive(Atom)` works only for \
102            structs with exactly one field";
103        return Err(Error::new(s.fields.span(), msg));
104    }
105
106    // Generate the code for `pack` and `unpack` which depends on whether it is
107    // a named or tuple-struct field.
108    let (field_access, struct_init) = match &field.ident {
109        Some(name) => (quote! { self.#name }, quote! { Self { #name: src } }),
110        None => (quote! { self.0 }, quote!{ Self(src) }),
111    };
112
113    let field_type = &field.ty;
114    Ok(quote! {
115        type Repr = <#field_type as atomig::Atom>::Repr;
116
117        fn pack(self) -> Self::Repr {
118            <#field_type as atomig::Atom>::pack(#field_access)
119        }
120        fn unpack(src: Self::Repr) -> Self {
121            let src = <#field_type as atomig::Atom>::unpack(src);
122            #struct_init
123        }
124    })
125}
126
127/// Generates the body of the `impl Atom` block for the given enum definition.
128fn atom_impl_for_enum(input: &DeriveInput, e: &DataEnum) -> Result<TokenStream2, Error> {
129    const INTEGER_NAMES: &[&str] = &[
130        "u8", "u16", "u32", "u64", "u128", "usize",
131        "i8", "i16", "i32", "i64", "i128", "isize",
132    ];
133
134    let mut repr_type = None;
135    for attr in &input.attrs {
136        // Make sure we have a `repr` attribute on the enum.
137        if attr.path().is_ident("repr") {
138            // Make sure the `repr` attribute has the correct syntax and actually
139            // specifies the primitive representation.
140            attr.parse_nested_meta(|meta| {
141                repr_type = Some(
142                    meta.path
143                        .get_ident()
144                        .filter(|ident| INTEGER_NAMES.iter().any(|int| ident == int))
145                        .ok_or_else(|| {
146                            let msg = "`repr(_)` attribute does not specify the primitive \
147                            representation (a primitive integer), but this is required \
148                            for `derive(Atom)`";
149                            Error::new(meta.input.span(), msg)
150                        })?
151                        .clone(),
152                );
153
154                Ok(())
155            })?
156        }
157    }
158
159    let repr_type = repr_type.ok_or_else(|| {
160        let msg = format!(
161            "no `repr(_)` attribute on enum '{}', but such an attribute is \
162                required to automatically derive `Atom`",
163            input.ident,
164        );
165        Error::new(Span::call_site(), msg)
166    })?;
167
168    // Check that all variants have no fields. In other words: that the enum is
169    // C-like.
170    let variant_with_fields = e.variants.iter().find(|variant| {
171        match variant.fields {
172            Fields::Unit => false,
173            _ => true,
174        }
175    });
176    if let Some(v) = variant_with_fields  {
177        let msg = "this variant has fields, but `derive(Atom)` only works \
178            for C-like enums";
179        return Err(Error::new(v.span(), msg));
180    }
181
182    // Generate the code for `unpack` which is more complicated than the `pack`
183    // code. For `pack` we can simply use the `as` cast, but for unpack we have
184    // to assemble a list of `if` statements. If you would hand code such a
185    // method, you would use a `match` statement. But we use 'ifs' so that we
186    // don't have to check for the discriminant values ourselves. That might be
187    // very hard.
188    let type_name = &input.ident;
189    let unpack_code = {
190        let checks: Vec<_> = e.variants.iter().map(|variant| {
191            let variant_name = &variant.ident;
192            quote! {
193                if src == #type_name::#variant_name as #repr_type {
194                    return #type_name::#variant_name;
195                }
196            }
197        }).collect();
198
199        let error = format!(
200            "invalid '{}' value '{{}}' for enum '{}' in `Atom::unpack`",
201            repr_type,
202            type_name,
203        );
204        quote! {
205            #(#checks)*
206            panic!(#error, src);
207        }
208    };
209
210
211    Ok(quote! {
212        type Repr = #repr_type;
213
214        fn pack(self) -> Self::Repr {
215            self as #repr_type
216        }
217        fn unpack(src: Self::Repr) -> Self {
218            #unpack_code
219        }
220    })
221}