Skip to main content

zerocopy_derive/
util.rs

1// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2//
3// Copyright 2019 The Fuchsia Authors
4//
5// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
6// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
7// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
8// This file may not be copied, modified, or distributed except according to
9// those terms.
10
11use 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    // The value of the last `#[zerocopy(on_error = ...)]` attribute, or `false`
28    // if none is provided.
29    pub(crate) skip_on_error: bool,
30
31    // The span of the last `#[zerocopy(on_error = ...)]` attribute, if any.
32    pub(crate) on_error_span: Option<proc_macro2::Span>,
33}
34
35impl Ctx {
36    /// Attempt to extract a crate path from the provided attributes. Defaults to
37    /// `::zerocopy` if not found.
38    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        // By checking both during the compilation of the proc macro *and* in
113        // the generated code, we ensure that `--cfg
114        // zerocopy_unstable_linux` need only be passed *either* when
115        // compiling this crate *or* when compiling the user's crate. The former
116        // is preferable, but in some situations (such as when cross-compiling
117        // using `cargo build --target`), it doesn't get propagated to this
118        // crate's build by default.
119        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    /// Extracts the names and types of all fields. For enums, extracts the
148    /// names and types of fields from each variant. For tuple structs, the
149    /// names are the indices used to index into the struct (ie, `0`, `1`, etc).
150    ///
151    /// FIXME: Extracting field names for enums doesn't really make sense. Types
152    /// makes sense because we don't care about where they live - we just care
153    /// about transitive ownership. But for field names, we'd only use them when
154    /// generating is_bit_valid, which cares about where they live.
155    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
258/// This enum describes what kind of padding check needs to be generated for the
259/// associated impl.
260pub(crate) enum PaddingCheck {
261    /// Check that the sum of the fields' sizes exactly equals the struct's
262    /// size.
263    Struct,
264    /// Check that a `repr(C)` struct has no padding.
265    ReprCStruct,
266    /// Check that the size of each field exactly equals the union's size.
267    Union,
268    /// Check that every variant of the enum contains no padding.
269    ///
270    /// Because doing so requires a tag enum, this padding check requires an
271    /// additional `TokenStream` which defines the tag enum as `___ZerocopyTag`.
272    Enum { tag_type_definition: TokenStream },
273}
274
275impl PaddingCheck {
276    /// Returns the idents of the trait to use and the macro to call in order to
277    /// validate that a type passes the relevant padding check.
278    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    /// Sometimes performing the padding check requires some additional
292    /// "context" code. For enums, this is the definition of the tag enum.
293    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        // According to [1], the format of the derived `Debug`` output is not
331        // stable and therefore not guaranteed to represent the variant names.
332        // Indeed with the (unstable) `fmt-debug` compiler flag [2], it can
333        // return only a minimalized output or empty string. To make sure this
334        // code will work in the future and independent of the compiler flag, we
335        // translate the variants to their names manually here.
336        //
337        // [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability
338        // [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html
339        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// FIXME(https://github.com/rust-lang/rust-clippy/issues/12908): This is a false
414// positive. Explicit lifetimes are actually necessary here.
415#[allow(clippy::needless_lifetimes)]
416impl<'a> SelfBounds<'a> {
417    pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
418}
419
420/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.
421pub(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        // In this documentation, we will refer to this hypothetical struct:
490        //
491        //   #[derive(FromBytes)]
492        //   struct Foo<T, I: Iterator>
493        //   where
494        //       T: Copy,
495        //       I: Clone,
496        //       I::Item: Clone,
497        //   {
498        //       a: u8,
499        //       b: T,
500        //       c: I::Item,
501        //   }
502        //
503        // We extract the field types, which in this case are `u8`, `T`, and
504        // `I::Item`. We re-use the existing parameters and where clauses. If
505        // `require_trait_bound == true` (as it is for `FromBytes), we add where
506        // bounds for each field's type:
507        //
508        //   impl<T, I: Iterator> FromBytes for Foo<T, I>
509        //   where
510        //       T: Copy,
511        //       I: Clone,
512        //       I::Item: Clone,
513        //       T: FromBytes,
514        //       I::Item: FromBytes,
515        //   {
516        //   }
517        //
518        // NOTE: It is standard practice to only emit bounds for the type
519        // parameters themselves, not for field types based on those parameters
520        // (e.g., `T` vs `T::Foo`). For a discussion of why this is standard
521        // practice, see https://github.com/rust-lang/rust/issues/26925.
522        //
523        // The reason we diverge from this standard is that doing it that way
524        // for us would be unsound. E.g., consider a type, `T` where `T:
525        // FromBytes` but `T::Foo: !FromBytes`. It would not be sound for us to
526        // accept a type with a `T::Foo` field as `FromBytes` simply because `T:
527        // FromBytes`.
528        //
529        // While there's no getting around this requirement for us, it does have
530        // the pretty serious downside that, when lifetimes are involved, the
531        // trait solver ties itself in knots:
532        //
533        //     #[derive(Unaligned)]
534        //     #[repr(C)]
535        //     struct Dup<'a, 'b> {
536        //         a: PhantomData<&'a u8>,
537        //         b: PhantomData<&'b u8>,
538        //     }
539        //
540        //     error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned`
541        //      --> src/main.rs:6:10
542        //       |
543        //     6 | #[derive(Unaligned)]
544        //       |          ^^^^^^^^^
545        //       |
546        //       = note: required by `zerocopy::Unaligned`
547
548        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                // Parse the repr for `align` and `packed` modifiers. Note that
577                // `Repr::<PrimitiveRepr, NonZeroU32>` is more permissive than
578                // what Rust supports for structs, enums, or unions, and thus
579                // reliably extracts these modifiers for any kind of type.
580                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        // The parameters with trait bounds, but without type defaults.
647        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        // For MSRV purposes, ensure that lifetimes precede types precede const
666        // generics.
667        params.sort_by_cached_key(|param| match param {
668            GenericParam::Lifetime(_) => 0,
669            GenericParam::Type(_) => 1,
670            GenericParam::Const(_) => 2,
671        });
672
673        // The identifiers of the parameters without trait bounds or type
674        // defaults.
675        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// A polyfill for `Option::then_some`, which was added after our MSRV.
712//
713// The `#[allow(unused)]` is necessary because, on sufficiently recent toolchain
714// versions, `b.then_some(...)` resolves to the inherent method rather than to
715// this trait, and so this trait is considered unused.
716//
717// FIXME(#67): Remove this once our MSRV is >= 1.62.
718#[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            // FIXME(#553): Add a test that generates a warning when
738            // `#[allow(deprecated)]` isn't present.
739            deprecated,
740            // Required on some rustc versions due to a lint that is only
741            // triggered when `derive(KnownLayout)` is applied to `repr(C)`
742            // structs that are generated by macros. See #2177 for details.
743            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        // While there are not currently any warnings that this suppresses
753        // (that we're aware of), it's good future-proofing hygiene.
754        #[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    // Don't include any `repr(align)` when generating the tag enum, as that
772    // could add padding after the tag but before any variants, which is not the
773    // correct behavior.
774    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        // SAFETY: `___ZerocopyTag` has no fields, and so it does not permit
787        // interior mutation.
788        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    /// Checks for hygiene violations in the generated code.
820    ///
821    /// # Panics
822    ///
823    /// Panics if a hygiene violation is found.
824    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}