dst_container_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Ident;
3use proc_macro_crate::{crate_name, FoundCrate};
4use quote::quote;
5use syn::{
6    parse::{Parse, Parser},
7    parse_str, Attribute, Data, DeriveInput, Field, Fields, GenericParam, Generics, Type,
8    Visibility,
9};
10
11struct PreDerive {
12    attrs: Vec<Attribute>,
13    vis: Visibility,
14    struct_name: Ident,
15    generics: Generics,
16    data: Data,
17    generic_inputs: proc_macro2::TokenStream,
18    dst_crate_name: proc_macro2::TokenStream,
19}
20
21fn pre_derive(input: TokenStream) -> PreDerive {
22    let struct_input: DeriveInput = syn::parse(input).unwrap();
23    let generics = struct_input.generics;
24    let generic_inputs = generics
25        .params
26        .iter()
27        .map(|p| match p {
28            GenericParam::Type(p) => {
29                let ident = &p.ident;
30                quote!(#ident)
31            }
32            GenericParam::Lifetime(p) => {
33                let life = &p.lifetime;
34                quote!(#life)
35            }
36            GenericParam::Const(p) => {
37                let ident = &p.ident;
38                quote!(#ident)
39            }
40        })
41        .collect::<Vec<_>>();
42    let generic_inputs = if generic_inputs.is_empty() {
43        quote!()
44    } else {
45        quote!(<#(#generic_inputs,)*>)
46    };
47
48    let dst_crate_name = match crate_name("dst-container").unwrap() {
49        FoundCrate::Itself => quote!(crate),
50        FoundCrate::Name(name) => {
51            let name = parse_str::<Ident>(&name).unwrap();
52            quote!(::#name)
53        }
54    };
55
56    PreDerive {
57        attrs: struct_input.attrs,
58        vis: struct_input.vis,
59        struct_name: struct_input.ident,
60        generics,
61        data: struct_input.data,
62        generic_inputs,
63        dst_crate_name,
64    }
65}
66
67#[proc_macro_derive(MaybeUninitProject)]
68pub fn derive_maybe_uninit_project(input: TokenStream) -> TokenStream {
69    let PreDerive {
70        attrs,
71        vis,
72        struct_name,
73        generics,
74        data,
75        generic_inputs,
76        dst_crate_name,
77    } = pre_derive(input);
78
79    let project_struct_name = parse_str::<Ident>(&format!("__MaybeUninit{struct_name}")).unwrap();
80
81    let repr = attrs
82        .iter()
83        .find(|attr| {
84            attr.path()
85                .get_ident()
86                .map_or(false, |ident| ident == "repr")
87        })
88        .expect("Need #[repr(...)].");
89    let repr_content = repr.meta.require_list().unwrap().tokens.to_string();
90    if !matches!(repr_content.as_str(), "C" | "packed" | "transparent") {
91        panic!(
92            "Expected #[repr(C)], #[repr(packed)], #[repr(transparent)] only, get `{}`.",
93            repr_content
94        );
95    }
96
97    let project_data_declare = match data {
98        Data::Struct(data) => match data.fields {
99            Fields::Named(fields) => {
100                let fields = fields.named.into_iter().collect::<Vec<_>>();
101                let fields = map_maybe_uninit_fields(fields, &dst_crate_name);
102                quote!({#(#fields,)*})
103            }
104            Fields::Unnamed(fields) => {
105                let fields = fields.unnamed.into_iter().collect::<Vec<_>>();
106                let fields = map_maybe_uninit_fields(fields, &dst_crate_name);
107                quote!((#(#fields,)*);)
108            }
109            _ => unimplemented!(),
110        },
111        _ => unimplemented!(),
112    };
113
114    let output = quote! {
115        #[doc(hidden)]
116        #repr
117        #vis struct #project_struct_name #generics #project_data_declare
118
119        impl #generics #dst_crate_name :: MaybeUninitProject for #struct_name #generic_inputs {
120            type Target = #project_struct_name #generic_inputs;
121        }
122    };
123    TokenStream::from(output)
124}
125
126fn map_maybe_uninit_fields(
127    fields: impl IntoIterator<Item = Field>,
128    dst_crate_name: &proc_macro2::TokenStream,
129) -> Vec<Field> {
130    fields
131        .into_iter()
132        .map(|mut field| {
133            let ty = field.ty;
134            field.ty = Type::parse
135                .parse2(quote!(<#ty as #dst_crate_name :: MaybeUninitProject>::Target))
136                .unwrap();
137            field
138        })
139        .collect()
140}
141
142#[proc_macro_derive(UnsizedClone)]
143pub fn derive_unsized_clone(input: TokenStream) -> TokenStream {
144    let PreDerive {
145        attrs: _,
146        vis: _,
147        struct_name,
148        generics,
149        data,
150        generic_inputs,
151        dst_crate_name,
152    } = pre_derive(input);
153
154    let (types, idents) = match data {
155        Data::Struct(data) => match data.fields {
156            Fields::Named(fields) => {
157                let fields = fields.named.into_iter().collect::<Vec<_>>();
158                let types = fields
159                    .iter()
160                    .map(|field| &field.ty)
161                    .cloned()
162                    .collect::<Vec<_>>();
163                let idents = fields
164                    .iter()
165                    .map(|field| field.ident.as_ref().unwrap())
166                    .cloned()
167                    .collect::<Vec<_>>();
168                (types, idents)
169            }
170            Fields::Unnamed(fields) => {
171                let fields = fields.unnamed.into_iter().collect::<Vec<_>>();
172                let types = fields
173                    .iter()
174                    .map(|field| &field.ty)
175                    .cloned()
176                    .collect::<Vec<_>>();
177                let idents = (0..fields.len())
178                    .map(|i| Ident::parse.parse_str(&i.to_string()).unwrap())
179                    .collect::<Vec<_>>();
180                (types, idents)
181            }
182            _ => unimplemented!(),
183        },
184        _ => unimplemented!(),
185    };
186
187    let where_clause = if types.is_empty() {
188        quote!()
189    } else {
190        quote!(where #(#types: UnsizedClone,)*)
191    };
192    let clone_to_statement = idents
193        .into_iter()
194        .map(|ident| quote!(self.#ident.clone_to(&mut dest.#ident);))
195        .collect::<Vec<_>>();
196
197    let output = quote! {
198        impl #generics #dst_crate_name :: UnsizedClone for #struct_name #generic_inputs #where_clause {
199            fn clone_to(&self, dest: &mut Self::Target) {
200                #(#clone_to_statement)*
201            }
202        }
203    };
204    TokenStream::from(output)
205}