1 use crate::syntax::Atom::{self, *};
2 use proc_macro2::{Literal, Span, TokenStream};
3 use quote::ToTokens;
4 use std::cmp::Ordering;
5 use std::collections::BTreeSet;
6 use std::fmt::{self, Display};
7 use std::str::FromStr;
8 use syn::{Error, Expr, Lit, Result, Token, UnOp};
9 
10 pub(crate) struct DiscriminantSet {
11     repr: Option<Atom>,
12     values: BTreeSet<Discriminant>,
13     previous: Option<Discriminant>,
14 }
15 
16 #[derive(Copy, Clone, Eq, PartialEq)]
17 pub(crate) struct Discriminant {
18     sign: Sign,
19     magnitude: u64,
20 }
21 
22 #[derive(Copy, Clone, Eq, PartialEq)]
23 enum Sign {
24     Negative,
25     Positive,
26 }
27 
28 impl DiscriminantSet {
new(repr: Option<Atom>) -> Self29     pub(crate) fn new(repr: Option<Atom>) -> Self {
30         DiscriminantSet {
31             repr,
32             values: BTreeSet::new(),
33             previous: None,
34         }
35     }
36 
insert(&mut self, expr: &Expr) -> Result<Discriminant>37     pub(crate) fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
38         let (discriminant, repr) = expr_to_discriminant(expr)?;
39         match (self.repr, repr) {
40             (None, Some(new_repr)) => {
41                 if let Some(limits) = Limits::of(new_repr) {
42                     for &past in &self.values {
43                         if limits.min <= past && past <= limits.max {
44                             continue;
45                         }
46                         let msg = format!(
47                             "discriminant value `{}` is outside the limits of {}",
48                             past, new_repr,
49                         );
50                         return Err(Error::new(Span::call_site(), msg));
51                     }
52                 }
53                 self.repr = Some(new_repr);
54             }
55             (Some(prev), Some(repr)) if prev != repr => {
56                 let msg = format!("expected {}, found {}", prev, repr);
57                 return Err(Error::new(Span::call_site(), msg));
58             }
59             _ => {}
60         }
61         insert(self, discriminant)
62     }
63 
insert_next(&mut self) -> Result<Discriminant>64     pub(crate) fn insert_next(&mut self) -> Result<Discriminant> {
65         let discriminant = match self.previous {
66             None => Discriminant::zero(),
67             Some(mut discriminant) => match discriminant.sign {
68                 Sign::Negative => {
69                     discriminant.magnitude -= 1;
70                     if discriminant.magnitude == 0 {
71                         discriminant.sign = Sign::Positive;
72                     }
73                     discriminant
74                 }
75                 Sign::Positive => {
76                     if discriminant.magnitude == u64::MAX {
77                         let msg = format!("discriminant overflow on value after {}", u64::MAX);
78                         return Err(Error::new(Span::call_site(), msg));
79                     }
80                     discriminant.magnitude += 1;
81                     discriminant
82                 }
83             },
84         };
85         insert(self, discriminant)
86     }
87 
inferred_repr(&self) -> Result<Atom>88     pub(crate) fn inferred_repr(&self) -> Result<Atom> {
89         if let Some(repr) = self.repr {
90             return Ok(repr);
91         }
92         if self.values.is_empty() {
93             return Ok(U8);
94         }
95         let min = *self.values.iter().next().unwrap();
96         let max = *self.values.iter().next_back().unwrap();
97         for limits in &LIMITS {
98             if limits.min <= min && max <= limits.max {
99                 return Ok(limits.repr);
100             }
101         }
102         let msg = "these discriminant values do not fit in any supported enum repr type";
103         Err(Error::new(Span::call_site(), msg))
104     }
105 }
106 
expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)>107 fn expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)> {
108     match expr {
109         Expr::Lit(expr) => {
110             if let Lit::Int(lit) = &expr.lit {
111                 let discriminant = lit.base10_parse::<Discriminant>()?;
112                 let repr = parse_int_suffix(lit.suffix())?;
113                 return Ok((discriminant, repr));
114             }
115         }
116         Expr::Unary(unary) => {
117             if let UnOp::Neg(_) = unary.op {
118                 let (mut discriminant, repr) = expr_to_discriminant(&unary.expr)?;
119                 discriminant.sign = match discriminant.sign {
120                     Sign::Positive => Sign::Negative,
121                     Sign::Negative => Sign::Positive,
122                 };
123                 return Ok((discriminant, repr));
124             }
125         }
126         _ => {}
127     }
128     Err(Error::new_spanned(
129         expr,
130         "enums with non-integer literal discriminants are not supported yet",
131     ))
132 }
133 
insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant>134 fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
135     if let Some(expected_repr) = set.repr {
136         if let Some(limits) = Limits::of(expected_repr) {
137             if discriminant < limits.min || limits.max < discriminant {
138                 let msg = format!(
139                     "discriminant value `{}` is outside the limits of {}",
140                     discriminant, expected_repr,
141                 );
142                 return Err(Error::new(Span::call_site(), msg));
143             }
144         }
145     }
146     set.values.insert(discriminant);
147     set.previous = Some(discriminant);
148     Ok(discriminant)
149 }
150 
151 impl Discriminant {
zero() -> Self152     pub(crate) const fn zero() -> Self {
153         Discriminant {
154             sign: Sign::Positive,
155             magnitude: 0,
156         }
157     }
158 
pos(u: u64) -> Self159     const fn pos(u: u64) -> Self {
160         Discriminant {
161             sign: Sign::Positive,
162             magnitude: u,
163         }
164     }
165 
neg(i: i64) -> Self166     const fn neg(i: i64) -> Self {
167         Discriminant {
168             sign: if i < 0 {
169                 Sign::Negative
170             } else {
171                 Sign::Positive
172             },
173             // This is `i.abs() as u64` but without overflow on MIN. Uses the
174             // fact that MIN.wrapping_abs() wraps back to MIN whose binary
175             // representation is 1<<63, and thus the `as u64` conversion
176             // produces 1<<63 too which happens to be the correct unsigned
177             // magnitude.
178             magnitude: i.wrapping_abs() as u64,
179         }
180     }
181 
182     #[cfg(feature = "experimental-enum-variants-from-header")]
checked_succ(self) -> Option<Self>183     pub(crate) const fn checked_succ(self) -> Option<Self> {
184         match self.sign {
185             Sign::Negative => {
186                 if self.magnitude == 1 {
187                     Some(Discriminant::zero())
188                 } else {
189                     Some(Discriminant {
190                         sign: Sign::Negative,
191                         magnitude: self.magnitude - 1,
192                     })
193                 }
194             }
195             Sign::Positive => match self.magnitude.checked_add(1) {
196                 Some(magnitude) => Some(Discriminant {
197                     sign: Sign::Positive,
198                     magnitude,
199                 }),
200                 None => None,
201             },
202         }
203     }
204 }
205 
206 impl Display for Discriminant {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result207     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
208         if self.sign == Sign::Negative {
209             f.write_str("-")?;
210         }
211         write!(f, "{}", self.magnitude)
212     }
213 }
214 
215 impl ToTokens for Discriminant {
to_tokens(&self, tokens: &mut TokenStream)216     fn to_tokens(&self, tokens: &mut TokenStream) {
217         if self.sign == Sign::Negative {
218             Token![-](Span::call_site()).to_tokens(tokens);
219         }
220         Literal::u64_unsuffixed(self.magnitude).to_tokens(tokens);
221     }
222 }
223 
224 impl FromStr for Discriminant {
225     type Err = Error;
226 
from_str(mut s: &str) -> Result<Self>227     fn from_str(mut s: &str) -> Result<Self> {
228         let sign = if s.starts_with('-') {
229             s = &s[1..];
230             Sign::Negative
231         } else {
232             Sign::Positive
233         };
234         match s.parse::<u64>() {
235             Ok(magnitude) => Ok(Discriminant { sign, magnitude }),
236             Err(_) => Err(Error::new(
237                 Span::call_site(),
238                 "discriminant value outside of supported range",
239             )),
240         }
241     }
242 }
243 
244 impl Ord for Discriminant {
cmp(&self, other: &Self) -> Ordering245     fn cmp(&self, other: &Self) -> Ordering {
246         use self::Sign::{Negative, Positive};
247         match (self.sign, other.sign) {
248             (Negative, Negative) => self.magnitude.cmp(&other.magnitude).reverse(),
249             (Negative, Positive) => Ordering::Less, // negative < positive
250             (Positive, Negative) => Ordering::Greater, // positive > negative
251             (Positive, Positive) => self.magnitude.cmp(&other.magnitude),
252         }
253     }
254 }
255 
256 impl PartialOrd for Discriminant {
partial_cmp(&self, other: &Self) -> Option<Ordering>257     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
258         Some(self.cmp(other))
259     }
260 }
261 
parse_int_suffix(suffix: &str) -> Result<Option<Atom>>262 fn parse_int_suffix(suffix: &str) -> Result<Option<Atom>> {
263     if suffix.is_empty() {
264         return Ok(None);
265     }
266     if let Some(atom) = Atom::from_str(suffix) {
267         match atom {
268             U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize => return Ok(Some(atom)),
269             _ => {}
270         }
271     }
272     let msg = format!("unrecognized integer suffix: `{}`", suffix);
273     Err(Error::new(Span::call_site(), msg))
274 }
275 
276 #[derive(Copy, Clone)]
277 struct Limits {
278     repr: Atom,
279     min: Discriminant,
280     max: Discriminant,
281 }
282 
283 impl Limits {
of(repr: Atom) -> Option<Limits>284     fn of(repr: Atom) -> Option<Limits> {
285         for limits in &LIMITS {
286             if limits.repr == repr {
287                 return Some(*limits);
288             }
289         }
290         None
291     }
292 }
293 
294 const LIMITS: [Limits; 8] = [
295     Limits {
296         repr: U8,
297         min: Discriminant::zero(),
298         max: Discriminant::pos(std::u8::MAX as u64),
299     },
300     Limits {
301         repr: I8,
302         min: Discriminant::neg(std::i8::MIN as i64),
303         max: Discriminant::pos(std::i8::MAX as u64),
304     },
305     Limits {
306         repr: U16,
307         min: Discriminant::zero(),
308         max: Discriminant::pos(std::u16::MAX as u64),
309     },
310     Limits {
311         repr: I16,
312         min: Discriminant::neg(std::i16::MIN as i64),
313         max: Discriminant::pos(std::i16::MAX as u64),
314     },
315     Limits {
316         repr: U32,
317         min: Discriminant::zero(),
318         max: Discriminant::pos(std::u32::MAX as u64),
319     },
320     Limits {
321         repr: I32,
322         min: Discriminant::neg(std::i32::MIN as i64),
323         max: Discriminant::pos(std::i32::MAX as u64),
324     },
325     Limits {
326         repr: U64,
327         min: Discriminant::zero(),
328         max: Discriminant::pos(std::u64::MAX),
329     },
330     Limits {
331         repr: I64,
332         min: Discriminant::neg(std::i64::MIN),
333         max: Discriminant::pos(std::i64::MAX as u64),
334     },
335 ];
336