Factor out a discriminant processing library
diff --git a/syntax/discriminant.rs b/syntax/discriminant.rs
new file mode 100644
index 0000000..2a55468
--- /dev/null
+++ b/syntax/discriminant.rs
@@ -0,0 +1,99 @@
+use proc_macro2::{Literal, Span, TokenStream};
+use quote::ToTokens;
+use std::collections::HashSet;
+use std::fmt::{self, Display};
+use std::str::FromStr;
+use syn::{Error, Expr, Lit, Result};
+
+pub struct DiscriminantSet {
+ values: HashSet<Discriminant>,
+ previous: Option<Discriminant>,
+}
+
+#[derive(Copy, Clone, Hash, Eq, PartialEq)]
+pub struct Discriminant {
+ magnitude: u32,
+}
+
+impl DiscriminantSet {
+ pub fn new() -> Self {
+ DiscriminantSet {
+ values: HashSet::new(),
+ previous: None,
+ }
+ }
+
+ pub fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
+ let discriminant = expr_to_discriminant(expr)?;
+ insert(self, discriminant)
+ }
+
+ pub fn insert_next(&mut self) -> Result<Discriminant> {
+ let discriminant = match self.previous {
+ None => Discriminant::zero(),
+ Some(mut discriminant) => {
+ if discriminant.magnitude == u32::MAX {
+ let msg = format!("discriminant overflow on value after {}", u32::MAX);
+ return Err(Error::new(Span::call_site(), msg));
+ }
+ discriminant.magnitude += 1;
+ discriminant
+ }
+ };
+ insert(self, discriminant)
+ }
+}
+
+fn expr_to_discriminant(expr: &Expr) -> Result<Discriminant> {
+ if let Expr::Lit(expr) = expr {
+ if let Lit::Int(lit) = &expr.lit {
+ return lit.base10_parse::<Discriminant>();
+ }
+ }
+ Err(Error::new_spanned(
+ expr,
+ "enums with non-integer literal discriminants are not supported yet",
+ ))
+}
+
+fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
+ if set.values.insert(discriminant) {
+ set.previous = Some(discriminant);
+ Ok(discriminant)
+ } else {
+ let msg = format!("discriminant value `{}` already exists", discriminant);
+ Err(Error::new(Span::call_site(), msg))
+ }
+}
+
+impl Discriminant {
+ fn zero() -> Self {
+ Discriminant { magnitude: 0 }
+ }
+}
+
+impl Display for Discriminant {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt(&self.magnitude, f)
+ }
+}
+
+impl ToTokens for Discriminant {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ Literal::u32_unsuffixed(self.magnitude).to_tokens(tokens);
+ }
+}
+
+impl FromStr for Discriminant {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self> {
+ match s.parse::<u32>() {
+ Ok(magnitude) => Ok(Discriminant { magnitude }),
+ Err(_) => Err(Error::new(
+ Span::call_site(),
+ "discriminant value outside of supported range",
+ )),
+ }
+ }
+}
diff --git a/syntax/mod.rs b/syntax/mod.rs
index d21c083..c109229 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -4,6 +4,7 @@
mod attrs;
pub mod check;
mod derive;
+mod discriminant;
mod doc;
pub mod error;
pub mod ident;
@@ -17,6 +18,7 @@
mod tokens;
pub mod types;
+use self::discriminant::Discriminant;
use self::parse::kw;
use proc_macro2::{Ident, Span};
use syn::punctuated::Punctuated;
@@ -106,7 +108,7 @@
pub struct Variant {
pub ident: Ident,
- pub discriminant: u32,
+ pub discriminant: Discriminant,
}
pub enum Type {
diff --git a/syntax/parse.rs b/syntax/parse.rs
index f84ff32..eff14c0 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -1,3 +1,4 @@
+use crate::syntax::discriminant::DiscriminantSet;
use crate::syntax::report::Errors;
use crate::syntax::Atom::*;
use crate::syntax::{
@@ -6,15 +7,12 @@
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
-use std::collections::HashSet;
-use std::u32;
use syn::parse::{ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::{
- Abi, Attribute, Error, Expr, ExprLit, Fields, FnArg, ForeignItem, ForeignItemFn,
- ForeignItemType, GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Lit, Pat,
- PathArguments, Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath,
- TypeReference, TypeSlice, Variant as RustVariant,
+ Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
+ GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Pat, PathArguments, Result,
+ ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice,
};
pub mod kw {
@@ -108,8 +106,7 @@
let doc = attrs::parse_doc(cx, &item.attrs);
let mut variants = Vec::new();
- let mut discriminants = HashSet::new();
- let mut prev_discriminant = None;
+ let mut discriminants = DiscriminantSet::new();
for variant in item.variants {
match variant.fields {
Fields::Unit => {}
@@ -120,21 +117,19 @@
));
}
}
- if variant.discriminant.is_none() && prev_discriminant == Some(u32::MAX) {
- let msg = format!("discriminant overflow on value after {}", u32::MAX);
- return Err(Error::new_spanned(variant, msg));
- }
- let discriminant =
- parse_discriminant(&variant)?.unwrap_or_else(|| prev_discriminant.map_or(0, |n| n + 1));
- if !discriminants.insert(discriminant) {
- let msg = format!("discriminant value `{}` already exists", discriminant);
- return Err(Error::new_spanned(variant, msg));
- }
+ let expr = variant.discriminant.as_ref().map(|(_, expr)| expr);
+ let try_discriminant = match &expr {
+ Some(lit) => discriminants.insert(lit),
+ None => discriminants.insert_next(),
+ };
+ let discriminant = match try_discriminant {
+ Ok(discriminant) => discriminant,
+ Err(err) => return Err(Error::new_spanned(variant, err)),
+ };
variants.push(Variant {
ident: variant.ident,
discriminant,
});
- prev_discriminant = Some(discriminant);
}
Ok(Api::Enum(Enum {
@@ -146,28 +141,6 @@
}))
}
-fn parse_discriminant(variant: &RustVariant) -> Result<Option<u32>> {
- match &variant.discriminant {
- None => Ok(None),
- Some((
- _,
- Expr::Lit(ExprLit {
- lit: Lit::Int(n), ..
- }),
- )) => match n.base10_parse() {
- Ok(val) => Ok(Some(val)),
- Err(_) => Err(Error::new_spanned(
- variant,
- "cannot parse enum discriminant as an integer",
- )),
- },
- _ => Err(Error::new_spanned(
- variant,
- "enums with non-integer literal discriminants are not supported yet",
- )),
- }
-}
-
fn parse_foreign_mod(cx: &mut Errors, foreign_mod: ItemForeignMod, out: &mut Vec<Api>) {
let lang = match parse_lang(foreign_mod.abi) {
Ok(lang) => lang,