Skip to main content

sealed_generic/
lib.rs

1use convert_case::{Case, Casing};
2use darling::*;
3use proc_macro::TokenStream;
4use quote::quote;
5use std::collections::HashMap;
6use syn::{Ident, Meta, TypePath, parse_macro_input};
7
8#[derive(Debug, FromField)]
9#[darling(attributes(define))]
10struct Field {
11    ident: Option<syn::Ident>,
12    ty: syn::Type,
13}
14
15#[derive(Debug, FromMeta)]
16enum Type {
17    Ty(syn::Path),
18    With {
19        ty: syn::Path,
20
21        #[darling(default, multiple)]
22        derives: Vec<syn::Path>,
23
24        #[darling(default, multiple)]
25        attr: Vec<String>,
26    },
27}
28
29impl Type {
30    fn ident(&self) -> &syn::Path {
31        match self {
32            Self::Ty(path) => path,
33            Self::With { ty, .. } => ty,
34        }
35    }
36
37    fn derives(&self) -> Vec<syn::Path> {
38        match self {
39            Self::With { derives, .. } => derives.clone(),
40            _ => vec![],
41        }
42    }
43
44    fn attrs(&self) -> Vec<String> {
45        match self {
46            Self::With { attr, .. } => attr.clone(),
47            _ => vec![],
48        }
49    }
50}
51
52#[derive(Debug, FromDeriveInput)]
53#[darling(attributes(define), supports(struct_any))]
54struct Input {
55    vis: syn::Visibility,
56    ident: syn::Ident,
57    generics: syn::Generics,
58    data: ast::Data<(), Field>,
59
60    #[darling(default, multiple)]
61    types: Vec<Type>,
62
63    #[darling(default, multiple)]
64    derives: Vec<syn::Path>,
65
66    #[darling(default, multiple)]
67    attr: Vec<String>,
68
69    #[darling(default)]
70    sealed: bool,
71}
72
73impl Input {
74    fn merge<T: Clone>(a1: Vec<T>, a2: &Vec<T>) -> Vec<T> {
75        let mut a1 = a1;
76        let mut a2 = a2.clone();
77        a1.append(&mut a2);
78        a1
79    }
80
81    fn derives(&self, derives: Vec<syn::Path>) -> Vec<syn::Path> {
82        Self::merge(derives, &self.derives)
83    }
84
85    fn attrs(&self, attrs: Vec<String>) -> Vec<String> {
86        Self::merge(attrs, &self.attr)
87    }
88}
89
90enum TypeOrGeneric {
91    Type { field: Ident, ty: syn::Type },
92    Generic(Ident),
93    GenericWithNested { field: Ident, path: TypePath },
94}
95
96impl ToTokens for Input {
97    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
98        let ident = self.ident.clone();
99
100        let vis = self.vis.clone();
101
102        let generic: Ident;
103
104        let tys = self.generics.type_params().collect::<Vec<_>>();
105        let lts = self.generics.lifetimes().collect::<Vec<_>>();
106
107        if tys.len() > 1 {
108            panic!("only one generic type may be specified")
109        }
110
111        generic = tys.get(0).unwrap().ident.clone();
112
113        let mut fields = vec![];
114        let mut fields_eq = vec![];
115        let mut field_lts = HashMap::new();
116
117        for f in self.data.as_ref().take_struct().expect("struct") {
118            let field = f.ident.clone().expect("ident");
119            let ty = f.ty.clone();
120
121            fields_eq.push(quote!(
122                #field: value.#field,
123            ));
124
125            let mut with_path = |path: &syn::TypePath| {
126                let segment = path.path.segments.first().expect("path segment");
127                let ident = segment.ident.clone();
128                let field = field.clone();
129                let ty = ty.clone();
130                if generic == ident {
131                    fields.push(TypeOrGeneric::Generic(field))
132                } else if !segment.arguments.is_empty() {
133                    fields.push(TypeOrGeneric::GenericWithNested {
134                        field: field,
135                        path: path.clone(),
136                    })
137                } else {
138                    fields.push(TypeOrGeneric::Type { field, ty })
139                }
140            };
141
142            match ty.clone() {
143                syn::Type::Reference(typeref) => {
144                    if let Some(lt) = typeref.lifetime {
145                        field_lts.insert(field.clone(), lt);
146                    }
147                    match *typeref.elem {
148                        syn::Type::Path(ref path) => with_path(path),
149                        ty => panic!("unknown type: {ty:#?}"),
150                    }
151                }
152                syn::Type::Path(path) => {
153                    with_path(&path);
154                }
155                _ => panic!("unknown type"),
156            }
157        }
158
159        let fields_eq: proc_macro2::TokenStream = fields_eq.into_iter().collect();
160
161        let seal_mod =
162            Ident::from_string(&("Sealed".to_string() + &ident.to_string()).to_case(Case::Pascal))
163                .expect("sealed trait ident to parse");
164
165        if self.sealed {
166            tokens.extend(quote!(
167                pub(crate) trait #seal_mod {}
168            ));
169        }
170
171        let get_field_lt = |field: &Ident, ty: proc_macro2::TokenStream| {
172            let field_pre = if let Some(lt) = field_lts.get(&field) {
173                let ex: proc_macro2::TokenStream = format!("&{} {}", quote!(#lt), ty.to_string())
174                    .parse()
175                    .expect("type with lifetime");
176
177                quote!(#field: #ex,)
178            } else {
179                quote!(
180                    #field: #ty,
181                )
182                .into()
183            };
184
185            field_pre
186        };
187
188        let has_lts = !lts.is_empty();
189        let lts: proc_macro2::TokenStream = lts.iter().map(|lt| quote!(#lt,)).collect();
190        let lt_quo = if has_lts { quote!(<#lts>) } else { quote!() };
191
192        for t in &self.types {
193            let tt = t.ident();
194            let new_ident = Ident::from_string(
195                &format!(
196                    "{}{}",
197                    ident.to_string(),
198                    quote!(#tt).to_string().to_case(Case::Pascal)
199                )
200                .to_case(Case::Pascal),
201            )
202            .expect("type ident to parse");
203
204            let new_fields: proc_macro2::TokenStream = fields
205                .iter()
206                .map(|it| match it {
207                    TypeOrGeneric::Generic(field) => get_field_lt(field, quote!(#tt)),
208                    TypeOrGeneric::GenericWithNested { field, path } => {
209                        let new_ty = quote!(#path).to_string().replace(" ", "").replace(
210                            &format!("<{}>", generic.to_string()),
211                            &quote!(<#tt>).to_string(),
212                        );
213                        let as_path = syn::Path::from_string(&new_ty).unwrap();
214                        get_field_lt(field, quote!(#as_path))
215                    }
216                    TypeOrGeneric::Type { field, ty } => get_field_lt(field, quote!(#ty)),
217                })
218                .collect();
219
220            let derives: proc_macro2::TokenStream = self
221                .derives(t.derives())
222                .iter()
223                .map(|it| quote::quote!(#it,))
224                .collect();
225
226            let atts: proc_macro2::TokenStream = self
227                .attrs(t.attrs())
228                .iter()
229                .map(|it| {
230                    let it: Meta = syn::parse_str(&it).expect("meta to parse");
231                    quote::quote!(#[#it])
232                })
233                .collect();
234
235            tokens.extend(quote! {
236                #[derive(#derives)]
237                #atts
238                #vis struct #new_ident #lt_quo {
239                    #new_fields
240                }
241            });
242
243            if self.sealed {
244                tokens.extend(quote!(
245                    impl #lt_quo #seal_mod for #tt #lt_quo {}
246                ));
247            }
248
249            tokens.extend(quote! {
250                impl #lt_quo From<#new_ident #lt_quo> for #ident<#lts #tt> {
251                    fn from(value: #new_ident #lt_quo) -> Self {
252                        Self {
253                            #fields_eq
254                        }
255                    }
256                }
257
258                impl #lt_quo From<#ident<#lts #tt>> for #new_ident #lt_quo {
259                    fn from(value: #ident<#lts #tt>) -> Self {
260                        Self {
261                            #fields_eq
262                        }
263                    }
264                }
265            })
266        }
267    }
268}
269
270#[proc_macro_derive(SealedGeneric, attributes(define))]
271pub fn def_gen(input: TokenStream) -> TokenStream {
272    let parsed = Input::from_derive_input(&parse_macro_input!(input)).expect("parse");
273    quote::quote!(#parsed).into()
274}