e/proc-macros/easy-from/core/src/lib.rs
2024-05-04 10:00:44 -04:00

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)
}
}