1use 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
38pub(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 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 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
117pub(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
138pub(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 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 type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
233
234 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 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 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 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 __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 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 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 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 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}