Skip to main content

zerocopy_derive/derive/
project.rs

1// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2//
3// Copyright 2026 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 proc_macro2::TokenStream;
12use quote::quote;
13use syn::{
14    parse_quote, spanned::Spanned as _, Data, DataEnum, DeriveInput, Error, Expr, Fields, Ident,
15    Index, Type,
16};
17
18use crate::{
19    repr::{EnumRepr, StructUnionRepr},
20    util::{
21        const_block, generate_tag_enum, Client, Ctx, DataExt, FieldBounds, FieldProjection,
22        ImplBlockBuilder, Trait,
23    },
24};
25
26pub(crate) fn tag_ident(variant: &Ident) -> Ident {
27    ident!(("___ZEROCOPY_TAG_{}", variant), variant.span())
28}
29
30pub(crate) fn variant_struct_ident(variant: &Ident) -> Ident {
31    ident!(("___ZerocopyVariantStruct_{}", variant), variant.span())
32}
33
34pub(crate) fn variants_union_field_ident(variant: &Ident) -> Ident {
35    ident!(("__field_{}", variant), variant.span())
36}
37
38/// Generates a constant for the tag associated with each variant of the enum.
39/// When we match on the enum's tag, each arm matches one of these constants. We
40/// have to use constants here because:
41///
42/// - The type that we're matching on is not the type of the tag, it's an
43///   integer of the same size as the tag type and with the same bit patterns.
44/// - We can't read the enum tag as an enum because the bytes may not represent
45///   a valid variant.
46/// - Patterns do not currently support const expressions, so we have to assign
47///   these constants to names rather than use them inline in the `match`
48///   statement.
49pub(crate) fn generate_tag_consts(data: &DataEnum) -> TokenStream {
50    let tags = data.variants.iter().map(|variant| {
51        let variant_ident = &variant.ident;
52        let tag = tag_ident(variant_ident);
53
54        quote! {
55            // This casts the enum variant to its discriminant, and then
56            // converts the discriminant to the target integral type via a
57            // numeric cast [1].
58            //
59            // Because these are the same size, this is defined to be a no-op
60            // and therefore is a lossless conversion [2].
61            //
62            // [1] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#enum-cast:
63            //
64            //   Casts an enum to its discriminant.
65            //
66            // [2] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#numeric-cast:
67            //
68            //   Casting between two integers of the same size (e.g. i32 -> u32)
69            //   is a no-op.
70            const #tag: ___ZerocopyTagPrimitive =
71                ___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
72        }
73    });
74
75    quote! {
76        #(#tags)*
77    }
78}
79
80#[derive(Copy, Clone, Eq, PartialEq)]
81enum Validity {
82    Uninit,
83    Initialized,
84    Safe,
85}
86
87impl Validity {
88    fn as_type(self, ctx: &Ctx) -> Type {
89        let zerocopy_crate = &ctx.zerocopy_crate;
90        match self {
91            Validity::Uninit => parse_quote!(#zerocopy_crate::invariant::Uninit),
92            Validity::Initialized => {
93                parse_quote!(#zerocopy_crate::invariant::Initialized)
94            }
95            Validity::Safe => parse_quote!(#zerocopy_crate::invariant::Safe),
96        }
97    }
98
99    /// Computes the output validity of projecting a struct or union field, or
100    /// returns `None` if that projection must not be generated.
101    ///
102    /// Struct fields preserve all three validity invariants. Union fields
103    /// preserve `Uninit` and `Initialized`. No projection is generated from a
104    /// `Safe` union.
105    fn output_validity_for_struct_or_union(self, data: &Data) -> Option<Self> {
106        match (data, self) {
107            (Data::Struct(_), validity) => Some(validity),
108            (Data::Union(_), validity @ (Validity::Uninit | Validity::Initialized)) => {
109                Some(validity)
110            }
111            (Data::Union(_), Validity::Safe) => None,
112            (Data::Enum(_), _) => unreachable!(),
113        }
114    }
115}
116
117/// Returns the projection variant ID for the struct or union described by
118/// `ctx`.
119///
120/// This intentionally does not handle enums: an enum field's variant ID
121/// identifies its source variant rather than the representation of its
122/// containing type.
123pub(crate) fn struct_union_variant_id(ctx: &Ctx) -> Box<Expr> {
124    let zerocopy_crate = &ctx.zerocopy_crate;
125    match &ctx.ast.data {
126        Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
127        Data::Union(_) => {
128            if StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap() {
129                parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
130            } else {
131                parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
132            }
133        }
134        Data::Enum(_) => unreachable!("struct_union_variant_id called for enum"),
135    }
136}
137
138/// Generates field projection implementations for a struct or union.
139///
140/// `repr(C)` unions use `REPR_C_UNION_VARIANT_ID`, so their projections
141/// additionally implement `pointer::cast::Cast`. Other unions use
142/// `UNION_VARIANT_ID`.
143pub(crate) fn derive_projection_struct_union(
144    ctx: &Ctx,
145    data: &dyn DataExt,
146    client: Client,
147) -> TokenStream {
148    let fields = ctx.ast.data.fields();
149    if fields.is_empty() {
150        return quote! {};
151    }
152
153    let field_tokens = fields.iter().map(|(vis, ident, _)| {
154        let ident = ident!(("__z{}", ident), ident.span());
155        quote! {
156            #vis enum #ident {}
157        }
158    });
159
160    let zerocopy_crate = &ctx.zerocopy_crate;
161    let variant_id = struct_union_variant_id(ctx);
162
163    let core = ctx.core_path();
164    let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag { client }, FieldBounds::None)
165        .inner_extras(quote! {
166            type Tag = ();
167            type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
168        })
169        .build();
170
171    let has_fields = fields.iter().map(move |(_, ident, ty)| {
172        let field_token = ident!(("__z{}", ident), ident.span());
173        let projection = FieldProjection {
174            variant_id: variant_id.clone(),
175            field: parse_quote!(#field_token),
176            field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
177        };
178        let has_field_trait = Trait::HasField { client, projection: projection.clone() };
179        let has_field_path = has_field_trait.crate_path(ctx);
180        let has_field = ImplBlockBuilder::new(ctx, data, has_field_trait, FieldBounds::None)
181            .inner_extras(quote! {
182                type Type = #ty;
183
184                #[inline(always)]
185                fn project(
186                    slf: #zerocopy_crate::pointer::PtrInner<'_, Self>,
187                ) -> *mut <Self as #has_field_path>::Type {
188                    let slf = slf.as_ptr();
189                    // SAFETY: By invariant on `PtrInner`, `slf` is a non-null
190                    // pointer whose referent is zero-sized or lives in a valid
191                    // allocation. Since `#ident` is a struct or union field of
192                    // `Self`, this projection preserves or shrinks the referent
193                    // size, and so the resulting referent also fits in the same
194                    // allocation.
195                    unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
196                }
197            })
198            .build();
199
200        let derive_project_field = |input_validity: Validity| {
201            let output_validity =
202                match input_validity.output_validity_for_struct_or_union(&ctx.ast.data) {
203                    Some(output_validity) => output_validity.as_type(ctx),
204                    None => return TokenStream::new(),
205                };
206            let input_validity = input_validity.as_type(ctx);
207            let projection_preserves_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
208                .map(|repr| repr.get_packed().is_none())
209                .unwrap();
210            let output_alignment = if projection_preserves_alignment {
211                quote! { ___ZcAlignment }
212            } else {
213                quote! { #zerocopy_crate::invariant::Unaligned }
214            };
215            ImplBlockBuilder::new(
216                ctx,
217                data,
218                Trait::ProjectField {
219                    client,
220                    projection: projection.clone(),
221                    invariants: parse_quote!((___ZcAliasing, ___ZcAlignment, #input_validity)),
222                },
223                FieldBounds::None,
224            )
225            .param_extras(vec![
226                parse_quote!(___ZcAliasing: #zerocopy_crate::invariant::Aliasing),
227                parse_quote!(___ZcAlignment: #zerocopy_crate::invariant::Alignment),
228            ])
229            .inner_extras(quote! {
230                // SAFETY: Struct and union projections do not depend on the value of
231                // the referent, and are therefore infallible.
232                type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
233
234                // SAFETY: Projection preserves aliasing. It also preserves
235                // alignment unless the containing type is packed. `Validity`
236                // determines whether a projection is emitted and, if so,
237                // computes its output validity: struct fields preserve validity;
238                // union fields preserve `Uninit` and `Initialized`, and no
239                // projection is emitted for `Safe` union input.
240                type Invariants = (___ZcAliasing, #output_alignment, #output_validity);
241            })
242            .build()
243        };
244
245        let project_uninit = derive_project_field(Validity::Uninit);
246        let project_initialized = derive_project_field(Validity::Initialized);
247        let project_safe = derive_project_field(Validity::Safe);
248
249        quote! {
250            #has_field
251            #project_uninit
252            #project_initialized
253            #project_safe
254        }
255    });
256
257    const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
258}
259
260pub(crate) fn derive_enum(
261    ctx: &Ctx,
262    data: &DataEnum,
263    client: Client,
264) -> Result<TokenStream, Error> {
265    // With no fields, there are no `HasField` or `ProjectField` impls to emit,
266    // and therefore no representation requirement to enforce.
267    if data.fields().is_empty() {
268        return Ok(TokenStream::new());
269    }
270
271    let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
272    let (outer_tag_type, inner_tag_type) = if repr.is_c() {
273        (quote! { ___ZerocopyTag }, quote! { () })
274    } else if repr.is_primitive() {
275        (quote! { () }, quote! { ___ZerocopyTag })
276    } else {
277        return Err(Error::new(
278            ctx.ast.span(),
279            "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
280        ));
281    };
282
283    let zerocopy_crate = &ctx.zerocopy_crate;
284    let core = &ctx.core_path();
285    let client_path = &client.crate_path(ctx);
286    let has_tag_trait = Trait::HasTag { client };
287    let has_tag_path = &has_tag_trait.crate_path(ctx);
288    let infallible =
289        quote! { #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible };
290    let tag_enum = generate_tag_enum(ctx, &repr, data);
291    let tag_consts = generate_tag_consts(data);
292
293    let generics = &ctx.ast.generics;
294    let (impl_generics, ref ty_generics, where_clause) = generics.split_for_impl();
295
296    // Synthesize a `repr(C)` struct for each fieldful variant. We must do this
297    // because Rust does not provide an `addr_of!` that works on enums.
298    let variant_structs = data.variants.iter().filter_map(move |variant| {
299        if matches!(variant.fields, Fields::Unit) {
300            return None;
301        }
302
303        let ident = variant_struct_ident(&variant.ident);
304        let field_types = variant.fields.iter().map(|field| &field.ty);
305        let enum_name = &ctx.ast.ident;
306        let variant_struct: DeriveInput = parse_quote! {
307            #[repr(C)]
308            struct #ident #impl_generics (
309                #core::mem::MaybeUninit<___ZerocopyInnerTag>,
310                #(#field_types,)*
311                #core::marker::PhantomData<#enum_name #ty_generics>,
312            ) #where_clause;
313        };
314
315        let projections = derive_projection_struct_union(
316            &ctx.with_input(&variant_struct),
317            &variant_struct.data,
318            client,
319        );
320
321        Some(quote! {
322            #variant_struct
323            #projections
324        })
325    });
326
327    // Synthesize a `repr(C)` union encompassing all synthesized variants.
328    let variants_union = {
329        let fields = data.variants.iter().filter_map(|variant| {
330            if matches!(variant.fields, Fields::Unit) {
331                return None;
332            }
333            let field_name = variants_union_field_ident(&variant.ident);
334            let variant_struct = variant_struct_ident(&variant.ident);
335            Some(quote! {
336                #field_name: #core::mem::ManuallyDrop<#variant_struct #ty_generics>,
337            })
338        });
339
340        let variants_union: DeriveInput = parse_quote! {
341            #[repr(C)]
342            union ___ZerocopyVariants #generics {
343                #(#fields)*
344                // A fieldless enum produces no variant structs, but a union must
345                // have at least one field. This unit does not affect `repr(C)`
346                // layout.
347                __nonempty: (),
348            }
349        };
350        let projections = derive_projection_struct_union(
351            &ctx.with_input(&variants_union),
352            &variants_union.data,
353            client,
354        );
355
356        quote! {
357            #variants_union
358            #projections
359        }
360    };
361
362    // Synthesize a `repr(C)` struct that pairs the the tag with the synthesized
363    // variants union. In practice, the actual tag storage may be here, or
364    // inside the synthesized variant structs, depending on the `repr` of the
365    // enum.
366    let raw_enum: DeriveInput = parse_quote! {
367        #[repr(C)]
368        struct ___ZerocopyRawEnum #generics {
369            tag: ___ZerocopyOuterTag,
370            variants: ___ZerocopyVariants #ty_generics,
371        }
372    };
373
374    let raw_projections =
375        derive_projection_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data, client);
376
377    let has_tag = ImplBlockBuilder::new(ctx, data, has_tag_trait, FieldBounds::None)
378        .inner_extras(quote! {
379            type Tag = ___ZerocopyTag;
380            type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
381        })
382        .build();
383
384    let aliasing_param: &syn::GenericParam =
385        &parse_quote!(___ZcAliasing: #zerocopy_crate::invariant::Aliasing);
386    let reference_param: &syn::GenericParam =
387        &parse_quote!(___ZcAliasing: #zerocopy_crate::invariant::Reference);
388    let alignment_param: &syn::GenericParam =
389        &parse_quote!(___ZcAlignment: #zerocopy_crate::invariant::Alignment);
390
391    let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
392        let variant_ident = &variant.unwrap().ident;
393        let variants_union_field = variants_union_field_ident(variant_ident);
394        let infallible = &infallible;
395
396        fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
397            // Rust does not presently support explicit visibility modifiers on
398            // enum fields. Keep this assertion so that a future language
399            // change cannot silently invalidate the visibility invariant.
400            assert!(matches!(vis, syn::Visibility::Inherited));
401            let projection = FieldProjection {
402                variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
403                field: parse_quote!(()),
404                field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
405            };
406            // The generated variant struct's first field is its inner tag.
407            let variant_struct_field_index = Index::from(idx + 1);
408            let has_field_trait = Trait::HasField {
409                client,
410                projection: projection.clone(),
411            };
412            let has_field_path = has_field_trait.crate_path(ctx);
413            let has_field = ImplBlockBuilder::new(ctx, data, has_field_trait, FieldBounds::None)
414                .inner_extras(quote! {
415                    type Type = #ty;
416
417                    #[inline(always)]
418                    fn project(
419                        slf: #zerocopy_crate::pointer::PtrInner<'_, Self>,
420                    ) -> *mut <Self as #has_field_path>::Type {
421                        use #zerocopy_crate::pointer::cast::{CastSized, Projection};
422
423                        slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
424                            .project::<_, Projection<#client_path, _, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
425                            .project::<_, Projection<#client_path, _, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field) }>>()
426                            .project::<_, Projection<#client_path, _, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
427                            .project::<_, Projection<#client_path, _, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
428                            .as_ptr()
429                    }
430                })
431                .build();
432
433            // SAFETY: `projection` is constructed from the current field and
434            // `variant_ident`, and that same `variant_ident` selects the
435            // variants union field through which `has_field` projects. Thus the
436            // `HasField` impl, `ProjectField` impls, and valid projection's tag
437            // check all identify the same enum field. `idx` is the current
438            // field's zero-based position, and the generated variant struct
439            // prepends its inner tag before reproducing the enum fields in
440            // source order, so `idx + 1` selects that same field. The assertion
441            // above enforces that `repr` is `repr(C)` or a primitive
442            // representation; `___ZerocopyRawEnum` and its nested types model
443            // exactly the representations specified by [1] and [2]. Those
444            // representations use `repr(C)` structs for variant fields, so field
445            // projection preserves alignment. Each `ProjectField` impl preserves
446            // the input validity. The `Uninit` and `Initialized` projections are
447            // infallible because those validity invariants do not depend on the
448            // enum's tag. The `Safe` projection first checks that the tag
449            // selects `variant_ident`; its `Reference` aliasing bound prevents
450            // mutation of the tag between that check and field projection. The
451            // corresponding `HasTag` impl projects to the raw representation's
452            // tag using `CastSized`.
453            //
454            // [1] Per https://doc.rust-lang.org/1.56.0/reference/type-layout.html#reprc-enums-with-fields:
455            //
456            //   The representation of a `repr(C)` enum with fields is a
457            //   `repr(C)` struct with two fields [...]: a `repr(C)` version of
458            //   the enum with all fields removed ("the tag") [and] a `repr(C)`
459            //   union of `repr(C)` structs for the fields of each variant [...].
460            //
461            // [2] Per https://doc.rust-lang.org/1.56.0/reference/type-layout.html#primitive-representation-of-enums-with-fields:
462            //
463            //   The representation of a primitive representation enum is a
464            //   `repr(C)` union of `repr(C)` structs for each variant with a
465            //   field. The first field of each struct in the union is [...] the
466            //   tag and the remaining fields are the fields of that variant.
467            let derive_enum_project_field = |validity| {
468                let (error, is_projectable, aliasing_bound) = if validity == Validity::Safe {
469                    assert!(!matches!(validity, Validity::Uninit | Validity::Initialized));
470                    (
471                        quote! { () },
472                        quote! {
473                            #[inline(always)]
474                            fn is_projectable(
475                                tag: #zerocopy_crate::pointer::Ptr<
476                                    '_,
477                                    <Self as #has_tag_path>::Tag,
478                                    (
479                                        #zerocopy_crate::invariant::Shared,
480                                        ___ZcAlignment,
481                                        #zerocopy_crate::invariant::Safe,
482                                    ),
483                                >,
484                            ) -> #core::result::Result<(), ()> {
485                                let tag = tag.read::<#zerocopy_crate::BecauseImmutable>();
486                                if tag == ___ZerocopyTag::#variant_ident {
487                                    #core::result::Result::Ok(())
488                                } else {
489                                    #core::result::Result::Err(())
490                                }
491                            }
492                        },
493                        reference_param.clone(),
494                    )
495                } else {
496                    (infallible.clone(), quote! {}, aliasing_param.clone())
497                };
498
499                let validity = validity.as_type(ctx);
500                let invariants: Box<Type> =
501                    parse_quote!((___ZcAliasing, ___ZcAlignment, #validity));
502                let project_field_trait = Trait::ProjectField {
503                    client,
504                    projection: projection.clone(),
505                    invariants: invariants.clone(),
506                };
507
508                ImplBlockBuilder::new(ctx, data, project_field_trait, FieldBounds::None)
509                    .param_extras(vec![aliasing_bound.clone(), alignment_param.clone()])
510                    .inner_extras(quote! {
511                        type Error = #error;
512                        type Invariants = #invariants;
513                        #is_projectable
514                    })
515                    .build()
516            };
517
518            let project_uninit = derive_enum_project_field(Validity::Uninit);
519            let project_initialized = derive_enum_project_field(Validity::Initialized);
520            let project_valid = derive_enum_project_field(Validity::Safe);
521
522            quote! {
523                #has_field
524                #project_uninit
525                #project_initialized
526                #project_valid
527            }
528        })
529    });
530
531    Ok(quote! {
532        #tag_enum
533
534        type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
535            { #core::mem::size_of::<___ZerocopyTag>() },
536        >;
537
538        #tag_consts
539
540        type ___ZerocopyOuterTag = #outer_tag_type;
541        type ___ZerocopyInnerTag = #inner_tag_type;
542
543        #(#variant_structs)*
544        #variants_union
545
546        #raw_enum
547        #raw_projections
548
549        #has_tag
550        #(#has_fields)*
551    })
552}
553
554pub(crate) fn derive(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
555    match &ctx.ast.data {
556        Data::Struct(strct) => {
557            Ok(derive_projection_struct_union(ctx, strct, Client::ProjectDerive))
558        }
559        Data::Union(unn) => Ok(derive_projection_struct_union(ctx, unn, Client::ProjectDerive)),
560        Data::Enum(enm) => {
561            let projections = derive_enum(ctx, enm, Client::ProjectDerive)?;
562            if projections.is_empty() {
563                Ok(projections)
564            } else {
565                Ok(const_block([Some(projections)]))
566            }
567        }
568    }
569}