extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote, quote_spanned};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
#[cfg(feature = "field-offset-trait")]
use syn::{VisRestricted, Visibility};
#[cfg_attr(
feature = "field-offset-trait",
doc = "
In addition, the macro also create a module `{ClassName}_field_offsets` which contains
zero-sized type that implement the `const_field_offset::ConstFieldOffset` trait
```rust
use const_field_offset::{FieldOffsets, FieldOffset, ConstFieldOffset};
#[repr(C)]
#[derive(FieldOffsets)]
struct Foo {
field_1 : u8,
field_2 : u32,
}
const FOO : FieldOffset<Foo, u32> = Foo_field_offsets::field_2::OFFSET;
assert_eq!(FOO.get_byte_offset(), 4);
```
"
)]
#[proc_macro_derive(FieldOffsets, attributes(const_field_offset, pin, pin_drop))]
pub fn const_field_offset(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut has_repr_c = false;
let mut crate_ = quote!(const_field_offset);
let mut pin = false;
let mut drop = false;
for a in &input.attrs {
if let Some(i) = a.path().get_ident() {
if i == "repr" {
let inner = a.parse_args::<syn::Ident>().map(|x| x.to_string());
match inner.as_ref().map(|x| x.as_str()) {
Ok("C") => has_repr_c = true,
Ok("packed") => {
return TokenStream::from(quote!(
compile_error! {"FieldOffsets does not work on #[repr(packed)]"}
))
}
_ => (),
}
} else if i == "const_field_offset" {
match a.parse_args::<syn::Path>() {
Ok(c) => crate_ = quote!(#c),
Err(_) => {
return TokenStream::from(
quote_spanned!(a.span()=> compile_error!{"const_field_offset attribute must be a crate name"}),
);
}
}
} else if i == "pin" {
pin = true;
} else if i == "pin_drop" {
drop = true;
pin = true;
}
}
}
if !has_repr_c {
return TokenStream::from(
quote! {compile_error!{"FieldOffsets only work for structures using repr(C)"}},
);
}
let struct_name = input.ident;
let struct_vis = input.vis;
let field_struct_name = quote::format_ident!("{}FieldsOffsets", struct_name);
let (fields, types, vis) = if let syn::Data::Struct(s) = &input.data {
if let syn::Fields::Named(n) = &s.fields {
let (f, tv): (Vec<_>, Vec<_>) =
n.named.iter().map(|f| (&f.ident, (&f.ty, &f.vis))).unzip();
let (t, v): (Vec<_>, Vec<_>) = tv.into_iter().unzip();
(f, t, v)
} else {
return TokenStream::from(quote! {compile_error!{"Only work for named fields"}});
}
} else {
return TokenStream::from(quote! {compile_error!("Only work for struct")});
};
let doc = format!(
"Helper struct containing the offsets of the fields of the struct [`{}`]\n\n\
Generated from the `#[derive(FieldOffsets)]` macro from the [`const-field-offset`]({}) crate",
struct_name, crate_
);
let (ensure_pin_safe, ensure_no_unpin, pin_flag, new_from_offset) = if !pin {
(None, None, quote!(#crate_::NotPinned), quote!(new_from_offset))
} else {
(
if drop {
None
} else {
let drop_trait_ident = format_ident!("{}MustNotImplDrop", struct_name);
Some(quote! {
#[allow(non_camel_case_types)]
trait #drop_trait_ident {}
impl<T: ::core::ops::Drop> #drop_trait_ident for T {}
impl #drop_trait_ident for #struct_name {}
})
},
Some(quote! {
const _ : () = {
#[allow(dead_code)]
struct __MustNotImplUnpin<'__dummy_lifetime> (
::core::marker::PhantomData<&'__dummy_lifetime ()>
);
impl<'__dummy_lifetime> Unpin for #struct_name where __MustNotImplUnpin<'__dummy_lifetime> : Unpin {};
};
}),
quote!(#crate_::AllowPin),
quote!(new_from_offset_pinned),
)
};
let pinned_drop_impl = if drop {
Some(quote!(
impl Drop for #struct_name {
fn drop(&mut self) {
use #crate_::PinnedDrop;
self.do_safe_pinned_drop();
}
}
))
} else {
None
};
let expanded = quote! {
#[doc = #doc]
#[allow(missing_docs, non_camel_case_types, dead_code)]
#struct_vis struct #field_struct_name {
#(#vis #fields : #crate_::FieldOffset<#struct_name, #types, #pin_flag>,)*
}
#[allow(clippy::eval_order_dependence)] impl #struct_name {
pub const FIELD_OFFSETS : #field_struct_name = {
#ensure_pin_safe;
let mut len = 0usize;
#field_struct_name {
#( #fields : {
let align = ::core::mem::align_of::<#types>();
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
len = len_rounded_up + ::core::mem::size_of::<#types>();
unsafe { #crate_::FieldOffset::<#struct_name, #types, _>::#new_from_offset(len_rounded_up) }
}, )*
}
};
}
#pinned_drop_impl
#ensure_no_unpin
};
#[cfg(feature = "field-offset-trait")]
let module_name = quote::format_ident!("{}_field_offsets", struct_name);
#[cfg(feature = "field-offset-trait")]
let in_mod_vis = vis.iter().map(|vis| min_vis(vis, &struct_vis)).map(|vis| match vis {
Visibility::Public(_) => quote! {#vis},
Visibility::Restricted(VisRestricted { pub_token, path, .. }) => {
if quote!(#path).to_string().starts_with("super") {
quote!(#pub_token(in super::#path))
} else {
quote!(#vis)
}
}
Visibility::Inherited => quote!(pub(super)),
});
#[cfg(feature = "field-offset-trait")]
let expanded = quote! { #expanded
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(missing_docs)]
#struct_vis mod #module_name {
#(
#[derive(Clone, Copy, Default)]
#in_mod_vis struct #fields;
)*
}
#(
impl #crate_::ConstFieldOffset for #module_name::#fields {
type Container = #struct_name;
type Field = #types;
type PinFlag = #pin_flag;
const OFFSET : #crate_::FieldOffset<#struct_name, #types, Self::PinFlag>
= #struct_name::FIELD_OFFSETS.#fields;
}
impl ::core::convert::Into<#crate_::FieldOffset<#struct_name, #types, #pin_flag>> for #module_name::#fields {
fn into(self) -> #crate_::FieldOffset<#struct_name, #types, #pin_flag> {
#struct_name::FIELD_OFFSETS.#fields
}
}
impl<Other> ::core::ops::Add<Other> for #module_name::#fields
where Other : #crate_::ConstFieldOffset<Container = #types>
{
type Output = #crate_::ConstFieldOffsetSum<Self, Other>;
fn add(self, other: Other) -> Self::Output {
#crate_::ConstFieldOffsetSum(self, other)
}
}
)*
};
TokenStream::from(expanded)
}
#[cfg(feature = "field-offset-trait")]
fn min_vis<'a>(a: &'a Visibility, b: &'a Visibility) -> &'a Visibility {
match (a, b) {
(Visibility::Public(_), _) => b,
(_, Visibility::Public(_)) => a,
(Visibility::Inherited, _) => a,
(_, Visibility::Inherited) => b,
_ => a,
}
}
#[cfg(doctest)]
const _NO_REPR_C: u32 = 0;
#[cfg(doctest)]
const _REPR_PACKED: u32 = 0;
#[cfg(doctest)]
const _PIN_NO_DROP: u32 = 0;
#[cfg(doctest)]
const _PIN_NO_UNPIN: u32 = 0;