1use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6
7#[proc_macro_derive(From, attributes(from))]
9pub fn impl_from(input: TokenStream) -> TokenStream {
10 let syn::DeriveInput { ident, data, attrs, .. } = syn::parse_macro_input!(input as syn::DeriveInput);
11
12 let global_impls = read_attr_values(&attrs, None)
13 .into_iter()
14 .map(|AttrValue { ty, expr }| {
15 quote! {
16 impl ::std::convert::From<#ty> for #ident {
17 fn from(value: #ty) -> Self {
18 #expr
19 }
20 }
21 }
22 });
23
24 match data {
25 syn::Data::Struct(_st) => {
27 quote! {
28 #(
29 #global_impls
30 )*
31 }.into()
32 },
33
34 syn::Data::Enum(en) => {
36 let var_impls = en.variants
37 .iter()
38 .map(|syn::Variant { ident: var_ident, attrs, fields, .. }| {
39 let var_impls = read_attr_values(&attrs, Some(&fields))
40 .into_iter()
41 .map(|AttrValue { ty, expr }| {
42 let output = match &fields {
43 syn::Fields::Named(_) => quote! { Self::#var_ident { #expr } },
44 syn::Fields::Unnamed(_) => quote! { Self::#var_ident(#expr) },
45 syn::Fields::Unit => quote! { Self::#var_ident }
46 };
47
48 quote! {
49 impl ::std::convert::From<#ty> for #ident {
50 fn from(value: #ty) -> Self {
51 #output
52 }
53 }
54 }
55 });
56
57 quote! { #(#var_impls)* }
58 });
59
60 quote! {
61 #(
62 #global_impls
63 )*
64
65 #(
66 #var_impls
67 )*
68 }.into()
69 },
70
71 _ => panic!("Expected a 'struct' or 'enum'")
72 }
73}
74
75fn read_attr_values(attrs: &[syn::Attribute], fields: Option<&syn::Fields>) -> Vec<AttrValue> {
77 attrs
78 .iter()
79 .filter(|attr| attr.path().is_ident("from"))
80 .map(|attr| {
81 match &attr.meta {
82 syn::Meta::List(list) => {
83 list
84 .parse_args()
85 .expect("Expected the attribute format like this '#[from(Type, \"a code..\")]'")
86 },
87
88 syn::Meta::Path(_) if fields.is_some() => {
89 let fields = fields.unwrap();
90 if fields.len() != 1 { panic!("Expected the one variant argument for the short attribute '#[from]'") }
91
92 let field = fields.iter().next().unwrap();
93
94 AttrValue {
95 ty: field.ty.clone(),
96 expr: syn::parse_str( &if let Some(ident) = &field.ident { format!("{ident}: value") }else{ format!("value")} ).unwrap()
97 }
98 },
99
100 _ => panic!("Expected the attribute format like this '#[from(Type, \"a code..\")]'")
101 }
102 })
103 .collect::<Vec<_>>()
104}
105
106struct AttrValue {
107 pub ty: syn::Type,
108 pub expr: TokenStream2
109}
110
111impl syn::parse::Parse for AttrValue {
112 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
113 let ty: syn::Type = input.parse()?;
114
115 input.parse::<syn::token::Comma>()?;
116
117 let expr_s: syn::LitStr = input.parse()?;
118 let expr: TokenStream2 = syn::parse_str(&expr_s.value())?;
119
120 Ok(AttrValue {
121 ty,
122 expr,
123 })
124 }
125}