diff --git a/examples/airports.rs b/examples/airports.rs index ba07eb9..ae8ee63 100644 --- a/examples/airports.rs +++ b/examples/airports.rs @@ -5,12 +5,18 @@ extern crate enumscribe; enum Foo { #[enumscribe(str = "b", case_insensitive)] Baa, - #[enumscribe(ignore)] + // #[enumscribe(ignore)] Baz(), #[enumscribe(other)] Lorem { inner: String } } fn main() { - println!("Hello world!"); + let foo = Foo::Baa; + + // let s: String = foo.into(); + + let s: String = <_ as std::convert::Into>::into(foo); + + println!("Hello, {}!", s); } diff --git a/src/enums.rs b/src/enums.rs index c9dac1f..d08d76e 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -1,28 +1,28 @@ use std::collections::HashSet; -use proc_macro2::Span; -use syn::{Attribute, Data, DataEnum, DeriveInput, Fields, LitStr}; -use syn::parse::{ParseBuffer, ParseStream}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{DataEnum, Fields}; use syn::spanned::Spanned; -use crate::{CRATE_ATTR, NAME, OTHER, IGNORE, CASE_INSENSITIVE}; -use crate::attribute::*; +use crate::{CASE_INSENSITIVE, CRATE_ATTR, IGNORE, NAME, OTHER}; +use crate::attribute::{Dict, Value}; use crate::error::{MacroError, MacroResult}; -#[derive(Clone, Debug)] -pub(crate) struct Enum { - pub(crate) variants: Vec, +#[derive(Clone)] +pub(crate) struct Enum<'a> { + pub(crate) variants: Vec>, } -#[derive(Clone, Debug)] -pub(crate) struct Variant { - pub(crate) ident: String, - pub(crate) v_type: VariantType, +#[derive(Clone)] +pub(crate) struct Variant<'a> { + pub(crate) data: &'a syn::Variant, + pub(crate) v_type: VariantType<'a>, pub(crate) span: Span, } -#[derive(Clone, Debug)] -pub(crate) enum VariantType { +#[derive(Clone)] +pub(crate) enum VariantType<'a> { Ignore, Named { name: String, @@ -30,7 +30,7 @@ pub(crate) enum VariantType { case_insensitive: bool, }, Other { - field_name: Option //use {} for constructor if Some, use () if None + field_name: Option<&'a Ident> //use {} for constructor if Some, use () if None }, } @@ -41,13 +41,47 @@ pub(crate) enum VariantConstructor { Brace, } -pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { +impl<'a> Variant<'a> { + pub(crate) fn match_variant(&self, named_fn: F, other_fn: G) -> Option<(TokenStream, TokenStream)> + where + F: Fn(&str) -> TokenStream, + G: Fn(TokenStream) -> TokenStream + { + let variant_ident = &self.data.ident; + + match &self.v_type { + VariantType::Ignore => None, + + VariantType::Named { name, constructor, .. } => { + let pattern = match constructor { + VariantConstructor::None => quote! { #variant_ident }, + VariantConstructor::Paren => quote! { #variant_ident () }, + VariantConstructor::Brace => quote! { #variant_ident {} } + }; + Some((pattern, named_fn(name))) + } + + VariantType::Other { field_name } => { + let field_name_tokens = match field_name { + Some(field_name) => field_name.to_token_stream(), + None => quote! { __other_inner } + }; + let pattern = match field_name { + Some(_) => quote! { #variant_ident { #field_name_tokens } }, + None => quote! { #variant_ident (#field_name_tokens) } + }; + Some((pattern, other_fn(field_name_tokens))) + } + } + } +} + +pub(crate) fn parse_enum(data: &DataEnum) -> MacroResult { let mut variants = Vec::with_capacity(data.variants.len()); let mut taken_names = HashSet::new(); let mut other_variant = false; - for variant in data.variants { - let variant_ident = variant.ident.to_string(); + for variant in data.variants.iter() { let variant_span = variant.span(); // Parse the `#[enumscribe(...)]` attributes for this variant into a single Dict @@ -64,7 +98,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { let scribe_variant = if ignore { Variant { - ident: variant_ident, + data: variant, v_type: VariantType::Ignore, span: variant_span, } @@ -87,7 +121,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { return Err(MacroError::new( format!( "cannot use {} for variant {} because it is marked as {}", - NAME, variant_ident, OTHER + NAME, variant.ident.to_string(), OTHER ), name_span, )); @@ -98,7 +132,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { return Err(MacroError::new( format!( "the variant {} must have exactly one field because it is marked as {}", - variant_ident, OTHER + variant.ident.to_string(), OTHER ), variant_span, )); @@ -106,10 +140,10 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { // Get the name of the variant's field (or None if it is unnamed) let field_name = variant.fields.iter().next() - .and_then(|field| field.ident.as_ref().map(|ident| ident.to_string())); + .and_then(|field| field.ident.as_ref()); Variant { - ident: variant_ident, + data: variant, v_type: VariantType::Other { field_name }, span: variant_span, } @@ -130,6 +164,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { // Return an error if the variant has any fields if variant.fields.len() != 0 { + let variant_ident = variant.ident.to_string(); return Err(MacroError::new( format!( "the variant {} must not have any fields\n\ @@ -152,7 +187,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult { taken_names.insert(name.clone()); Variant { - ident: variant_ident, + data: variant, v_type: VariantType::Named { name, constructor, case_insensitive }, span: variant_span, } diff --git a/src/error.rs b/src/error.rs index b506ab9..1b1964a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -41,6 +41,18 @@ impl From for MacroError { } } +impl From for proc_macro::TokenStream { + fn from(err: MacroError) -> Self { + err.to_token_stream() + } +} + +impl From for proc_macro2::TokenStream { + fn from(err: MacroError) -> Self { + err.to_token_stream2() + } +} + impl fmt::Display for MacroError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.message) diff --git a/src/lib.rs b/src/lib.rs index 812c6e2..a641cf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,13 @@ use proc_macro::TokenStream; -use std::collections::HashSet; +use std::iter; -use proc_macro2::Span; -use quote::{quote, quote_spanned}; -use syn::{Attribute, Data, DataEnum, DeriveInput, Fields, LitStr}; -use syn::parse::{ParseBuffer, ParseStream}; -use syn::spanned::Spanned; +use quote::quote; +use syn::{Data, DataEnum, DeriveInput}; -use attribute::*; use error::{MacroError, MacroResult}; +use crate::enums::VariantType; + mod enums; mod attribute; mod error; @@ -21,22 +19,69 @@ const OTHER: &'static str = "other"; const IGNORE: &'static str = "ignore"; const CASE_INSENSITIVE: &'static str = "case_insensitive"; +macro_rules! proc_try { + ($x:expr) => { + match $x { + Ok(val) => val, + Err(err) => return err.into() + } + }; +} + #[proc_macro_derive(EnumToString, attributes(enumscribe))] pub fn derive_enum_to_string(input: TokenStream) -> TokenStream { - let input: DeriveInput = syn::parse(input).unwrap(); + let input: DeriveInput = syn::parse(input) + .expect("failed to parse input"); - let enum_data = match input.data { - Data::Enum(data) => data, - Data::Struct(_) => return MacroError::new("cannot use enumscribe for structs", input.ident.span()).to_token_stream(), - Data::Union(_) => return MacroError::new("cannot use enumscribe for unions", input.ident.span()).to_token_stream() - }; + let enum_data = proc_try!(get_enum_data(&input)); + let parsed_enum = proc_try!(enums::parse_enum(enum_data)); - let variants = match enums::parse_enum(enum_data) { - Ok(variants) => variants, - Err(err) => return err.to_token_stream() - }; + let enum_ident = &input.ident; + let enum_idents = iter::repeat(enum_ident); + + if parsed_enum.variants.is_empty() { + return MacroError::new(format!( + "cannot derive EnumToString for {} because it has no variants", + enum_ident.to_string() + ), input.ident.span()).into(); + } + + let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); + let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); - println!("{:?}", variants); + for variant in parsed_enum.variants.iter() { + if let Some((pattern, result)) = variant.match_variant( + |name| quote! { <_ as ::std::borrow::ToOwned>::to_owned(#name) }, + |field| quote! { <_ as ::std::convert::Into<::std::string::String>>::into(#field) } + ) { + match_patterns.push(pattern); + match_results.push(result); + } else { + return MacroError::new(format!( + "cannot derive EnumToString for {} because the variant {} is marked as {}\n\ + explanation: since {} is ignored, it cannot be guaranteed that the enum can \ + always be successfully converted to a String", //TODO: suggest another derive to use instead + enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, + variant.data.ident.to_string(), + ), variant.span).into(); + } + } + + (quote! { + impl ::std::convert::From<#enum_ident> for ::std::string::String { + fn from(__enum_to_scribe: #enum_ident) -> Self { + match __enum_to_scribe { + #(#enum_idents::#match_patterns => #match_results),* + } + } + } + }).into() +} - TokenStream::new() +fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> { + match &input.data { + Data::Enum(data) => Ok(data), + Data::Struct(_) => Err(MacroError::new("enumscribe cannot be used for structs", input.ident.span())), + Data::Union(_) => Err(MacroError::new("enumscribe cannot be used for unions", input.ident.span())) + } }