diff --git a/Cargo.lock b/Cargo.lock index 7e498d1..c8701b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,6 +126,9 @@ name = "template" version = "0.1.0" dependencies = [ "anyhow", + "proc-macro2", + "quote", + "syn", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index fec31db..afa63c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,10 @@ license = "MIT" proc-macro = true [dependencies] +proc-macro2 = "1.0.86" +quote = "1.0.37" +syn = { version = "2.0.77", features = ["extra-traits"] } + +[dev-dependencies] anyhow = "1.0.87" tokio = { version = "1.40.0", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/examples/enum_macro.rs b/examples/enum_macro.rs new file mode 100644 index 0000000..e06b915 --- /dev/null +++ b/examples/enum_macro.rs @@ -0,0 +1,28 @@ +use template::EnumFrom; + +#[allow(unused)] +#[derive(Debug, EnumFrom)] +enum Direction { + Up(DirectionUp), + Down, + Left(u32), + Right(u32, u32), +} + +#[allow(unused)] +#[derive(Debug)] +struct DirectionUp { + speed: u32, +} + +impl DirectionUp { + fn new(speed: u32) -> Self { + Self { speed } + } +} + +fn main() { + let up: Direction = DirectionUp::new(42).into(); + let left: Direction = 10.into(); + println!("Up: {:?}, Left: {:?}", up, left); +} diff --git a/src/lib.rs b/src/lib.rs index 8b1a393..531c9ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,49 @@ -// empty +// proc macro crate + +use proc_macro::TokenStream; +use quote::quote; + +// for enum, we'd like to generate From impls for each variant +#[proc_macro_derive(EnumFrom)] +pub fn derive_enum_from(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + // println!("{:#?}", input); + + // get the ident + let ident = input.ident; + //get enum variants + let variants = match input.data { + syn::Data::Enum(data) => data.variants, + _ => panic!("EnumFrom can only be applied to enums"), + }; + //for each variant, get the ident and fields + let from_impls = variants.iter().map(|v| { + let var = &v.ident; + match &v.fields { + syn::Fields::Unnamed(fields) => { + // only support one field + if fields.unnamed.len() != 1 { + quote! {} + } else { + let field = fields.unnamed.first().expect("Should have 1 field"); + let ty = &field.ty; + quote! { + impl From<#ty> for #ident { + fn from(v: #ty) -> Self { + #ident::#var(v) + } + } + } + } + } + syn::Fields::Unit => quote! {}, + syn::Fields::Named(_fields) => quote! {}, + } + }); + + // quote return proc-macro2 TokenStream so we need to convert it to TokenStream + quote! { + #(#from_impls)* + } + .into() +}