Skip to main content

stable_type_layout_derive/
lib.rs

1//! Derive macro for `stable_type_layout::TypeLayout`.
2//!
3//! Use it through the [`stable-type-layout`](https://docs.rs/stable-type-layout) crate, which re-exports this macro
4//! alongside the trait it implements.
5
6use proc_macro::TokenStream;
7use proc_macro2::TokenStream as TokenStream2;
8use quote::quote;
9use syn::{Data, DeriveInput, Fields, Ident, Index, parse_macro_input};
10
11/// Implements `TypeLayout`, recording the type's size, alignment, field offsets and enum variant discriminants as a
12/// `const`.
13///
14/// Structs and unions record every field's name and byte offset. Enums record every variant's name and declaration
15/// index, plus its discriminant when the enum is fieldless. Lifetime, type and const parameters are carried onto the
16/// impl, so a generic type describes whichever instantiation is asked for.
17///
18/// See the `stable_type_layout` crate docs for examples; this crate cannot depend on it to run one here.
19#[proc_macro_derive(TypeLayout)]
20pub fn derive_type_layout(input: TokenStream) -> TokenStream {
21	let ast = parse_macro_input!(input as DeriveInput);
22	let ident = &ast.ident;
23	let name = ident.to_string();
24	let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
25	let structure = match &ast.data {
26		Data::Struct(data) => {
27			let fields = fields(&data.fields);
28			quote! { ::stable_type_layout::TypeStructure::Struct { fields: &[#(#fields),*] } }
29		}
30		Data::Union(data) => {
31			let fields = data.fields.named.iter().map(|f| named_field(f.ident.as_ref().unwrap()));
32			quote! { ::stable_type_layout::TypeStructure::Union { fields: &[#(#fields),*] } }
33		}
34		Data::Enum(data) => {
35			// A fieldless enum is castable, so its real compiler-assigned tag value can be recorded. A data-carrying variant
36			// is not, and only its index is pinned.
37			let fieldless = data.variants.iter().all(|v| matches!(v.fields, Fields::Unit));
38			let variants = data.variants.iter().enumerate().map(|(index, variant)| {
39				let variant_ident = &variant.ident;
40				let variant_name = variant_ident.to_string();
41				let discriminant = if fieldless {
42					quote! { ::core::option::Option::Some(Self::#variant_ident as i64) }
43				} else {
44					quote! { ::core::option::Option::None }
45				};
46				quote! {
47					::stable_type_layout::Variant {
48						name: #variant_name,
49						index: #index,
50						discriminant: #discriminant,
51					}
52				}
53			});
54			quote! { ::stable_type_layout::TypeStructure::Enum { variants: &[#(#variants),*] } }
55		}
56	};
57	quote! {
58		impl #impl_generics ::stable_type_layout::TypeLayout for #ident #ty_generics #where_clause {
59			const TYPE_LAYOUT: ::stable_type_layout::TypeLayoutInfo = ::stable_type_layout::TypeLayoutInfo {
60				name: #name,
61				size: ::core::mem::size_of::<Self>(),
62				align: ::core::mem::align_of::<Self>(),
63				structure: #structure,
64			};
65		}
66	}
67	.into()
68}
69
70fn fields(fields: &Fields) -> Vec<TokenStream2> {
71	match fields {
72		Fields::Named(named) => named.named.iter().map(|f| named_field(f.ident.as_ref().unwrap())).collect(),
73		Fields::Unnamed(unnamed) => (0..unnamed.unnamed.len())
74			.map(|i| {
75				let index = Index::from(i);
76				let name = i.to_string();
77				quote! { ::stable_type_layout::Field { name: #name, offset: ::core::mem::offset_of!(Self, #index) } }
78			})
79			.collect(),
80		Fields::Unit => vec![],
81	}
82}
83
84fn named_field(ident: &Ident) -> TokenStream2 {
85	let name = ident.to_string();
86	quote! { ::stable_type_layout::Field { name: #name, offset: ::core::mem::offset_of!(Self, #ident) } }
87}