const_field_offset_macro/
macro.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4/*!
5This crate allow to get the offset of a field of a structure in a const or static context.
6
7To be used re-exported from the `const_field_offset` crate
8
9*/
10extern 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/**
19
20The macro FieldOffsets adds a `FIELD_OFFSETS` associated const to the struct. That
21is an object which has fields with the same name as the fields of the original struct,
22each field is of type `const_field_offset::FieldOffset`
23
24```rust
25use const_field_offset::FieldOffsets;
26#[repr(C)]
27#[derive(FieldOffsets)]
28struct Foo {
29    field_1 : u8,
30    field_2 : u32,
31}
32
33const FOO : usize = Foo::FIELD_OFFSETS.field_2.get_byte_offset();
34assert_eq!(FOO, 4);
35
36// This would not work on stable rust at the moment (rust 1.43)
37// const FOO : usize = memoffsets::offsetof!(Foo, field_2);
38```
39
40*/
41#[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/**
62
63## Limitations
64
65Only work with named #[repr(C)] structures.
66
67## Attributes
68
69### `pin`
70
71Add a `AllowPin` to the FieldOffset.
72
73In order for this to be safe, the macro will add code to prevent a
74custom `Drop` or `Unpin` implementation.
75
76```rust
77use const_field_offset::*;
78#[repr(C)]
79#[derive(FieldOffsets)]
80#[pin]
81struct Foo {
82    field_1 : u8,
83    field_2 : u32,
84}
85
86const FIELD_2 : FieldOffset<Foo, u32, AllowPin> = Foo::FIELD_OFFSETS.field_2;
87let pin_box = Box::pin(Foo{field_1: 1, field_2: 2});
88assert_eq!(*FIELD_2.apply_pin(pin_box.as_ref()), 2);
89```
90
91### `pin_drop`
92
93This attribute works like the `pin` attribute but it does not prevent a custom
94Drop implementation. Instead it provides a Drop implementation that forwards to
95the [PinnedDrop](../const_field_offset/trait.PinnedDrop.html) trait that you need to implement for our type.
96
97```rust
98use const_field_offset::*;
99use core::pin::Pin;
100
101struct TypeThatRequiresSpecialDropHandling(); // ...
102
103#[repr(C)]
104#[derive(FieldOffsets)]
105#[pin_drop]
106struct Foo {
107    field : TypeThatRequiresSpecialDropHandling,
108}
109
110impl PinnedDrop for Foo {
111    fn drop(self: Pin<&mut Self>) {
112        // Do you safe drop handling here
113    }
114}
115```
116
117### `const-field-offset`
118
119In case the `const-field-offset` crate is re-exported, it is possible to
120specify the crate name using the `const_field_offset` attribute.
121
122```rust
123// suppose you re-export the const_field_offset create from a different module
124mod xxx { pub use const_field_offset as cfo; }
125#[repr(C)]
126#[derive(xxx::cfo::FieldOffsets)]
127#[const_field_offset(xxx::cfo)]
128struct Foo {
129    field_1 : u8,
130    field_2 : u32,
131}
132```
133
134*/
135#[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                    /// Make sure that Drop is not implemented
212                    #[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                    /// Make sure that Unpin is not implemented
222                    #[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    // Build the output, possibly using quasi-quotation
248    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)] // The point of this code is to depend on the order!
256        impl #struct_name {
257            /// Return a struct containing the offset of for the fields of this struct
258            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                        // from Layout::padding_needed_for which is not yet stable
265                        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                        /// Safety: According to the rules of repr(C), this is the right offset
268                        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    // Hand the output tokens back to the compiler
330    TokenStream::from(expanded)
331}
332
333#[cfg(feature = "field-offset-trait")]
334/// Returns the most restricted visibility
335fn 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        // FIXME: compare two paths
342        _ => a,
343    }
344}
345
346/**
347```compile_fail
348use const_field_offset::*;
349#[derive(FieldOffsets)]
350struct Foo {
351    x: u32,
352}
353```
354*/
355#[cfg(doctest)]
356const _NO_REPR_C: u32 = 0;
357
358/**
359```compile_fail
360use const_field_offset::*;
361#[derive(FieldOffsets)]
362#[repr(C)]
363#[repr(packed)]
364struct Foo {
365    x: u32,
366}
367```
368*/
369#[cfg(doctest)]
370const _REPR_PACKED: u32 = 0;
371
372/**
373```compile_fail
374use const_field_offset::*;
375#[derive(FieldOffsets)]
376#[repr(C)]
377#[pin]
378struct Foo {
379    x: u32,
380}
381
382impl Drop for Foo {
383    fn drop(&mut self) {}
384}
385```
386*/
387#[cfg(doctest)]
388const _PIN_NO_DROP: u32 = 0;
389
390/**
391```compile_fail
392use const_field_offset::*;
393#[derive(FieldOffsets)]
394#[repr(C)]
395#[pin]
396struct Foo {
397    q: std::marker::PhantomPinned,
398    x: u32,
399}
400
401impl Unpin for Foo {}
402```
403*/
404#[cfg(doctest)]
405const _PIN_NO_UNPIN: u32 = 0;