1#![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
12fn has_repr_c(attrs: &[Attribute]) -> bool {
14 attrs.iter().any(|attr| {
15 if attr.path().is_ident("repr") {
16 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
32fn extract_repr_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
34 attrs
35 .iter()
36 .filter(|attr| attr.path().is_ident("repr"))
37 .collect()
38}
39
40#[proc_macro_attribute]
91pub fn padding_struct(args: TokenStream, input: TokenStream) -> TokenStream {
92 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 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 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 let repr_attrs = extract_repr_attrs(&input.attrs);
121
122 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 let padded_attrs: Vec<_> = input
135 .attrs
136 .iter()
137 .filter(|attr| !attr.path().is_ident("padding_struct"))
138 .collect();
139
140 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 padded_fields.push(quote! {
152 #(#field_attrs)*
153 #field_vis #field_name: #field_ty
154 });
155
156 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 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 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 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 let size_align_check = quote! {
194 const _: () = {
195 const _: [(); ::core::mem::size_of::<#ref_name #ty_generics>()] =
197 [(); ::core::mem::size_of::<#name #ty_generics>()];
198
199 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}