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#[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
340fn gen_discriminant_assert(
343 name: &syn::Ident,
344 variants: &Punctuated<syn::Variant, Token![,]>,
345 tag: &syn::Type,
346) -> proc_macro2::TokenStream {
347 struct QualifySelf<'a>(&'a syn::Ident);
349 impl syn::visit_mut::VisitMut for QualifySelf<'_> {
350 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 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 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, "e! { 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}