use proc_macro2::TokenStream; use quote::{quote, TokenStreamExt}; use syn::{spanned::Spanned, Fields, Ident, ItemEnum, ItemStruct, Meta}; fn handle_enum(e: ItemEnum) -> TokenStream { let name = &e.ident; let mut stream: TokenStream = e .variants .iter() .filter_map(|variant| { let variant_name = &variant.ident; let has_from_attr = variant.attrs.iter().any(|a| { let path = match &a.meta { Meta::Path(p) => p, _ => return false, }; path.get_ident().is_some_and(|i| *i == "from") }); if !has_from_attr { return None; } match &variant.fields { Fields::Named(_n) => todo!(), Fields::Unnamed(u) => { match u.unnamed.iter().collect::>().as_slice() { &[] => todo!(), &[field] => { let ty = &field.ty; Some(quote! { impl ::core::convert::From<#ty> for #name { fn from(value: #ty) -> Self { Self::#variant_name(value) } } }) }, _sl => todo!(), } }, Fields::Unit => todo!(), } }) .collect(); let has_convert_infallible = e.attrs.iter().any(|a| match &a.meta { Meta::List(list) => { if list.path.get_ident().is_some_and(|i| *i == "from") { let ident: Ident = match syn::parse2(list.tokens.clone()) { Ok(i) => i, _ => return false, }; ident == "infallible" } else { false } }, _ => false, }); if has_convert_infallible { stream.append_all(quote! { impl ::core::convert::From<::core::convert::Infallible> for #name { fn from(value: ::core::convert::Infallible) -> Self { unreachable!() } } }); } stream } fn handle_struct(s: ItemStruct) -> TokenStream { match &s.fields { Fields::Unnamed(u) => { if u.unnamed.len() != 1 { proc_macro_error::abort!( s.span(), "Tuple struct must have single field" ); } let field = u.unnamed.first().expect("Should be len 1"); let ty = &field.ty; let name = &s.ident; let generics = &s.generics; quote! { impl #generics ::core::convert::From<#ty> for #name #generics { fn from(value: #ty) -> Self { Self(value) } } } }, _ => todo!(), } } pub fn easy_from_core(input: TokenStream) -> TokenStream { match syn::parse2(input.clone()) { Ok(e) => handle_enum(e), Err(_) => match syn::parse2(input) { Ok(s) => handle_struct(s), Err(e) => e.to_compile_error(), }, } } #[cfg(test)] mod test { use quote::quote; use crate::easy_from_core; #[test] fn test_enum_all_fields_covered() { let input = quote! { #[derive(EasyFrom)] enum Foo { #[from] Bar(u64), #[from] Baz(String), } }; let output = easy_from_core(input).to_string(); let expected = quote! { impl ::core::convert::From for Foo { fn from(value: u64) -> Self { Self::Bar(value) } } impl ::core::convert::From for Foo { fn from(value: String) -> Self { Self::Baz(value) } } } .to_string(); assert_eq!(output, expected) } #[test] fn test_enum_some_fields_covered() { let input = quote! { #[derive(EasyFrom)] enum Foo { #[from] Bar(u64), Baz(String), } }; let output = easy_from_core(input).to_string(); let expected = quote! { impl ::core::convert::From for Foo { fn from(value: u64) -> Self { Self::Bar(value) } } } .to_string(); assert_eq!(output, expected) } #[test] fn test_enum_from_infallible() { let input = quote! { #[derive(EasyFrom)] #[from(infallible)] enum Never {} }; let output = easy_from_core(input).to_string(); let expected = quote! { impl ::core::convert::From<::core::convert::Infallible> for Never { fn from(value: ::core::convert::Infallible) -> Self { unreachable!() } } } .to_string(); assert_eq!(output, expected) } #[test] fn test_newtype_struct() { let input = quote! { #[derive(EasyFrom)] struct NewType(#[from] T); }; let output = easy_from_core(input).to_string(); let expected = quote! { impl ::core::convert::From for NewType { fn from(value: T) -> Self { Self(value) } } } .to_string(); assert_eq!(output, expected) } }