Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

minimal extraction of enum_metadata from PR #207. #208

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions strum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
// only for documentation purposes
pub mod additional_attributes;

use core::ops;

/// The ParseError enum is a collection of all the possible reasons
/// an enum can fail to parse from a string.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
Expand Down Expand Up @@ -190,6 +192,44 @@ pub trait VariantNames {
const VARIANTS: &'static [&'static str];
}

pub trait EnumMetadata {
/// The repr type as a trait associated type.
type Repr: Copy
+ ops::BitOr
+ ops::BitAnd
+ ops::BitXor
+ ops::Shr
+ ops::Shl
+ ops::Not
+ ops::BitOrAssign
+ ops::BitAndAssign
+ ops::BitXorAssign
+ ops::ShrAssign
+ ops::ShlAssign
+ core::cmp::Eq
+ core::cmp::Ord
+ core::cmp::PartialEq
+ core::cmp::PartialOrd
+ core::fmt::Display
+ core::fmt::Debug;

/// The enum type, generally Self unless deriving EnumMetadata for a type which returns
/// metadata for another enum.
type EnumT: EnumMetadata;

/// Variant names
const VARIANTS: &'static [&'static str];
/// Number of variants
const COUNT: usize;
/// std::mem::size_of<Self::Repr>().
const REPR_SIZE: usize;

/// convert to the enums #[repr(..)] equivalent to `self as ..`
fn to_repr(self) -> Self::Repr;
/// Trait equivalent of EnumT::from_repr(...)
fn from_repr(repr: Self::Repr) -> Option<Self::EnumT>;
}

#[cfg(feature = "derive")]
pub use strum_macros::*;

Expand All @@ -214,6 +254,7 @@ DocumentMacroRexports! {
EnumCount,
EnumDiscriminants,
EnumIter,
EnumMetadata,
EnumMessage,
EnumProperty,
EnumString,
Expand Down
214 changes: 214 additions & 0 deletions strum_macros/src/helpers/metadata_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{
Data, DeriveInput, ImplGenerics, PathArguments, Type, TypeGenerics, TypeParen, WhereClause,
};

use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};

pub struct MetadataImpl<'a> {
ast: &'a syn::DeriveInput,
variants: &'a syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
gen_names: Option<Vec<syn::LitStr>>,
gen_from_repr: Option<FromReprTokens>,
generics_split: (ImplGenerics<'a>, TypeGenerics<'a>, Option<&'a WhereClause>),
pub enum_count: usize,
pub has_additional_data: bool,
}

pub struct FromReprTokens {
pub constant_defs: Vec<TokenStream>,
pub match_arms: Vec<TokenStream>,
}

impl<'a> MetadataImpl<'a> {
pub fn new(ast: &'a DeriveInput) -> syn::Result<Self> {
let gen = &ast.generics;
let generics_split = gen.split_for_impl();

match &ast.data {
Data::Enum(_) => (),
_ => return Err(non_enum_error()),
};

let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => return Err(non_enum_error()),
};
let enum_count = variants.len();

Ok(MetadataImpl {
ast,
enum_count,
gen_names: None,
gen_from_repr: None,
generics_split,
variants,
has_additional_data: false,
})
}

pub fn use_name_info(mut self) -> Self {
self.gen_names = Some(Vec::new());
self
}

pub fn use_from_repr(mut self) -> syn::Result<Self> {
let gen = &self.ast.generics;
if gen.lifetimes().count() > 0 {
return Err(syn::Error::new(
Span::call_site(),
"This macro doesn't support enums with lifetimes. \
The resulting enums would be unbounded.",
));
}

self.gen_from_repr = Some(FromReprTokens {
constant_defs: Vec::new(),
match_arms: Vec::new(),
});
Ok(self)
}

pub fn discriminant_type(&self) -> Type {
let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap();
for attr in &self.ast.attrs {
let path = &attr.path;
let tokens = &attr.tokens;
if path.leading_colon.is_some() {
continue;
}
if path.segments.len() != 1 {
continue;
}
let segment = path.segments.first().unwrap();
if segment.ident != "repr" {
continue;
}
if segment.arguments != PathArguments::None {
continue;
}
let typ_paren = match syn::parse2::<Type>(tokens.clone()) {
Ok(Type::Paren(TypeParen { elem, .. })) => *elem,
_ => continue,
};
let inner_path = match &typ_paren {
Type::Path(t) => t,
_ => continue,
};
if let Some(seg) = inner_path.path.segments.last() {
for t in &[
"u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
] {
if seg.ident == t {
discriminant_type = typ_paren;
break;
}
}
}
}
discriminant_type
}

