use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse2, Attribute, Fields, ItemStruct, Result};
pub fn expand(_attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
let input: ItemStruct = parse2(item)?;
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
if !has_repr_c_or_transparent(&input.attrs) {
return Err(syn::Error::new_spanned(
&input,
"#[hopper::pod] requires #[repr(C)] or #[repr(transparent)] so the \
zero-copy overlay has a stable layout",
));
}
let field_types: Vec<_> = match &input.fields {
Fields::Named(f) => f.named.iter().map(|f| f.ty.clone()).collect(),
Fields::Unnamed(f) => f.unnamed.iter().map(|f| f.ty.clone()).collect(),
Fields::Unit => Vec::new(),
};
let sum_sizes = if field_types.is_empty() {
quote! { 0usize }
} else {
let pieces = field_types.iter().map(|ty| {
quote! { ::core::mem::size_of::<#ty>() }
});
quote! { #(#pieces)+* }
};
let struct_name_str = name.to_string();
let size_msg = format!(
"#[hopper::pod] struct `{}` has implicit padding; sum of field \
sizes must equal size_of::<Self>(). Add explicit padding fields \
or reorder for alignment-1 layout.",
struct_name_str,
);
let align_msg = format!(
"#[hopper::pod] struct `{}` must be alignment-1 (use Hopper wire \
types such as WireU64, WireI32, or TypedAddress<T> instead of \
raw u64/i32/Pubkey).",
struct_name_str,
);
let nonzero_msg = format!(
"#[hopper::pod] struct `{}` has zero size; zero-sized overlays \
project to dangling pointers and are rejected.",
struct_name_str,
);
let expanded = quote! {
#input
#[doc(hidden)]
const _: () = {
struct __FieldPodProof<
T: ::hopper::__runtime::__hopper_native::bytemuck::Pod
+ ::hopper::__runtime::__hopper_native::bytemuck::Zeroable,
>(::core::marker::PhantomData<T>);
#(
#[allow(dead_code)]
const _: __FieldPodProof<#field_types> =
__FieldPodProof(::core::marker::PhantomData);
)*
};
unsafe impl #impl_generics ::hopper::__runtime::__hopper_native::bytemuck::Zeroable
for #name #ty_generics #where_clause {}
unsafe impl #impl_generics ::hopper::__runtime::__hopper_native::bytemuck::Pod
for #name #ty_generics #where_clause {}
unsafe impl #impl_generics ::hopper::__runtime::Pod for #name #ty_generics #where_clause {}
unsafe impl #impl_generics ::hopper::__runtime::__sealed::HopperZeroCopySealed
for #name #ty_generics #where_clause {}
impl #impl_generics ::hopper::hopper_core::account::FixedLayout
for #name #ty_generics #where_clause
{
const SIZE: usize = ::core::mem::size_of::<Self>();
}
impl #impl_generics #name #ty_generics #where_clause {
pub const INIT_SPACE: usize = ::core::mem::size_of::<Self>();
}
const _: () = {
assert!(
::core::mem::align_of::<#name #ty_generics>() == 1,
#align_msg,
);
assert!(
::core::mem::size_of::<#name #ty_generics>() == (#sum_sizes),
#size_msg,
);
assert!(
::core::mem::size_of::<#name #ty_generics>() > 0,
#nonzero_msg,
);
};
};
Ok(expanded)
}
fn has_repr_c_or_transparent(attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| {
if !attr.path().is_ident("repr") {
return false;
}
let tokens = attr.meta.to_token_stream().to_string();
tokens.contains("C") || tokens.contains("transparent")
})
}