type_layout_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use proc_macro2::{Ident, Literal};
6use quote::{quote, quote_spanned, ToTokens};
7use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Fields};
8
9#[proc_macro_derive(TypeLayout)]
10pub fn derive_type_layout(input: TokenStream) -> TokenStream {
11    // Parse the input tokens into a syntax tree
12    let input = parse_macro_input!(input as DeriveInput);
13
14    // Used in the quasi-quotation below as `#name`.
15    let name = input.ident;
16    let name_str = Literal::string(&name.to_string());
17
18    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
19    let layout = layout_of_type(&name, &input.data);
20
21    // Build the output, possibly using quasi-quotation
22    let expanded = quote! {
23        impl #impl_generics ::type_layout::TypeLayout for #name #ty_generics #where_clause {
24            fn type_layout() -> ::type_layout::TypeLayoutInfo {
25                let mut last_field_end = 0;
26                let mut fields = Vec::new();
27
28                #layout
29
30                ::type_layout::TypeLayoutInfo {
31                    name: ::std::borrow::Cow::Borrowed(#name_str),
32                    size: std::mem::size_of::<#name>(),
33                    alignment: ::std::mem::align_of::<#name>(),
34                    fields,
35                }
36            }
37        }
38    };
39
40    // Hand the output tokens back to the compiler
41    TokenStream::from(expanded)
42}
43
44fn layout_of_type(struct_name: &Ident, data: &Data) -> proc_macro2::TokenStream {
45    match data {
46        Data::Struct(data) => match &data.fields {
47            Fields::Named(fields) => {
48                let values = fields.named.iter().map(|field| {
49                    let field_name = field.ident.as_ref().unwrap();
50                    let field_name_str = Literal::string(&field_name.to_string());
51                    let field_ty = &field.ty;
52                    let field_ty_str = Literal::string(&field_ty.to_token_stream().to_string());
53
54                    quote_spanned! { field.span() =>
55                        #[allow(unused_assignments)]
56                        {
57                            let size = ::std::mem::size_of::<#field_ty>();
58                            let offset = ::type_layout::memoffset::offset_of!(#struct_name, #field_name);
59
60                            if offset > last_field_end {
61                                fields.push(::type_layout::Field::Padding {
62                                    size: offset - last_field_end
63                                });
64                            }
65
66                            fields.push(::type_layout::Field::Field {
67                                name: ::std::borrow::Cow::Borrowed(#field_name_str),
68                                ty: ::std::borrow::Cow::Borrowed(#field_ty_str),
69                                size,
70                            });
71
72                            last_field_end = offset + size;
73                        }
74                    }
75                });
76
77                quote! {
78                    #(#values)*
79
80                    let struct_size = ::std::mem::size_of::<#struct_name>();
81                    if struct_size > last_field_end {
82                        fields.push(::type_layout::Field::Padding {
83                            size: struct_size - last_field_end,
84                        });
85                    }
86                }
87            }
88            Fields::Unnamed(_) => unimplemented!(),
89            Fields::Unit => unimplemented!(),
90        },
91        Data::Enum(_) | Data::Union(_) => unimplemented!("type-layout only supports structs"),
92    }
93}