Skip to main content

padding_struct/
lib.rs

1// SPDX-License-Identifier: MPL-2.0
2
3#![doc = include_str!("../README.md")]
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{
8    Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Token, parse_macro_input,
9    punctuated::Punctuated, spanned::Spanned,
10};
11
12/// Checks if the struct has a `#[repr(C)]` attribute (possibly with other `repr` options)
13fn has_repr_c(attrs: &[Attribute]) -> bool {
14    attrs.iter().any(|attr| {
15        if attr.path().is_ident("repr") {
16            // Parse the attribute using a custom parser
17            let result = attr.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated);
18
19            if let Ok(list) = result {
20                return list
21                    .iter()
22                    .any(|meta| matches!(meta, syn::Meta::Path(path) if path.is_ident("C")));
23            }
24
25            false
26        } else {
27            false
28        }
29    })
30}
31
32/// Extracts all `#[repr(...)]` attributes from the given attributes
33fn extract_repr_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
34    attrs
35        .iter()
36        .filter(|attr| attr.path().is_ident("repr"))
37        .collect()
38}
39
40/// Procedural macro to automatically add padding fields to a `#[repr(C)]` struct.
41///
42/// This macro generates two structs:
43/// 1. A reference struct (prefixed and suffixed with `__`) containing the original fields without padding.
44/// 2. A padded struct (using the original name) with `__padN` padding fields after each original field.
45///
46/// # Padding Calculation Rules
47///
48/// - For non-last fields: padding size = next field's offset - current field's offset - current field's size.
49/// - For the last field: padding size = struct total size - current field's offset - current field's size.
50///
51/// # Requirements
52///
53/// - The struct must have a `#[repr(C)]` attribute.
54/// - The struct must have named fields.
55///
56/// # Examples
57///
58/// ```rust
59/// use padding_struct::padding_struct;
60///
61/// #[repr(C)]
62/// #[padding_struct]
63/// struct MyStruct {
64///     a: u8,
65///     b: u32,
66/// }
67/// ```
68///
69/// This generates code equivalent to:
70///
71/// ```rust
72/// use core::mem::offset_of;
73///
74/// #[repr(C)]
75/// struct __MyStruct__ {
76///     a: u8,
77///     b: u32,
78/// }
79///
80/// #[repr(C)]
81/// struct MyStruct {
82///     a: u8,
83///     pub __pad1:
84///         [u8; const { offset_of!(__MyStruct__, b) - offset_of!(__MyStruct__, a) - size_of::<u8>() }],
85///     b: u32,
86///     pub __pad2:
87///         [u8; const { size_of::<__MyStruct__>() - offset_of!(__MyStruct__, b) - size_of::<u32>() }],
88/// }
89/// ```
90#[proc_macro_attribute]
91pub fn padding_struct(args: TokenStream, input: TokenStream) -> TokenStream {
92    // Reject any provided arguments to the attribute
93    if !args.is_empty() {
94        panic!("`#[padding_struct]` does not accept any arguments");
95    }
96    let input = parse_macro_input!(input as DeriveInput);
97
98    // Ensure it's a struct
99    let fields = match &input.data {
100        Data::Struct(DataStruct {
101            fields: Fields::Named(fields),
102            ..
103        }) => &fields.named,
104        _ => panic!("`#[padding_struct]` only supports named-field structs"),
105    };
106
107    // Ensure #[repr(C)] is present
108    if !has_repr_c(&input.attrs) {
109        panic!("`#[padding_struct]` requires `#[repr(C)]` or `#[repr(C, ...)]` on struct");
110    }
111
112    let name = &input.ident;
113    let vis = &input.vis;
114    let generics = &input.generics;
115    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
116
117    let ref_name = Ident::new(&format!("__{}__", name), name.span());
118
119    // Extract all repr attributes for the reference struct
120    let repr_attrs = extract_repr_attrs(&input.attrs);
121
122    // Generate reference struct (same fields, no padding, with all repr attributes)
123    let ref_fields: Vec<_> = fields.iter().collect();
124    let ref_struct = quote! {
125        #(#repr_attrs)*
126        #[allow(missing_docs)]
127        #[doc(hidden)]
128        struct #ref_name #impl_generics #where_clause {
129            #(#ref_fields),*
130        }
131    };
132
133    // Filter attributes for the padded struct (keep all except #[padding_struct])
134    let padded_attrs: Vec<_> = input
135        .attrs
136        .iter()
137        .filter(|attr| !attr.path().is_ident("padding_struct"))
138        .collect();
139
140    // Generate padded struct with inline const expressions
141    let mut padded_fields = Vec::new();
142    let field_vec: Vec<_> = fields.iter().collect();
143
144    for (i, field) in field_vec.iter().enumerate() {
145        let field_name = &field.ident;
146        let field_ty = &field.ty;
147        let field_attrs = &field.attrs;
148        let field_vis = &field.vis;
149
150        // Add original field with its attributes and comments
151        padded_fields.push(quote! {
152            #(#field_attrs)*
153            #field_vis #field_name: #field_ty
154        });
155
156        // Generate padding field with inline const expression
157        let pad_num = i + 1;
158        let pad_ident = Ident::new(&format!("__pad{}", pad_num), field.span());
159
160        let pad_size_expr = if i == field_vec.len() - 1 {
161            // Last field: padding to end of struct
162            quote! {
163                ::core::mem::size_of::<#ref_name #ty_generics>()
164                    - ::core::mem::offset_of!(#ref_name #ty_generics, #field_name)
165                    - ::core::mem::size_of::<#field_ty>()
166            }
167        } else {
168            // Middle field: padding to next field
169            let next_field = field_vec[i + 1];
170            let next_field_name = &next_field.ident;
171            quote! {
172                ::core::mem::offset_of!(#ref_name #ty_generics, #next_field_name)
173                    - ::core::mem::offset_of!(#ref_name #ty_generics, #field_name)
174                    - ::core::mem::size_of::<#field_ty>()
175            }
176        };
177
178        // Add padding field with inline const block
179        padded_fields.push(quote! {
180            #[allow(missing_docs)]
181            pub #pad_ident: [u8; { #pad_size_expr }]
182        });
183    }
184
185    let padded_struct = quote! {
186        #(#padded_attrs)*
187        #vis struct #name #impl_generics #where_clause {
188            #(#padded_fields),*
189        }
190    };
191
192    // Generate compile-time assertions to ensure size and alignment match
193    let size_align_check = quote! {
194        const _: () = {
195            // Assert that sizes are equal
196            const _: [(); ::core::mem::size_of::<#ref_name #ty_generics>()] =
197                [(); ::core::mem::size_of::<#name #ty_generics>()];
198
199            // Assert that alignments are equal
200            const _: [(); ::core::mem::align_of::<#ref_name #ty_generics>()] =
201                [(); ::core::mem::align_of::<#name #ty_generics>()];
202        };
203    };
204
205    let expanded = quote! {
206        #ref_struct
207
208        #padded_struct
209
210        #size_align_check
211    };
212
213    TokenStream::from(expanded)
214}