//! The `EnumAs` derive macros automatically generates methods on an enum to conditionally get a variant. //! //! For any newtype variant `Variant(T)`, the following methods are generated: //! //! * `fn as_{variant}(&self) -> Option<&T>` //! * `fn as_{variant}_mut(&mut self) -> Option<&mut T>` //! * `fn into_{variant}(self) -> Option` //! //! In addition, the following methods are generated for all variants: //! //! * `fn is_variant(&self) -> bool` //! //! The name is generated by converting the variant name into snake case (e.g. `MyVariant` gets methods `is_my_variant`, `as_my_variant` etc.). //! //! # Example //! //! ``` //! # use enum_as_derive::EnumAs; //! #[derive(EnumAs)] //! enum StringOrNumber { //! String(String), //! Number(i32), //! } //! //! let value = StringOrNumber::Number(123); //! //! assert_eq!(value.as_string(), None); //! assert_eq!(value.as_number(), Some(&123)); //! ``` extern crate proc_macro; use heck::SnakeCase; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parse_macro_input, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Fields, FieldsUnnamed, Ident, }; /// Generates methods on an enum to conditinally retrieve variants. /// /// See [the module level documentation](index.html) for more. #[proc_macro_derive(EnumAs)] pub fn enum_as_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); enum_as(input) .unwrap_or_else(|err| err.to_compile_error()) .into() } fn enum_as(input: DeriveInput) -> syn::Result { // Destructure input let DeriveInput { ident, vis, data, generics, .. } = &input; // Ensure that we're using the macro on an enum, and // get all of the variants of the enum let variants = match data { Data::Enum(DataEnum { variants, .. }) => variants, Data::Struct(DataStruct { struct_token, .. }) => { return Err(syn::Error::new_spanned( struct_token, "Can only use EnumAs derive macro on enum items", )) } Data::Union(DataUnion { union_token, .. }) => { return Err(syn::Error::new_spanned( union_token, "Can only use EnumAs derive macro on enum items", )) } }; // For each variant, generate the methods for it let mut methods = Vec::new(); for variant in variants { // Get field (or None is variant is not a newtype variant, i.e. "Variant(T)") let field = match &variant.fields { Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { if unnamed.len() == 1 { Some(&unnamed[0]) } else { None } } _ => None, }; let variant_ident = &variant.ident; let variant_name = variant.ident.to_string().to_snake_case(); // Generate "is_{variant}" method let method_name_is = Ident::new(&format!("is_{}", variant_name), Span::call_site()); let empty_capture_pattern = match &variant.fields { Fields::Named(_) => quote!{ #ident::#variant_ident{..} }, Fields::Unnamed(_) => quote!{ #ident::#variant_ident(..) }, Fields::Unit => quote!{ #ident::#variant_ident }, }; methods.push(quote! { #vis fn #method_name_is(&self) -> bool { if let #empty_capture_pattern = self { true } else { false } } }); // If newtype variant, generate "as_{variant}", "as_{variant}_mut" etc. methods if let Some(field) = field { let variant_type = &field.ty; let method_name_as = Ident::new(&format!("as_{}", variant_name), Span::call_site()); let method_name_mut = Ident::new(&format!("as_{}_mut", variant_name), Span::call_site()); let method_name_into = Ident::new(&format!("into_{}", variant_name), Span::call_site()); methods.push(quote! { #vis fn #method_name_as(&self) -> Option<&#variant_type> { if let #ident::#variant_ident(value) = self { Some(value) } else { None } } #vis fn #method_name_mut(&mut self) -> Option<&mut #variant_type> { if let #ident::#variant_ident(value) = self { Some(value) } else { None } } #vis fn #method_name_into(self) -> Option<#variant_type> { if let #ident::#variant_ident(value) = self { Some(value) } else { None } } }); } } // Wrap all the generated methods in an impl block let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); Ok(quote! { impl #impl_generics #ident #ty_generics #where_clause { #( #methods )* } }) }