superbitty_macros/bitfields/
mod.rs

1mod derives;
2mod parse;
3
4use proc_macro2::{Ident, Span, TokenStream};
5use quote::{format_ident, quote, quote_spanned};
6use syn::spanned::Spanned;
7
8use self::derives::derives;
9use crate::utils::{is_unsigned_int_primitive, SynErrors};
10
11pub(crate) fn bitfields_impl(item: TokenStream) -> syn::Result<TokenStream> {
12    let mut item = syn::parse2::<parse::BitfieldsStruct>(item)?;
13
14    disallow_invalid_attributes(&item.attrs)?;
15
16    let (struct_attrs, derives) = derives(item.attrs.drain(..))?;
17
18    verify_base_ty(&item.base_ty)?;
19
20    let assert_bitfields_compatible = assert_bitfields_compatible(&item.fields);
21
22    let derives = TokenStream::from_iter(derives.into_iter().map(|derive| derive(&item)));
23
24    let base_ty = &item.base_ty;
25
26    let (bitfields, after_last_bitfield_offset) = bitfields(item.fields);
27    let assert_bitfields_size = quote! {
28        const _: () = assert!(
29            #after_last_bitfield_offset <= #base_ty::BITS,
30            "bitfield size is too big - choose another base type",
31        );
32    };
33
34    let new_method = new_method(
35        &bitfields,
36        constructor_name(&item.constructor),
37        constructor_vis(&item.constructor, &item.vis),
38        &item.base_ty,
39    );
40    let bitfields_accessors =
41        bitfields.iter().map(|bitfield| bitfield_accessors(bitfield, &item.base_ty));
42
43    let struct_vis = &item.vis;
44    let struct_kw = &item.struct_token;
45    let struct_name = &item.ident;
46    let generics = &item.generics;
47    let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();
48    let result = quote! {
49        #(#struct_attrs)*
50        #struct_vis #struct_kw #struct_name #generics(
51            // Invariant: Always holds valid instances of the bit fields.
52            ::superbitty::Raw<#base_ty>,
53        )
54            #where_clause;
55
56        #assert_bitfields_compatible
57        #assert_bitfields_size
58
59        #derives
60
61        impl #impl_generics #struct_name #type_generics
62        #where_clause
63        {
64            #new_method
65
66            #(#bitfields_accessors)*
67        }
68    };
69    Ok(result)
70}
71
72/// Proc macros can cause unsoundness - they can replace the inner representation with some evil
73/// type that implements the bitwise operators incorrectly, causing us to create invalid instances
74/// of types such as enums. Because of that we disallow them here.
75fn disallow_invalid_attributes(attrs: &[syn::Attribute]) -> syn::Result<()> {
76    let mut errors = SynErrors::default();
77    for attr in attrs {
78        if !attr.path.is_ident("doc") && !attr.path.is_ident("derive") {
79            errors.push(syn::Error::new_spanned(
80                attr,
81                "only doc comments and `#[derive(…)]` are allowed with `bitfields!`",
82            ));
83        }
84    }
85    errors.into_result()
86}
87
88struct Bitfield {
89    parse: parse::BitfieldsStructField,
90    bit_offset: TokenStream,
91    type_shift: TokenStream,
92    bits_mask: TokenStream,
93    is_last: bool,
94}
95
96fn bitfields(fields: parse::BitfieldsStructFields) -> (Vec<Bitfield>, TokenStream) {
97    let mut prev_offset = quote!(0);
98    let mut result = Vec::with_capacity(fields.fields.len());
99    let fields_count = fields.fields.len();
100    for (index, field) in fields.fields.into_iter().enumerate() {
101        let bit_offset = quote! { ( #prev_offset ) };
102        let field_ty = &field.ty;
103        let bits_mask = quote! {
104            <#field_ty as ::superbitty::BitFieldCompatible>::BITS_MASK
105        };
106        prev_offset.extend(std::iter::once(quote! {
107            + <#field_ty as ::superbitty::BitFieldCompatible>::BITS_LEN
108        }));
109        let type_shift = quote! { <#field_ty as ::superbitty::BitFieldCompatible>::SHIFT };
110        let is_last = index == fields_count - 1;
111        result.push(Bitfield { parse: field, bit_offset, type_shift, bits_mask, is_last })
112    }
113    (result, quote! { ( #prev_offset) })
114}
115
116fn constructor_name(constructor: &parse::Constructor) -> Ident {
117    match constructor {
118        Some((_, constructor_name)) => constructor_name.clone(),
119        None => syn::Ident::new("new", Span::call_site()),
120    }
121}
122fn constructor_vis<'a>(
123    constructor: &'a parse::Constructor,
124    item_vis: &'a syn::Visibility,
125) -> &'a syn::Visibility {
126    match constructor {
127        Some((constructor_vis, _)) => constructor_vis,
128        None => item_vis,
129    }
130}
131
132fn new_method(
133    bitfields: &[Bitfield],
134    method_name: syn::Ident,
135    method_vis: &syn::Visibility,
136    base_ty: &syn::Type,
137) -> TokenStream {
138    let args = bitfields.iter().map(
139        |Bitfield { parse: parse::BitfieldsStructField { ident: name, ty, .. }, .. }| quote!(#name : #ty),
140    );
141    let fields_calculation = bitfields.iter().map(
142        |Bitfield {
143             parse: parse::BitfieldsStructField { ident: name, ty, .. },
144             bit_offset,
145             type_shift,
146             ..
147         }| {
148            quote! {
149                ((<#ty as ::superbitty::BitFieldCompatible>::into_raw(#name) >> #type_shift)
150                    << #bit_offset)
151            }
152        },
153    );
154    quote! {
155        #method_vis fn #method_name( #( #args, )* ) -> Self {
156            Self(
157                // SAFETY: We're combining valid values from `into_raw()` that by
158                // `BitFieldCompatible`'s preconditions guaranteed to return valid
159                // discriminants.
160                unsafe { ::superbitty::Raw::new(( 0 #( | #fields_calculation )* ) as #base_ty) },
161            )
162        }
163    }
164}
165
166fn bitfield_accessors(
167    Bitfield {
168        parse: parse::BitfieldsStructField { attrs, vis, ident: field_name, ty },
169        bit_offset,
170        type_shift,
171        bits_mask,
172        is_last,
173    }: &Bitfield,
174    base_ty: &syn::Type,
175) -> TokenStream {
176    let setter_name = format_ident!("set_{field_name}");
177    let mut getter_stripped_field =
178        quote! { ((::superbitty::Raw::raw(self.0) as u128) >> #bit_offset) };
179    if !is_last {
180        getter_stripped_field = quote! { (#getter_stripped_field & #bits_mask) }
181    }
182    quote_spanned! {field_name.span()=>
183        #(#attrs)* // We put the attributes on the getter mainly for documentation comments.
184        #[inline]
185        #vis fn #field_name(&self) -> #ty {
186            // SAFETY: Since `self.0` always holds valid instances, and all bitfields are
187            // `Copy`, we can convert the bitfield to its enum soundly.
188            unsafe {
189                <#ty as ::superbitty::BitFieldCompatible>::from_raw(
190                    #getter_stripped_field << #type_shift,
191                )
192            }
193        }
194
195        #[inline]
196        #[allow(dead_code)] // User may use the constructor and getters only.
197        #vis fn #setter_name(&mut self, value: #ty) {
198            let raw_without_field = (::superbitty::Raw::raw(self.0) as u128) & !(#bits_mask << #bit_offset);
199            let field_in_place =
200                (<#ty as ::superbitty::BitFieldCompatible>::into_raw(value) >> #type_shift)
201                    << #bit_offset;
202            // SAFETY: We only trim irrelevant bits that by `BitFieldCompatible`'s precondition
203            // should be safe.
204            *unsafe { ::superbitty::Raw::get_mut(&mut self.0) } =
205                (raw_without_field | field_in_place) as #base_ty;
206        }
207    }
208}
209
210fn verify_base_ty(base_ty: &syn::Type) -> syn::Result<()> {
211    // We can leave that out because `Raw` will validate that but this gives better error message.
212    if is_unsigned_int_primitive(base_ty) {
213        return Ok(());
214    }
215
216    return Err(syn::Error::new_spanned(
217        base_ty,
218        "unsupported base type for `bitfields!`: only primitive unsigned types are supported",
219    ));
220}
221
222fn assert_bitfields_compatible(fields: &parse::BitfieldsStructFields) -> TokenStream {
223    let field_asserts = fields.fields.iter().map(|parse::BitfieldsStructField { ty, .. }| {
224        quote_spanned! {ty.span()=>
225            ::superbitty::__helpers::assert_bitfield_compatible::<#ty>();
226        }
227    });
228    quote! {
229        const _: () = {
230            #(#field_asserts)*
231        };
232    }
233}