Skip to main content

rust_spec_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{Token, punctuated::Punctuated};
4
5use crate::repr::{
6    ReprKind, has_unresolved_discriminants, infer_repr, is_exhaustive_enum, parse_repr,
7};
8
9mod classify;
10mod repr;
11
12/// Derives `RustSpec` for a struct, enum, or union.
13///
14/// Add `#[rust_spec(custom_drop)]` if the type itself implements [`Drop`].
15/// Add `#[rust_spec(custom_niche)]` if a struct has a custom niche.
16/// Add `#[rust_spec(opaque)]` to classify the type as `Opacity = Opaque`
17///
18/// # Safety hazards of private invariants
19///
20/// The derive infers `RustSpec` axes from the fields. It cannot see invariants enforced through
21/// private constructors.
22///
23/// For example, if an invariant of `#[repr(C)] Wrapper(u8)` is that it may never hold `u8::MAX`,
24/// `RustSpec` will infer `Layout = Stable` and `Trap = Robust` which downstream consumers may
25/// interpret as a permission to transmute `u8` into `Wrapper` and cause UB.
26///
27///Likewise, be very careful when the inner fields are raw pointers, `NonNull<T>` or `MaybeUninit<T>`.
28/// `UnsafeCell<T>` and `ManuallyDrop<T>` fields, however, don't require special care from the user.
29#[proc_macro_derive(RustSpec, attributes(rust_spec))]
30pub fn rust_spec(item: TokenStream) -> TokenStream {
31    let input = syn::parse_macro_input!(item as syn::DeriveInput);
32    expand(&input)
33        .unwrap_or_else(syn::Error::into_compile_error)
34        .into()
35}
36
37fn crate_path() -> proc_macro2::TokenStream {
38    let rust_spec = proc_macro_crate::crate_name("rust-spec");
39    match rust_spec {
40        Ok(proc_macro_crate::FoundCrate::Itself) => return quote! { rust_spec },
41        Ok(proc_macro_crate::FoundCrate::Name(name)) => {
42            let name = format_ident!("{}", name);
43            return quote! { #name };
44        }
45        Err(_) => {}
46    }
47
48    match proc_macro_crate::crate_name("co3") {
49        Ok(proc_macro_crate::FoundCrate::Itself) => quote! { crate::rust_spec },
50        Ok(proc_macro_crate::FoundCrate::Name(name)) => {
51            let name = format_ident!("{}", name);
52            quote! { #name::rust_spec }
53        }
54        Err(_) => quote! { co3::rust_spec },
55    }
56}
57
58struct AggregateFamily {
59    kind: proc_macro2::TokenStream,
60    aggregate_bounds: Vec<proc_macro2::TokenStream>,
61}
62
63struct TypeSpecFamilies {
64    layout: AggregateFamily,
65    size: AggregateFamily,
66    alignment: AggregateFamily,
67    trap: AggregateFamily,
68    niche: AggregateFamily,
69    drop: AggregateFamily,
70    mutability: AggregateFamily,
71    indirect_trap: AggregateFamily,
72}
73
74impl AggregateFamily {
75    fn fixed(kind: proc_macro2::TokenStream) -> Self {
76        Self {
77            kind,
78            aggregate_bounds: Vec::new(),
79        }
80    }
81
82    fn aggregate_parts(
83        operator: proc_macro2::TokenStream,
84        axis: proc_macro2::TokenStream,
85        generics: &syn::Generics,
86        fields: &[&syn::Type],
87    ) -> (
88        Option<proc_macro2::TokenStream>,
89        Option<proc_macro2::TokenStream>,
90        Vec<proc_macro2::TokenStream>,
91    ) {
92        let mut aggregate_bounds = Vec::new();
93
94        let (concrete_fields, parameterized_fields): (Vec<_>, Vec<_>) = fields
95            .iter()
96            .copied()
97            .partition(|field| !field_has_type_params(field, generics));
98
99        let mut concrete_fields = concrete_fields.into_iter();
100        let concrete_kind = concrete_fields.next().map(|field| {
101            let mut kind = field_axis_kind(field, &axis);
102
103            for field in concrete_fields {
104                let field_kind = field_axis_kind(field, &axis);
105                kind = quote! { <#kind as #operator<#field_kind>>::Output };
106            }
107
108            kind
109        });
110
111        let mut parameterized_fields = parameterized_fields.into_iter();
112        let parameterized_kind = parameterized_fields.next().map(|field| {
113            let mut kind = field_axis_kind(field, &axis);
114
115            for field in parameterized_fields {
116                let field_kind = field_axis_kind(field, &axis);
117
118                aggregate_bounds.push(quote! { #kind: #operator<#field_kind> });
119                kind = quote! { <#kind as #operator<#field_kind>>::Output };
120            }
121
122            kind
123        });
124
125        (concrete_kind, parameterized_kind, aggregate_bounds)
126    }
127
128    fn aggregate(
129        operator: proc_macro2::TokenStream,
130        axis: proc_macro2::TokenStream,
131        generics: &syn::Generics,
132        fields: &[&syn::Type],
133    ) -> Option<Self> {
134        let (concrete_kind, parameterized_kind, mut aggregate_bounds) =
135            Self::aggregate_parts(operator.clone(), axis, generics, fields);
136        let kind = match (concrete_kind, parameterized_kind) {
137            (Some(concrete), Some(parameterized)) => {
138                aggregate_bounds.push(quote! { #concrete: #operator<#parameterized> });
139
140                quote! { <#concrete as #operator<#parameterized>>::Output }
141            }
142            (Some(concrete), None) => concrete,
143            (None, Some(parameterized)) => parameterized,
144            (None, None) => return None,
145        };
146
147        Some(Self {
148            kind,
149            aggregate_bounds,
150        })
151    }
152
153    fn aggregate_with_seed(
154        operator: proc_macro2::TokenStream,
155        axis: proc_macro2::TokenStream,
156        seed: proc_macro2::TokenStream,
157        generics: &syn::Generics,
158        fields: &[&syn::Type],
159    ) -> Self {
160        let (concrete_kind, parameterized_kind, mut aggregate_bounds) =
161            Self::aggregate_parts(operator.clone(), axis, generics, fields);
162
163        let kind = match (concrete_kind, parameterized_kind) {
164            (Some(concrete), Some(parameterized)) => {
165                aggregate_bounds.push(
166                    quote! { <#seed as #operator<#concrete>>::Output: #operator<#parameterized> },
167                );
168                quote! {
169                    <<#seed as #operator<#concrete>>::Output as #operator<#parameterized>>::Output
170                }
171            }
172            (Some(concrete), None) => quote! { <#seed as #operator<#concrete>>::Output },
173            (None, Some(parameterized)) => {
174                aggregate_bounds.push(quote! { #seed: #operator<#parameterized> });
175                quote! { <#seed as #operator<#parameterized>>::Output }
176            }
177            (None, None) => return Self::fixed(seed),
178        };
179
180        Self {
181            kind,
182            aggregate_bounds,
183        }
184    }
185}
186
187fn field_axis_kind(field: &syn::Type, axis: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
188    let crate_ = crate_path();
189
190    quote! { <#field as #crate_::RustSpec>::#axis }
191}
192
193fn field_has_type_params(ty: &syn::Type, generics: &syn::Generics) -> bool {
194    use syn::visit::Visit;
195
196    struct Visitor<'a> {
197        type_params: Vec<&'a syn::Ident>,
198        found: bool,
199    }
200
201    impl<'a> Visitor<'a> {
202        fn new(generics: &'a syn::Generics) -> Self {
203            Self {
204                type_params: generics.type_params().map(|p| &p.ident).collect(),
205                found: false,
206            }
207        }
208    }
209
210    impl syn::visit::Visit<'_> for Visitor<'_> {
211        fn visit_type_path(&mut self, type_path: &syn::TypePath) {
212            if type_path.qself.is_none()
213                && let Some(first_segment) = type_path.path.segments.first()
214                && self.type_params.contains(&&first_segment.ident)
215            {
216                self.found = true;
217            }
218
219            syn::visit::visit_type_path(self, type_path);
220        }
221    }
222
223    let mut visitor = Visitor::new(generics);
224    visitor.visit_type(ty);
225    visitor.found
226}
227
228fn expand(input: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
229    let attrs = parse_rust_spec_attrs(&input.attrs)?;
230    let repr = parse_repr(&input.attrs)?;
231    let alignment = repr.align;
232    let repr = repr.kind.as_ref();
233    let name = &input.ident;
234    let generics = &input.generics;
235    if attrs.opaque && attrs.custom_niche {
236        return Err(syn::Error::new_spanned(
237            name,
238            "`custom_niche` cannot be combined with `opaque`",
239        ));
240    }
241    if attrs.opaque && attrs.custom_drop {
242        return Err(syn::Error::new_spanned(
243            name,
244            "`custom_drop` cannot be combined with `opaque`",
245        ));
246    }
247    if attrs.opaque {
248        let crate_ = crate_path();
249        let mut rust_spec_generics = generics.clone();
250        rust_spec_generics
251            .make_where_clause()
252            .predicates
253            .push(syn::parse_quote!(Self: Sized));
254        let (impl_generics, ty_generics, where_clause) = rust_spec_generics.split_for_impl();
255        return Ok(quote! {
256            unsafe impl #impl_generics #crate_::RustSpec for #name #ty_generics #where_clause {
257                type Layout = #crate_::Stable;
258                type Size = #crate_::size::Sized<#crate_::Gt<#crate_::Zero>>;
259                type __Alignment = #crate_::One;
260                type Trap = #crate_::layout::Robust;
261                type Niche = #crate_::niche::WithoutNiche;
262                type Drop = #crate_::drop::NoDrop;
263                type Mutability = #crate_::mutability::Exclusive<#crate_::mutability::WithoutInterior>;
264                type Opacity = #crate_::Opaque;
265                type __IndirectTrap = #crate_::layout::Robust;
266            }
267        });
268    }
269    if attrs.custom_niche && !matches!(&input.data, syn::Data::Struct(_)) {
270        return Err(syn::Error::new_spanned(
271            name,
272            "`custom_niche` is only supported on structs",
273        ));
274    }
275    let discriminant_assert = if repr.is_none()
276        && let syn::Data::Enum(data) = &input.data
277    {
278        let tag = infer_repr(&data.variants)?;
279        has_unresolved_discriminants(&data.variants)
280            .then(|| gen_discriminant_assert(name, &data.variants, &tag))
281    } else {
282        None
283    };
284    let rust_spec_impl = match &input.data {
285        syn::Data::Struct(data) => {
286            gen_struct_impl(repr, alignment, name, generics, &data.fields, attrs)
287        }
288        syn::Data::Enum(data) if is_fieldless_enum(data) => {
289            gen_fieldless_enum_impl(repr, alignment, name, generics, &data.variants, attrs)
290        }
291        syn::Data::Enum(data)
292            if matches!(repr, Some(ReprKind::Transparent))
293                || repr.is_none() && data.variants.len() == 1 =>
294        {
295            let Some(variant) = data.variants.first() else {
296                return Ok(quote! {});
297            };
298
299            let fields = field_types(&variant.fields);
300            gen_struct_fields_impl(
301                repr,
302                alignment,
303                name,
304                generics,
305                &fields,
306                attrs,
307                gen_mutability_family(generics, &fields),
308            )
309        }
310        syn::Data::Enum(data) => {
311            gen_enum_impl(repr, alignment, name, generics, &data.variants, attrs)
312        }
313        syn::Data::Union(data) if matches!(repr, Some(ReprKind::Transparent)) => {
314            let fields = data
315                .fields
316                .named
317                .iter()
318                .map(|field| &field.ty)
319                .collect::<Vec<_>>();
320
321            gen_struct_fields_impl(
322                Some(&ReprKind::Transparent),
323                alignment,
324                name,
325                generics,
326                &fields,
327                attrs,
328                gen_mutability_family(generics, &fields),
329            )
330        }
331        syn::Data::Union(data) => {
332            gen_union_impl(repr, alignment, name, generics, &data.fields, attrs)
333        }
334    };
335
336    let drop_assert = gen_drop_assert(name, generics, attrs.custom_drop);
337    Ok(quote! { #discriminant_assert #drop_assert #rust_spec_impl })
338}
339
340/// Re-evaluate unresolved discriminants in rustc, including implicit increments.
341/// A default-representation discriminant cannot depend on generic parameters.
342fn gen_discriminant_assert(
343    name: &syn::Ident,
344    variants: &Punctuated<syn::Variant, Token![,]>,
345    tag: &syn::Type,
346) -> proc_macro2::TokenStream {
347    // Rewrite paths, not macro input (e.g. `stringify!(Self)` must stay unchanged).
348    struct QualifySelf<'a>(&'a syn::Ident);
349    impl syn::visit_mut::VisitMut for QualifySelf<'_> {
350        // Nested items have their own Self context.
351        fn visit_item_mut(&mut self, _: &mut syn::Item) {}
352
353        fn visit_path_mut(&mut self, path: &mut syn::Path) {
354            if let Some(first) = path.segments.first_mut()
355                && first.ident == "Self"
356            {
357                first.ident = self.0.clone();
358            }
359            syn::visit_mut::visit_path_mut(self, path);
360        }
361    }
362    let mut base = quote! { 0 };
363    let mut base_index = 0usize;
364    let checks = variants.iter().enumerate().map(|(index, variant)| {
365        if let Some((_, expr)) = &variant.discriminant {
366            let mut expr = expr.clone();
367            syn::visit_mut::VisitMut::visit_expr_mut(&mut QualifySelf(name), &mut expr);
368            base = quote! { #expr };
369            base_index = index;
370        }
371        let offset = index - base_index;
372        // Supply the original expected type without introducing local bindings
373        // that could shadow names used by a discriminant expression.
374        let value = quote! { (core::convert::identity::<isize>(#base) as i128 + #offset as i128) };
375        quote! {
376            assert!(
377                #value >= <#tag>::MIN as i128 && #value <= <#tag>::MAX as i128,
378                concat!("enum discriminant does not fit inferred ", stringify!(#tag),
379                    "; add an explicit integer #[repr(...)]"),
380            );
381        }
382    });
383    quote! { const _: () = { #(#checks)* }; }
384}
385
386fn gen_drop_assert(
387    name: &syn::Ident,
388    generics: &syn::Generics,
389    custom_drop: bool,
390) -> proc_macro2::TokenStream {
391    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
392    if custom_drop {
393        quote! {
394            const _: () = {
395                trait AssertHasDrop { fn check(); }
396                impl #impl_generics AssertHasDrop for #name #ty_generics #where_clause {
397                    fn check() {
398                        fn require_drop<T: core::ops::Drop>() {}
399                        let _ = require_drop::<#name #ty_generics>;
400                    }
401                }
402            };
403        }
404    } else {
405        quote! {
406            const _: () = {
407                trait AssertNoDrop { fn check(); }
408                impl #impl_generics AssertNoDrop for #name #ty_generics #where_clause {
409                    fn check() {
410                        trait DropImplIsNotSupported<A> { fn check() {} }
411                        impl<T: ?Sized> DropImplIsNotSupported<()> for T {}
412                        struct DropDetected;
413                        impl<T: ?Sized + core::ops::Drop> DropImplIsNotSupported<DropDetected> for T {}
414                        let _ = <#name #ty_generics as DropImplIsNotSupported<_>>::check;
415                    }
416                }
417            };
418        }
419    }
420}
421
422#[derive(Clone, Copy, Default)]
423struct RustSpecAttrs {
424    custom_niche: bool,
425    custom_drop: bool,
426    opaque: bool,
427}
428
429fn parse_rust_spec_attrs(attrs: &[syn::Attribute]) -> syn::Result<RustSpecAttrs> {
430    let mut result = RustSpecAttrs::default();
431
432    for attr in attrs
433        .iter()
434        .filter(|attr| attr.path().is_ident("rust_spec"))
435    {
436        attr.parse_nested_meta(|meta| {
437            if meta.path.is_ident("opaque") {
438                if result.opaque {
439                    return Err(meta.error("duplicate `opaque` within attribute"));
440                }
441                result.opaque = true;
442                return Ok(());
443            }
444            if meta.path.is_ident("custom_niche") {
445                if result.custom_niche {
446                    return Err(meta.error("duplicate `custom_niche` within attribute"));
447                }
448                result.custom_niche = true;
449                return Ok(());
450            }
451            if meta.path.is_ident("custom_drop") {
452                if result.custom_drop {
453                    return Err(meta.error("duplicate `custom_drop` within attribute"));
454                }
455                result.custom_drop = true;
456                return Ok(());
457            }
458            Err(meta.error("unknown rust_spec attribute"))
459        })?;
460    }
461
462    Ok(result)
463}
464
465fn is_fieldless_enum(data: &syn::DataEnum) -> bool {
466    data.variants
467        .iter()
468        .all(|variant| matches!(variant.fields, syn::Fields::Unit))
469}
470
471fn field_types(fields: &syn::Fields) -> Vec<&syn::Type> {
472    fields.iter().map(|field| &field.ty).collect()
473}
474
475fn variant_field_types(variants: &Punctuated<syn::Variant, Token![,]>) -> Vec<&syn::Type> {
476    variants
477        .iter()
478        .flat_map(|variant| field_types(&variant.fields))
479        .collect()
480}
481
482fn gen_struct_impl(
483    repr: Option<&ReprKind>,
484    repr_alignment: Option<usize>,
485    name: &syn::Ident,
486    generics: &syn::Generics,
487    fields: &syn::Fields,
488    attrs: RustSpecAttrs,
489) -> proc_macro2::TokenStream {
490    let fields = field_types(fields);
491
492    let mutability = gen_mutability_family(generics, &fields);
493    gen_struct_fields_impl(
494        repr,
495        repr_alignment,
496        name,
497        generics,
498        &fields,
499        attrs,
500        mutability,
501    )
502}
503
504fn gen_struct_fields_impl(
505    repr: Option<&ReprKind>,
506    alignment: Option<usize>,
507    name: &syn::Ident,
508    generics: &syn::Generics,
509    fields: &[&syn::Type],
510    attrs: RustSpecAttrs,
511    mutability: AggregateFamily,
512) -> proc_macro2::TokenStream {
513    let layout = if repr.is_some() {
514        gen_stable_layout_family(generics, fields)
515    } else {
516        gen_unstable_layout_family()
517    };
518    let size = gen_size_family(generics, fields);
519    let alignment = apply_repr_alignment(gen_alignment_family(generics, fields, None), alignment);
520    let trap = if attrs.custom_niche {
521        let crate_ = crate_path();
522        AggregateFamily::fixed(quote! { #crate_::layout::NonRobust })
523    } else {
524        gen_trap_family(generics, fields, false)
525    };
526    let niche = if attrs.custom_niche {
527        let crate_ = crate_path();
528        AggregateFamily::fixed(quote! { #crate_::niche::WithNiche<#crate_::Unstable> })
529    } else if let Some(ReprKind::Transparent) = repr {
530        gen_transparent_niche_family(generics, fields)
531    } else {
532        gen_niche_family(generics, fields)
533    };
534
535    let drop = gen_drop_family(generics, fields, attrs.custom_drop);
536    let indirect_trap = gen_indirect_trap_family(generics, fields);
537
538    let spec = TypeSpecFamilies {
539        layout,
540        size,
541        alignment,
542        trap,
543        niche,
544        drop,
545        mutability,
546        indirect_trap,
547    };
548
549    gen_type_spec_impl(name, generics, fields, spec, quote! {})
550}
551
552fn gen_enum_impl(
553    repr: Option<&ReprKind>,
554    repr_alignment: Option<usize>,
555    name: &syn::Ident,
556    generics: &syn::Generics,
557    variants: &Punctuated<syn::Variant, Token![,]>,
558    attrs: RustSpecAttrs,
559) -> proc_macro2::TokenStream {
560    if matches!(repr, Some(ReprKind::C(None))) {
561        return gen_plain_c_enum_impls(repr_alignment, name, generics, variants, attrs);
562    }
563
564    let crate_ = crate_path();
565    let fields = variant_field_types(variants);
566    let has_trap_tag_values = match repr {
567        None => rust_tag_has_traps(variants),
568        Some(ReprKind::C(Some(tag)) | ReprKind::Primitive(tag)) => {
569            primitive_tag_has_traps(tag, variants.len())
570        }
571        Some(ReprKind::C(None)) => unreachable!("handled by gen_plain_c_enum_impls"),
572        Some(ReprKind::Transparent) => false,
573    };
574    let layout = if repr.is_some() {
575        gen_enum_layout_family(generics, &fields)
576    } else {
577        gen_unstable_layout_family()
578    };
579    let size = AggregateFamily::fixed(quote! {
580        #crate_::size::Sized<#crate_::Gt<#crate_::Zero>>
581    });
582    let tag = match repr {
583        Some(ReprKind::Primitive(tag) | ReprKind::C(Some(tag))) => Some(tag.as_ref().clone()),
584        Some(ReprKind::C(None)) => unreachable!("handled by gen_plain_c_enum_impls"),
585        Some(ReprKind::Transparent) => None,
586        None => Some(infer_repr(variants).expect("validated enum discriminants")),
587    };
588    let alignment = apply_repr_alignment(
589        gen_alignment_family(generics, &fields, tag.as_ref()),
590        repr_alignment,
591    );
592    let trap = gen_trap_family(generics, &fields, has_trap_tag_values);
593
594    let niche = gen_enum_niche_family(has_trap_tag_values);
595    let drop = gen_drop_family(generics, &fields, attrs.custom_drop);
596    let mutability = gen_mutability_family(generics, &fields);
597    let indirect_trap = gen_indirect_trap_family(generics, &fields);
598
599    let spec = TypeSpecFamilies {
600        layout,
601        size,
602        alignment,
603        trap,
604        niche,
605        drop,
606        mutability,
607        indirect_trap,
608    };
609
610    gen_type_spec_impl(name, generics, &fields, spec, quote! {})
611}
612
613fn gen_union_impl(
614    repr: Option<&ReprKind>,
615    repr_alignment: Option<usize>,
616    name: &syn::Ident,
617    generics: &syn::Generics,
618    fields: &syn::FieldsNamed,
619    attrs: RustSpecAttrs,
620) -> proc_macro2::TokenStream {
621    let crate_ = crate_path();
622
623    let fields = fields
624        .named
625        .iter()
626        .map(|field| &field.ty)
627        .collect::<Vec<_>>();
628
629    let layout = if repr.is_some() {
630        gen_stable_layout_family(generics, &fields)
631    } else {
632        gen_unstable_layout_family()
633    };
634    let size = gen_size_family(generics, &fields);
635    let alignment = apply_repr_alignment(
636        gen_alignment_family(generics, &fields, None),
637        repr_alignment,
638    );
639    let trap = AggregateFamily::fixed(quote! {
640        #crate_::layout::Robust
641    });
642    let niche = AggregateFamily::fixed(quote! {
643        #crate_::niche::WithoutNiche
644    });
645    let drop = gen_drop_family(generics, &fields, attrs.custom_drop);
646
647    let mutability = gen_mutability_family(generics, &fields);
648    let indirect_trap = AggregateFamily::fixed(quote! {
649        #crate_::layout::Robust
650    });
651
652    let spec = TypeSpecFamilies {
653        layout,
654        size,
655        alignment,
656        trap,
657        niche,
658        drop,
659        mutability,
660        indirect_trap,
661    };
662
663    gen_type_spec_impl(name, generics, &fields, spec, quote! {})
664}
665
666fn gen_fieldless_enum_impl(
667    repr: Option<&ReprKind>,
668    repr_alignment: Option<usize>,
669    name: &syn::Ident,
670    generics: &syn::Generics,
671    variants: &Punctuated<syn::Variant, Token![,]>,
672    attrs: RustSpecAttrs,
673) -> proc_macro2::TokenStream {
674    if matches!(repr, Some(ReprKind::C(None))) {
675        return gen_plain_c_enum_impls(repr_alignment, name, generics, variants, attrs);
676    }
677
678    let crate_ = crate_path();
679    let layout_kind = match repr {
680        None => quote! { #crate_::Unstable },
681        Some(ReprKind::C(None)) => unreachable!("handled by gen_plain_c_enum_impls"),
682        Some(ReprKind::Transparent) => quote! {
683            #crate_::Stable
684        },
685        Some(ReprKind::C(Some(_)) | ReprKind::Primitive(_)) => {
686            quote! { #crate_::Stable }
687        }
688    };
689    let has_tag = !variants.is_empty()
690        && match repr {
691            Some(ReprKind::C(_) | ReprKind::Primitive(_)) => true,
692            Some(ReprKind::Transparent) | None => variants.len() > 1,
693        };
694
695    let size = if has_tag {
696        AggregateFamily::fixed(quote! { #crate_::size::Sized<#crate_::Gt<#crate_::Zero>> })
697    } else {
698        AggregateFamily::fixed(quote! { #crate_::size::Sized<#crate_::size::Zero> })
699    };
700
701    let niche = if has_tag {
702        let has_trap_tag_values = match repr {
703            None => rust_tag_has_traps(variants),
704            Some(ReprKind::C(Some(tag)) | ReprKind::Primitive(tag)) => {
705                primitive_tag_has_traps(tag, variants.len())
706            }
707            Some(ReprKind::C(None)) => unreachable!("handled by gen_plain_c_enum_impls"),
708            Some(ReprKind::Transparent) => false,
709        };
710        gen_enum_niche_family(has_trap_tag_values)
711    } else {
712        AggregateFamily::fixed(quote! { #crate_::niche::WithoutNiche })
713    };
714
715    let has_trap_tag_values = has_tag
716        && match repr {
717            None => rust_tag_has_traps(variants),
718            Some(ReprKind::C(Some(tag)) | ReprKind::Primitive(tag)) => {
719                primitive_tag_has_traps(tag, variants.len())
720            }
721            Some(ReprKind::C(None)) => unreachable!("handled by gen_plain_c_enum_impls"),
722            Some(ReprKind::Transparent) => false,
723        };
724    let layout = AggregateFamily::fixed(layout_kind);
725    let alignment = if has_tag {
726        let tag = match repr {
727            Some(ReprKind::Primitive(tag) | ReprKind::C(Some(tag))) => tag.as_ref().clone(),
728            Some(ReprKind::C(None)) => unreachable!("handled by gen_plain_c_enum_impls"),
729            Some(ReprKind::Transparent) => {
730                infer_repr(variants).expect("validated enum discriminants")
731            }
732            None => infer_repr(variants).expect("validated enum discriminants"),
733        };
734        gen_alignment_family(generics, &[], Some(&tag))
735    } else {
736        gen_alignment_family(generics, &[], None)
737    };
738    let alignment = apply_repr_alignment(alignment, repr_alignment);
739    let trap = AggregateFamily::fixed(if has_trap_tag_values {
740        quote! { #crate_::layout::NonRobust }
741    } else {
742        quote! { #crate_::layout::Robust }
743    });
744    let mutability = gen_exclusive_mutability_family();
745    let drop = gen_drop_family(generics, &[], attrs.custom_drop);
746    let indirect_trap = AggregateFamily::fixed(quote! { #crate_::layout::Robust });
747
748    let spec = TypeSpecFamilies {
749        layout,
750        size,
751        alignment,
752        trap,
753        niche,
754        drop,
755        mutability,
756        indirect_trap,
757    };
758
759    gen_type_spec_impl(name, generics, &[], spec, quote! {})
760}
761
762fn gen_unstable_layout_family() -> AggregateFamily {
763    let crate_ = crate_path();
764    AggregateFamily::fixed(quote! { #crate_::Unstable })
765}
766
767fn gen_stable_layout_family(generics: &syn::Generics, fields: &[&syn::Type]) -> AggregateFamily {
768    let crate_ = crate_path();
769    if fields
770        .iter()
771        .any(|field| classify::has_unstable_layout(field))
772    {
773        return gen_unstable_layout_family();
774    }
775
776    AggregateFamily::aggregate(
777        quote! { core::ops::Add },
778        quote! { Layout },
779        generics,
780        fields,
781    )
782    .unwrap_or_else(|| AggregateFamily::fixed(quote! { #crate_::Stable }))
783}
784
785fn gen_enum_layout_family(generics: &syn::Generics, fields: &[&syn::Type]) -> AggregateFamily {
786    gen_stable_layout_family(generics, fields)
787}
788
789fn gen_plain_c_enum_impls(
790    repr_alignment: Option<usize>,
791    name: &syn::Ident,
792    generics: &syn::Generics,
793    variants: &Punctuated<syn::Variant, Token![,]>,
794    attrs: RustSpecAttrs,
795) -> proc_macro2::TokenStream {
796    let crate_ = crate_path();
797    let fields = variant_field_types(variants);
798
799    let eight_bit_targets = quote! {
800        #[cfg(all(
801            target_arch = "arm",
802            any(target_os = "none", target_os = "nuttx", target_os = "rtems"),
803        ))]
804    };
805    let sixteen_bit_targets = quote! {
806        #[cfg(any(target_arch = "avr", target_arch = "msp430"))]
807    };
808    let normal_targets = quote! {
809        #[cfg(not(any(
810            all(
811                target_arch = "arm",
812                any(target_os = "none", target_os = "nuttx", target_os = "rtems"),
813            ),
814            target_arch = "avr",
815            target_arch = "msp430",
816        )))]
817    };
818
819    [
820        (eight_bit_targets, 8),
821        (sixteen_bit_targets, 16),
822        (normal_targets, 32),
823    ]
824    .into_iter()
825    .map(|(cfg, bits)| {
826        let tag = c_tag_type(bits);
827        let has_trap_tag_values = primitive_tag_has_traps(&tag, variants.len());
828        let layout = gen_stable_layout_family(generics, &fields);
829        let niche = gen_enum_niche_family(has_trap_tag_values);
830        let spec = TypeSpecFamilies {
831            layout,
832            size: AggregateFamily::fixed(quote! {
833                #crate_::size::Sized<#crate_::Gt<#crate_::Zero>>
834            }),
835            alignment: apply_repr_alignment(
836                gen_alignment_family(generics, &fields, Some(&tag)),
837                repr_alignment,
838            ),
839            trap: gen_trap_family(generics, &fields, has_trap_tag_values),
840            niche,
841            drop: gen_drop_family(generics, &fields, attrs.custom_drop),
842            mutability: gen_mutability_family(generics, &fields),
843            indirect_trap: gen_indirect_trap_family(generics, &fields),
844        };
845
846        gen_type_spec_impl(name, generics, &fields, spec, cfg)
847    })
848    .collect()
849}
850
851fn gen_indirect_trap_family(generics: &syn::Generics, fields: &[&syn::Type]) -> AggregateFamily {
852    let crate_ = crate_path();
853
854    AggregateFamily::aggregate(
855        quote! { core::ops::Add },
856        quote! { __IndirectTrap },
857        generics,
858        fields,
859    )
860    .unwrap_or_else(|| AggregateFamily::fixed(quote! { #crate_::layout::Robust }))
861}
862
863fn gen_drop_family(
864    generics: &syn::Generics,
865    fields: &[&syn::Type],
866    custom_drop: bool,
867) -> AggregateFamily {
868    let crate_ = crate_path();
869    // These fields contribute NoDrop regardless of their pointees' drop behavior.
870    let fields: Vec<_> = fields
871        .iter()
872        .copied()
873        .filter(|field| !classify::is_pointer_or_reference(field))
874        .collect();
875    let mut family = AggregateFamily::aggregate_with_seed(
876        quote! { core::ops::Add },
877        quote! { Drop },
878        quote! { #crate_::drop::NoDrop },
879        generics,
880        &fields,
881    );
882    if custom_drop {
883        let kind = family.kind;
884        family
885            .aggregate_bounds
886            .push(quote! { #kind: #crate_::drop::DropKind });
887        family.kind = quote! { #crate_::drop::CustomDrop<#kind> };
888    }
889    family
890}
891
892fn gen_trap_family(
893    generics: &syn::Generics,
894    fields: &[&syn::Type],
895    has_trap_values: bool,
896) -> AggregateFamily {
897    let crate_ = crate_path();
898
899    if has_trap_values || fields.iter().any(|field| classify::is_reference(field)) {
900        return AggregateFamily::fixed(quote! { #crate_::layout::NonRobust });
901    }
902    let init = quote! { #crate_::layout::Robust };
903
904    AggregateFamily::aggregate_with_seed(
905        quote! { core::ops::Add },
906        quote! { Trap },
907        init,
908        generics,
909        fields,
910    )
911}
912
913fn gen_size_family(generics: &syn::Generics, fields: &[&syn::Type]) -> AggregateFamily {
914    let crate_ = crate_path();
915    if fields.last().is_some_and(|tail| classify::is_slice(tail)) {
916        return AggregateFamily::fixed(quote! {
917            #crate_::size::MetaSized<#crate_::size::SliceLike>
918        });
919    }
920    if fields
921        .iter()
922        .all(|field| classify::classify_nonzero(field) == classify::NonZeroClass::No)
923    {
924        return AggregateFamily::fixed(quote! { #crate_::size::Sized<#crate_::Zero> });
925    }
926    if let Some(tail) = fields.last()
927        && classify::classify_type(tail, generics) == classify::TypeClass::Sized
928        && classify::classify_nonzero(tail) == classify::NonZeroClass::Yes
929    {
930        return AggregateFamily::fixed(quote! { #crate_::size::Sized<#crate_::Gt<#crate_::Zero>> });
931    }
932
933    AggregateFamily::aggregate(quote! { core::ops::Add }, quote! { Size }, generics, fields)
934        .unwrap_or_else(|| AggregateFamily::fixed(quote! { #crate_::size::Sized<#crate_::Zero> }))
935}
936
937fn gen_alignment_family(
938    generics: &syn::Generics,
939    fields: &[&syn::Type],
940    tag: Option<&syn::Type>,
941) -> AggregateFamily {
942    let crate_ = crate_path();
943
944    let mut family = AggregateFamily::aggregate(
945        quote! { #crate_::Max },
946        quote! { __Alignment },
947        generics,
948        fields,
949    )
950    .unwrap_or_else(|| AggregateFamily::fixed(quote! { #crate_::One }));
951
952    if let Some(tag) = tag {
953        let kind = family.kind;
954
955        let tag_kind = quote! { <#tag as #crate_::RustSpec>::__Alignment };
956        family.kind = quote! { #crate_::Maximum<#tag_kind, #kind> };
957    }
958
959    family
960}
961
962fn apply_repr_alignment(mut family: AggregateFamily, align: Option<usize>) -> AggregateFamily {
963    let crate_ = crate_path();
964
965    if matches!(align, Some(value) if value > 1) {
966        let minimum = quote! { #crate_::Gt<#crate_::One> };
967        let kind = family.kind;
968        family.kind = quote! { #crate_::Maximum<#minimum, #kind> };
969    }
970
971    family
972}
973
974fn gen_niche_family(generics: &syn::Generics, fields: &[&syn::Type]) -> AggregateFamily {
975    let crate_ = crate_path();
976
977    AggregateFamily::aggregate_with_seed(
978        quote! { core::ops::Add },
979        quote! { Niche },
980        quote! { #crate_::niche::WithoutNiche },
981        generics,
982        fields,
983    )
984}
985
986fn gen_transparent_niche_family(
987    generics: &syn::Generics,
988    fields: &[&syn::Type],
989) -> AggregateFamily {
990    match fields {
991        [] => {
992            let crate_ = crate_path();
993            AggregateFamily::fixed(quote! { #crate_::niche::WithoutNiche })
994        }
995        [field] => AggregateFamily::fixed(field_axis_kind(field, &quote! { Niche })),
996        _ => gen_niche_family(generics, fields),
997    }
998}
999
1000fn gen_mutability_family(generics: &syn::Generics, fields: &[&syn::Type]) -> AggregateFamily {
1001    AggregateFamily::aggregate(
1002        quote! { core::ops::Add },
1003        quote! { Mutability },
1004        generics,
1005        fields,
1006    )
1007    .unwrap_or_else(gen_exclusive_mutability_family)
1008}
1009
1010fn gen_exclusive_mutability_family() -> AggregateFamily {
1011    let crate_ = crate_path();
1012    AggregateFamily::fixed(
1013        quote! { #crate_::mutability::Exclusive<#crate_::mutability::WithoutInterior> },
1014    )
1015}
1016
1017fn gen_type_spec_impl(
1018    name: &syn::Ident,
1019    generics: &syn::Generics,
1020    fields: &[&syn::Type],
1021    families: TypeSpecFamilies,
1022    attrs: proc_macro2::TokenStream,
1023) -> proc_macro2::TokenStream {
1024    let TypeSpecFamilies {
1025        layout,
1026        size,
1027        alignment,
1028        trap,
1029        niche,
1030        drop,
1031        mutability,
1032        indirect_trap,
1033    } = families;
1034
1035    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1036    let predicates = where_clause
1037        .as_ref()
1038        .map(|where_clause| &where_clause.predicates);
1039
1040    let crate_ = crate_path();
1041    let field_bounds = fields
1042        .iter()
1043        .filter(|field| field_has_type_params(field, generics))
1044        .map(|field| quote! { #field: #crate_::RustSpec });
1045
1046    let aggregate_bounds = layout
1047        .aggregate_bounds
1048        .into_iter()
1049        .chain(size.aggregate_bounds)
1050        .chain(alignment.aggregate_bounds)
1051        .chain(trap.aggregate_bounds)
1052        .chain(niche.aggregate_bounds)
1053        .chain(drop.aggregate_bounds)
1054        .chain(mutability.aggregate_bounds)
1055        .chain(indirect_trap.aggregate_bounds);
1056
1057    let layout_kind = layout.kind;
1058    let size_kind = size.kind;
1059    let alignment_kind = alignment.kind;
1060    let trap_kind = trap.kind;
1061    let niche_kind = niche.kind;
1062    let drop_kind = drop.kind;
1063    let mutability_kind = mutability.kind;
1064    let indirect_trap_kind = indirect_trap.kind;
1065
1066    quote! {
1067        #attrs
1068        unsafe impl #impl_generics #crate_::RustSpec for #name #ty_generics where
1069            #(#field_bounds,)*
1070            #(#aggregate_bounds,)*
1071            #predicates
1072        {
1073            type Layout = #layout_kind;
1074            type Size = #size_kind;
1075            type __Alignment = #alignment_kind;
1076            type Trap = #trap_kind;
1077            type Niche = #niche_kind;
1078            type Drop = #drop_kind;
1079            type Mutability = #mutability_kind;
1080            type Opacity = #crate_::NonOpaque;
1081            type __IndirectTrap = #indirect_trap_kind;
1082        }
1083    }
1084}
1085
1086fn gen_enum_niche_family(has_trap_tag_values: bool) -> AggregateFamily {
1087    let crate_ = crate_path();
1088
1089    let niche_kind = if has_trap_tag_values {
1090        quote! { #crate_::niche::WithNiche<#crate_::Unstable> }
1091    } else {
1092        quote! { #crate_::niche::WithoutNiche }
1093    };
1094
1095    AggregateFamily::fixed(niche_kind)
1096}
1097
1098fn primitive_tag_has_traps(tag: &syn::Type, variant_count: usize) -> bool {
1099    !is_exhaustive_enum(variant_count, tag)
1100}
1101
1102fn rust_tag_has_traps(variants: &Punctuated<syn::Variant, Token![,]>) -> bool {
1103    let tag = infer_repr(variants).expect("validated enum discriminants");
1104    primitive_tag_has_traps(&tag, variants.len())
1105}
1106
1107fn c_tag_type(c_enum_bits: u32) -> syn::Type {
1108    match c_enum_bits {
1109        8 => syn::parse_quote!(u8),
1110        16 => syn::parse_quote!(u16),
1111        32 => syn::parse_quote!(u32),
1112        _ => unreachable!("unsupported C enum width"),
1113    }
1114}
1115
1116#[cfg(test)]
1117mod axis_short_circuit_tests {
1118    use super::*;
1119    use syn::parse_quote;
1120
1121    #[test]
1122    fn known_fields_remove_generic_axis_combinations() {
1123        let generics = parse_quote!(<T, U>);
1124        let first: syn::Type = parse_quote!(T);
1125        let second: syn::Type = parse_quote!(U);
1126        let crate_ = crate_path();
1127
1128        for known in [
1129            parse_quote!((T,)),
1130            parse_quote!(&[T]),
1131            parse_quote!(&mut [T]),
1132            parse_quote!(*const [T]),
1133            parse_quote!(*mut [T]),
1134        ] {
1135            let family = gen_stable_layout_family(&generics, &[&first, &second, &known]);
1136            assert!(family.aggregate_bounds.is_empty());
1137            assert_eq!(
1138                family.kind.to_string(),
1139                quote!(#crate_::Unstable).to_string()
1140            );
1141        }
1142        for known in [parse_quote!(&T), parse_quote!(&mut T)] {
1143            let family = gen_trap_family(&generics, &[&first, &second, &known], false);
1144            assert!(family.aggregate_bounds.is_empty());
1145            assert_eq!(
1146                family.kind.to_string(),
1147                quote!(#crate_::layout::NonRobust).to_string()
1148            );
1149        }
1150        let tagged = gen_trap_family(&generics, &[&first, &second], true);
1151        assert!(tagged.aggregate_bounds.is_empty());
1152        assert_eq!(
1153            tagged.kind.to_string(),
1154            quote!(#crate_::layout::NonRobust).to_string()
1155        );
1156
1157        let tail = parse_quote!([U]);
1158        let family = gen_size_family(&generics, &[&first, &tail]);
1159        assert!(family.aggregate_bounds.is_empty());
1160        assert_eq!(
1161            family.kind.to_string(),
1162            quote!(#crate_::size::MetaSized<#crate_::size::SliceLike>).to_string()
1163        );
1164    }
1165
1166    #[test]
1167    fn unknown_layouts_and_raw_pointer_traps_are_not_short_circuited() {
1168        for field in [
1169            parse_quote!(()),
1170            parse_quote!([T]),
1171            parse_quote!(*const T),
1172            parse_quote!(&T),
1173            parse_quote!(CStr),
1174        ] {
1175            assert!(!classify::has_unstable_layout(&field));
1176        }
1177        assert!(!classify::is_reference(&parse_quote!(*const T)));
1178        assert!(classify::has_unstable_layout(&parse_quote!(
1179            ((*const ([T])))
1180        )));
1181        assert!(classify::is_reference(&parse_quote!((&mut T))));
1182    }
1183}