1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT OR Apache-2.0 |
3 | |
4 | /*! |
5 | This crate allow to get the offset of a field of a structure in a const or static context. |
6 | |
7 | To be used re-exported from the `const_field_offset` crate |
8 | |
9 | */ |
10 | extern crate proc_macro; |
11 | |
12 | use proc_macro::TokenStream; |
13 | use quote::{format_ident, quote, quote_spanned}; |
14 | use syn::{parse_macro_input, spanned::Spanned, DeriveInput}; |
15 | #[cfg (feature = "field-offset-trait" )] |
16 | use syn::{VisRestricted, Visibility}; |
17 | |
18 | /** |
19 | |
20 | The macro FieldOffsets adds a `FIELD_OFFSETS` associated const to the struct. That |
21 | is an object which has fields with the same name as the fields of the original struct, |
22 | each field is of type `const_field_offset::FieldOffset` |
23 | |
24 | ```rust |
25 | use const_field_offset::FieldOffsets; |
26 | #[repr(C)] |
27 | #[derive(FieldOffsets)] |
28 | struct Foo { |
29 | field_1 : u8, |
30 | field_2 : u32, |
31 | } |
32 | |
33 | const FOO : usize = Foo::FIELD_OFFSETS.field_2.get_byte_offset(); |
34 | assert_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 = " |
44 | In addition, the macro also create a module `{ClassName}_field_offsets` which contains |
45 | zero-sized type that implement the `const_field_offset::ConstFieldOffset` trait |
46 | |
47 | ```rust |
48 | use const_field_offset::{FieldOffsets, FieldOffset, ConstFieldOffset}; |
49 | #[repr(C)] |
50 | #[derive(FieldOffsets)] |
51 | struct Foo { |
52 | field_1 : u8, |
53 | field_2 : u32, |
54 | } |
55 | |
56 | const FOO : FieldOffset<Foo, u32> = Foo_field_offsets::field_2::OFFSET; |
57 | assert_eq!(FOO.get_byte_offset(), 4); |
58 | ``` |
59 | " |
60 | )] |
61 | /** |
62 | |
63 | ## Limitations |
64 | |
65 | Only work with named #[repr(C)] structures. |
66 | |
67 | ## Attributes |
68 | |
69 | ### `pin` |
70 | |
71 | Add a `AllowPin` to the FieldOffset. |
72 | |
73 | In order for this to be safe, the macro will add code to prevent a |
74 | custom `Drop` or `Unpin` implementation. |
75 | |
76 | ```rust |
77 | use const_field_offset::*; |
78 | #[repr(C)] |
79 | #[derive(FieldOffsets)] |
80 | #[pin] |
81 | struct Foo { |
82 | field_1 : u8, |
83 | field_2 : u32, |
84 | } |
85 | |
86 | const FIELD_2 : FieldOffset<Foo, u32, AllowPin> = Foo::FIELD_OFFSETS.field_2; |
87 | let pin_box = Box::pin(Foo{field_1: 1, field_2: 2}); |
88 | assert_eq!(*FIELD_2.apply_pin(pin_box.as_ref()), 2); |
89 | ``` |
90 | |
91 | ### `pin_drop` |
92 | |
93 | This attribute works like the `pin` attribute but it does not prevent a custom |
94 | Drop implementation. Instead it provides a Drop implementation that forwards to |
95 | the [PinnedDrop](../const_field_offset/trait.PinnedDrop.html) trait that you need to implement for our type. |
96 | |
97 | ```rust |
98 | use const_field_offset::*; |
99 | use core::pin::Pin; |
100 | |
101 | struct TypeThatRequiresSpecialDropHandling(); // ... |
102 | |
103 | #[repr(C)] |
104 | #[derive(FieldOffsets)] |
105 | #[pin_drop] |
106 | struct Foo { |
107 | field : TypeThatRequiresSpecialDropHandling, |
108 | } |
109 | |
110 | impl 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 | |
119 | In case the `const-field-offset` crate is re-exported, it is possible to |
120 | specify 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 |
124 | mod xxx { pub use const_field_offset as cfo; } |
125 | #[repr(C)] |
126 | #[derive(xxx::cfo::FieldOffsets)] |
127 | #[const_field_offset(xxx::cfo)] |
128 | struct 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))] |
136 | pub 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 [` {struct_name}`] \n\n\ |
198 | Generated from the `#[derive(FieldOffsets)]` macro from the [`const-field-offset`]( {crate_}) crate" , |
199 | ); |
200 | |
201 | let (ensure_pin_safe, ensure_no_unpin, pin_flag, new_from_offset) = if !pin { |
202 | (None, None, quote!(#crate_::NotPinned), quote!(new_from_offset)) |
203 | } else { |
204 | ( |
205 | if drop { |
206 | None |
207 | } else { |
208 | let drop_trait_ident = format_ident!(" {}MustNotImplDrop" , struct_name); |
209 | Some(quote! { |
210 | /// Make sure that Drop is not implemented |
211 | #[allow(non_camel_case_types)] |
212 | trait #drop_trait_ident {} |
213 | impl<T: ::core::ops::Drop> #drop_trait_ident for T {} |
214 | impl #drop_trait_ident for #struct_name {} |
215 | |
216 | }) |
217 | }, |
218 | Some(quote! { |
219 | const _ : () = { |
220 | /// Make sure that Unpin is not implemented |
221 | #[allow(dead_code)] |
222 | struct __MustNotImplUnpin<'__dummy_lifetime> ( |
223 | ::core::marker::PhantomData<&'__dummy_lifetime ()> |
224 | ); |
225 | impl<'__dummy_lifetime> Unpin for #struct_name where __MustNotImplUnpin<'__dummy_lifetime> : Unpin {}; |
226 | }; |
227 | }), |
228 | quote!(#crate_::AllowPin), |
229 | quote!(new_from_offset_pinned), |
230 | ) |
231 | }; |
232 | |
233 | let pinned_drop_impl = if drop { |
234 | Some(quote!( |
235 | impl Drop for #struct_name { |
236 | fn drop(&mut self) { |
237 | use #crate_::PinnedDrop; |
238 | self.do_safe_pinned_drop(); |
239 | } |
240 | } |
241 | )) |
242 | } else { |
243 | None |
244 | }; |
245 | |
246 | // Build the output, possibly using quasi-quotation |
247 | let expanded = quote! { |
248 | #[doc = #doc] |
249 | #[allow(missing_docs, non_camel_case_types, dead_code)] |
250 | #struct_vis struct #field_struct_name { |
251 | #(#vis #fields : #crate_::FieldOffset<#struct_name, #types, #pin_flag>,)* |
252 | } |
253 | |
254 | #[allow(clippy::eval_order_dependence)] // The point of this code is to depend on the order! |
255 | impl #struct_name { |
256 | /// Return a struct containing the offset of for the fields of this struct |
257 | pub const FIELD_OFFSETS : #field_struct_name = { |
258 | #ensure_pin_safe; |
259 | let mut len = 0usize; |
260 | #field_struct_name { |
261 | #( #fields : { |
262 | let align = ::core::mem::align_of::<#types>(); |
263 | // from Layout::padding_needed_for which is not yet stable |
264 | let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1); |
265 | len = len_rounded_up + ::core::mem::size_of::<#types>(); |
266 | /// Safety: According to the rules of repr(C), this is the right offset |
267 | unsafe { #crate_::FieldOffset::<#struct_name, #types, _>::#new_from_offset(len_rounded_up) } |
268 | }, )* |
269 | } |
270 | }; |
271 | } |
272 | |
273 | #pinned_drop_impl |
274 | #ensure_no_unpin |
275 | }; |
276 | |
277 | #[cfg (feature = "field-offset-trait" )] |
278 | let module_name = quote::format_ident!(" {}_field_offsets" , struct_name); |
279 | |
280 | #[cfg (feature = "field-offset-trait" )] |
281 | let in_mod_vis = vis.iter().map(|vis| min_vis(vis, &struct_vis)).map(|vis| match vis { |
282 | Visibility::Public(_) => quote! {#vis}, |
283 | Visibility::Restricted(VisRestricted { pub_token, path, .. }) => { |
284 | if quote!(#path).to_string().starts_with("super" ) { |
285 | quote!(#pub_token(in super::#path)) |
286 | } else { |
287 | quote!(#vis) |
288 | } |
289 | } |
290 | Visibility::Inherited => quote!(pub(super)), |
291 | }); |
292 | |
293 | #[cfg (feature = "field-offset-trait" )] |
294 | let expanded = quote! { #expanded |
295 | #[allow(non_camel_case_types)] |
296 | #[allow(non_snake_case)] |
297 | #[allow(missing_docs)] |
298 | #struct_vis mod #module_name { |
299 | #( |
300 | #[derive(Clone, Copy, Default)] |
301 | #in_mod_vis struct #fields; |
302 | )* |
303 | } |
304 | #( |
305 | impl #crate_::ConstFieldOffset for #module_name::#fields { |
306 | type Container = #struct_name; |
307 | type Field = #types; |
308 | type PinFlag = #pin_flag; |
309 | const OFFSET : #crate_::FieldOffset<#struct_name, #types, Self::PinFlag> |
310 | = #struct_name::FIELD_OFFSETS.#fields; |
311 | } |
312 | impl ::core::convert::Into<#crate_::FieldOffset<#struct_name, #types, #pin_flag>> for #module_name::#fields { |
313 | fn into(self) -> #crate_::FieldOffset<#struct_name, #types, #pin_flag> { |
314 | #struct_name::FIELD_OFFSETS.#fields |
315 | } |
316 | } |
317 | impl<Other> ::core::ops::Add<Other> for #module_name::#fields |
318 | where Other : #crate_::ConstFieldOffset<Container = #types> |
319 | { |
320 | type Output = #crate_::ConstFieldOffsetSum<Self, Other>; |
321 | fn add(self, other: Other) -> Self::Output { |
322 | #crate_::ConstFieldOffsetSum(self, other) |
323 | } |
324 | } |
325 | )* |
326 | }; |
327 | |
328 | // Hand the output tokens back to the compiler |
329 | TokenStream::from(expanded) |
330 | } |
331 | |
332 | #[cfg (feature = "field-offset-trait" )] |
333 | /// Returns the most restricted visibility |
334 | fn min_vis<'a>(a: &'a Visibility, b: &'a Visibility) -> &'a Visibility { |
335 | match (a, b) { |
336 | (Visibility::Public(_), _) => b, |
337 | (_, Visibility::Public(_)) => a, |
338 | (Visibility::Inherited, _) => a, |
339 | (_, Visibility::Inherited) => b, |
340 | // FIXME: compare two paths |
341 | _ => a, |
342 | } |
343 | } |
344 | |
345 | /** |
346 | ```compile_fail |
347 | use const_field_offset::*; |
348 | #[derive(FieldOffsets)] |
349 | struct Foo { |
350 | x: u32, |
351 | } |
352 | ``` |
353 | */ |
354 | #[cfg (doctest)] |
355 | const _NO_REPR_C: u32 = 0; |
356 | |
357 | /** |
358 | ```compile_fail |
359 | use const_field_offset::*; |
360 | #[derive(FieldOffsets)] |
361 | #[repr(C)] |
362 | #[repr(packed)] |
363 | struct Foo { |
364 | x: u32, |
365 | } |
366 | ``` |
367 | */ |
368 | #[cfg (doctest)] |
369 | const _REPR_PACKED: u32 = 0; |
370 | |
371 | /** |
372 | ```compile_fail |
373 | use const_field_offset::*; |
374 | #[derive(FieldOffsets)] |
375 | #[repr(C)] |
376 | #[pin] |
377 | struct Foo { |
378 | x: u32, |
379 | } |
380 | |
381 | impl Drop for Foo { |
382 | fn drop(&mut self) {} |
383 | } |
384 | ``` |
385 | */ |
386 | #[cfg (doctest)] |
387 | const _PIN_NO_DROP: u32 = 0; |
388 | |
389 | /** |
390 | ```compile_fail |
391 | use const_field_offset::*; |
392 | #[derive(FieldOffsets)] |
393 | #[repr(C)] |
394 | #[pin] |
395 | struct Foo { |
396 | q: std::marker::PhantomPinned, |
397 | x: u32, |
398 | } |
399 | |
400 | impl Unpin for Foo {} |
401 | ``` |
402 | */ |
403 | #[cfg (doctest)] |
404 | const _PIN_NO_UNPIN: u32 = 0; |
405 | |