raffia_macro 0.2.0

Macros for Raffia.
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DataStruct, DeriveInput, Fields, Ident};

#[proc_macro_derive(Spanned)]
pub fn spanned_derive(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();
    let name = &ast.ident;
    let generics = &ast.generics;

    let generated = match &ast.data {
        Data::Struct(..) => quote! {
            impl #generics crate::pos::Spanned for #name #generics {
                #[inline]
                fn span(&self) -> &Span {
                    &self.span
                }
            }
        },
        Data::Enum(data_enum) => {
            let variants = data_enum.variants.iter().map(|variant| {
                let ident = &variant.ident;
                let fields = &variant.fields;
                match fields {
                    Fields::Unnamed(..) => quote! {
                        Self::#ident(x) => x.span(),
                    },
                    Fields::Unit => quote! {
                        Self::#ident => unimplemented!(),
                    },
                    _ => unimplemented!("enum variant with named fields is not supported"),
                }
            });
            quote! {
                impl #generics crate::pos::Spanned for #name #generics {
                    fn span(&self) -> &Span {
                        match self {
                            #(#variants)*
                        }
                    }
                }
            }
        }
        _ => unimplemented!(),
    };

    generated.into()
}

#[proc_macro_derive(SpanIgnoredEq)]
pub fn span_ignored_eq_derive(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();
    let name = &ast.ident;
    let generics = &ast.generics;

    let generated = match &ast.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(fields),
            ..
        }) => {
            let comparions = fields
                .named
                .iter()
                .filter_map(|field| field.ident.as_ref())
                .filter(|ident| *ident != "span")
                .map(|ident| quote! { && self.#ident.span_ignored_eq(&other.#ident) });
            quote! {
                impl #generics crate::SpanIgnoredEq for #name #generics {
                    #[must_use]
                    fn span_ignored_eq(&self, other: &Self) -> bool {
                        true #(#comparions)*
                    }
                }
            }
        }
        Data::Enum(data_enum) => {
            let variants = data_enum.variants.iter().map(|variant| {
                let ident = &variant.ident;
                let fields = &variant.fields;
                match fields {
                    Fields::Unnamed(..) => quote! {
                        (Self::#ident(a), Self::#ident(b)) => a.span_ignored_eq(&b),
                    },
                    Fields::Unit => quote! {
                        (Self::#ident, Self::#ident) => true,
                    },
                    _ => unimplemented!("enum variant with named fields is not supported"),
                }
            });
            quote! {
                impl #generics crate::SpanIgnoredEq for #name #generics {
                    #[must_use]
                    fn span_ignored_eq(&self, other: &Self) -> bool {
                        match (self, other) {
                            #(#variants)*
                            _ => false,
                        }
                    }
                }
            }
        }
        _ => unimplemented!(),
    };

    generated.into()
}

#[proc_macro_derive(EnumAsIs)]
pub fn enum_as_is_derive(input: TokenStream) -> TokenStream {
    use heck::ToSnakeCase;

    let ast: DeriveInput = syn::parse(input).unwrap();
    let name = &ast.ident;
    let generics = &ast.generics;

    let generated = match &ast.data {
        Data::Enum(data_enum) => {
            let variants = data_enum.variants.iter().map(|variant| {
                let original_name = &variant.ident;
                let variant_name = original_name.to_string().to_snake_case();

                let ident_is = Ident::new(&format!("is_{variant_name}"), variant.ident.span());
                let ident_as = Ident::new(&format!("as_{variant_name}"), variant.ident.span());

                let doc_is = format!("Checks inner field whether it's of variant [`{original_name}`](Self::{original_name}) or not.");
                let doc_as = format!("Returns [`Some`] with a reference to inner field if it's of variant [`{original_name}`](Self::{original_name}), otherwise returns [`None`].");

                match &variant.fields {
                    Fields::Unnamed(unamed_fields) => {
                        let ty = &unamed_fields.unnamed.first().unwrap().ty;
                        quote! {
                            #[doc = #doc_is]
                            #[inline]
                            pub fn #ident_is(&self) -> bool {
                                match self {
                                    Self::#original_name(..) => true,
                                    _ => false
                                }
                            }
                            #[doc = #doc_as]
                            #[inline]
                            pub fn #ident_as(&self) -> Option<&#ty> {
                                match self {
                                    Self::#original_name(value) => Some(value),
                                    _ => None,
                                }
                            }
                        }
                    }
                    Fields::Unit => quote! {
                        #[doc = #doc_is]
                        #[inline]
                        pub fn #ident_is(&self) -> bool {
                            match self {
                                Self::#original_name => true,
                                _ => false,
                            }
                        }
                    },
                    _ => unimplemented!("enum variant with named fields is not supported"),
                }
            });

            quote! {
                impl #generics #name #generics {
                    #(#variants)*
                }
            }
        }
        _ => unimplemented!("only enum is supported"),
    };

    generated.into()
}