| 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 | |