const_field_offset_macro/
macro.rs1extern crate proc_macro;
11
12use proc_macro::TokenStream;
13use quote::{format_ident, quote, quote_spanned};
14use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
15#[cfg(feature = "field-offset-trait")]
16use syn::{VisRestricted, Visibility};
17
18#[cfg_attr(
42 feature = "field-offset-trait",
43 doc = "
44In addition, the macro also create a module `{ClassName}_field_offsets` which contains
45zero-sized type that implement the `const_field_offset::ConstFieldOffset` trait
46
47```rust
48use const_field_offset::{FieldOffsets, FieldOffset, ConstFieldOffset};
49#[repr(C)]
50#[derive(FieldOffsets)]
51struct Foo {
52 field_1 : u8,
53 field_2 : u32,
54}
55
56const FOO : FieldOffset<Foo, u32> = Foo_field_offsets::field_2::OFFSET;
57assert_eq!(FOO.get_byte_offset(), 4);
58```
59"
60)]
61#[proc_macro_derive(FieldOffsets, attributes(const_field_offset, pin, pin_drop))]
136pub fn const_field_offset(input: TokenStream) -> TokenStream {
137 let input = parse_macro_input!(input as DeriveInput);
138
139 let mut has_repr_c = false;
140 let mut crate_ = quote!(const_field_offset);
141 let mut pin = false;
142 let mut drop = false;
143 for a in &input.attrs {
144 if let Some(i) = a.path().get_ident() {
145 if i == "repr" {
146 let inner = a.parse_args::<syn::Ident>().map(|x| x.to_string());
147 match inner.as_ref().map(|x| x.as_str()) {
148 Ok("C") => has_repr_c = true,
149 Ok("packed") => {
150 return TokenStream::from(quote!(
151 compile_error! {"FieldOffsets does not work on #[repr(packed)]"}
152 ))
153 }
154 _ => (),
155 }
156 } else if i == "const_field_offset" {
157 match a.parse_args::<syn::Path>() {
158 Ok(c) => crate_ = quote!(#c),
159 Err(_) => {
160 return TokenStream::from(
161 quote_spanned!(a.span()=> compile_error!{"const_field_offset attribute must be a crate name"}),
162 );
163 }
164 }
165 } else if i == "pin" {
166 pin = true;
167 } else if i == "pin_drop" {
168 drop = true;
169 pin = true;
170 }
171 }
172 }
173 if !has_repr_c {
174 return TokenStream::from(
175 quote! {compile_error!{"FieldOffsets only work for structures using repr(C)"}},
176 );
177 }
178
179 let struct_name = input.ident;
180 let struct_vis = input.vis;
181 let field_struct_name = quote::format_ident!("{}FieldsOffsets", struct_name);
182
183 let (fields, types, vis) = if let syn::Data::Struct(s) = &input.data {
184 if let syn::Fields::Named(n) = &s.fields {
185 let (f, tv): (Vec<_>, Vec<_>) =
186 n.named.iter().map(|f| (&f.ident, (&f.ty, &f.vis))).unzip();
187 let (t, v): (Vec<_>, Vec<_>) = tv.into_iter().unzip();
188 (f, t, v)
189 } else {
190 return TokenStream::from(quote! {compile_error!{"Only work for named fields"}});
191 }
192 } else {
193 return TokenStream::from(quote! {compile_error!("Only work for struct")});
194 };
195
196 let doc = format!(
197 "Helper struct containing the offsets of the fields of the struct [`{}`]\n\n\
198 Generated from the `#[derive(FieldOffsets)]` macro from the [`const-field-offset`]({}) crate",
199 struct_name, crate_
200 );
201
202 let (ensure_pin_safe, ensure_no_unpin, pin_flag, new_from_offset) = if !pin {
203 (None, None, quote!(#crate_::NotPinned), quote!(new_from_offset))
204 } else {
205 (
206 if drop {
207 None
208 } else {
209 let drop_trait_ident = format_ident!("{}MustNotImplDrop", struct_name);
210 Some(quote! {
211 #[allow(non_camel_case_types)]
213 trait #drop_trait_ident {}
214 impl<T: ::core::ops::Drop> #drop_trait_ident for T {}
215 impl #drop_trait_ident for #struct_name {}
216
217 })
218 },
219 Some(quote! {
220 const _ : () = {
221 #[allow(dead_code)]
223 struct __MustNotImplUnpin<'__dummy_lifetime> (
224 ::core::marker::PhantomData<&'__dummy_lifetime ()>
225 );
226 impl<'__dummy_lifetime> Unpin for #struct_name where __MustNotImplUnpin<'__dummy_lifetime> : Unpin {};
227 };
228 }),
229 quote!(#crate_::AllowPin),
230 quote!(new_from_offset_pinned),
231 )
232 };
233
234 let pinned_drop_impl = if drop {
235 Some(quote!(
236 impl Drop for #struct_name {
237 fn drop(&mut self) {
238 use #crate_::PinnedDrop;
239 self.do_safe_pinned_drop();
240 }
241 }
242 ))
243 } else {
244 None
245 };
246
247 let expanded = quote! {
249 #[doc = #doc]
250 #[allow(missing_docs, non_camel_case_types, dead_code)]
251 #struct_vis struct #field_struct_name {
252 #(#vis #fields : #crate_::FieldOffset<#struct_name, #types, #pin_flag>,)*
253 }
254
255 #[allow(clippy::eval_order_dependence)] impl #struct_name {
257 pub const FIELD_OFFSETS : #field_struct_name = {
259 #ensure_pin_safe;
260 let mut len = 0usize;
261 #field_struct_name {
262 #( #fields : {
263 let align = ::core::mem::align_of::<#types>();
264 let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
266 len = len_rounded_up + ::core::mem::size_of::<#types>();
267 unsafe { #crate_::FieldOffset::<#struct_name, #types, _>::#new_from_offset(len_rounded_up) }
269 }, )*
270 }
271 };
272 }
273
274 #pinned_drop_impl
275 #ensure_no_unpin
276 };
277
278 #[cfg(feature = "field-offset-trait")]
279 let module_name = quote::format_ident!("{}_field_offsets", struct_name);
280
281 #[cfg(feature = "field-offset-trait")]
282 let in_mod_vis = vis.iter().map(|vis| min_vis(vis, &struct_vis)).map(|vis| match vis {
283 Visibility::Public(_) => quote! {#vis},
284 Visibility::Restricted(VisRestricted { pub_token, path, .. }) => {
285 if quote!(#path).to_string().starts_with("super") {
286 quote!(#pub_token(in super::#path))
287 } else {
288 quote!(#vis)
289 }
290 }
291 Visibility::Inherited => quote!(pub(super)),
292 });
293
294 #[cfg(feature = "field-offset-trait")]
295 let expanded = quote! { #expanded
296 #[allow(non_camel_case_types)]
297 #[allow(non_snake_case)]
298 #[allow(missing_docs)]
299 #struct_vis mod #module_name {
300 #(
301 #[derive(Clone, Copy, Default)]
302 #in_mod_vis struct #fields;
303 )*
304 }
305 #(
306 impl #crate_::ConstFieldOffset for #module_name::#fields {
307 type Container = #struct_name;
308 type Field = #types;
309 type PinFlag = #pin_flag;
310 const OFFSET : #crate_::FieldOffset<#struct_name, #types, Self::PinFlag>
311 = #struct_name::FIELD_OFFSETS.#fields;
312 }
313 impl ::core::convert::Into<#crate_::FieldOffset<#struct_name, #types, #pin_flag>> for #module_name::#fields {
314 fn into(self) -> #crate_::FieldOffset<#struct_name, #types, #pin_flag> {
315 #struct_name::FIELD_OFFSETS.#fields
316 }
317 }
318 impl<Other> ::core::ops::Add<Other> for #module_name::#fields
319 where Other : #crate_::ConstFieldOffset<Container = #types>
320 {
321 type Output = #crate_::ConstFieldOffsetSum<Self, Other>;
322 fn add(self, other: Other) -> Self::Output {
323 #crate_::ConstFieldOffsetSum(self, other)
324 }
325 }
326 )*
327 };
328
329 TokenStream::from(expanded)
331}
332
333#[cfg(feature = "field-offset-trait")]
334fn min_vis<'a>(a: &'a Visibility, b: &'a Visibility) -> &'a Visibility {
336 match (a, b) {
337 (Visibility::Public(_), _) => b,
338 (_, Visibility::Public(_)) => a,
339 (Visibility::Inherited, _) => a,
340 (_, Visibility::Inherited) => b,
341 _ => a,
343 }
344}
345
346#[cfg(doctest)]
356const _NO_REPR_C: u32 = 0;
357
358#[cfg(doctest)]
370const _REPR_PACKED: u32 = 0;
371
372#[cfg(doctest)]
388const _PIN_NO_DROP: u32 = 0;
389
390#[cfg(doctest)]
405const _PIN_NO_UNPIN: u32 = 0;