sealed_generic/
lib.rs

1use convert_case::{Case, Casing};
2use darling::*;
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{Ident, Meta, parse_macro_input};
6
7#[derive(Debug, FromField)]
8#[darling(attributes(define))]
9struct Field {
10    ident: Option<syn::Ident>,
11    ty: syn::Type,
12}
13
14#[derive(Debug, FromMeta)]
15enum Type {
16    Ty(syn::Path),
17    With {
18        ty: syn::Path,
19
20        #[darling(default, multiple)]
21        derives: Vec<syn::Path>,
22
23        #[darling(default, multiple)]
24        attr: Vec<String>,
25    },
26}
27
28impl Type {
29    fn ident(&self) -> &syn::Path {
30        match self {
31            Self::Ty(path) => path,
32            Self::With { ty, .. } => ty,
33        }
34    }
35
36    fn derives(&self) -> Vec<syn::Path> {
37        match self {
38            Self::With { derives, .. } => derives.clone(),
39            _ => vec![],
40        }
41    }
42
43    fn attrs(&self) -> Vec<String> {
44        match self {
45            Self::With { attr, .. } => attr.clone(),
46            _ => vec![],
47        }
48    }
49}
50
51#[derive(Debug, FromDeriveInput)]
52#[darling(attributes(define), supports(struct_any))]
53struct Input {
54    vis: syn::Visibility,
55    ident: syn::Ident,
56    generics: syn::Generics,
57    data: ast::Data<(), Field>,
58
59    #[darling(default, multiple)]
60    types: Vec<Type>,
61
62    #[darling(default, multiple)]
63    derives: Vec<syn::Path>,
64
65    #[darling(default, multiple)]
66    attr: Vec<String>,
67
68    #[darling(default)]
69    sealed: bool,
70}
71
72impl Input {
73    fn merge<T: Clone>(a1: Vec<T>, a2: &Vec<T>) -> Vec<T> {
74        let mut a1 = a1;
75        let mut a2 = a2.clone();
76        a1.append(&mut a2);
77        a1
78    }
79
80    fn derives(&self, derives: Vec<syn::Path>) -> Vec<syn::Path> {
81        Self::merge(derives, &self.derives)
82    }
83
84    fn attrs(&self, attrs: Vec<String>) -> Vec<String> {
85        Self::merge(attrs, &self.attr)
86    }
87}
88
89enum TypeOrGeneric {
90    Type(proc_macro2::TokenStream),
91    Generic(Ident),
92}
93
94impl ToTokens for Input {
95    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
96        let ident = self.ident.clone();
97
98        let vis = self.vis.clone();
99        let mut tys = vec![];
100        for param in &self.generics.params {
101            match param {
102                syn::GenericParam::Const(..) => {}
103                syn::GenericParam::Lifetime(..) => {}
104                syn::GenericParam::Type(ty) => tys.push(ty.ident.clone()),
105            }
106        }
107
108        let mut fields = vec![];
109        let mut fields_eq = vec![];
110
111        for f in self.data.as_ref().take_struct().unwrap() {
112            let field = f.ident.clone().unwrap();
113            let ty = f.ty.clone();
114            let ident = Ident::from_string(
115                &quote!(
116                    #ty
117                )
118                .to_string(),
119            )
120            .unwrap();
121
122            fields_eq.push(quote!(
123                #field: value.#field,
124            ));
125            if tys.contains(&ident) {
126                fields.push(TypeOrGeneric::Generic(field))
127                // is generic
128            } else {
129                fields.push(TypeOrGeneric::Type(quote!(
130                    #field: #ty,
131                )))
132            }
133        }
134
135        let fields_eq: proc_macro2::TokenStream = fields_eq.into_iter().collect();
136
137        let seal_mod =
138            Ident::from_string(&("Sealed".to_string() + &ident.to_string()).to_case(Case::Pascal))
139                .unwrap();
140
141        if self.sealed {
142            tokens.extend(quote!(
143                pub(crate) trait #seal_mod {}
144            ));
145        }
146
147        for t in &self.types {
148            let tt = t.ident();
149            let new_ident = Ident::from_string(
150                &format!(
151                    "{}{}",
152                    ident.to_string(),
153                    quote!(#tt).to_string().to_case(Case::Pascal)
154                )
155                .to_case(Case::Pascal),
156            )
157            .unwrap();
158
159            let new_fields: proc_macro2::TokenStream = fields
160                .iter()
161                .map(|it| match it {
162                    TypeOrGeneric::Generic(field) => {
163                        quote!(
164                            #field: #tt,
165                        )
166                    }
167                    TypeOrGeneric::Type(tt) => tt.clone(),
168                })
169                .collect();
170
171            let derives: proc_macro2::TokenStream = self
172                .derives(t.derives())
173                .iter()
174                .map(|it| quote::quote!(#it,))
175                .collect();
176
177            let atts: proc_macro2::TokenStream = self
178                .attrs(t.attrs())
179                .iter()
180                .map(|it| {
181                    let it: Meta = syn::parse_str(&it).unwrap();
182                    quote::quote!(#[#it])
183                })
184                .collect();
185
186            tokens.extend(quote! {
187                #[derive(#derives)]
188                #atts
189                #vis struct #new_ident {
190                    #new_fields
191                }
192            });
193
194            if self.sealed {
195                tokens.extend(quote!(
196                    impl #seal_mod for #tt {}
197                ));
198            }
199
200            tokens.extend(quote! {
201                impl From<#new_ident> for #ident<#tt> {
202                    fn from(value: #new_ident) -> Self {
203                        Self {
204                            #fields_eq
205                        }
206                    }
207                }
208
209                impl From<#ident<#tt>> for #new_ident {
210                    fn from(value: #ident<#tt>) -> Self {
211                        Self {
212                            #fields_eq
213                        }
214                    }
215                }
216            })
217        }
218    }
219}
220
221#[proc_macro_derive(SealedGeneric, attributes(define))]
222pub fn def_gen(input: TokenStream) -> TokenStream {
223    let parsed = Input::from_derive_input(&parse_macro_input!(input)).unwrap();
224    quote::quote!(#parsed).into()
225}