blob: 06a6d1b2be65604e0e1ed64e77f1c34206b127d7 [file] [log] [blame]
David Tolnay17e137f2020-05-08 15:55:28 -07001use proc_macro2::{Literal, Span, TokenStream};
2use quote::ToTokens;
3use std::collections::HashSet;
4use std::fmt::{self, Display};
5use std::str::FromStr;
David Tolnay69c79602020-05-10 18:53:31 -07006use syn::{Error, Expr, Lit, Result, Token, UnOp};
David Tolnay17e137f2020-05-08 15:55:28 -07007
8pub struct DiscriminantSet {
9 values: HashSet<Discriminant>,
10 previous: Option<Discriminant>,
11}
12
13#[derive(Copy, Clone, Hash, Eq, PartialEq)]
14pub struct Discriminant {
David Tolnay69c79602020-05-10 18:53:31 -070015 negative: bool,
David Tolnay17e137f2020-05-08 15:55:28 -070016 magnitude: u32,
17}
18
19impl DiscriminantSet {
20 pub fn new() -> Self {
21 DiscriminantSet {
22 values: HashSet::new(),
23 previous: None,
24 }
25 }
26
27 pub fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
28 let discriminant = expr_to_discriminant(expr)?;
29 insert(self, discriminant)
30 }
31
32 pub fn insert_next(&mut self) -> Result<Discriminant> {
33 let discriminant = match self.previous {
34 None => Discriminant::zero(),
David Tolnay69c79602020-05-10 18:53:31 -070035 Some(mut discriminant) if discriminant.negative => {
36 discriminant.magnitude -= 1;
37 if discriminant.magnitude == 0 {
38 discriminant.negative = false;
39 }
40 discriminant
41 }
David Tolnay17e137f2020-05-08 15:55:28 -070042 Some(mut discriminant) => {
43 if discriminant.magnitude == u32::MAX {
44 let msg = format!("discriminant overflow on value after {}", u32::MAX);
45 return Err(Error::new(Span::call_site(), msg));
46 }
47 discriminant.magnitude += 1;
48 discriminant
49 }
50 };
51 insert(self, discriminant)
52 }
53}
54
55fn expr_to_discriminant(expr: &Expr) -> Result<Discriminant> {
David Tolnay69c79602020-05-10 18:53:31 -070056 match expr {
57 Expr::Lit(expr) => {
58 if let Lit::Int(lit) = &expr.lit {
59 return lit.base10_parse::<Discriminant>();
60 }
David Tolnay17e137f2020-05-08 15:55:28 -070061 }
David Tolnay69c79602020-05-10 18:53:31 -070062 Expr::Unary(unary) => {
63 if let UnOp::Neg(_) = unary.op {
64 let mut discriminant = expr_to_discriminant(&unary.expr)?;
65 discriminant.negative ^= true;
66 return Ok(discriminant);
67 }
68 }
69 _ => {}
David Tolnay17e137f2020-05-08 15:55:28 -070070 }
71 Err(Error::new_spanned(
72 expr,
73 "enums with non-integer literal discriminants are not supported yet",
74 ))
75}
76
77fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
78 if set.values.insert(discriminant) {
79 set.previous = Some(discriminant);
80 Ok(discriminant)
81 } else {
82 let msg = format!("discriminant value `{}` already exists", discriminant);
83 Err(Error::new(Span::call_site(), msg))
84 }
85}
86
87impl Discriminant {
88 fn zero() -> Self {
David Tolnay69c79602020-05-10 18:53:31 -070089 Discriminant {
90 negative: false,
91 magnitude: 0,
92 }
David Tolnay17e137f2020-05-08 15:55:28 -070093 }
94}
95
96impl Display for Discriminant {
97 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
David Tolnay69c79602020-05-10 18:53:31 -070098 if self.negative {
99 f.write_str("-")?;
100 }
David Tolnay17e137f2020-05-08 15:55:28 -0700101 Display::fmt(&self.magnitude, f)
102 }
103}
104
105impl ToTokens for Discriminant {
106 fn to_tokens(&self, tokens: &mut TokenStream) {
David Tolnay69c79602020-05-10 18:53:31 -0700107 if self.negative {
108 Token![-](Span::call_site()).to_tokens(tokens);
109 }
David Tolnay17e137f2020-05-08 15:55:28 -0700110 Literal::u32_unsuffixed(self.magnitude).to_tokens(tokens);
111 }
112}
113
114impl FromStr for Discriminant {
115 type Err = Error;
116
David Tolnay69c79602020-05-10 18:53:31 -0700117 fn from_str(mut s: &str) -> Result<Self> {
118 let negative = s.starts_with('-');
119 if negative {
120 s = &s[1..];
121 }
David Tolnay17e137f2020-05-08 15:55:28 -0700122 match s.parse::<u32>() {
David Tolnay69c79602020-05-10 18:53:31 -0700123 Ok(magnitude) => Ok(Discriminant {
124 negative,
125 magnitude,
126 }),
David Tolnay17e137f2020-05-08 15:55:28 -0700127 Err(_) => Err(Error::new(
128 Span::call_site(),
129 "discriminant value outside of supported range",
130 )),
131 }
132 }
133}