Skip to main content

macron_impl_into/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6use std::collections::HashMap;
7
8/// The implementation of [std::convert::Into] trait
9#[proc_macro_derive(Into, attributes(into))]
10pub fn impl_into(input: TokenStream) -> TokenStream {
11    let syn::DeriveInput {
12        ident, data, attrs, ..
13    } = syn::parse_macro_input!(input as syn::DeriveInput);
14
15    let struct_fields = match &data {
16        syn::Data::Struct(st) => Some(&st.fields),
17        _ => None,
18    };
19
20    let global_attrs = read_attr_values(&attrs, struct_fields);
21    let has_skip = global_attrs.iter().any(|a| matches!(a, AttrValue::Skip));
22
23    let global_impls = global_attrs.into_iter().filter_map(|attr| match attr {
24        AttrValue::Custom { ty, expr } => Some(quote! {
25            impl ::std::convert::Into<#ty> for #ident {
26                fn into(self) -> #ty {
27                    let value = self;
28                    #expr
29                }
30            }
31        }),
32        AttrValue::Skip => None,
33    });
34
35    match &data {
36        syn::Data::Struct(st) => {
37            let mut struct_field_impls = Vec::new();
38
39            if st.fields.len() == 1 && !has_skip {
40                let field = st.fields.iter().next().unwrap();
41                let ty = &field.ty;
42                let body = match &field.ident {
43                    Some(id) => quote! { self.#id },
44                    None => quote! { self.0 },
45                };
46
47                struct_field_impls.push(quote! {
48                    impl ::std::convert::Into<#ty> for #ident {
49                        fn into(self) -> #ty {
50                            #body
51                        }
52                    }
53                });
54            }
55
56            quote! {
57                #(#global_impls)*
58                #(#struct_field_impls)*
59            }
60            .into()
61        }
62
63        syn::Data::Enum(en) => {
64            // Карта: Ключ = Целевой тип, Значение = (syn::Type, Vec<MatchArm>)
65            let mut target_types: HashMap<String, (syn::Type, Vec<TokenStream2>)> = HashMap::new();
66
67            for variant in &en.variants {
68                let parsed_attrs = read_attr_values(&variant.attrs, Some(&variant.fields));
69
70                if parsed_attrs.iter().any(|a| matches!(a, AttrValue::Skip)) {
71                    continue;
72                }
73
74                let var_ident = &variant.ident;
75
76                // Собираем токены для деструктуризации текущего варианта
77                let match_pattern = match &variant.fields {
78                    syn::Fields::Named(fields_named) => {
79                        let idents = fields_named.named.iter().map(|f| &f.ident);
80                        quote! { Self::#var_ident { #(#idents),* } }
81                    }
82                    syn::Fields::Unnamed(fields_unnamed) => {
83                        let placeholders = (0..fields_unnamed.unnamed.len())
84                            .map(|i| quote::format_ident!("__{}", i));
85                        quote! { Self::#var_ident ( #(#placeholders),* ) }
86                    }
87                    syn::Fields::Unit => quote! { Self::#var_ident },
88                };
89
90                // Создаем биндинг переменной `value`
91                let binding = match &variant.fields {
92                    syn::Fields::Named(fields_named) if fields_named.named.len() == 1 => {
93                        let id = &fields_named.named[0].ident;
94                        quote! { let value = #id; }
95                    }
96                    syn::Fields::Unnamed(fields_unnamed) if fields_unnamed.unnamed.len() == 1 => {
97                        quote! { let value = __0; }
98                    }
99                    _ => quote! {},
100                };
101
102                // ЕСЛИ АТРИБУТОВ НЕТ: Проверяем на авто-Into (ровно 1 поле)
103                if parsed_attrs.is_empty() && variant.fields.len() == 1 {
104                    let field = variant.fields.iter().next().unwrap();
105                    let ty = &field.ty;
106                    let ty_string = quote! { #ty }.to_string();
107
108                    let arm = quote! {
109                        #match_pattern => {
110                            #binding
111                            value
112                        }
113                    };
114
115                    target_types
116                        .entry(ty_string)
117                        .or_insert_with(|| (ty.clone(), Vec::new()))
118                        .1
119                        .push(arm);
120                } else {
121                    // ЕСЛИ АТРИБУТЫ ЕСТЬ: Обрабатываем каждый
122                    for attr in parsed_attrs {
123                        if let AttrValue::Custom { ty, expr } = attr {
124                            let ty_string = quote! { #ty }.to_string();
125
126                            let arm = quote! {
127                                #match_pattern => {
128                                    #binding
129                                    #expr
130                                }
131                            };
132
133                            target_types
134                                .entry(ty_string)
135                                .or_insert_with(|| (ty.clone(), Vec::new()))
136                                .1
137                                .push(arm);
138                        }
139                    }
140                }
141            }
142
143            // Генерируем блоки `impl`
144            let enum_impls = target_types.into_iter().map(|(_, (ty, arms))| {
145                quote! {
146                    impl ::std::convert::Into<#ty> for #ident {
147                        fn into(self) -> #ty {
148                            match self {
149                                #(#arms)*
150                                _ => panic!("Incompatible enum variant for Into conversion"),
151                            }
152                        }
153                    }
154                }
155            });
156
157            quote! {
158                #(#global_impls)*
159                #(#enum_impls)*
160            }
161            .into()
162        }
163
164        _ => panic!("Expected a 'struct' or 'enum'"),
165    }
166}
167
168enum AttrValue {
169    Custom { ty: syn::Type, expr: TokenStream2 },
170    Skip,
171}
172
173fn read_attr_values(attrs: &[syn::Attribute], _fields: Option<&syn::Fields>) -> Vec<AttrValue> {
174    attrs
175        .iter()
176        .filter(|attr| attr.path().is_ident("into"))
177        .map(|attr| match &attr.meta {
178            syn::Meta::List(list) => {
179                let args: IntoArgs = list.parse_args().expect("Invalid arguments");
180                match args {
181                    IntoArgs::With(ty, path) => AttrValue::Custom {
182                        ty: ty.clone(),
183                        expr: quote! { #path(value) },
184                    },
185                    IntoArgs::Expr(ty, expr) => AttrValue::Custom {
186                        ty: ty.clone(),
187                        expr: quote! { #expr },
188                    },
189                    IntoArgs::Skip => AttrValue::Skip,
190                }
191            }
192            _ => panic!("Unsupported attribute format. Use #[into(skip)] or #[into(Type, ...)]"),
193        })
194        .collect()
195}
196
197enum IntoArgs {
198    With(syn::Type, syn::Path),
199    Expr(syn::Type, syn::Expr),
200    Skip,
201}
202
203impl syn::parse::Parse for IntoArgs {
204    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
205        if input.peek(syn::Ident) {
206            let fork = input.fork();
207            let ident: syn::Ident = fork.parse()?;
208            if ident == "skip" {
209                input.parse::<syn::Ident>()?;
210                return Ok(IntoArgs::Skip);
211            }
212        }
213
214        let ty: syn::Type = input.parse()?;
215        input.parse::<syn::token::Comma>()?;
216        let ident: syn::Ident = input.parse()?;
217        input.parse::<syn::token::Eq>()?;
218
219        if ident == "with" {
220            let path: syn::Path = input.parse()?;
221            Ok(IntoArgs::With(ty, path))
222        } else if ident == "expr" {
223            let expr: syn::Expr = input.parse()?;
224            Ok(IntoArgs::Expr(ty, expr))
225        } else {
226            Err(syn::Error::new(
227                ident.span(),
228                "expected `with`, `expr` or `skip`",
229            ))
230        }
231    }
232}