use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
Attribute, Data, DataEnum, DeriveInput, Field, Fields, FieldsNamed, Ident, Type, TypePath,
};
lazy_static::lazy_static! {
pub(crate) static ref COPY_IMPL_CACHE: Arc<Mutex<HashMap<String, bool>>> = Arc::new(Mutex::new(HashMap::new()));
}
fn create_unique_type_key(ident: &Ident) -> String {
format!("{}:{:?}", ident, ident.span())
}
pub enum InputType<'a> {
Struct(&'a FieldsNamed),
UnitStruct, Enum(&'a DataEnum),
}
pub fn process_input(
input: &DeriveInput,
) -> syn::Result<(
&Ident, // Original struct name
proc_macro2::Ident, // Z-struct name
proc_macro2::Ident, // Z-struct meta name
Option<&FieldsNamed>, // Struct fields (None for unit structs)
)> {
let name = &input.ident;
let z_struct_name = format_ident!("Z{}", name);
let z_struct_meta_name = format_ident!("Z{}Meta", name);
let _ = struct_implements_copy(input);
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => Some(fields),
Fields::Unit => None, _ => {
return Err(syn::Error::new_spanned(
&data.fields,
"ZeroCopy only supports structs with named fields or unit structs",
))
}
},
_ => {
return Err(syn::Error::new_spanned(
input,
"ZeroCopy only supports structs",
))
}
};
Ok((name, z_struct_name, z_struct_meta_name, fields))
}
pub fn process_input_generic(
input: &DeriveInput,
) -> syn::Result<(
&Ident, // Original name
proc_macro2::Ident, // Z-name
InputType<'_>, // Input type (struct or enum)
)> {
let name = &input.ident;
let z_name = format_ident!("Z{}", name);
let _ = struct_implements_copy(input);
let input_type = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => InputType::Struct(fields),
Fields::Unit => InputType::UnitStruct, _ => {
return Err(syn::Error::new_spanned(
&data.fields,
"ZeroCopy only supports structs with named fields or unit structs",
))
}
},
Data::Enum(data) => InputType::Enum(data),
_ => {
return Err(syn::Error::new_spanned(
input,
"ZeroCopy only supports structs and enums",
))
}
};
Ok((name, z_name, input_type))
}
pub fn process_fields(fields: &FieldsNamed) -> (Vec<&Field>, Vec<&Field>) {
let mut meta_fields = Vec::new();
let mut struct_fields = Vec::new();
let mut reached_vec_or_option = false;
for field in fields.named.iter() {
if !reached_vec_or_option {
if is_vec_or_option(&field.ty) || !is_copy_type(&field.ty) {
reached_vec_or_option = true;
struct_fields.push(field);
} else {
meta_fields.push(field);
}
} else {
struct_fields.push(field);
}
}
(meta_fields, struct_fields)
}
pub fn is_vec_or_option(ty: &Type) -> bool {
is_vec_type(ty) || is_option_type(ty)
}
pub fn is_vec_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
return segment.ident == "Vec";
}
}
false
}
pub fn is_option_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
return segment.ident == "Option";
}
}
false
}
pub fn get_vec_inner_type(ty: &Type) -> Option<&Type> {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
if segment.ident == "Vec" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return Some(inner_ty);
}
}
}
}
}
None
}
pub fn get_option_inner_type(ty: &Type) -> Option<&Type> {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return Some(inner_ty);
}
}
}
}
}
None
}
pub fn is_primitive_integer(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
let ident = &segment.ident;
return ident == "u16"
|| ident == "u32"
|| ident == "u64"
|| ident == "i16"
|| ident == "i32"
|| ident == "i64"
|| ident == "u8"
|| ident == "i8";
}
}
false
}
pub fn is_bool_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
return segment.ident == "bool";
}
}
false
}
pub fn is_specific_primitive_type(ty: &Type, type_name: &str) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
return segment.ident == type_name;
}
}
false
}
pub fn is_pubkey_type(ty: &Type) -> bool {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(segment) = path.segments.last() {
return segment.ident == "Pubkey";
}
}
false
}
pub fn convert_to_zerocopy_type(ty: &Type) -> TokenStream {
match ty {
Type::Path(TypePath { path, .. }) => {
if let Some(segment) = path.segments.last() {
let ident = &segment.ident;
match ident.to_string().as_str() {
"u16" => quote! { ::light_zero_copy::little_endian::U16 },
"u32" => quote! { ::light_zero_copy::little_endian::U32 },
"u64" => quote! { ::light_zero_copy::little_endian::U64 },
"i16" => quote! { ::light_zero_copy::little_endian::I16 },
"i32" => quote! { ::light_zero_copy::little_endian::I32 },
"i64" => quote! { ::light_zero_copy::little_endian::I64 },
"bool" => quote! { u8 },
_ => {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
let transformed_args: Vec<TokenStream> = args
.args
.iter()
.map(|arg| {
if let syn::GenericArgument::Type(inner_type) = arg {
convert_to_zerocopy_type(inner_type)
} else {
quote! { #arg }
}
})
.collect();
quote! { #ident<#(#transformed_args),*> }
} else {
quote! { #ty }
}
}
}
} else {
quote! { #ty }
}
}
Type::Array(array) => {
let elem = convert_to_zerocopy_type(&array.elem);
let len = &array.len;
quote! { [#elem; #len] }
}
_ => {
quote! { #ty }
}
}
}
fn struct_has_copy_derive(attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| {
attr.path().is_ident("derive") && {
let mut found_copy = false;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("Copy") {
found_copy = true;
}
Ok(()) })
.is_ok()
&& found_copy
}
})
}
pub fn struct_has_light_hasher_attribute(attrs: &[Attribute]) -> bool {
attrs
.iter()
.any(|attr| attr.path().is_ident("light_hasher"))
}
pub fn struct_implements_copy(input: &DeriveInput) -> bool {
let cache_key = create_unique_type_key(&input.ident);
if let Ok(cache) = COPY_IMPL_CACHE.lock() {
if let Some(implements_copy) = cache.get(&cache_key) {
return *implements_copy;
}
}
let implements_copy = struct_has_copy_derive(&input.attrs);
if let Ok(mut cache) = COPY_IMPL_CACHE.lock() {
cache.insert(cache_key, implements_copy);
}
implements_copy
}
pub fn is_copy_type(ty: &Type) -> bool {
match ty {
Type::Path(TypePath { path, .. }) => {
if let Some(segment) = path.segments.last() {
let ident = &segment.ident;
if ident == "u8"
|| ident == "u16"
|| ident == "u32"
|| ident == "u64"
|| ident == "i8"
|| ident == "i16"
|| ident == "i32"
|| ident == "i64"
|| ident == "bool" || ident == "char"
|| ident == "Pubkey"
{
return true;
}
let cache_key = create_unique_type_key(ident);
if let Ok(cache) = COPY_IMPL_CACHE.lock() {
if let Some(implements_copy) = cache.get(&cache_key) {
return *implements_copy;
}
}
}
}
Type::Array(array) => {
return is_copy_type(&array.elem);
}
_ => {}
}
false
}
pub fn needs_struct_inner_trait(ty: &Type) -> bool {
if matches!(ty, Type::Array(_)) {
return false;
}
if is_primitive_integer(ty) || is_bool_type(ty) || is_pubkey_type(ty) {
return false;
}
true
}
pub fn has_repr_c_attribute(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("repr") {
let tokens = attr.meta.clone();
if let syn::Meta::List(list) = tokens {
let tokens_str = list.tokens.to_string();
for part in tokens_str.split(',') {
let trimmed = part.trim();
if trimmed == "C" {
return true;
}
}
} else if let syn::Meta::Path(path) = tokens {
return path.is_ident("C");
}
false
} else {
false
}
})
}
pub fn validate_repr_c_required(attrs: &[syn::Attribute], item_type: &str) -> syn::Result<()> {
if !has_repr_c_attribute(attrs) {
return Err(syn::Error::new_spanned(
attrs.first().unwrap_or(&syn::parse_quote!(#[dummy])),
format!(
"{} requires #[repr(C)] attribute for memory layout safety. Add #[repr(C)] above the {} declaration.",
item_type, item_type.to_lowercase()
)
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use quote::quote;
use super::has_repr_c_attribute;
#[test]
fn test_repr_c_detection() {
let input = quote! {
#[repr(C)]
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
has_repr_c_attribute(&parsed.attrs),
"Should detect #[repr(C)]"
);
let input = quote! {
#[repr(C, packed)]
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
has_repr_c_attribute(&parsed.attrs),
"Should detect C in #[repr(C, packed)]"
);
let input = quote! {
#[repr(C, align(8))]
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
has_repr_c_attribute(&parsed.attrs),
"Should detect C in #[repr(C, align(8))]"
);
let input = quote! {
#[repr(packed, C)]
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
has_repr_c_attribute(&parsed.attrs),
"Should detect C in #[repr(packed, C)]"
);
let input = quote! {
#[repr(packed)]
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
!has_repr_c_attribute(&parsed.attrs),
"Should not detect C in #[repr(packed)]"
);
let input = quote! {
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
!has_repr_c_attribute(&parsed.attrs),
"Should not detect C without repr"
);
let input = quote! {
#[repr(Rust)]
struct Test {}
};
let parsed: syn::DeriveInput = syn::parse2(input).unwrap();
assert!(
!has_repr_c_attribute(&parsed.attrs),
"Should not detect C in #[repr(Rust)]"
);
}
}