Skip to main content

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::{DeriveInput, parse_macro_input, spanned::Spanned};
15
16/**
17
18The macro FieldOffsets adds a `FIELD_OFFSETS` associated const to the struct. That
19is an object which has fields with the same name as the fields of the original struct,
20each field is of type `const_field_offset::FieldOffset`
21
22```rust
23use const_field_offset::FieldOffsets;
24#[repr(C)]
25#[derive(FieldOffsets)]
26struct Foo {
27    field_1 : u8,
28    field_2 : u32,
29}
30
31const FOO : usize = Foo::FIELD_OFFSETS.field_2().get_byte_offset();
32assert_eq!(FOO, 4);
33```
34
35## Attributes
36
37### `pin`
38
39Add a `AllowPin` to the FieldOffset.
40
41In order for this to be safe, the macro will add code to prevent a
42custom `Drop` or `Unpin` implementation.
43
44```rust
45use const_field_offset::*;
46#[derive(FieldOffsets)]
47#[pin]
48struct Foo {
49    field_1 : u8,
50    field_2 : u32,
51}
52
53const FIELD_2 : FieldOffset<Foo, u32, AllowPin> = Foo::FIELD_OFFSETS.field_2();
54let pin_box = Box::pin(Foo{field_1: 1, field_2: 2});
55assert_eq!(*FIELD_2.apply_pin(pin_box.as_ref()), 2);
56```
57
58### `pin_drop`
59
60This attribute works like the `pin` attribute but it does not prevent a custom
61Drop implementation. Instead it provides a Drop implementation that forwards to
62the [PinnedDrop](../const_field_offset/trait.PinnedDrop.html) trait that you need to implement for our type.
63
64```rust
65use const_field_offset::*;
66use core::pin::Pin;
67
68struct TypeThatRequiresSpecialDropHandling(); // ...
69
70#[derive(FieldOffsets)]
71#[pin_drop]
72struct Foo {
73    field : TypeThatRequiresSpecialDropHandling,
74}
75
76impl PinnedDrop for Foo {
77    fn drop(self: Pin<&mut Self>) {
78        // Do you safe drop handling here
79    }
80}
81```
82
83### `const-field-offset`
84
85In case the `const-field-offset` crate is re-exported, it is possible to
86specify the crate name using the `const_field_offset` attribute.
87
88```rust
89// suppose you re-export the const_field_offset create from a different module
90mod xxx { pub use const_field_offset as cfo; }
91#[derive(xxx::cfo::FieldOffsets)]
92#[const_field_offset(xxx::cfo)]
93struct Foo {
94    field_1 : u8,
95    field_2 : u32,
96}
97```
98
99*/
100#[proc_macro_derive(FieldOffsets, attributes(const_field_offset, pin, pin_drop))]
101pub fn const_field_offset(input: TokenStream) -> TokenStream {
102    let input = parse_macro_input!(input as DeriveInput);
103
104    let mut crate_ = quote!(const_field_offset);
105    let mut pin = false;
106    let mut drop = false;
107    for a in &input.attrs {
108        if let Some(i) = a.path().get_ident() {
109            if i == "const_field_offset" {
110                match a.parse_args::<syn::Path>() {
111                    Ok(c) => crate_ = quote!(#c),
112                    Err(_) => {
113                        return TokenStream::from(
114                            quote_spanned!(a.span()=> compile_error!{"const_field_offset attribute must be a crate name"}),
115                        );
116                    }
117                }
118            } else if i == "pin" {
119                pin = true;
120            } else if i == "pin_drop" {
121                drop = true;
122                pin = true;
123            }
124        }
125    }
126
127    let struct_name = input.ident;
128    let struct_vis = input.vis;
129    let field_struct_name = quote::format_ident!("{}FieldsOffsets", struct_name);
130
131    let (fields, types, vis) = if let syn::Data::Struct(s) = &input.data {
132        if let syn::Fields::Named(n) = &s.fields {
133            let (f, tv): (Vec<_>, Vec<_>) =
134                n.named.iter().map(|f| (&f.ident, (&f.ty, &f.vis))).unzip();
135            let (t, v): (Vec<_>, Vec<_>) = tv.into_iter().unzip();
136            (f, t, v)
137        } else {
138            return TokenStream::from(quote! {compile_error!{"Only work for named fields"}});
139        }
140    } else {
141        return TokenStream::from(quote! {compile_error!("Only work for struct")});
142    };
143
144    let doc = format!(
145        "Helper struct containing the offsets of the fields of the struct [`{struct_name}`]\n\n\
146        Generated from the `#[derive(FieldOffsets)]` macro from the [`const-field-offset`]({crate_}) crate",
147    );
148
149    let (ensure_pin_safe, ensure_no_unpin, pin_flag, new_from_offset) = if !pin {
150        (None, None, quote!(#crate_::NotPinned), quote!(new_from_offset))
151    } else {
152        (
153            if drop {
154                None
155            } else {
156                let drop_trait_ident = format_ident!("{}MustNotImplDrop", struct_name);
157                Some(quote! {
158                    /// Make sure that Drop is not implemented
159                    #[allow(non_camel_case_types)]
160                    trait #drop_trait_ident {}
161                    impl<T: ::core::ops::Drop> #drop_trait_ident for T {}
162                    impl #drop_trait_ident for #struct_name {}
163
164                })
165            },
166            Some(quote! {
167                const _ : () = {
168                    /// Make sure that Unpin is not implemented
169                    #[allow(dead_code)]
170                    struct __MustNotImplUnpin<'__dummy_lifetime> (
171                        ::core::marker::PhantomData<&'__dummy_lifetime ()>
172                    );
173                    impl<'__dummy_lifetime> Unpin for #struct_name where __MustNotImplUnpin<'__dummy_lifetime> : Unpin {};
174                };
175            }),
176            quote!(#crate_::AllowPin),
177            quote!(new_from_offset_pinned),
178        )
179    };
180
181    let pinned_drop_impl = if drop {
182        Some(quote!(
183            impl Drop for #struct_name {
184                fn drop(&mut self) {
185                    use #crate_::PinnedDrop;
186                    self.do_safe_pinned_drop();
187                }
188            }
189        ))
190    } else {
191        None
192    };
193
194    // Build the output, possibly using quasi-quotation
195    let expanded = quote! {
196        #[doc = #doc]
197        #[allow(missing_docs, non_camel_case_types, dead_code)]
198        #struct_vis struct #field_struct_name;
199
200        #ensure_pin_safe
201
202        #[allow(non_snake_case, missing_docs)]
203        impl #field_struct_name {
204            #(
205                #vis const fn #fields(self) -> #crate_::FieldOffset<#struct_name, #types, #pin_flag> {
206                    // Safety: offset_of! returns the correct byte offset for this field
207                    unsafe { #crate_::FieldOffset::<#struct_name, #types, _>::#new_from_offset(
208                        ::core::mem::offset_of!(#struct_name, #fields)
209                    ) }
210                }
211            )*
212        }
213
214        impl #struct_name {
215            /// Return a zero-sized helper whose methods return field offsets.
216            pub const FIELD_OFFSETS : #field_struct_name = #field_struct_name;
217        }
218
219        #pinned_drop_impl
220        #ensure_no_unpin
221    };
222
223    // Hand the output tokens back to the compiler
224    TokenStream::from(expanded)
225}
226
227/**
228```compile_fail
229use const_field_offset::*;
230#[derive(FieldOffsets)]
231#[pin]
232struct Foo {
233    x: u32,
234}
235
236impl Drop for Foo {
237    fn drop(&mut self) {}
238}
239```
240*/
241#[cfg(doctest)]
242const _PIN_NO_DROP: u32 = 0;
243
244/**
245```compile_fail
246use const_field_offset::*;
247#[derive(FieldOffsets)]
248#[pin]
249struct Foo {
250    q: std::marker::PhantomPinned,
251    x: u32,
252}
253
254impl Unpin for Foo {}
255```
256*/
257#[cfg(doctest)]
258const _PIN_NO_UNPIN: u32 = 0;