fn params_from_fields(fields: &syn::Fields) -> (TokenStream, bool) {
use syn::Fields::*;
match &fields {
Unit => (quote! {}, false),
Unnamed(fields) => {
let defaults = ::std::iter::repeat(quote!(::core::default::Default::default()))
.take(fields.unnamed.len());
(quote! { (#(#defaults),*) }, true)
}
Named(fields) => {
let fields = fields
.named
.iter()
.map(|field| field.ident.as_ref().unwrap());
(
quote! { {#(#fields: ::core::default::Default::default()),*} },
true,
)
}
}
}

fn case_style(&self) -> Option<crate::helpers::case_style::CaseStyle> {
if let Ok(props) = self.ast.get_type_properties() {
props.case_style
} else {
None
}
}

pub fn generate(&mut self) -> syn::Result<()> {
let name = &self.ast.ident;
let discriminant_type = self.discriminant_type();

let case_style = self.case_style();
let mut prev_const_var_ident = None;

for variant in self.variants {
let props = variant.get_variant_properties()?;

if let Some(variant_names) = &mut self.gen_names {
variant_names.push(props.get_preferred_name(case_style));
}

if let Some(FromReprTokens {
match_arms,
constant_defs,
}) = &mut self.gen_from_repr
{
if props.disabled.is_some() {
continue;
}

let ident = &variant.ident;
let (params, has_additional_data) = Self::params_from_fields(&variant.fields);
if has_additional_data {
self.has_additional_data = has_additional_data;
}

let const_var_ident = {
use heck::ToShoutySnakeCase;
let const_var_str = format!("{}_DISCRIMINANT", ident).to_shouty_snake_case();
format_ident!("{}", const_var_str)
};

let const_val_expr = match &variant.discriminant {
Some((_, expr)) => quote! { #expr },
None => match &prev_const_var_ident {
Some(prev) => quote! { #prev + 1 },
None => quote! { 0 },
},
};

constant_defs
.push(quote! {const #const_var_ident: #discriminant_type = #const_val_expr;});
match_arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)});
prev_const_var_ident = Some(const_var_ident);
}
}
if let Some(FromReprTokens { match_arms, .. }) = &mut self.gen_from_repr {
match_arms.push(quote! { _ => ::core::option::Option::None });
}

Ok(())
}

pub fn variant_names(&self) -> &Option<Vec<syn::LitStr>> {
&self.gen_names
}

pub fn from_repr(&self) -> &Option<FromReprTokens> {
&self.gen_from_repr
}

pub fn enum_count(&self) -> usize {
self.enum_count
}

pub fn generics_split(&self) -> &(ImplGenerics<'a>, TypeGenerics<'a>, Option<&'a WhereClause>) {
&self.generics_split
}
}
1 change: 1 addition & 0 deletions strum_macros/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub use self::variant_props::HasStrumVariantProperties;

pub mod case_style;
mod metadata;
pub mod metadata_impl;
pub mod type_props;
pub mod variant_props;

Expand Down
11 changes: 11 additions & 0 deletions strum_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,17 @@ pub fn from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
toks.into()
}

/// Enum Metadata documentation.
#[proc_macro_derive(EnumMetadata, attributes(strum))]
pub fn enum_metadata(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);

let toks = macros::enum_metadata::enum_metadata_inner(&ast)
.unwrap_or_else(|err| err.to_compile_error());
debug_print_generated(&ast, &toks);
toks.into()
}

/// Add a verbose message to an enum variant.
///
/// Encode strings into the enum itself. The `strum_macros::EmumMessage` macro implements the `strum::EnumMessage` trait.
Expand Down
10 changes: 4 additions & 6 deletions strum_macros/src/macros/enum_count.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use crate::helpers::metadata_impl::MetadataImpl;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};
use syn::DeriveInput;

use crate::helpers::{non_enum_error, HasTypeProperties};
use crate::helpers::HasTypeProperties;

pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let n = match &ast.data {
Data::Enum(v) => v.variants.len(),
_ => return Err(non_enum_error()),
};
let n = MetadataImpl::new(ast)?.enum_count;
let type_properties = ast.get_type_properties()?;
let strum_module_path = type_properties.crate_module_path();

Expand Down
47 changes: 47 additions & 0 deletions strum_macros/src/macros/enum_metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::helpers::metadata_impl::{FromReprTokens, MetadataImpl};
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

pub fn enum_metadata_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let mut metadata = MetadataImpl::new(ast)?.use_name_info().use_from_repr()?;
let discriminant_type = metadata.discriminant_type();
metadata.generate()?;
let enum_count = metadata.enum_count();
let variant_names = metadata.variant_names().as_ref().unwrap();
let (impl_generics, ty_generics, where_clause) = &metadata.generics_split();

let FromReprTokens {
constant_defs,
match_arms,
} = &metadata.from_repr().as_ref().unwrap();

Ok(quote! {
impl #impl_generics EnumMetadata for #name #ty_generics #where_clause {
#[doc = "The Repr type."]
type Repr = #discriminant_type;
#[doc = "The Enum type, typically Self unless implementing EnumMetadata for another enum type."]
type EnumT = Self;

const VARIANTS: &'static [&'static str] = &[ #(#variant_names),* ];
const COUNT: usize = #enum_count;
const REPR_SIZE: usize = ::core::mem::size_of::<Self::Repr>();

fn to_repr(self) -> #discriminant_type {
self as #discriminant_type
}

// Note: synchronize changes with `FromRepr::from_repr`,
// it duplicates this logic in an inherent impl.
// Making it possible to have both impls on the same type;
// so their behavior must be kept the same.
fn from_repr(discriminant: #discriminant_type) -> Option<Self> {
#(#constant_defs)*
match discriminant {
#(#match_arms),*
}
}
}
})
}
Loading