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(Into, attributes(into))]
11pub fn impl_into(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::Into<#ty> for #ident {
19 fn into(self) -> #ty {
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 quote! {
39 #(
40 #global_impls
41 )*
42 }.into()
43 },
44
45 _ => panic!("Expected a 'struct' or 'enum'")
46 }
47}
48
49fn read_attr_values(attrs: &[syn::Attribute], fields: Option<&syn::Fields>) -> Vec<AttrValue> {
51 attrs
52 .iter()
53 .filter(|attr| attr.path().is_ident("into"))
54 .map(|attr| {
55 match &attr.meta {
56 syn::Meta::List(list) => {
57 list
58 .parse_args()
59 .expect("Expected the attribute format like this '#[into(Type, \"a code..\")]'")
60 },
61
62 syn::Meta::Path(_) if fields.is_some() => {
63 let fields = fields.unwrap();
64 if fields.len() != 1 { panic!("Expected the one variant argument for the short attribute '#[into]'") }
65
66 let field = fields.iter().next().unwrap();
67
68 AttrValue {
69 ty: field.ty.clone(),
70 expr: syn::parse_str( &if let Some(ident) = &field.ident { format!("self.{ident}") }else{ format!("arg0")} ).unwrap()
71 }
72 },
73
74 _ => panic!("Expected the attribute format like this '#[into(Type, \"a code..\")]'")
75 }
76 })
77 .collect::<Vec<_>>()
78}
79
80struct AttrValue {
81 pub ty: syn::Type,
82 pub expr: TokenStream2
83}
84
85impl syn::parse::Parse for AttrValue {
86 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
87 let ty: syn::Type = input.parse()?;
88
89 input.parse::<syn::token::Comma>()?;
90
91 let expr_s: syn::LitStr = input.parse()?;
92 let expr: TokenStream2 = syn::parse_str(&expr_s.value())?;
93
94 Ok(AttrValue {
95 ty,
96 expr,
97 })
98 }
99}