1use std::num::NonZeroU32;
12
13use proc_macro2::{Span, TokenStream};
14use quote::{quote, quote_spanned, ToTokens};
15use syn::{
16 parse::ParseBuffer, parse_quote, spanned::Spanned as _, token::PathSep, Data, DataEnum,
17 DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit, Field, GenericParam, Ident, Index,
18 Lit, LitStr, Meta, Path, Type, Variant, 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 pub(crate) invariant_span: Option<Span>,
36}
37
38#[derive(Eq, PartialEq)]
39enum CratePath {
40 External,
41 CrateRelative,
42 ModuleRelative,
43}
44
45fn validate_crate_path(path: &Path) -> Result<CratePath, ()> {
46 if path.segments.is_empty() {
47 return Err(());
48 }
49
50 enum ModuleRelative {
51 Yes,
52 No,
53 }
54 let first = path.segments[0].ident.to_string();
55 let (mut prev_segment, path_type) = match first.as_str() {
56 "Self" => return Err(()),
57 "crate" => {
58 if path.leading_colon.is_some() {
59 return Err(());
60 }
61 (ModuleRelative::No, CratePath::CrateRelative)
62 }
63 "self" | "super" => {
64 if path.leading_colon.is_some() {
65 return Err(());
66 }
67 (ModuleRelative::Yes, CratePath::ModuleRelative)
68 }
69 _ => (ModuleRelative::No, CratePath::External),
70 };
71
72 for seg in path.segments.iter().skip(1) {
73 let ident = seg.ident.to_string();
74 match ident.as_str() {
75 "Self" | "crate" | "self" => return Err(()),
76 "super" => match prev_segment {
77 ModuleRelative::Yes => {}
78 ModuleRelative::No => return Err(()),
79 },
80 _ => {
81 prev_segment = ModuleRelative::No;
82 }
83 }
84 }
85 Ok(path_type)
86}
87
88impl Ctx {
89 pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {
92 let mut path = parse_quote!(::zerocopy);
93 let mut skip_on_error = false;
94 let mut on_error_span = None;
95
96 for attr in &ast.attrs {
97 if let Meta::List(ref meta_list) = attr.meta {
98 if path_is_ident(&meta_list.path, "zerocopy") {
99 attr.parse_nested_meta(|meta| {
100 if path_is_ident(&meta.path, "crate") {
101 let expr = meta.value().and_then(ParseBuffer::parse);
102 if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {
103 if let Ok(mut path_lit) = lit.parse_with(Path::parse_mod_style) {
104 if let Ok(crate_path) = validate_crate_path(&path_lit) {
105 if path_lit.leading_colon.is_none() && crate_path == CratePath::External {
107 path_lit.leading_colon = Some(PathSep::default());
108 }
109 path = path_lit;
110 return Ok(());
111 }
112
113 return Err(Error::new(
114 lit.span(),
115 "`crate` attribute requires a valid module path",
116 ));
117 }
118
119 return Err(Error::new(
120 lit.span(),
121 "`crate` attribute requires a path as the value",
122 ));
123 }
124
125 return Err(Error::new(
126 meta.path.span(),
127 "`crate` attribute requires a path as the value",
128 ));
129 }
130
131 if path_is_ident(&meta.path, "on_error") {
132 on_error_span = Some(meta.path.span());
133 let value = meta.value()?;
134 let s: LitStr = value.parse()?;
135 match s.value().as_str() {
136 "skip" => skip_on_error = true,
137 "fail" => skip_on_error = false,
138 _ => return Err(Error::new(
139 s.span(),
140 "unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",
141 )),
142 }
143 return Ok(());
144 }
145
146 Err(Error::new(
147 Span::call_site(),
148 format!(
149 "unknown attribute encountered: {}",
150 meta.path.into_token_stream()
151 ),
152 ))
153 })?;
154 }
155 }
156 }
157
158 let invariant_span = crate::invariant::validate(&ast)?;
159 Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span, invariant_span })
160 }
161
162 pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {
163 Self {
164 ast: input.clone(),
165 zerocopy_crate: self.zerocopy_crate.clone(),
166 skip_on_error: self.skip_on_error,
167 on_error_span: self.on_error_span,
168 invariant_span: self.invariant_span,
169 }
170 }
171
172 pub(crate) fn skip_on_error(mut self) -> Self {
173 self.skip_on_error = true;
174 self
175 }
176
177 pub(crate) fn core_path(&self) -> TokenStream {
178 let zerocopy_crate = &self.zerocopy_crate;
179 quote!(#zerocopy_crate::util::macro_util::core_reexport)
180 }
181
182 pub(crate) fn fresh_ident(&self, name: &str) -> Ident {
187 let idents = crate::invariant::idents(self.ast.to_token_stream());
188 let mut name = name.to_owned();
189 while idents.iter().any(|ident| ident.to_string().trim_start_matches("r#") == name) {
190 name.push('_');
191 }
192 Ident::new(&name, Span::mixed_site())
193 }
194
195 pub(crate) fn const_block(
198 &self,
199 items: impl IntoIterator<Item = Option<TokenStream>>,
200 ) -> TokenStream {
201 if self.invariant_span.is_none() {
202 const_block(items)
203 } else {
204 let items = items.into_iter().flatten();
205 quote! { const _: () = { #(#items)* }; }
206 }
207 }
208
209 pub(crate) fn cfg_compile_error(&self) -> TokenStream {
210 let on_error = if cfg!(zerocopy_unstable_linux) {
217 quote!()
218 } else if let Some(span) = self.on_error_span {
219 let core = self.core_path();
220 let error_message =
221 "`on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable";
222 quote::quote_spanned! {span=>
223 #[allow(unused_attributes, unexpected_cfgs)]
224 const _: () = {
225 #[cfg(not(zerocopy_unstable_linux))]
226 #core::compile_error!(#error_message);
227 };
228 }
229 } else {
230 quote!()
231 };
232 let invariant = if cfg!(zerocopy_unstable_ptr) {
233 quote!()
234 } else if let Some(span) = self.invariant_span {
235 let core = self.core_path();
236 let error_message =
237 "`invariant` is experimental; pass '--cfg zerocopy_unstable_ptr' to enable";
238 quote::quote_spanned! {span=>
239 #[allow(unused_attributes, unexpected_cfgs)]
240 const _: () = {
241 #[cfg(not(zerocopy_unstable_ptr))]
242 #core::compile_error!(#error_message);
243 };
244 }
245 } else {
246 quote!()
247 };
248 quote!(#on_error #invariant)
249 }
250
251 pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {
252 if self.skip_on_error {
253 Ok(self.cfg_compile_error())
254 } else {
255 Err(error)
256 }
257 }
258}
259
260pub(crate) trait DataExt {
261 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
270
271 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;
272
273 fn tag(&self) -> Option<Ident>;
274}
275
276impl DataExt for Data {
277 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
278 match self {
279 Data::Struct(strc) => strc.fields(),
280 Data::Enum(enm) => enm.fields(),
281 Data::Union(un) => un.fields(),
282 }
283 }
284
285 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
286 match self {
287 Data::Struct(strc) => strc.variants(),
288 Data::Enum(enm) => enm.variants(),
289 Data::Union(un) => un.variants(),
290 }
291 }
292
293 fn tag(&self) -> Option<Ident> {
294 match self {
295 Data::Struct(strc) => strc.tag(),
296 Data::Enum(enm) => enm.tag(),
297 Data::Union(un) => un.tag(),
298 }
299 }
300}
301
302impl DataExt for DataStruct {
303 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
304 map_fields(&self.fields)
305 }
306
307 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
308 vec![(None, self.fields())]
309 }
310
311 fn tag(&self) -> Option<Ident> {
312 None
313 }
314}
315
316impl DataExt for DataEnum {
317 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
318 map_fields(self.variants.iter().flat_map(|var| &var.fields))
319 }
320
321 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
322 self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
323 }
324
325 fn tag(&self) -> Option<Ident> {
326 Some(Ident::new("___ZerocopyTag", Span::call_site()))
327 }
328}
329
330impl DataExt for DataUnion {
331 fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
332 map_fields(&self.fields.named)
333 }
334
335 fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
336 vec![(None, self.fields())]
337 }
338
339 fn tag(&self) -> Option<Ident> {
340 None
341 }
342}
343
344fn map_fields<'a>(
345 fields: impl 'a + IntoIterator<Item = &'a Field>,
346) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
347 fields
348 .into_iter()
349 .enumerate()
350 .map(|(idx, f)| {
351 (
352 &f.vis,
353 f.ident
354 .as_ref()
355 .map(ToTokens::to_token_stream)
356 .unwrap_or_else(|| Index::from(idx).to_token_stream()),
357 &f.ty,
358 )
359 })
360 .collect()
361}
362
363pub(crate) fn to_ident_str(t: &impl ToString) -> String {
364 let s = t.to_string();
365 if let Some(stripped) = s.strip_prefix("r#") {
366 stripped.to_string()
367 } else {
368 s
369 }
370}
371
372pub(crate) fn path_is_ident(path: &Path, expected: &str) -> bool {
378 match path.get_ident() {
379 Some(ident) => to_ident_str(ident) == expected,
380 None => false,
381 }
382}
383
384pub(crate) enum PaddingCheck {
387 Struct,
390 ReprCStruct,
392 Union,
394 Enum { tag_type_definition: TokenStream },
399}
400
401impl PaddingCheck {
402 pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {
405 let (trt, mcro) = match self {
406 PaddingCheck::Struct => ("PaddingFree", "struct_padding"),
407 PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),
408 PaddingCheck::Union => ("PaddingFree", "union_padding"),
409 PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),
410 };
411
412 let trt = Ident::new(trt, Span::call_site());
413 let mcro = Ident::new(mcro, Span::call_site());
414 (trt, mcro)
415 }
416
417 pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {
420 match self {
421 PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,
422 PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
423 }
424 }
425}
426
427#[derive(Copy, Clone)]
428pub(crate) enum Client {
429 ProjectDerive,
430 TryFromBytesDerive,
431}
432
433impl ToTokens for Client {
434 fn to_tokens(&self, tokens: &mut TokenStream) {
435 let s = match self {
436 Client::ProjectDerive => "ProjectDerive",
437 Client::TryFromBytesDerive => "TryFromBytesDerive",
438 };
439 let ident = Ident::new(s, Span::call_site());
440 tokens.extend(quote!(project_clients::#ident));
441 }
442}
443
444impl Client {
445 pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
446 let zerocopy_crate = &ctx.zerocopy_crate;
447 parse_quote!(#zerocopy_crate::#self)
448 }
449}
450
451#[derive(Clone)]
453pub(crate) struct FieldProjection {
454 pub(crate) variant_id: Box<Expr>,
455 pub(crate) field: Box<Type>,
456 pub(crate) field_id: Box<Expr>,
457}
458
459#[derive(Clone)]
460pub(crate) enum Trait {
461 KnownLayout,
462 HasTag { client: Client },
463 HasField { client: Client, projection: FieldProjection },
464 ProjectField { client: Client, projection: FieldProjection, invariants: Box<Type> },
465 Immutable,
466 Project,
467 TryFromBytes,
468 FromZeros,
469 FromBytes,
470 IntoBytes,
471 Unaligned,
472 Sized,
473 ByteHash,
474 ByteEq,
475 SplitAt,
476}
477
478impl ToTokens for Trait {
479 fn to_tokens(&self, tokens: &mut TokenStream) {
480 let s = match self {
490 Trait::HasField { .. } => "HasField",
491 Trait::ProjectField { .. } => "ProjectField",
492 Trait::KnownLayout => "KnownLayout",
493 Trait::HasTag { .. } => "HasTag",
494 Trait::Immutable => "Immutable",
495 Trait::Project => "Project",
496 Trait::TryFromBytes => "TryFromBytes",
497 Trait::FromZeros => "FromZeros",
498 Trait::FromBytes => "FromBytes",
499 Trait::IntoBytes => "IntoBytes",
500 Trait::Unaligned => "Unaligned",
501 Trait::Sized => "Sized",
502 Trait::ByteHash => "ByteHash",
503 Trait::ByteEq => "ByteEq",
504 Trait::SplitAt => "SplitAt",
505 };
506 let ident = Ident::new(s, Span::call_site());
507 let arguments: Option<syn::AngleBracketedGenericArguments> = match self {
508 Trait::HasTag { client } => Some(parse_quote!(<#client>)),
509 Trait::HasField { client, projection } => {
510 let FieldProjection { variant_id, field, field_id } = projection;
511 Some(parse_quote!(<#client, #field, #variant_id, #field_id>))
512 }
513 Trait::ProjectField { client, projection, invariants } => {
514 let FieldProjection { variant_id, field, field_id } = projection;
515 Some(parse_quote!(<#client, #field, #invariants, #variant_id, #field_id>))
516 }
517 Trait::KnownLayout
518 | Trait::Immutable
519 | Trait::Project
520 | Trait::TryFromBytes
521 | Trait::FromZeros
522 | Trait::FromBytes
523 | Trait::IntoBytes
524 | Trait::Unaligned
525 | Trait::Sized
526 | Trait::ByteHash
527 | Trait::ByteEq
528 | Trait::SplitAt => None,
529 };
530 tokens.extend(quote!(#ident #arguments));
531 }
532}
533
534impl Trait {
535 pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
536 let zerocopy_crate = &ctx.zerocopy_crate;
537 let core = ctx.core_path();
538 match self {
539 Self::Sized => parse_quote!(#core::marker::#self),
540 Self::HasTag { client } => {
541 let client = client.crate_path(ctx);
542 parse_quote!(#zerocopy_crate::HasTag<#client>)
543 }
544 Self::HasField { client, projection } => {
545 let FieldProjection { variant_id, field, field_id } = projection;
546 let client = client.crate_path(ctx);
547 parse_quote!(#zerocopy_crate::HasField<#client, #field, #variant_id, #field_id>)
548 }
549 Self::ProjectField { client, projection, invariants } => {
550 let FieldProjection { variant_id, field, field_id } = projection;
551 let client = client.crate_path(ctx);
552 parse_quote!(
553 #zerocopy_crate::ProjectField<
554 #client,
555 #field,
556 #invariants,
557 #variant_id,
558 #field_id
559 >
560 )
561 }
562 _ => parse_quote!(#zerocopy_crate::#self),
563 }
564 }
565}
566
567pub(crate) enum TraitBound {
568 Slf,
569 Other(Trait),
570}
571
572pub(crate) enum FieldBounds<'a> {
573 None,
574 All(&'a [TraitBound]),
575 Trailing(&'a [TraitBound]),
576 Explicit(Vec<WherePredicate>),
577}
578
579impl<'a> FieldBounds<'a> {
580 pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
581 pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
582}
583
584pub(crate) enum SelfBounds<'a> {
585 None,
586 All(&'a [Trait]),
587}
588
589#[allow(clippy::needless_lifetimes)]
592impl<'a> SelfBounds<'a> {
593 pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
594}
595
596pub(crate) fn normalize_bounds<'a>(
598 slf: &'a Trait,
599 bounds: &'a [TraitBound],
600) -> impl 'a + Iterator<Item = Trait> {
601 bounds.iter().map(move |bound| match bound {
602 TraitBound::Slf => slf.clone(),
603 TraitBound::Other(trt) => trt.clone(),
604 })
605}
606
607pub(crate) struct ImplBlockBuilder<'a> {
608 ctx: &'a Ctx,
609 data: &'a dyn DataExt,
610 trt: Trait,
611 field_type_trait_bounds: FieldBounds<'a>,
612 self_type_trait_bounds: SelfBounds<'a>,
613 padding_check: Option<PaddingCheck>,
614 param_extras: Vec<GenericParam>,
615 inner_extras: Option<TokenStream>,
616 outer_extras: Option<TokenStream>,
617}
618
619impl<'a> ImplBlockBuilder<'a> {
620 pub(crate) fn new(
621 ctx: &'a Ctx,
622 data: &'a dyn DataExt,
623 trt: Trait,
624 field_type_trait_bounds: FieldBounds<'a>,
625 ) -> Self {
626 Self {
627 ctx,
628 data,
629 trt,
630 field_type_trait_bounds,
631 self_type_trait_bounds: SelfBounds::None,
632 padding_check: None,
633 param_extras: Vec::new(),
634 inner_extras: None,
635 outer_extras: None,
636 }
637 }
638
639 pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
640 self.self_type_trait_bounds = self_type_trait_bounds;
641 self
642 }
643
644 pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
645 self.padding_check = padding_check.into();
646 self
647 }
648
649 pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {
650 self.param_extras.extend(param_extras);
651 self
652 }
653
654 pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
655 self.inner_extras = Some(inner_extras);
656 self
657 }
658
659 pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
660 self.outer_extras = outer_extras.into();
661 self
662 }
663
664 pub(crate) fn build(self) -> TokenStream {
665 let type_ident = &self.ctx.ast.ident;
725 let trait_path = self.trt.crate_path(self.ctx);
726 let fields = self.data.fields();
727 let variants = self.data.variants();
728 let tag = self.data.tag();
729 let zerocopy_crate = &self.ctx.zerocopy_crate;
730
731 fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>, ctx: &Ctx) -> WherePredicate {
732 let traits = traits.map(|t| t.crate_path(ctx));
733 parse_quote!(#ty: #(#traits)+*)
734 }
735 let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) {
736 (FieldBounds::All(traits), _) => fields
737 .iter()
738 .map(|(_vis, _name, ty)| {
739 bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx)
740 })
741 .collect(),
742 (FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
743 (FieldBounds::Trailing(traits), [.., last]) => {
744 vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)]
745 }
746 (FieldBounds::Explicit(bounds), _) => bounds,
747 };
748
749 let padding_check_bound = self
750 .padding_check
751 .map(|check| {
752 let repr =
757 Repr::<PrimitiveRepr, NonZeroU32>::from_attrs(&self.ctx.ast.attrs).unwrap();
758 let core = self.ctx.core_path();
759 let option = quote! { #core::option::Option };
760 let nonzero = quote! { #core::num::NonZeroUsize };
761 let none = quote! { #option::None::<#nonzero> };
762 let repr_align =
763 repr.get_align().map(|spanned| {
764 let n = spanned.t.get();
765 quote_spanned! { spanned.span => (#nonzero::new(#n as usize)) }
766 }).unwrap_or(quote! { (#none) });
767 let repr_packed =
768 repr.get_packed().map(|packed| {
769 let n = packed.get();
770 quote! { (#nonzero::new(#n as usize)) }
771 }).unwrap_or(quote! { (#none) });
772 let variant_types = variants.iter().map(|(_, fields)| {
773 let types = fields.iter().map(|(_vis, _name, ty)| ty);
774 quote!([#((#types)),*])
775 });
776 let validator_context = check.validator_macro_context();
777 let (trt, validator_macro) = check.validator_trait_and_macro_idents();
778 let t = tag.iter();
779 parse_quote! {
780 (): #zerocopy_crate::util::macro_util::#trt<
781 Self,
782 {
783 #validator_context
784 #zerocopy_crate::#validator_macro!(Self, #repr_align, #repr_packed, #(#t,)* #(#variant_types),*)
785 }
786 >
787 }
788 });
789
790 let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
791 SelfBounds::None => None,
792 SelfBounds::All(traits) => {
793 Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx))
794 }
795 };
796
797 let zerocopy_bounds =
798 field_type_bounds
799 .into_iter()
800 .chain(padding_check_bound)
801 .chain(self_bounds)
802 .map(|bound| {
803 if self.ctx.skip_on_error {
804 parse_quote!(for<'zc> #bound)
805 } else {
806 bound.clone()
807 }
808 })
809 .collect::<Vec<_>>();
810
811 let bounds = self
812 .ctx
813 .ast
814 .generics
815 .where_clause
816 .as_ref()
817 .map(|where_clause| where_clause.predicates.iter())
818 .into_iter()
819 .flatten()
820 .chain(zerocopy_bounds.iter());
821
822 let mut params: Vec<_> = self
824 .ctx
825 .ast
826 .generics
827 .params
828 .clone()
829 .into_iter()
830 .map(|mut param| {
831 match &mut param {
832 GenericParam::Type(ty) => ty.default = None,
833 GenericParam::Const(cnst) => cnst.default = None,
834 GenericParam::Lifetime(_) => {}
835 }
836 parse_quote!(#param)
837 })
838 .chain(self.param_extras)
839 .collect();
840
841 params.sort_by_cached_key(|param| match param {
844 GenericParam::Lifetime(_) => 0,
845 GenericParam::Type(_) => 1,
846 GenericParam::Const(_) => 2,
847 });
848
849 let param_idents = self.ctx.ast.generics.params.iter().map(|param| match param {
852 GenericParam::Type(ty) => {
853 let ident = &ty.ident;
854 quote!(#ident)
855 }
856 GenericParam::Lifetime(l) => {
857 let ident = &l.lifetime;
858 quote!(#ident)
859 }
860 GenericParam::Const(cnst) => {
861 let ident = &cnst.ident;
862 quote!({#ident})
863 }
864 });
865
866 let inner_extras = self.inner_extras;
867 let allow_trivial_bounds =
868 if self.ctx.skip_on_error { quote!(#[allow(trivial_bounds)]) } else { quote!() };
869 let impl_tokens = quote! {
870 #allow_trivial_bounds
871 unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
872 where
873 #(#bounds,)*
874 {
875 fn only_derive_is_allowed_to_implement_this_trait() {}
876
877 #inner_extras
878 }
879 };
880
881 let outer_extras = self.outer_extras.filter(|e| !e.is_empty());
882 let cfg_compile_error = self.ctx.cfg_compile_error();
883 let items = [Some(cfg_compile_error), Some(impl_tokens), outer_extras];
884 if matches!(self.trt, Trait::TryFromBytes) {
885 self.ctx.const_block(items)
886 } else {
887 const_block(items)
888 }
889 }
890}
891
892#[allow(unused)]
900trait BoolExt {
901 fn then_some<T>(self, t: T) -> Option<T>;
902}
903
904impl BoolExt for bool {
905 fn then_some<T>(self, t: T) -> Option<T> {
906 if self {
907 Some(t)
908 } else {
909 None
910 }
911 }
912}
913
914pub(crate) fn allow_generated_code() -> TokenStream {
915 quote! {
916 #[allow(
917 deprecated,
920 private_bounds,
924 non_local_definitions,
925 non_camel_case_types,
926 non_upper_case_globals,
927 non_snake_case,
928 non_ascii_idents,
929 clippy::missing_inline_in_public_items,
930 )]
931 }
932}
933
934pub(crate) fn const_block(items: impl IntoIterator<Item = Option<TokenStream>>) -> TokenStream {
935 let items = items.into_iter().flatten();
936 let allow = allow_generated_code();
937 quote! {
938 #allow
939 #[deny(ambiguous_associated_items)]
940 #[automatically_derived]
943 const _: () = {
944 #(#items)*
945 };
946 }
947}
948pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream {
949 let zerocopy_crate = &ctx.zerocopy_crate;
950 let variants = data.variants.iter().map(|v| {
951 let ident = &v.ident;
952 if let Some((eq, discriminant)) = &v.discriminant {
953 quote! { #ident #eq #discriminant }
954 } else {
955 quote! { #ident }
956 }
957 });
958
959 let repr = match repr {
963 EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
964 EnumRepr::Compound(c, _) => quote! { #c },
965 };
966
967 quote! {
968 #repr
969 #[allow(dead_code, clippy::derive_partial_eq_without_eq)]
970 #[derive(Copy, Clone, PartialEq)]
971 pub enum ___ZerocopyTag {
972 #(#variants,)*
973 }
974
975 unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag {
978 fn only_derive_is_allowed_to_implement_this_trait() {}
979 }
980 }
981}
982pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
983 use CompoundRepr::*;
984 use PrimitiveRepr::*;
985 use Repr::*;
986 match repr {
987 Transparent(span)
988 | Compound(
989 Spanned {
990 t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | Usize | Isize),
991 span,
992 },
993 _,
994 ) => Err(Error::new(
995 *span,
996 "`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`",
997 )),
998 Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
999 Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
1000 }
1001}
1002
1003#[cfg(test)]
1004pub(crate) mod testutil {
1005 use proc_macro2::TokenStream;
1006 use syn::visit::{self, Visit};
1007
1008 pub(crate) fn check_hygiene(ts: TokenStream) {
1014 struct AmbiguousItemVisitor;
1015
1016 fn panic_ambiguous_path(path: &impl quote::ToTokens) -> ! {
1017 panic!(
1018 "Found ambiguous path `{}` in generated output. \
1019 All associated item access must be fully qualified (e.g., `<Self as Trait>::Item`) \
1020 to prevent hygiene issues.",
1021 quote::quote!(#path)
1022 );
1023 }
1024
1025 impl<'ast> Visit<'ast> for AmbiguousItemVisitor {
1026 fn visit_expr_path(&mut self, i: &'ast syn::ExprPath) {
1027 if let Some(qself) = &i.qself {
1028 if qself.position == 0 {
1029 panic_ambiguous_path(i);
1030 }
1031 }
1032 visit::visit_expr_path(self, i);
1033 }
1034
1035 fn visit_path(&mut self, i: &'ast syn::Path) {
1036 if i.segments.len() > 1 && i.segments.first().unwrap().ident == "Self" {
1037 panic_ambiguous_path(i);
1038 }
1039 visit::visit_path(self, i);
1040 }
1041
1042 fn visit_type_path(&mut self, i: &'ast syn::TypePath) {
1043 if let Some(qself) = &i.qself {
1044 if qself.position == 0 {
1045 panic_ambiguous_path(i);
1046 }
1047 }
1048 visit::visit_type_path(self, i);
1049 }
1050 }
1051
1052 let file = syn::parse2::<syn::File>(ts).expect("failed to parse generated output as File");
1053 AmbiguousItemVisitor.visit_file(&file);
1054 }
1055
1056 #[test]
1057 fn test_check_hygiene_success() {
1058 check_hygiene(quote::quote! {
1059 fn foo() {
1060 let _ = <Self as Trait>::Item;
1061 }
1062 });
1063 }
1064
1065 #[test]
1066 #[should_panic(expected = "Found ambiguous path `Self :: Ambiguous`")]
1067 fn test_check_hygiene_failure() {
1068 check_hygiene(quote::quote! {
1069 fn foo() {
1070 let _ = Self::Ambiguous;
1071 }
1072 });
1073 }
1074
1075 #[test]
1076 #[should_panic(expected = "Found ambiguous path `< T > :: Ambiguous`")]
1077 fn test_check_hygiene_type_relative_expr_failure() {
1078 check_hygiene(quote::quote! {
1079 fn foo<T>() {
1080 let _ = <T>::Ambiguous;
1081 }
1082 });
1083 }
1084
1085 #[test]
1086 #[should_panic(expected = "Found ambiguous path `< T > :: Ambiguous`")]
1087 fn test_check_hygiene_type_relative_type_failure() {
1088 check_hygiene(quote::quote! {
1089 fn foo<T>() {
1090 let _: <T>::Ambiguous;
1091 }
1092 });
1093 }
1094
1095 #[test]
1096 fn test_validate_crate_path() {
1097 use syn::parse_str;
1098
1099 let valid = [
1100 "zerocopy",
1101 "crate",
1102 "crate::foo::bar",
1103 "self",
1104 "self::foo",
1105 "self::super::foo",
1106 "super",
1107 "super::foo",
1108 "super::super::foo",
1109 "super::super::super",
1110 "foo::bar::baz",
1111 "::foo::bar",
1112 ];
1113
1114 for path_str in valid {
1115 let path = parse_str::<syn::Path>(path_str).unwrap();
1116 assert!(
1117 super::validate_crate_path(&path).is_ok(),
1118 "expected valid path for `{}`",
1119 path_str
1120 );
1121 }
1122
1123 let invalid = [
1124 "::crate::foo",
1125 "::self::foo",
1126 "::super::foo",
1127 "Self",
1128 "Self::foo",
1129 "foo::Self::bar",
1130 "foo::crate::bar",
1131 "foo::super::bar",
1132 "foo::self",
1133 "super::foo::super",
1134 "super::crate::foo",
1135 "crate::super::foo",
1136 "self::self::foo",
1137 ];
1138
1139 for path_str in invalid {
1140 let path = parse_str::<syn::Path>(path_str).unwrap();
1141 assert!(
1142 super::validate_crate_path(&path).is_err(),
1143 "expected invalid path for `{}`",
1144 path_str
1145 );
1146 }
1147 }
1148
1149 #[test]
1150 fn test_path_is_ident() {
1151 use syn::parse_str;
1152
1153 for (expected, ordinary, raw) in [
1154 ("doc", "doc", "r#doc"),
1155 ("repr", "repr", "r#repr"),
1156 ("zerocopy", "zerocopy", "r#zerocopy"),
1157 ("on_error", "on_error", "r#on_error"),
1158 ] {
1159 let ordinary = parse_str::<syn::Path>(ordinary).unwrap();
1160 let raw = parse_str::<syn::Path>(raw).unwrap();
1161 assert!(super::path_is_ident(&ordinary, expected));
1162 assert!(super::path_is_ident(&raw, expected));
1163 }
1164
1165 let crate_path = parse_str::<syn::Path>("crate").unwrap();
1167 assert!(super::path_is_ident(&crate_path, "crate"));
1168
1169 for path in ["::zerocopy", "module::zerocopy"] {
1170 let path = parse_str::<syn::Path>(path).unwrap();
1171 assert!(!super::path_is_ident(&path, "zerocopy"));
1172 }
1173 }
1174
1175 #[test]
1176 fn test_raw_zerocopy_attributes() {
1177 let ast: syn::DeriveInput = syn::parse_quote! {
1178 #[r#zerocopy(crate = "renamed", r#on_error = "skip")]
1179 struct Foo;
1180 };
1181
1182 let ctx = super::Ctx::try_from_derive_input(ast).unwrap();
1183 let path = &ctx.zerocopy_crate;
1184 assert_eq!(quote::quote!(#path).to_string(), ":: renamed");
1185 assert!(ctx.skip_on_error);
1186 }
1187}