Conversion from enum to owned string
This commit is contained in:
1 parent
1a6feab1f2
commit
bfc8a82627
4 files changed
+143
-45
No files matched your search
@@ -5,12 +5,18 @@ extern crate enumscribe;
|
|||||||
enum Foo {
|
enum Foo {
|
||||||
#[enumscribe(str = "b", case_insensitive)]
|
#[enumscribe(str = "b", case_insensitive)]
|
||||||
Baa,
|
Baa,
|
||||||
#[enumscribe(ignore)]
|
// #[enumscribe(ignore)]
|
||||||
Baz(),
|
Baz(),
|
||||||
#[enumscribe(other)]
|
#[enumscribe(other)]
|
||||||
Lorem { inner: String }
|
Lorem { inner: String }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello world!");
|
let foo = Foo::Baa;
|
||||||
|
|
||||||
|
// let s: String = foo.into();
|
||||||
|
|
||||||
|
let s: String = <_ as std::convert::Into<String>>::into(foo);
|
||||||
|
|
||||||
|
println!("Hello, {}!", s);
|
||||||
}
|
}
|
||||||
+59
-24
@@ -1,28 +1,28 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use syn::{Attribute, Data, DataEnum, DeriveInput, Fields, LitStr};
|
use quote::{quote, ToTokens};
|
||||||
use syn::parse::{ParseBuffer, ParseStream};
|
use syn::{DataEnum, Fields};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
use crate::{CRATE_ATTR, NAME, OTHER, IGNORE, CASE_INSENSITIVE};
|
use crate::{CASE_INSENSITIVE, CRATE_ATTR, IGNORE, NAME, OTHER};
|
||||||
use crate::attribute::*;
|
use crate::attribute::{Dict, Value};
|
||||||
use crate::error::{MacroError, MacroResult};
|
use crate::error::{MacroError, MacroResult};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Enum {
|
pub(crate) struct Enum<'a> {
|
||||||
pub(crate) variants: Vec<Variant>,
|
pub(crate) variants: Vec<Variant<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Variant {
|
pub(crate) struct Variant<'a> {
|
||||||
pub(crate) ident: String,
|
pub(crate) data: &'a syn::Variant,
|
||||||
pub(crate) v_type: VariantType,
|
pub(crate) v_type: VariantType<'a>,
|
||||||
pub(crate) span: Span,
|
pub(crate) span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum VariantType {
|
pub(crate) enum VariantType<'a> {
|
||||||
Ignore,
|
Ignore,
|
||||||
Named {
|
Named {
|
||||||
name: String,
|
name: String,
|
||||||
@@ -30,7 +30,7 @@ pub(crate) enum VariantType {
|
|||||||
case_insensitive: bool,
|
case_insensitive: bool,
|
||||||
},
|
},
|
||||||
Other {
|
Other {
|
||||||
field_name: Option<String> //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,
|
Brace,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
impl<'a> Variant<'a> {
|
||||||
|
pub(crate) fn match_variant<F, G>(&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<Enum> {
|
||||||
let mut variants = Vec::with_capacity(data.variants.len());
|
let mut variants = Vec::with_capacity(data.variants.len());
|
||||||
let mut taken_names = HashSet::new();
|
let mut taken_names = HashSet::new();
|
||||||
let mut other_variant = false;
|
let mut other_variant = false;
|
||||||
|
|
||||||
for variant in data.variants {
|
for variant in data.variants.iter() {
|
||||||
let variant_ident = variant.ident.to_string();
|
|
||||||
let variant_span = variant.span();
|
let variant_span = variant.span();
|
||||||
|
|
||||||
// Parse the `#[enumscribe(...)]` attributes for this variant into a single Dict
|
// Parse the `#[enumscribe(...)]` attributes for this variant into a single Dict
|
||||||
@@ -64,7 +98,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|||||||
|
|
||||||
let scribe_variant = if ignore {
|
let scribe_variant = if ignore {
|
||||||
Variant {
|
Variant {
|
||||||
ident: variant_ident,
|
data: variant,
|
||||||
v_type: VariantType::Ignore,
|
v_type: VariantType::Ignore,
|
||||||
span: variant_span,
|
span: variant_span,
|
||||||
}
|
}
|
||||||
@@ -87,7 +121,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|||||||
return Err(MacroError::new(
|
return Err(MacroError::new(
|
||||||
format!(
|
format!(
|
||||||
"cannot use {} for variant {} because it is marked as {}",
|
"cannot use {} for variant {} because it is marked as {}",
|
||||||
NAME, variant_ident, OTHER
|
NAME, variant.ident.to_string(), OTHER
|
||||||
),
|
),
|
||||||
name_span,
|
name_span,
|
||||||
));
|
));
|
||||||
@@ -98,7 +132,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|||||||
return Err(MacroError::new(
|
return Err(MacroError::new(
|
||||||
format!(
|
format!(
|
||||||
"the variant {} must have exactly one field because it is marked as {}",
|
"the variant {} must have exactly one field because it is marked as {}",
|
||||||
variant_ident, OTHER
|
variant.ident.to_string(), OTHER
|
||||||
),
|
),
|
||||||
variant_span,
|
variant_span,
|
||||||
));
|
));
|
||||||
@@ -106,10 +140,10 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|||||||
|
|
||||||
// Get the name of the variant's field (or None if it is unnamed)
|
// Get the name of the variant's field (or None if it is unnamed)
|
||||||
let field_name = variant.fields.iter().next()
|
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 {
|
Variant {
|
||||||
ident: variant_ident,
|
data: variant,
|
||||||
v_type: VariantType::Other { field_name },
|
v_type: VariantType::Other { field_name },
|
||||||
span: variant_span,
|
span: variant_span,
|
||||||
}
|
}
|
||||||
@@ -130,6 +164,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|||||||
|
|
||||||
// Return an error if the variant has any fields
|
// Return an error if the variant has any fields
|
||||||
if variant.fields.len() != 0 {
|
if variant.fields.len() != 0 {
|
||||||
|
let variant_ident = variant.ident.to_string();
|
||||||
return Err(MacroError::new(
|
return Err(MacroError::new(
|
||||||
format!(
|
format!(
|
||||||
"the variant {} must not have any fields\n\
|
"the variant {} must not have any fields\n\
|
||||||
@@ -152,7 +187,7 @@ pub(crate) fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|||||||
taken_names.insert(name.clone());
|
taken_names.insert(name.clone());
|
||||||
|
|
||||||
Variant {
|
Variant {
|
||||||
ident: variant_ident,
|
data: variant,
|
||||||
v_type: VariantType::Named { name, constructor, case_insensitive },
|
v_type: VariantType::Named { name, constructor, case_insensitive },
|
||||||
span: variant_span,
|
span: variant_span,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,18 @@ impl From<syn::Error> for MacroError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<MacroError> for proc_macro::TokenStream {
|
||||||
|
fn from(err: MacroError) -> Self {
|
||||||
|
err.to_token_stream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MacroError> for proc_macro2::TokenStream {
|
||||||
|
fn from(err: MacroError) -> Self {
|
||||||
|
err.to_token_stream2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for MacroError {
|
impl fmt::Display for MacroError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.message)
|
write!(f, "{}", self.message)
|
||||||
|
|||||||
+64
-19
@@ -1,15 +1,13 @@
|
|||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use std::collections::HashSet;
|
use std::iter;
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use quote::quote;
|
||||||
use quote::{quote, quote_spanned};
|
use syn::{Data, DataEnum, DeriveInput};
|
||||||
use syn::{Attribute, Data, DataEnum, DeriveInput, Fields, LitStr};
|
|
||||||
use syn::parse::{ParseBuffer, ParseStream};
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
use attribute::*;
|
|
||||||
use error::{MacroError, MacroResult};
|
use error::{MacroError, MacroResult};
|
||||||
|
|
||||||
|
use crate::enums::VariantType;
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
mod attribute;
|
mod attribute;
|
||||||
mod error;
|
mod error;
|
||||||
@@ -21,22 +19,69 @@ const OTHER: &'static str = "other";
|
|||||||
const IGNORE: &'static str = "ignore";
|
const IGNORE: &'static str = "ignore";
|
||||||
const CASE_INSENSITIVE: &'static str = "case_insensitive";
|
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))]
|
#[proc_macro_derive(EnumToString, attributes(enumscribe))]
|
||||||
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
|
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 {
|
let enum_data = proc_try!(get_enum_data(&input));
|
||||||
Data::Enum(data) => data,
|
let parsed_enum = proc_try!(enums::parse_enum(enum_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 variants = match enums::parse_enum(enum_data) {
|
let enum_ident = &input.ident;
|
||||||
Ok(variants) => variants,
|
let enum_idents = iter::repeat(enum_ident);
|
||||||
Err(err) => return err.to_token_stream()
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{:?}", variants);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
TokenStream::new()
|
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
|
||||||
|
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user