1use std::num::NonZeroU32;
12
13use proc_macro2::{Span, TokenStream};
14use quote::{quote, quote_spanned, ToTokens};
15use syn::{
16 parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
17 Expr, ExprLit, Field, GenericParam, Ident, Index, Lit, LitStr, Meta, Path, Type, Variant,
18 Visibility, WherePredicate,
19};
20
21use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned};
22
23pub(crate) struct Ctx {
24 pub(crate) ast: DeriveInput,
25 pub(crate) zerocopy_crate: Path,
26
27 pub(crate) skip_on_error: bool,
30
31 pub(crate) on_error_span: Option<proc_macro2::Span>,
33}
34
35impl Ctx {
36 pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {
39 let mut path = parse_quote!(::zerocopy);
40 let mut skip_on_error = false;
41 let mut on_error_span = None;
42
43 for attr in &ast.attrs {
44 if let Meta::List(ref meta_list) = attr.meta {
45 if meta_list.path.is_ident("zerocopy") {
46 attr.parse_nested_meta(|meta| {
47 if meta.path.is_ident("crate") {
48 let expr = meta.value().and_then(|value| value.parse());
49 if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {
50 if let Ok(path_lit) = lit.parse::<Ident>() {
51 path = parse_quote!(::#path_lit);
52 return Ok(());
53 }
54 }
55
56 return Err(Error::new(
57 Span::call_site(),
58 "`crate` attribute requires a path as the value",
59 ));
60 }
61
62 if meta.path.is_ident("on_error") {
63 on_error_span = Some(meta.path.span());
64 let value = meta.value()?;
65 let s: LitStr = value.parse()?;
66 match s.value().as_str() {
67 "skip" => skip_on_error = true,
68 "fail" => skip_on_error = false,
69 _ => return Err(Error::new(
70 s.span(),
71 "unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",
72 )),
73 }
74 return Ok(());
75 }
76
77 Err(Error::new(
78 Span::call_site(),
79 format!(
80 "unknown attribute encountered: {}",
81 meta.path.into_token_stream()
82 ),
83 ))
84 })?;
85 }
86 }
87 }
88
89 Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span })
90 }
91
92 pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {
93 Self {
94 ast: input.clone(),
95 zerocopy_crate: self.zerocopy_crate.clone(),
96 skip_on_error: self.skip_on_error,
97 on_error_span: self.on_error_span,
98 }
99 }
100
101 pub(crate) fn skip_on_error(mut self) -> Self {
102 self.skip_on_error = true;
103 self
104 }
105
106 pub(crate) fn core_path(&self) -> TokenStream {
107 let zerocopy_crate = &self.zerocopy_crate;
108 quote!(#zerocopy_crate::util::macro_util::core_reexport)
109 }
110
111 pub(crate) fn cfg_compile_error(&self) -> TokenStream {
112 if cfg!(zerocopy_unstable_linux) {
120 quote!()
121 } else if let Some(span) = self.on_error_span {
122 let core = self.core_path();
123 let error_message =
124 "`on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable";
125 quote::quote_spanned! {span=>
126 #[allow(unused_attributes, unexpected_cfgs)]
127 const _: () = {
128 #[cfg(not(zerocopy_unstable_linux))]
129 #core::compile_error!(#error_message);
130 };
131 }
132 } else {
133 quote!()
134 }
135 }
136
137 pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {
138 if self.skip_on_error {
139 Ok(self.cfg_compile_error())
140 } else {
141 Err(error)
142 }
143 }
144}
145
146pub(crate) trait DataExt {
147 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
156
157 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;
158
159 fn tag(&self) -> Option<Ident>;
160}
161
162impl DataExt for Data {
163 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
164 match self {
165 Data::Struct(strc) => strc.fields(),
166 Data::Enum(enm) => enm.fields(),
167 Data::Union(un) => un.fields(),
168 }
169 }
170
171 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
172 match self {
173 Data::Struct(strc) => strc.variants(),
174 Data::Enum(enm) => enm.variants(),
175 Data::Union(un) => un.variants(),
176 }
177 }
178
179 fn tag(&self) -> Option<Ident> {
180 match self {
181 Data::Struct(strc) => strc.tag(),
182 Data::Enum(enm) => enm.tag(),
183 Data::Union(un) => un.tag(),
184 }
185 }
186}
187
188impl DataExt for DataStruct {
189 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
190 map_fields(&self.fields)
191 }
192
193 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
194 vec![(None, self.fields())]
195 }
196
197 fn tag(&self) -> Option<Ident> {
198 None
199 }
200}
201
202impl DataExt for DataEnum {
203 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
204 map_fields(self.variants.iter().flat_map(|var| &var.fields))
205 }
206
207 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
208 self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
209 }
210
211 fn tag(&self) -> Option<Ident> {
212 Some(Ident::new("___ZerocopyTag", Span::call_site()))
213 }
214}
215
216impl DataExt for DataUnion {
217 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
218 map_fields(&self.fields.named)
219 }
220
221 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
222 vec![(None, self.fields())]
223 }
224
225 fn tag(&self) -> Option<Ident> {
226 None
227 }
228}
229
230fn map_fields<'a>(
231 fields: impl 'a + IntoIterator<Item = &'a Field>,
232) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
233 fields
234 .into_iter()
235 .enumerate()
236 .map(|(idx, f)| {
237 (
238 &f.vis,
239 f.ident
240 .as_ref()
241 .map(ToTokens::to_token_stream)
242 .unwrap_or_else(|| Index::from(idx).to_token_stream()),
243 &f.ty,
244 )
245 })
246 .collect()
247}
248
249pub(crate) fn to_ident_str(t: &impl ToString) -> String {
250 let s = t.to_string();
251 if let Some(stripped) = s.strip_prefix("r#") {
252 stripped.to_string()
253 } else {
254 s
255 }
256}
257
258pub(crate) enum PaddingCheck {
261 Struct,
264 ReprCStruct,
266 Union,
268 Enum { tag_type_definition: TokenStream },
273}
274
275impl PaddingCheck {
276 pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {
279 let (trt, mcro) = match self {
280 PaddingCheck::Struct => ("PaddingFree", "struct_padding"),
281 PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),
282 PaddingCheck::Union => ("PaddingFree", "union_padding"),
283 PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),
284 };
285
286 let trt = Ident::new(trt, Span::call_site());
287 let mcro = Ident::new(mcro, Span::call_site());
288 (trt, mcro)
289 }
290
291 pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {
294 match self {
295 PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,
296 PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
297 }
298 }
299}
300
301#[derive(Clone)]
302pub(crate) enum Trait {
303 KnownLayout,
304 HasTag,
305 HasField {
306 variant_id: Box<Expr>,
307 field: Box<Type>,
308 field_id: Box<Expr>,
309 },
310 ProjectField {
311 variant_id: Box<Expr>,
312 field: Box<Type>,
313 field_id: Box<Expr>,
314 invariants: Box<Type>,
315 },
316 Immutable,
317 TryFromBytes,
318 FromZeros,
319 FromBytes,
320 IntoBytes,
321 Unaligned,
322 Sized,
323 ByteHash,
324 ByteEq,
325 SplitAt,
326}
327
328impl ToTokens for Trait {
329 fn to_tokens(&self, tokens: &mut TokenStream) {
330 let s = match self {
340 Trait::HasField { .. } => "HasField",
341 Trait::ProjectField { .. } => "ProjectField",
342 Trait::KnownLayout => "KnownLayout",
343 Trait::HasTag => "HasTag",
344 Trait::Immutable => "Immutable",
345 Trait::TryFromBytes => "TryFromBytes",
346 Trait::FromZeros => "FromZeros",
347 Trait::FromBytes => "FromBytes",
348 Trait::IntoBytes => "IntoBytes",
349 Trait::Unaligned => "Unaligned",
350 Trait::Sized => "Sized",
351 Trait::ByteHash => "ByteHash",
352 Trait::ByteEq => "ByteEq",
353 Trait::SplitAt => "SplitAt",
354 };
355 let ident = Ident::new(s, Span::call_site());
356 let arguments: Option<syn::AngleBracketedGenericArguments> = match self {
357 Trait::HasField { variant_id, field, field_id } => {
358 Some(parse_quote!(<#field, #variant_id, #field_id>))
359 }
360 Trait::ProjectField { variant_id, field, field_id, invariants } => {
361 Some(parse_quote!(<#field, #invariants, #variant_id, #field_id>))
362 }
363 Trait::KnownLayout
364 | Trait::HasTag
365 | Trait::Immutable
366 | Trait::TryFromBytes
367 | Trait::FromZeros
368 | Trait::FromBytes
369 | Trait::IntoBytes
370 | Trait::Unaligned
371 | Trait::Sized
372 | Trait::ByteHash
373 | Trait::ByteEq
374 | Trait::SplitAt => None,
375 };
376 tokens.extend(quote!(#ident #arguments));
377 }
378}
379
380impl Trait {
381 pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
382 let zerocopy_crate = &ctx.zerocopy_crate;
383 let core = ctx.core_path();
384 match self {
385 Self::Sized => parse_quote!(#core::marker::#self),
386 _ => parse_quote!(#zerocopy_crate::#self),
387 }
388 }
389}
390
391pub(crate) enum TraitBound {
392 Slf,
393 Other(Trait),
394}
395
396pub(crate) enum FieldBounds<'a> {
397 None,
398 All(&'a [TraitBound]),
399 Trailing(&'a [TraitBound]),
400 Explicit(Vec<WherePredicate>),
401}
402
403impl<'a> FieldBounds<'a> {
404 pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
405 pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
406}
407
408pub(crate) enum SelfBounds<'a> {
409 None,
410 All(&'a [Trait]),
411}
412
413#[allow(clippy::needless_lifetimes)]
416impl<'a> SelfBounds<'a> {
417 pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
418}
419
420pub(crate) fn normalize_bounds<'a>(
422 slf: &'a Trait,
423 bounds: &'a [TraitBound],
424) -> impl 'a + Iterator<Item = Trait> {
425 bounds.iter().map(move |bound| match bound {
426 TraitBound::Slf => slf.clone(),
427 TraitBound::Other(trt) => trt.clone(),
428 })
429}
430
431pub(crate) struct ImplBlockBuilder<'a> {
432 ctx: &'a Ctx,
433 data: &'a dyn DataExt,
434 trt: Trait,
435 field_type_trait_bounds: FieldBounds<'a>,
436 self_type_trait_bounds: SelfBounds<'a>,
437 padding_check: Option<PaddingCheck>,
438 param_extras: Vec<GenericParam>,
439 inner_extras: Option<TokenStream>,
440 outer_extras: Option<TokenStream>,
441}
442
443impl<'a> ImplBlockBuilder<'a> {
444 pub(crate) fn new(
445 ctx: &'a Ctx,
446 data: &'a dyn DataExt,
447 trt: Trait,
448 field_type_trait_bounds: FieldBounds<'a>,
449 ) -> Self {
450 Self {
451 ctx,
452 data,
453 trt,
454 field_type_trait_bounds,
455 self_type_trait_bounds: SelfBounds::None,
456 padding_check: None,
457 param_extras: Vec::new(),
458 inner_extras: None,
459 outer_extras: None,
460 }
461 }
462
463 pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
464 self.self_type_trait_bounds = self_type_trait_bounds;
465 self
466 }
467
468 pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
469 self.padding_check = padding_check.into();
470 self
471 }
472
473 pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {
474 self.param_extras.extend(param_extras);
475 self
476 }
477
478 pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
479 self.inner_extras = Some(inner_extras);
480 self
481 }
482
483 pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
484 self.outer_extras = outer_extras.into();
485 self
486 }
487
488 pub(crate) fn build(self) -> TokenStream {
489 let type_ident = &self.ctx.ast.ident;
549 let trait_path = self.trt.crate_path(self.ctx);
550 let fields = self.data.fields();
551 let variants = self.data.variants();
552 let tag = self.data.tag();
553 let zerocopy_crate = &self.ctx.zerocopy_crate;
554
555 fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>, ctx: &Ctx) -> WherePredicate {
556 let traits = traits.map(|t| t.crate_path(ctx));
557 parse_quote!(#ty: #(#traits)+*)
558 }
559 let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) {
560 (FieldBounds::All(traits), _) => fields
561 .iter()
562 .map(|(_vis, _name, ty)| {
563 bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx)
564 })
565 .collect(),
566 (FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
567 (FieldBounds::Trailing(traits), [.., last]) => {
568 vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)]
569 }
570 (FieldBounds::Explicit(bounds), _) => bounds,
571 };
572
573 let padding_check_bound = self
574 .padding_check
575 .map(|check| {
576 let repr =
581 Repr::<PrimitiveRepr, NonZeroU32>::from_attrs(&self.ctx.ast.attrs).unwrap();
582 let core = self.ctx.core_path();
583 let option = quote! { #core::option::Option };
584 let nonzero = quote! { #core::num::NonZeroUsize };
585 let none = quote! { #option::None::<#nonzero> };
586 let repr_align =
587 repr.get_align().map(|spanned| {
588 let n = spanned.t.get();
589 quote_spanned! { spanned.span => (#nonzero::new(#n as usize)) }
590 }).unwrap_or(quote! { (#none) });
591 let repr_packed =
592 repr.get_packed().map(|packed| {
593 let n = packed.get();
594 quote! { (#nonzero::new(#n as usize)) }
595 }).unwrap_or(quote! { (#none) });
596 let variant_types = variants.iter().map(|(_, fields)| {
597 let types = fields.iter().map(|(_vis, _name, ty)| ty);
598 quote!([#((#types)),*])
599 });
600 let validator_context = check.validator_macro_context();
601 let (trt, validator_macro) = check.validator_trait_and_macro_idents();
602 let t = tag.iter();
603 parse_quote! {
604 (): #zerocopy_crate::util::macro_util::#trt<
605 Self,
606 {
607 #validator_context
608 #zerocopy_crate::#validator_macro!(Self, #repr_align, #repr_packed, #(#t,)* #(#variant_types),*)
609 }
610 >
611 }
612 });
613
614 let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
615 SelfBounds::None => None,
616 SelfBounds::All(traits) => {
617 Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx))
618 }
619 };
620
621 let zerocopy_bounds =
622 field_type_bounds
623 .into_iter()
624 .chain(padding_check_bound)
625 .chain(self_bounds)
626 .map(|bound| {
627 if self.ctx.skip_on_error {
628 parse_quote!(for<'zc> #bound)
629 } else {
630 bound.clone()
631 }
632 })
633 .collect::<Vec<_>>();
634
635 let bounds = self
636 .ctx
637 .ast
638 .generics
639 .where_clause
640 .as_ref()
641 .map(|where_clause| where_clause.predicates.iter())
642 .into_iter()
643 .flatten()
644 .chain(zerocopy_bounds.iter());
645
646 let mut params: Vec<_> = self
648 .ctx
649 .ast
650 .generics
651 .params
652 .clone()
653 .into_iter()
654 .map(|mut param| {
655 match &mut param {
656 GenericParam::Type(ty) => ty.default = None,
657 GenericParam::Const(cnst) => cnst.default = None,
658 GenericParam::Lifetime(_) => {}
659 }
660 parse_quote!(#param)
661 })
662 .chain(self.param_extras)
663 .collect();
664
665 params.sort_by_cached_key(|param| match param {
668 GenericParam::Lifetime(_) => 0,
669 GenericParam::Type(_) => 1,
670 GenericParam::Const(_) => 2,
671 });
672
673 let param_idents = self.ctx.ast.generics.params.iter().map(|param| match param {
676 GenericParam::Type(ty) => {
677 let ident = &ty.ident;
678 quote!(#ident)
679 }
680 GenericParam::Lifetime(l) => {
681 let ident = &l.lifetime;
682 quote!(#ident)
683 }
684 GenericParam::Const(cnst) => {
685 let ident = &cnst.ident;
686 quote!({#ident})
687 }
688 });
689
690 let inner_extras = self.inner_extras;
691 let allow_trivial_bounds =
692 if self.ctx.skip_on_error { quote!(#[allow(trivial_bounds)]) } else { quote!() };
693 let impl_tokens = quote! {
694 #allow_trivial_bounds
695 unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
696 where
697 #(#bounds,)*
698 {
699 fn only_derive_is_allowed_to_implement_this_trait() {}
700
701 #inner_extras
702 }
703 };
704
705 let outer_extras = self.outer_extras.filter(|e| !e.is_empty());
706 let cfg_compile_error = self.ctx.cfg_compile_error();
707 const_block([Some(cfg_compile_error), Some(impl_tokens), outer_extras])
708 }
709}
710
711#[allow(unused)]
719trait BoolExt {
720 fn then_some<T>(self, t: T) -> Option<T>;
721}
722
723impl BoolExt for bool {
724 fn then_some<T>(self, t: T) -> Option<T> {
725 if self {
726 Some(t)
727 } else {
728 None
729 }
730 }
731}
732
733pub(crate) fn const_block(items: impl IntoIterator<Item = Option<TokenStream>>) -> TokenStream {
734 let items = items.into_iter().flatten();
735 quote! {
736 #[allow(
737 deprecated,
740 private_bounds,
744 non_local_definitions,
745 non_camel_case_types,
746 non_upper_case_globals,
747 non_snake_case,
748 non_ascii_idents,
749 clippy::missing_inline_in_public_items,
750 )]
751 #[deny(ambiguous_associated_items)]
752 #[automatically_derived]
755 const _: () = {
756 #(#items)*
757 };
758 }
759}
760pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream {
761 let zerocopy_crate = &ctx.zerocopy_crate;
762 let variants = data.variants.iter().map(|v| {
763 let ident = &v.ident;
764 if let Some((eq, discriminant)) = &v.discriminant {
765 quote! { #ident #eq #discriminant }
766 } else {
767 quote! { #ident }
768 }
769 });
770
771 let repr = match repr {
775 EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
776 EnumRepr::Compound(c, _) => quote! { #c },
777 };
778
779 quote! {
780 #repr
781 #[allow(dead_code)]
782 pub enum ___ZerocopyTag {
783 #(#variants,)*
784 }
785
786 unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag {
789 fn only_derive_is_allowed_to_implement_this_trait() {}
790 }
791 }
792}
793pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
794 use CompoundRepr::*;
795 use PrimitiveRepr::*;
796 use Repr::*;
797 match repr {
798 Transparent(span)
799 | Compound(
800 Spanned {
801 t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | Usize | Isize),
802 span,
803 },
804 _,
805 ) => Err(Error::new(
806 *span,
807 "`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`",
808 )),
809 Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
810 Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
811 }
812}
813
814#[cfg(test)]
815pub(crate) mod testutil {
816 use proc_macro2::TokenStream;
817 use syn::visit::{self, Visit};
818
819 pub(crate) fn check_hygiene(ts: TokenStream) {
825 struct AmbiguousItemVisitor;
826
827 impl<'ast> Visit<'ast> for AmbiguousItemVisitor {
828 fn visit_path(&mut self, i: &'ast syn::Path) {
829 if i.segments.len() > 1 && i.segments.first().unwrap().ident == "Self" {
830 panic!(
831 "Found ambiguous path `{}` in generated output. \
832 All associated item access must be fully qualified (e.g., `<Self as Trait>::Item`) \
833 to prevent hygiene issues.",
834 quote::quote!(#i)
835 );
836 }
837 visit::visit_path(self, i);
838 }
839 }
840
841 let file = syn::parse2::<syn::File>(ts).expect("failed to parse generated output as File");
842 AmbiguousItemVisitor.visit_file(&file);
843 }
844
845 #[test]
846 fn test_check_hygiene_success() {
847 check_hygiene(quote::quote! {
848 fn foo() {
849 let _ = <Self as Trait>::Item;
850 }
851 });
852 }
853
854 #[test]
855 #[should_panic(expected = "Found ambiguous path `Self :: Ambiguous`")]
856 fn test_check_hygiene_failure() {
857 check_hygiene(quote::quote! {
858 fn foo() {
859 let _ = Self::Ambiguous;
860 }
861 });
862 }
863}