enum-utils-0.1.2/.gitignore010064400017500001750000000000351346413177100140320ustar0000000000000000target **/*.rs.bk Cargo.lock enum-utils-0.1.2/Cargo.toml.orig010066400017500001750000000014751355112406600147400ustar0000000000000000[package] name = "enum-utils" version = "0.1.2" # remember to update html_root_url authors = ["Dylan MacKenzie "] edition = "2018" description = "A set of useful proc macros for enums" repository = "https://github.com/ecstatic-morse/enum-utils" readme = "README.md" license = "MIT" categories = ["development-tools"] [badges.azure-devops] project = "ecstaticmorse/enum-utils" pipeline = "enum-utils" [workspace] members = [ "bench", "from-str", ] [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" enum-utils-from-str = { path = "from-str", version = "0.1.2" } serde_derive_internals = "0.25" syn = { version = "1.0", features = ["extra-traits"] } [dependencies.failure] version = "0.1" default-features = false features = ["std"] [dev-dependencies] version-sync = "0.8" enum-utils-0.1.2/Cargo.toml0000644000000024570000000000000112030ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "enum-utils" version = "0.1.2" authors = ["Dylan MacKenzie "] description = "A set of useful proc macros for enums" readme = "README.md" categories = ["development-tools"] license = "MIT" repository = "https://github.com/ecstatic-morse/enum-utils" [lib] proc-macro = true [dependencies.enum-utils-from-str] version = "0.1.2" [dependencies.failure] version = "0.1" features = ["std"] default-features = false [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.serde_derive_internals] version = "0.25" [dependencies.syn] version = "1.0" features = ["extra-traits"] [dev-dependencies.version-sync] version = "0.8" [badges.azure-devops] pipeline = "enum-utils" project = "ecstaticmorse/enum-utils" enum-utils-0.1.2/README.md010066400017500001750000000040501355112362400133170ustar0000000000000000# enum-utils [![crates.io](https://img.shields.io/crates/v/enum-utils.svg)](https://crates.io/crates/enum-utils) [![docs.rs](https://docs.rs/enum-utils/badge.svg)](https://docs.rs/enum-utils) [![Build Status](https://dev.azure.com/ecstaticmorse/enum-utils/_apis/build/status/ecstatic-morse.enum-utils?branchName=master)](https://dev.azure.com/ecstaticmorse/enum-utils/_build/latest?definitionId=1&branchName=master) A set of procedural macros for deriving useful functionality on enums. See [the API docs] for more information. [the API docs]: https://docs.rs/enum-utils ## [`FromStr`] An efficient, configurable [`FromStr`][from-str-std] implementation for C-like enums. [`FromStr`]: https://docs.rs/enum-utils/0.1.1/enum_utils/derive.FromStr.html [from-str-std]: https://doc.rust-lang.org/std/str/trait.FromStr.html ```rust #[derive(Debug, PartialEq, enum_utils::FromStr)] enum Test { Alpha, Beta, } assert_eq!("Alpha".parse(), Ok(Test::Alpha)); assert_eq!("Beta".parse(), Ok(Test::Beta)); ``` ## [`IterVariants`] A static method returning an iterator over the variants of an enum. [`IterVariants`]: https://docs.rs/enum-utils/0.1.1/enum_utils/derive.IterVariants.html ```rust #[derive(Debug, PartialEq, Eq, enum_utils::IterVariants)] #[repr(u8)] pub enum Direction { North = 1, East, South, West, } use Direction::*; assert_eq!(Direction::iter().collect::>(), vec![North, East, South, West]); ``` ## [`TryFromRepr`] and [`ReprFrom`] Conversion to and from the discriminant of a C-like enum. [`ReprFrom`]: https://docs.rs/enum-utils/0.1.1/enum_utils/derive.ReprFrom.html [`TryFromRepr`]: https://docs.rs/enum-utils/0.1.1/enum_utils/derive.TryFromRepr.html ```rust use std::convert::TryInto; #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::ReprFrom, enum_utils::TryFromRepr)] #[repr(u8)] pub enum Direction { North = 1, East, South, West } use Direction::*; assert_eq!(1u8, North.into()); assert_eq!(4u8, West.into()); assert_eq!(North, 1u8.try_into().unwrap()); assert_eq!(West, 4u8.try_into().unwrap()); ``` enum-utils-0.1.2/azure-pipelines.yml010066400017500001750000000011061355112362400156760ustar0000000000000000pool: vmImage: 'ubuntu-latest' strategy: matrix: stable: rustup_toolchain: stable beta: rustup_toolchain: beta nightly: rustup_toolchain: nightly steps: - script: | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" displayName: Install rust - script: sudo apt-get install gperf displayName: Install gperf - script: cargo build --all displayName: Cargo build - script: cargo test --all displayName: Cargo test enum-utils-0.1.2/src/attr.rs010066400017500001750000000255241355111664300141630ustar0000000000000000use std::collections::{BTreeSet, LinkedList}; use std::convert::{TryFrom, TryInto}; use std::fmt; use failure::{bail, format_err, Fallible}; #[derive(Debug, Clone, Copy)] pub enum Primitive { U8, U16, U32, U64, U128, Usize, I8, I16, I32, I64, I128, Isize, } impl TryFrom<&syn::Path> for Primitive { type Error = (); fn try_from(path: &syn::Path) -> Result { use self::Primitive::*; let ident = path.get_ident().ok_or(())?; match ident.to_string().as_str() { "u8" => Ok(U8), "u16" => Ok(U16), "u32" => Ok(U32), "u64" => Ok(U64), "u128" => Ok(U128), "usize" => Ok(Usize), "i8" => Ok(I8), "i16" => Ok(I16), "i32" => Ok(I32), "i64" => Ok(I64), "i128" => Ok(I128), "isize" => Ok(Isize), _ => Err(()), } } } impl Primitive { pub fn max_value(&self) -> Option { use self::Primitive::*; match self { U8 => Some(u8::max_value() as u128), U16 => Some(u16::max_value() as u128), U32 => Some(u32::max_value() as u128), U64 => Some(u64::max_value() as u128), U128 => Some(u128::max_value()), I8 => Some(i8::max_value() as u128), I16 => Some(i16::max_value() as u128), I32 => Some(i32::max_value() as u128), I64 => Some(i64::max_value() as u128), I128 => Some(i128::max_value() as u128), Usize | Isize => None, } } } pub fn parse_primitive_repr<'a>(attrs: impl 'a + Iterator) -> Fallible> { let mut repr = None; for attr in attrs { if !attr.path.is_ident("repr") { continue; } let list = match attr.parse_meta()? { syn::Meta::List(list) => list, _ => continue, }; debug_assert!(list.path.is_ident("repr")); // Iterate over `a` and `b` in `#[repr(a, b)]` for arg in &list.nested { match arg { syn::NestedMeta::Meta(syn::Meta::Path(path)) => { match path.try_into() { Ok(_) if repr.is_some() => bail!("Multiple primitive `#[repr(...)]`s"), Ok(prim) => repr = Some((prim, path.clone())), Err(_) => continue, } }, _ => continue, } } } Ok(repr) } pub struct RenameRule(serde_derive_internals::attr::RenameRule); impl RenameRule { pub fn apply_to_variant(&self, s: &str) -> String { self.0.apply_to_variant(s) } } impl fmt::Debug for RenameRule { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RenameRule") .finish() } } pub type ErrorList = LinkedList; macro_rules! bail_list { ($msg:literal $( , $args:expr )* $(,)?) => { { let mut list = ErrorList::new(); list.push_back(failure::format_err!($msg, $($args),*)); return Err(list); } } } #[derive(Debug)] pub enum Attr { CaseInsensitive, Skip, Rename(String), RenameAll(RenameRule), Alias(String), } impl Attr { pub fn parse_attrs(attr: &syn::Attribute) -> impl Iterator> { use syn::NestedMeta; Self::get_args(attr) .map(|arg| { match arg { NestedMeta::Meta(m) => Attr::try_from(&m), _ => bail!("Argument to attribute cannot be a literal"), } }) } /// Returns an iterator over the items in `...` if this attribute looks like `#[enumeration(...)]` fn get_args(attr: &syn::Attribute) -> impl Iterator { use syn::{token, Meta, MetaList, NestedMeta}; if let Ok(Meta::List(MetaList { path, nested, .. })) = attr.parse_meta() { if path.is_ident("enumeration") { return nested.into_iter(); } } syn::punctuated::Punctuated::::new().into_iter() } } /// Parse an attr from the `syn::Meta` inside parens after "enumeration". impl TryFrom<&'_ syn::Meta> for Attr { type Error = failure::Error; fn try_from(meta: &syn::Meta) -> Result { use syn::{Lit, Meta, MetaNameValue}; // Extracts a string literal from a MetaNameValue let lit_val = |lit: &syn::Lit| { match lit { Lit::Str(v) => Ok(v.value()), _ => bail!("Non-string literal"), } }; match meta { // #[enumeration(skip)] Meta::Path(path) if path.is_ident("skip") => Ok(Attr::Skip), // #[enumeration(case_insensitive)] Meta::Path(path) if path.is_ident("case_insensitive") => Ok(Attr::CaseInsensitive), // #[enumeration(rename = "...")] Meta::NameValue(MetaNameValue { path, lit, .. }) if path.is_ident("rename") => Ok(Attr::Rename(lit_val(lit)?)), // #[enumeration(rename_all = "...")] Meta::NameValue(MetaNameValue { path, lit, .. }) if path.is_ident("rename_all") => { let rule = lit_val(lit)?.parse().map_err(|_| format_err!("Invalid RenameAll rule"))?; Ok(Attr::RenameAll(RenameRule(rule))) } // #[enumeration(alias = "...")] Meta::NameValue(MetaNameValue { path, lit, .. }) if path.is_ident("alias") => Ok(Attr::Alias(lit_val(lit)?)), _ => bail!("Unknown attribute argument") } } } #[derive(Debug, Default)] pub struct VariantAttrs { pub skip: bool, pub rename: Option, pub aliases: BTreeSet, } impl VariantAttrs { pub fn from_attrs(attrs: T) -> Result where T: IntoIterator>, { let mut ret = VariantAttrs::default(); let mut errors = ErrorList::default(); for attr in attrs { match attr { Ok(Attr::Skip) => ret.skip = true, Ok(Attr::Rename(s)) => if ret.rename.is_none() { ret.rename = Some(s); } else { errors.push_back(format_err!("Variant cannot be renamed multiple times")); }, Ok(Attr::Alias(s)) => { ret.aliases.insert(s); }, Ok(attr) => errors.push_back(format_err!("Attribute \"{:?}\" is not valid for a variant", attr)), Err(e) => errors.push_back(e), } } if errors.is_empty() { Ok(ret) } else { Err(errors) } } } #[derive(Default)] pub struct EnumAttrs { pub nocase: bool, pub rename_rule: Option, } impl EnumAttrs { pub fn from_attrs(attrs: T) -> Result where T: IntoIterator>, { let mut ret = EnumAttrs::default(); let mut errors = ErrorList::default(); for attr in attrs { match attr { Ok(Attr::CaseInsensitive) => ret.nocase = true, Ok(Attr::RenameAll(r)) => if ret.rename_rule.is_none() { ret.rename_rule = Some(r); } else { errors.push_back(format_err!("Enum can only have a single \"rename_all\" attribute")); }, Ok(attr) => errors.push_back(format_err!("Attribute \"{:?}\" is not valid for an enum", attr)), Err(e) => errors.push_back(e), } } if errors.is_empty() { Ok(ret) } else { Err(errors) } } } pub type Discriminant = i128; pub struct Enum<'a> { pub name: &'a syn::Ident, pub attrs: EnumAttrs, /// This will be `None` if no `#[repr]` was specified, or an error if parsing failed or /// multiple `#[repr]`s were specified. pub primitive_repr: Fallible>, pub variants: Vec<(&'a syn::Variant, VariantAttrs)>, /// None if the enum is not C-like pub discriminants: Option>, } impl<'a> Enum<'a> { pub fn parse(input: &'a syn::DeriveInput) -> Result { use syn::{Data, DataEnum, Expr, ExprLit, Lit}; let DataEnum { variants, .. } = match &input.data { Data::Enum(e) => e, _ => bail_list!("Input must be an enum"), }; let mut errors = ErrorList::default(); let enum_attrs = EnumAttrs::from_attrs(input.attrs .iter() .flat_map(Attr::parse_attrs)); let enum_attrs = match enum_attrs { Ok(attrs) => attrs, Err(e) => { errors = e; Default::default() } }; let mut discriminants = Some(vec![]); let mut parsed_variants = vec![]; for v in variants.iter() { let attrs = VariantAttrs::from_attrs(v.attrs .iter() .flat_map(Attr::parse_attrs)); let attrs = match attrs { Ok(a) => a, Err(mut e) => { errors.append(&mut e); continue; } }; parsed_variants.push((v, attrs)); if v.fields != syn::Fields::Unit { discriminants = None; continue; } if let Some(ds) = discriminants.as_mut() { match &v.discriminant { // An integer literal Some((_, Expr::Lit(ExprLit { lit: Lit::Int(i), .. }))) => { ds.push(i.base10_parse::().expect("Variant overflowed i128")); } // An expr with an unknown value (e.g. a const defined elsewhere) Some(_) => { discriminants = None; } // No discriminant None => { let d = ds.last().map(|&x| x + 1).unwrap_or(0); ds.push(d); } } } } let primitive_repr = parse_primitive_repr(input.attrs.iter()); if !errors.is_empty() { return Err(errors); } Ok(Enum { name: &input.ident, attrs: enum_attrs, variants: parsed_variants, primitive_repr, discriminants, }) } /* pub fn is_c_like(&self) -> bool { self.discriminants.is_some() } */ } enum-utils-0.1.2/src/conv.rs010064400017500001750000000052401353435456200141520ustar0000000000000000use failure::format_err; use proc_macro2::{TokenStream, Span}; use quote::quote; use crate::attr::{Enum, ErrorList}; pub fn derive_try_from_repr(input: &syn::DeriveInput) -> Result { let Enum { name, variants, primitive_repr, .. } = Enum::parse(input)?; let mut errors = ErrorList::new(); let repr = match primitive_repr { Ok(Some((_, repr))) => repr, Ok(None) => bail_list!("`#[repr(...)]` must be specified to derive `TryFrom`"), Err(e) => { errors.push_back(e); return Err(errors); } }; for (v, _) in variants.iter() { if v.fields != syn::Fields::Unit { errors.push_back(format_err!("Variant cannot have fields")); continue; } } if !errors.is_empty() { return Err(errors); } let consts = variants.iter() .map(|(v, _)| { let s = "DISCRIMINANT_".to_owned() + &v.ident.to_string(); syn::Ident::new(s.as_str(), Span::call_site()) }); let ctors = variants.iter() .map(|(v, _)| { let v = &v.ident; quote!(#name::#v) }); // `as` casts are not valid as part of a pattern, so we need to do define new `consts` to hold // them. let const_defs = consts.clone() .zip(ctors.clone()) .map(|(v, ctor)| quote!(const #v: #repr = #ctor as #repr)); Ok(quote! { impl ::std::convert::TryFrom<#repr> for #name { type Error = (); #[allow(non_upper_case_globals)] fn try_from(d: #repr) -> Result { #( #const_defs; )* match d { #( #consts => Ok(#ctors), )* _ => Err(()) } } } }) } pub fn derive_repr_from(input: &syn::DeriveInput) -> Result { let Enum { name, variants, primitive_repr, .. } = Enum::parse(input)?; let mut errors = ErrorList::new(); let repr = match primitive_repr { Ok(Some((_, repr))) => repr, Ok(None) => bail_list!("`#[repr(...)]` must be specified to derive `TryFrom`"), Err(e) => { errors.push_back(e); return Err(errors); } }; for (v, _) in variants.iter() { if v.fields != syn::Fields::Unit { errors.push_back(format_err!("Variant cannot have fields")); continue; } } if !errors.is_empty() { return Err(errors); } Ok(quote! { impl ::std::convert::From<#name> for #repr { fn from(d: #name) -> Self { d as #repr } } }) } enum-utils-0.1.2/src/from_str.rs010064400017500001750000000044701355112366600150420ustar0000000000000000use std::collections::BTreeMap; use failure::format_err; use proc_macro2::TokenStream; use quote::quote; use crate::attr::{Enum, ErrorList}; use enum_utils_from_str::{Case, StrMapFunc}; struct FromStrImpl { nocase: bool, enum_name: syn::Ident, variants: BTreeMap, } impl FromStrImpl { pub fn parse(input: &syn::DeriveInput) -> Result { let Enum { name, attrs: enum_attrs, variants, .. } = Enum::parse(input)?; let mut errors = ErrorList::default(); let mut name_map = BTreeMap::default(); for (v, attrs) in variants.iter() { if attrs.skip { continue; } if v.fields != syn::Fields::Unit { errors.push_back(format_err!("An (unskipped) variant cannot have fields")); } if let Some(name) = &attrs.rename { name_map.insert(name.clone(), v.ident.clone()); } else if let Some(rename_rule) = &enum_attrs.rename_rule { let s = v.ident.to_string(); name_map.insert(rename_rule.apply_to_variant(&*s), v.ident.clone()); } else { let s = v.ident.to_string(); name_map.insert(s, v.ident.clone()); } for alias in &attrs.aliases { name_map.insert(alias.clone(), v.ident.clone()); } } if !errors.is_empty() { return Err(errors); } Ok(FromStrImpl { nocase: enum_attrs.nocase, enum_name: name.clone(), variants: name_map, }) } } pub fn derive(ast: &syn::DeriveInput) -> Result { let FromStrImpl { nocase, enum_name, variants } = FromStrImpl::parse(ast)?; let mut trie = StrMapFunc::new("_parse", &enum_name.to_string()); let case = if nocase { Case::Insensitive } else { Case::Sensitive }; trie.case(case); for (alias, variant) in variants { let path = quote!(#enum_name::#variant); trie.entry(alias.as_str(), path); } Ok(quote!{ impl ::std::str::FromStr for #enum_name { type Err = (); fn from_str(s: &str) -> Result { #trie _parse(s.as_bytes()).ok_or(()) } } }) } enum-utils-0.1.2/src/iter.rs010066400017500001750000000124171355111664300141510ustar0000000000000000use std::ops::{Range, RangeInclusive}; use failure::format_err; use proc_macro2::{Literal, TokenStream}; use quote::quote; use crate::attr::{Discriminant, Enum, ErrorList}; enum IterImpl { Empty, Range { repr: syn::Path, range: Range, }, RangeInclusive { repr: syn::Path, range: RangeInclusive, }, Slice(Vec), } impl IterImpl { /// Constructs the fastest `IterImpl` for the given set of discriminants. /// /// If the discriminants form a single, contiguous, increasing run, we will create a /// `Range` (or `RangeInclusive`) containing the discriminants as the `#[repr(...)]` of the /// enum. fn for_enum(Enum { name, variants, discriminants, primitive_repr, .. }: &Enum) -> Result { // See if we can generate a fast, transmute-based iterator. if let Some(discriminants) = discriminants { let is_zst = discriminants.len() <= 1; if let Ok(Some((repr, repr_path))) = primitive_repr { let unskipped_discriminants: Vec<_> = discriminants .iter() .cloned() .zip(variants.iter()) .filter(|(_, (_, attr))| !attr.skip) .map(|(d, _)| d) .collect(); if unskipped_discriminants.is_empty() { return Ok(IterImpl::Empty); } if !is_zst { if let Some(range) = detect_contiguous_run(unskipped_discriminants.into_iter()) { // If range.end() is less than the maximum value of the primitive repr, we can // use the (faster) non-inclusive `Range` let end = *range.end(); if end < 0 || repr.max_value().map_or(false, |max| (end as u128) < max) { return Ok(IterImpl::Range { repr: repr_path.clone(), range: *range.start()..(end + 1), }) } return Ok(IterImpl::RangeInclusive { repr: repr_path.clone(), range, }) } } } } // ...if not, fall back to the slice based one. let mut errors = ErrorList::new(); let unskipped_variants: Vec<_> = variants .iter() .filter_map(|(v, attr)| { if attr.skip { return None; } if v.fields != syn::Fields::Unit { errors.push_back(format_err!("An (unskipped) variant cannot have fields")); return None; } let vident = &v.ident; Some(quote!(#name::#vident)) }) .collect(); if !errors.is_empty() { return Err(errors); } if unskipped_variants.is_empty() { return Ok(IterImpl::Empty); } Ok(IterImpl::Slice(unskipped_variants)) } fn tokens(&self, ty: &syn::Ident) -> TokenStream { let body = match self { IterImpl::Empty => quote! { ::std::iter::empty() }, IterImpl::Range { range, repr } => { let start = Literal::i128_unsuffixed(range.start); let end = Literal::i128_unsuffixed(range.end); quote! { let start: #repr = #start; let end: #repr = #end; (start .. end).map(|discrim| unsafe { ::std::mem::transmute(discrim) }) } }, IterImpl::RangeInclusive { range, repr } => { let start = Literal::i128_unsuffixed(*range.start()); let end = Literal::i128_unsuffixed(*range.end()); quote! { let start: #repr = #start; let end: #repr = #end; (start ..= end).map(|discrim| unsafe { ::std::mem::transmute(discrim) }) } }, IterImpl::Slice(variants) => quote! { const VARIANTS: &[#ty] = &[#( #variants ),*]; VARIANTS.iter().cloned() }, }; quote! { impl #ty { fn iter() -> impl Iterator + Clone { #body } } } } } /// Returns a range containing the discriminants of this enum if they comprise a single, contiguous /// run. Returns `None` if there were no discriminants or they were not contiguous. fn detect_contiguous_run(mut discriminants: impl Iterator) -> Option> { let first = discriminants.next()?; let mut last = first; while let Some(next) = discriminants.next() { if last.checked_add(1)? != next { return None; } last = next } Some(first..=last) } pub fn derive(input: &syn::DeriveInput) -> Result { let input = Enum::parse(input)?; let imp = IterImpl::for_enum(&input)?; Ok(imp.tokens(&input.name)) } enum-utils-0.1.2/src/lib.rs010066400017500001750000000224511355112362400137500ustar0000000000000000//! A set of procedural macros for deriving useful functionality on enums. #![doc(html_root_url = "https://docs.rs/enum-utils/0.1.2")] extern crate proc_macro; #[macro_use] mod attr; mod iter; mod from_str; mod conv; use proc_macro::TokenStream; use syn::{DeriveInput, parse_macro_input}; fn unwrap_errors(res: Result) -> T { match res { Ok(x) => x, Err(list) => { // TODO: print error spans with proc_macro_diagnostic let desc: String = list.iter() .map(|e| format!("\n{}", e)) .collect(); panic!("enum_utils encountered one or more errors:{}", desc); } } } /// Derives [`FromStr`] for C-like enums. /// /// The generated code will be more efficient than a simple `match` statement for most enums. It is /// guaranteed to run in `O(m)` time (where `m` is the length of the input string) rather than the /// `O(mn)` time (where `n` is the number of variants) which would be required by the naive /// approach. /// /// # Examples /// /// [`FromStr`] can be derived for C-like enums by deriving `enum_utils::FromStr`. /// /// ``` /// #[derive(Debug, PartialEq, enum_utils::FromStr)] /// enum Test { /// Alpha, /// Beta, /// } /// /// assert_eq!("Alpha".parse(), Ok(Test::Alpha)); /// assert_eq!("Beta".parse(), Ok(Test::Beta)); /// ``` /// /// # Attributes /// /// The implementation can be customized by attributes of the form `#[enumeration(...)]`. These /// are based on the ones in [`serde`]. /// /// ## `#[enumeration(skip)]` /// /// This attribute causes a single variant of the enum to be ignored when deserializing. /// Variants which are skipped may have data fields. /// /// ``` /// #[derive(Debug, PartialEq, enum_utils::FromStr)] /// enum Skip { /// #[enumeration(skip)] /// Alpha(usize), /// Beta, /// } /// /// assert_eq!("Alpha".parse::(), Err(())); /// assert_eq!("Beta".parse(), Ok(Skip::Beta)); /// ``` /// /// ## `#[enumeration(rename = "...")]` /// /// This attribute renames a single variant of an enum. This replaces the name of the variant and /// overrides [`rename_all`]. /// /// Only one [`rename`] attribute can appear for each enum variant. /// /// ``` /// #[derive(Debug, PartialEq, enum_utils::FromStr)] /// enum Rename { /// #[enumeration(rename = "α")] /// Alpha, /// Beta, /// } /// /// assert_eq!("Alpha".parse::(), Err(())); /// assert_eq!("α".parse(), Ok(Rename::Alpha)); /// assert_eq!("Beta".parse(), Ok(Rename::Beta)); /// ``` /// /// ## `#[enumeration(alias = "...")]` /// /// This attribute is similar to [`rename`], but it does not replace the name of the variant. /// /// Unlike [`rename`], there is no limit to the number of `alias` attributes which can be applied. /// This allows multiple strings to serialize to the same variant. /// /// ``` /// #[derive(Debug, PartialEq, enum_utils::FromStr)] /// enum Alias { /// #[enumeration(alias = "A", alias = "α")] /// Alpha, /// Beta, /// } /// /// assert_eq!("Alpha".parse(), Ok(Alias::Alpha)); /// assert_eq!("A".parse(), Ok(Alias::Alpha)); /// assert_eq!("α".parse(), Ok(Alias::Alpha)); /// assert_eq!("Beta".parse(), Ok(Alias::Beta)); /// ``` /// /// ## `#[enumeration(rename_all = "...")]` /// /// This attribute can be applied to an entire enum, and causes all fields to be renamed according /// to the given [rename rule]. All rename rules defined in [`serde`] are supported. /// /// ``` /// #[enumeration(rename_all = "snake_case")] /// #[derive(Debug, PartialEq, enum_utils::FromStr)] /// enum RenameAll { /// FooBar, /// BarFoo, /// } /// /// assert_eq!("foo_bar".parse(), Ok(RenameAll::FooBar)); /// assert_eq!("bar_foo".parse(), Ok(RenameAll::BarFoo)); /// ``` /// /// ## `#[enumeration(case_insensitive)]` /// /// This attribute can be applied to an entire enum, it causes all variants to be parsed /// case-insensitively. /// /// ``` /// #[enumeration(case_insensitive)] /// #[derive(Debug, PartialEq, enum_utils::FromStr)] /// enum NoCase { /// Alpha, /// Beta, /// } /// /// assert_eq!("ALPHA".parse(), Ok(NoCase::Alpha)); /// assert_eq!("beta".parse(), Ok(NoCase::Beta)); /// ``` /// /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html /// [`serde`]: https://serde.rs/attributes.html /// [`rename`]: #enumerationrename-- /// [`rename_all`]: #enumerationrename_all-- /// [rename rule]: https://serde.rs/container-attrs.html#rename_all #[proc_macro_derive(FromStr, attributes(enumeration))] pub fn from_str_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); unwrap_errors(from_str::derive(&ast)).into() } /// Derives a static method, `iter()`, which iterates over the variants of an enum. /// /// # Examples /// /// Variants are yielded in the order they are defined. If the discriminants of the enum form a /// single, increasing run and `#[repr(...)]` is specified as in the following example, a fast /// implementation of `iter` can be generated which does not require the enum to implement `Clone`. /// /// ``` /// /// The discriminants of this enum are `[1, 2, 3, 4]`. /// #[derive(Debug, PartialEq, Eq, enum_utils::IterVariants)] /// #[repr(u8)] /// pub enum Direction { /// North = 1, /// East, /// South, /// West, /// } /// /// use Direction::*; /// assert_eq!(Direction::iter().collect::>(), vec![North, East, South, West]); /// ``` /// /// If the preceding conditions are not met, `Clone` must be implemented to successfully derive /// `IterVariants`. `enum_utils` will create a `const` array containing each variant and iterate /// over that. /// /// ``` /// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::IterVariants)] /// #[repr(u16)] /// pub enum Bitmask { /// Empty = 0x0000, /// Full = 0xffff, /// } /// /// use Bitmask::*; /// assert_eq!(Bitmask::iter().collect::>(), vec![Empty, Full]); /// ``` /// /// Named constants or complex expressions (beyond an integer literal) are not evaluated when used /// as a discriminant and will cause `IterVariants` to default to the `Clone`-based implementation. /// /// ```compile_fail /// #[derive(Debug, PartialEq, Eq, enum_utils::IterVariants)] // Missing `Clone` impl /// #[repr(u8)] /// pub enum Bitmask { /// Bit1 = 1 << 0, /// Bit2 = 1 << 1, /// } /// ``` /// /// # Attributes /// /// ## `#[enumeration(skip)]` /// /// Use `#[enumeration(skip)]` to avoid iterating over a variant. This can be useful when an enum /// contains a "catch-all" variant. /// /// ``` /// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::IterVariants)] /// pub enum Http2FrameType { /// Data, /// Headers, /// /// /* ... */ /// /// Continuation, /// /// #[enumeration(skip)] /// Unknown(u8), /// } /// /// use Http2FrameType::*; /// assert_eq!(Http2FrameType::iter().collect::>(), /// vec![Data, Headers, /* ... */ Continuation]); /// ``` #[proc_macro_derive(IterVariants, attributes(enumeration))] pub fn iter_variants_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); unwrap_errors(iter::derive(&ast)).into() } /// Derives [`TryFrom`] for a C-like enum, where `Repr` is a [primitive representation] /// specified in `#[repr(...)]`. /// /// [`TryFrom`]: https://doc.rust-lang.org/std/convert/trait.TryFrom.html /// [primitive representation]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations /// /// # Examples /// /// ``` /// use std::convert::{TryFrom, TryInto}; /// /// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::TryFromRepr)] /// #[repr(u8)] /// pub enum Direction { /// North = 1, /// East, /// South, /// West /// } /// /// use Direction::*; /// assert_eq!(North, 1u8.try_into().unwrap()); /// assert_eq!(West, 4u8.try_into().unwrap()); /// assert_eq!(Err(()), Direction::try_from(0u8)); /// assert_eq!(Err(()), Direction::try_from(5u8)); /// ``` /// /// This macro only works on C-like enums. /// /// ```compile_fail /// #[derive(Debug, Clone, enum_utils::TryFromRepr)] /// #[repr(u8)] /// pub enum Letter { /// A, /// B, /// Other(u8), /// } /// ``` #[proc_macro_derive(TryFromRepr, attributes(enumeration))] pub fn try_from_repr_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); unwrap_errors(conv::derive_try_from_repr(&ast)).into() } /// Derives [`From`] for the [primitive representation] specified in `#[repr(...)]`. /// /// [`From`]: https://doc.rust-lang.org/std/convert/trait.From.html /// [primitive representation]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations /// /// # Examples /// /// ``` /// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::ReprFrom)] /// #[repr(u8)] /// pub enum Direction { /// North = 1, /// East, /// South, /// West /// } /// /// use Direction::*; /// assert_eq!(1u8, North.into()); /// assert_eq!(4u8, West.into()); /// ``` /// /// This macro only works on C-like enums. /// /// ```compile_fail /// #[derive(Debug, Clone, enum_utils::TryFromRepr)] /// #[repr(u8)] /// pub enum Letter { /// A, /// B, /// Other(u8), /// } /// ``` #[proc_macro_derive(ReprFrom, attributes(enumeration))] pub fn repr_from_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); unwrap_errors(conv::derive_repr_from(&ast)).into() } enum-utils-0.1.2/tests/iter.rs010066400017500001750000000023631355112362400145200ustar0000000000000000use enum_utils::IterVariants; #[derive(Debug, IterVariants, PartialEq, Eq)] #[repr(u32)] enum LargeDiscriminant { MaxMinusOne = 0xffff_fffe, Max = 0xffff_ffff, } #[test] fn large_discriminant() { use self::LargeDiscriminant::*; assert_eq!(vec![MaxMinusOne, Max], LargeDiscriminant::iter().collect::>()); } #[derive(Debug, Clone, IterVariants, PartialEq, Eq)] #[repr(u8)] enum Zst { Singleton, } #[test] fn zst() { assert_eq!(vec![Zst::Singleton], Zst::iter().collect::>()); } #[derive(Debug, IterVariants, PartialEq, Eq)] enum Empty {} #[test] fn empty() { assert_eq!(Vec::::new(), Empty::iter().collect::>()); } #[derive(Debug, Clone, IterVariants, PartialEq, Eq)] #[repr(u8)] #[allow(unused)] enum SkipCLike { A, #[enumeration(skip)] B, C, } #[test] fn skip_c_like() { use self::SkipCLike::*; assert_eq!(vec![A, C], SkipCLike::iter().collect::>()); } #[derive(Debug, IterVariants, PartialEq, Eq)] #[repr(C, u16)] #[repr(align(2))] enum MultiRepr { A, B, } #[test] fn multi_repr() { use self::MultiRepr::*; assert_eq!(vec![A, B], MultiRepr::iter().collect::>()); } enum-utils-0.1.2/tests/version.rs010066400017500001750000000003041355112362400152330ustar0000000000000000#[test] fn test_readme_deps() { version_sync::assert_markdown_deps_updated!("README.md"); } #[test] fn test_html_root_url() { version_sync::assert_html_root_url_updated!("src/lib.rs"); } enum-utils-0.1.2/.cargo_vcs_info.json0000644000000001120000000000000131670ustar00{ "git": { "sha1": "c1b8645459e8b9cad1e58295e3a79344e2052bd3" } }