215 lines
4.1 KiB
Rust
215 lines
4.1 KiB
Rust
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::<Vec<_>>().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<u64> for Foo {
|
|
fn from(value: u64) -> Self {
|
|
Self::Bar(value)
|
|
}
|
|
}
|
|
|
|
impl ::core::convert::From<String> 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<u64> 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<T>(#[from] T);
|
|
};
|
|
let output = easy_from_core(input).to_string();
|
|
let expected = quote! {
|
|
impl<T> ::core::convert::From<T> for NewType<T> {
|
|
fn from(value: T) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
}
|
|
.to_string();
|
|
|
|
assert_eq!(output, expected)
|
|
}
|
|
}
|