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