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#[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#[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#[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
69fn gen_atom_impl(input: &DeriveInput) -> Result<TokenStream2, Error> {
71 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 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
88fn atom_impl_for_struct(s: &DataStruct) -> Result<TokenStream2, Error> {
90 let mut it = s.fields.iter();
91
92 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 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 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
127fn 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 if attr.path().is_ident("repr") {
138 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 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 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}