#![warn(
clippy::default_trait_access,
clippy::dbg_macro,
clippy::print_stdout,
clippy::unimplemented,
clippy::use_self,
missing_copy_implementations,
missing_docs,
non_snake_case,
non_upper_case_globals,
rust_2018_idioms,
unreachable_pub
)]
use heck::ToSnakeCase;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
fn unit_fields_return(variant_name: &syn::Ident, function_name: &Ident, doc: &str) -> TokenStream {
quote!(
#[doc = #doc]
#[inline]
pub fn #function_name(&self) -> bool {
matches!(self, Self::#variant_name)
}
)
}
fn unnamed_fields_return(
variant_name: &syn::Ident,
(function_name_is, doc_is): (&Ident, &str),
(function_name_mut_ref, doc_mut_ref): (&Ident, &str),
(function_name_ref, doc_ref): (&Ident, &str),
(function_name_val, doc_val): (&Ident, &str),
fields: &syn::FieldsUnnamed,
) -> TokenStream {
let (returns_mut_ref, returns_ref, returns_val, matches) = match fields.unnamed.len() {
1 => {
let field = fields.unnamed.first().expect("no fields on type");
let returns = &field.ty;
let returns_mut_ref = quote!(&mut #returns);
let returns_ref = quote!(&#returns);
let returns_val = quote!(#returns);
let matches = quote!(inner);
(returns_mut_ref, returns_ref, returns_val, matches)
}
0 => (quote!(()), quote!(()), quote!(()), quote!()),
_ => {
let mut returns_mut_ref = TokenStream::new();
let mut returns_ref = TokenStream::new();
let mut returns_val = TokenStream::new();
let mut matches = TokenStream::new();
for (i, field) in fields.unnamed.iter().enumerate() {
let rt = &field.ty;
let match_name = Ident::new(&format!("match_{}", i), Span::call_site());
returns_mut_ref.extend(quote!(&mut #rt,));
returns_ref.extend(quote!(&#rt,));
returns_val.extend(quote!(#rt,));
matches.extend(quote!(#match_name,));
}
(
quote!((#returns_mut_ref)),
quote!((#returns_ref)),
quote!((#returns_val)),
quote!(#matches),
)
}
};
quote!(
#[doc = #doc_is ]
#[inline]
#[allow(unused_variables)]
pub fn #function_name_is(&self) -> bool {
matches!(self, Self::#variant_name(#matches))
}
#[doc = #doc_mut_ref ]
#[inline]
pub fn #function_name_mut_ref(&mut self) -> ::core::option::Option<#returns_mut_ref> {
match self {
Self::#variant_name(#matches) => {
::core::option::Option::Some((#matches))
}
_ => ::core::option::Option::None
}
}
#[doc = #doc_ref ]
#[inline]
pub fn #function_name_ref(&self) -> ::core::option::Option<#returns_ref> {
match self {
Self::#variant_name(#matches) => {
::core::option::Option::Some((#matches))
}
_ => ::core::option::Option::None
}
}
#[doc = #doc_val ]
#[inline]
pub fn #function_name_val(self) -> ::core::result::Result<#returns_val, Self> {
match self {
Self::#variant_name(#matches) => {
::core::result::Result::Ok((#matches))
},
_ => ::core::result::Result::Err(self)
}
}
)
}
fn named_fields_return(
variant_name: &syn::Ident,
(function_name_is, doc_is): (&Ident, &str),
(function_name_mut_ref, doc_mut_ref): (&Ident, &str),
(function_name_ref, doc_ref): (&Ident, &str),
(function_name_val, doc_val): (&Ident, &str),
fields: &syn::FieldsNamed,
) -> TokenStream {
let (returns_mut_ref, returns_ref, returns_val, matches) = match fields.named.len() {
1 => {
let field = fields.named.first().expect("no fields on type");
let match_name = field.ident.as_ref().expect("expected a named field");
let returns = &field.ty;
let returns_mut_ref = quote!(&mut #returns);
let returns_ref = quote!(&#returns);
let returns_val = quote!(#returns);
let matches = quote!(#match_name);
(returns_mut_ref, returns_ref, returns_val, matches)
}
0 => (quote!(()), quote!(()), quote!(()), quote!(())),
_ => {
let mut returns_mut_ref = TokenStream::new();
let mut returns_ref = TokenStream::new();
let mut returns_val = TokenStream::new();
let mut matches = TokenStream::new();
for field in fields.named.iter() {
let rt = &field.ty;
let match_name = field.ident.as_ref().expect("expected a named field");
returns_mut_ref.extend(quote!(&mut #rt,));
returns_ref.extend(quote!(&#rt,));
returns_val.extend(quote!(#rt,));
matches.extend(quote!(#match_name,));
}
(
quote!((#returns_mut_ref)),
quote!((#returns_ref)),
quote!((#returns_val)),
quote!(#matches),
)
}
};
quote!(
#[doc = #doc_is ]
#[inline]
#[allow(unused_variables)]
pub fn #function_name_is(&self) -> bool {
matches!(self, Self::#variant_name{ #matches })
}
#[doc = #doc_mut_ref ]
#[inline]
pub fn #function_name_mut_ref(&mut self) -> ::core::option::Option<#returns_mut_ref> {
match self {
Self::#variant_name{ #matches } => {
::core::option::Option::Some((#matches))
}
_ => ::core::option::Option::None
}
}
#[doc = #doc_ref ]
#[inline]
pub fn #function_name_ref(&self) -> ::core::option::Option<#returns_ref> {
match self {
Self::#variant_name{ #matches } => {
::core::option::Option::Some((#matches))
}
_ => ::core::option::Option::None
}
}
#[doc = #doc_val ]
#[inline]
pub fn #function_name_val(self) -> ::core::result::Result<#returns_val, Self> {
match self {
Self::#variant_name{ #matches } => {
::core::result::Result::Ok((#matches))
}
_ => ::core::result::Result::Err(self)
}
}
)
}
fn impl_all_as_fns(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let generics = &ast.generics;
let enum_data = if let syn::Data::Enum(data) = &ast.data {
data
} else {
panic!("{} is not an enum", name);
};
let mut stream = TokenStream::new();
for variant_data in &enum_data.variants {
let variant_name = &variant_data.ident;
let function_name_ref = Ident::new(
&format!("as_{}", variant_name).to_snake_case(),
Span::call_site(),
);
let doc_ref = format!(
"Optionally returns references to the inner fields if this is a `{}::{}`, otherwise `None`",
name,
variant_name,
);
let function_name_mut_ref = Ident::new(
&format!("as_{}_mut", variant_name).to_snake_case(),
Span::call_site(),
);
let doc_mut_ref = format!(
"Optionally returns mutable references to the inner fields if this is a `{}::{}`, otherwise `None`",
name,
variant_name,
);
let function_name_val = Ident::new(
&format!("into_{}", variant_name).to_snake_case(),
Span::call_site(),
);
let doc_val = format!(
"Returns the inner fields if this is a `{}::{}`, otherwise returns back the enum in the `Err` case of the result",
name,
variant_name,
);
let function_name_is = Ident::new(
&format!("is_{}", variant_name).to_snake_case(),
Span::call_site(),
);
let doc_is = format!(
"Returns true if this is a `{}::{}`, otherwise false",
name, variant_name,
);
let tokens = match &variant_data.fields {
syn::Fields::Unit => unit_fields_return(variant_name, &function_name_is, &doc_is),
syn::Fields::Unnamed(unnamed) => unnamed_fields_return(
variant_name,
(&function_name_is, &doc_is),
(&function_name_mut_ref, &doc_mut_ref),
(&function_name_ref, &doc_ref),
(&function_name_val, &doc_val),
unnamed,
),
syn::Fields::Named(named) => named_fields_return(
variant_name,
(&function_name_is, &doc_is),
(&function_name_mut_ref, &doc_mut_ref),
(&function_name_ref, &doc_ref),
(&function_name_val, &doc_val),
named,
),
};
stream.extend(tokens);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote!(
impl #impl_generics #name #ty_generics #where_clause {
#stream
}
)
}
#[proc_macro_derive(EnumAsInner)]
pub fn enum_as_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let expanded: TokenStream = impl_all_as_fns(&ast);
proc_macro::TokenStream::from(expanded)
}