rune_alloc_macros/
try_clone.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::parse::Parse;
4
5use crate::context::{Context, Tokens};
6
7pub(super) fn expand(mut input: syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
8    let cx = Context::new();
9
10    let attr = parse_type_attr(&cx, &input.attrs);
11
12    let tokens = cx.tokens_with_module(attr.module.as_ref());
13
14    if !attr.predicates.is_empty() {
15        input
16            .generics
17            .make_where_clause()
18            .predicates
19            .extend(attr.predicates);
20    }
21
22    let Tokens {
23        try_clone, alloc, ..
24    } = &tokens;
25
26    let implementation = if attr.copy {
27        quote!(*self)
28    } else {
29        match input.data {
30            syn::Data::Struct(st) => {
31                let fields = st.fields.into_iter().enumerate().map(|(index, f)| {
32                    let member = match &f.ident {
33                        Some(ident) => syn::Member::Named(ident.clone()),
34                        None => syn::Member::Unnamed(syn::Index::from(index)),
35                    };
36
37                    let attr = parse_field_attr(&cx, &f.attrs);
38
39                    let expr = match attr.with {
40                        With::Copy => quote! { self.#member },
41                        With::None => quote! { #try_clone::try_clone(&self.#member)? },
42                        With::With(with) => quote! { #with(&self.#member) },
43                        With::TryWith(with) => quote! { #with(&self.#member)? },
44                    };
45
46                    syn::FieldValue {
47                        attrs: Vec::new(),
48                        member,
49                        colon_token: Some(<syn::Token![:]>::default()),
50                        expr: syn::Expr::Verbatim(expr),
51                    }
52                });
53
54                quote! {
55                    Self { #(#fields),* }
56                }
57            }
58            syn::Data::Enum(en) => {
59                let variants = en.variants.into_iter().map(|v| {
60                    let name = v.ident;
61
62                    let members = v.fields.iter().enumerate().map(|(index, f)| {
63                        let (member, var) = match &f.ident {
64                            Some(ident) => (
65                                syn::Member::Named(ident.clone()),
66                                quote::format_ident!("{}", ident),
67                            ),
68                            None => (
69                                syn::Member::Unnamed(syn::Index::from(index)),
70                                quote::format_ident!("_{}", index),
71                            ),
72                        };
73
74                        let attr = parse_field_attr(&cx, &f.attrs);
75                        (index, f, member, var, attr)
76                    });
77
78                    let assigns = members.clone().map(|(index, f, member, var, _)| {
79                        if f.ident.is_some() {
80                            syn::FieldValue {
81                                attrs: Vec::new(),
82                                member,
83                                colon_token: None,
84                                expr: syn::Expr::Verbatim(quote!()),
85                            }
86                        } else {
87                            let member = syn::Member::Unnamed(syn::Index::from(index));
88
89                            let expr = syn::Expr::Path(syn::ExprPath {
90                                attrs: Vec::new(),
91                                qself: None,
92                                path: syn::Path::from(var),
93                            });
94
95                            syn::FieldValue {
96                                attrs: Vec::new(),
97                                member,
98                                colon_token: Some(<syn::Token![:]>::default()),
99                                expr,
100                            }
101                        }
102                    });
103
104                    let fields = members.clone().map(|(_, _, member, var, attr)| {
105                        let expr = match attr.with {
106                            With::Copy => quote! { *#var },
107                            With::None => quote! { #try_clone::try_clone(#var)? },
108                            With::With(with) => quote! { #with(#var) },
109                            With::TryWith(with) => quote! { #with(#var)? },
110                        };
111
112                        syn::FieldValue {
113                            attrs: Vec::new(),
114                            member,
115                            colon_token: Some(<syn::Token![:]>::default()),
116                            expr: syn::Expr::Verbatim(expr),
117                        }
118                    });
119
120                    quote! {
121                        Self::#name { #(#assigns),* } => {
122                            Self::#name { #(#fields),* }
123                        }
124                    }
125                });
126
127                quote! {
128                    match self {
129                        #(#variants),*
130                    }
131                }
132            }
133            syn::Data::Union(un) => {
134                cx.error(syn::Error::new_spanned(
135                    un.union_token,
136                    "TryClone: Unions are not supported",
137                ));
138                quote!()
139            }
140        }
141    };
142
143    if cx.has_errors() {
144        return Err(cx.into_errors());
145    }
146
147    let name = input.ident;
148    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
149
150    Ok(quote! {
151        impl #impl_generics #try_clone for #name #ty_generics #where_clause {
152            fn try_clone(&self) -> #alloc::Result<Self> {
153                Ok(#implementation)
154            }
155        }
156    })
157}
158
159#[derive(Default)]
160struct TypeAttr {
161    predicates: syn::punctuated::Punctuated<syn::WherePredicate, syn::Token![,]>,
162    copy: bool,
163    module: Option<syn::Path>,
164}
165
166fn parse_type_attr(cx: &Context, input: &[syn::Attribute]) -> TypeAttr {
167    let mut attr = TypeAttr::default();
168
169    for a in input {
170        if !a.path().is_ident("try_clone") {
171            continue;
172        }
173
174        let result = a.parse_nested_meta(|parser| {
175            if parser.path.is_ident("bound") {
176                parser.input.parse::<syn::Token![=]>()?;
177                let content;
178                syn::braced!(content in parser.input);
179                attr.predicates
180                    .extend(content.parse_terminated(syn::WherePredicate::parse, syn::Token![,])?);
181                return Ok(());
182            }
183
184            if parser.path.is_ident("copy") {
185                attr.copy = true;
186                return Ok(());
187            }
188
189            if parser.path.is_ident("crate") {
190                if parser.input.parse::<Option<syn::Token![=]>>()?.is_some() {
191                    attr.module = Some(parser.input.parse::<syn::Path>()?);
192                } else {
193                    attr.module = Some(syn::parse_quote!(crate));
194                }
195
196                return Ok(());
197            }
198
199            Err(syn::Error::new(
200                parser.input.span(),
201                "unsupported attribute",
202            ))
203        });
204
205        if let Err(error) = result {
206            cx.error(error);
207        }
208    }
209
210    attr
211}
212
213#[derive(Default, Clone)]
214enum With {
215    #[default]
216    None,
217    Copy,
218    With(syn::Path),
219    TryWith(syn::Path),
220}
221
222#[derive(Default, Clone)]
223struct FieldAttr {
224    with: With,
225}
226
227fn parse_field_attr(cx: &Context, input: &[syn::Attribute]) -> FieldAttr {
228    let mut attr = FieldAttr::default();
229
230    for a in input {
231        if !a.path().is_ident("try_clone") {
232            continue;
233        }
234
235        let result = a.parse_nested_meta(|parser| {
236            if parser.path.is_ident("with") {
237                parser.input.parse::<syn::Token![=]>()?;
238                attr.with = With::With(parser.input.parse()?);
239                return Ok(());
240            }
241
242            if parser.path.is_ident("try_with") {
243                parser.input.parse::<syn::Token![=]>()?;
244                attr.with = With::TryWith(parser.input.parse()?);
245                return Ok(());
246            }
247
248            if parser.path.is_ident("copy") {
249                attr.with = With::Copy;
250                return Ok(());
251            }
252
253            Err(syn::Error::new(
254                parser.input.span(),
255                "unsupported attribute",
256            ))
257        });
258
259        if let Err(error) = result {
260            cx.error(error);
261        }
262    }
263
264    attr
265}