blob: 7ce18ff9c3ae0c23d46acc665fe2721b8d78d855 [file] [log] [blame]
David Tolnay9bcb4c62020-05-10 17:42:06 -07001use crate::syntax::Atom::{self, *};
David Tolnay17e137f2020-05-08 15:55:28 -07002use proc_macro2::{Literal, Span, TokenStream};
3use quote::ToTokens;
David Tolnaye2e303f2020-05-10 20:52:00 -07004use std::cmp::Ordering;
5use std::collections::BTreeSet;
David Tolnay17e137f2020-05-08 15:55:28 -07006use std::fmt::{self, Display};
7use std::str::FromStr;
David Tolnay69c79602020-05-10 18:53:31 -07008use syn::{Error, Expr, Lit, Result, Token, UnOp};
David Tolnay17e137f2020-05-08 15:55:28 -07009
10pub struct DiscriminantSet {
David Tolnay9bcb4c62020-05-10 17:42:06 -070011 repr: Option<Atom>,
David Tolnaye2e303f2020-05-10 20:52:00 -070012 values: BTreeSet<Discriminant>,
David Tolnay17e137f2020-05-08 15:55:28 -070013 previous: Option<Discriminant>,
14}
15
David Tolnaye2e303f2020-05-10 20:52:00 -070016#[derive(Copy, Clone, Eq, PartialEq)]
David Tolnay17e137f2020-05-08 15:55:28 -070017pub struct Discriminant {
David Tolnay69c79602020-05-10 18:53:31 -070018 negative: bool,
David Tolnayf2d58412020-05-10 23:22:23 -070019 magnitude: u64,
David Tolnay17e137f2020-05-08 15:55:28 -070020}
21
22impl DiscriminantSet {
David Tolnayddf69e22020-05-10 22:08:20 -070023 pub fn new(repr: Option<Atom>) -> Self {
David Tolnay17e137f2020-05-08 15:55:28 -070024 DiscriminantSet {
David Tolnayddf69e22020-05-10 22:08:20 -070025 repr,
David Tolnaye2e303f2020-05-10 20:52:00 -070026 values: BTreeSet::new(),
David Tolnay17e137f2020-05-08 15:55:28 -070027 previous: None,
28 }
29 }
30
31 pub fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
David Tolnay9bcb4c62020-05-10 17:42:06 -070032 let (discriminant, repr) = expr_to_discriminant(expr)?;
David Tolnay94cce002020-05-10 23:19:39 -070033 match (self.repr, repr) {
34 (None, _) => self.repr = repr,
35 (Some(prev), Some(repr)) if prev != repr => {
36 let msg = format!("expected {}, found {}", prev, repr);
37 return Err(Error::new(Span::call_site(), msg));
38 }
39 _ => {}
40 }
David Tolnay17e137f2020-05-08 15:55:28 -070041 insert(self, discriminant)
42 }
43
44 pub fn insert_next(&mut self) -> Result<Discriminant> {
45 let discriminant = match self.previous {
46 None => Discriminant::zero(),
David Tolnay69c79602020-05-10 18:53:31 -070047 Some(mut discriminant) if discriminant.negative => {
48 discriminant.magnitude -= 1;
49 if discriminant.magnitude == 0 {
50 discriminant.negative = false;
51 }
52 discriminant
53 }
David Tolnay17e137f2020-05-08 15:55:28 -070054 Some(mut discriminant) => {
David Tolnayf2d58412020-05-10 23:22:23 -070055 if discriminant.magnitude == u64::MAX {
56 let msg = format!("discriminant overflow on value after {}", u64::MAX);
David Tolnay17e137f2020-05-08 15:55:28 -070057 return Err(Error::new(Span::call_site(), msg));
58 }
59 discriminant.magnitude += 1;
60 discriminant
61 }
62 };
63 insert(self, discriminant)
64 }
David Tolnaye2e303f2020-05-10 20:52:00 -070065
66 pub fn inferred_repr(&self) -> Result<Atom> {
67 if let Some(repr) = self.repr {
68 return Ok(repr);
69 }
70 if self.values.is_empty() {
71 return Ok(U8);
72 }
73 let min = *self.values.iter().next().unwrap();
74 let max = *self.values.iter().next_back().unwrap();
75 for bounds in &BOUNDS {
76 if bounds.min <= min && max <= bounds.max {
77 return Ok(bounds.repr);
78 }
79 }
80 let msg = "these discriminant values do not fit in any supported enum repr type";
81 Err(Error::new(Span::call_site(), msg))
82 }
David Tolnay17e137f2020-05-08 15:55:28 -070083}
84
David Tolnay9bcb4c62020-05-10 17:42:06 -070085fn expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)> {
David Tolnay69c79602020-05-10 18:53:31 -070086 match expr {
87 Expr::Lit(expr) => {
88 if let Lit::Int(lit) = &expr.lit {
David Tolnay9bcb4c62020-05-10 17:42:06 -070089 let discriminant = lit.base10_parse::<Discriminant>()?;
90 let repr = parse_int_suffix(lit.suffix())?;
91 return Ok((discriminant, repr));
David Tolnay69c79602020-05-10 18:53:31 -070092 }
David Tolnay17e137f2020-05-08 15:55:28 -070093 }
David Tolnay69c79602020-05-10 18:53:31 -070094 Expr::Unary(unary) => {
95 if let UnOp::Neg(_) = unary.op {
David Tolnay9bcb4c62020-05-10 17:42:06 -070096 let (mut discriminant, repr) = expr_to_discriminant(&unary.expr)?;
David Tolnay69c79602020-05-10 18:53:31 -070097 discriminant.negative ^= true;
David Tolnay9bcb4c62020-05-10 17:42:06 -070098 return Ok((discriminant, repr));
David Tolnay69c79602020-05-10 18:53:31 -070099 }
100 }
101 _ => {}
David Tolnay17e137f2020-05-08 15:55:28 -0700102 }
103 Err(Error::new_spanned(
104 expr,
105 "enums with non-integer literal discriminants are not supported yet",
106 ))
107}
108
109fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
David Tolnay5966f7b2020-05-10 22:53:24 -0700110 if let Some(expected_repr) = set.repr {
111 for bounds in &BOUNDS {
112 if bounds.repr != expected_repr {
113 continue;
114 }
115 if bounds.min <= discriminant && discriminant <= bounds.max {
116 break;
117 }
118 let msg = format!(
119 "discriminant value `{}` is outside the limits of {}",
120 discriminant, expected_repr,
121 );
122 return Err(Error::new(Span::call_site(), msg));
123 }
124 }
David Tolnay17e137f2020-05-08 15:55:28 -0700125 if set.values.insert(discriminant) {
126 set.previous = Some(discriminant);
127 Ok(discriminant)
128 } else {
129 let msg = format!("discriminant value `{}` already exists", discriminant);
130 Err(Error::new(Span::call_site(), msg))
131 }
132}
133
134impl Discriminant {
David Tolnayf1715fa2020-05-10 18:58:49 -0700135 const fn zero() -> Self {
David Tolnay69c79602020-05-10 18:53:31 -0700136 Discriminant {
137 negative: false,
138 magnitude: 0,
139 }
David Tolnay17e137f2020-05-08 15:55:28 -0700140 }
David Tolnayf1715fa2020-05-10 18:58:49 -0700141
David Tolnayf2d58412020-05-10 23:22:23 -0700142 const fn pos(u: u64) -> Self {
David Tolnayf1715fa2020-05-10 18:58:49 -0700143 Discriminant {
144 negative: false,
145 magnitude: u,
146 }
147 }
148
David Tolnayf2d58412020-05-10 23:22:23 -0700149 const fn neg(i: i64) -> Self {
David Tolnayf1715fa2020-05-10 18:58:49 -0700150 Discriminant {
151 negative: i < 0,
David Tolnayf2d58412020-05-10 23:22:23 -0700152 // This is `i.abs() as u64` but without overflow on MIN. Uses the
David Tolnayf1715fa2020-05-10 18:58:49 -0700153 // fact that MIN.wrapping_abs() wraps back to MIN whose binary
David Tolnayf2d58412020-05-10 23:22:23 -0700154 // representation is 1<<63, and thus the `as u64` conversion
155 // produces 1<<63 too which happens to be the correct unsigned
David Tolnayf1715fa2020-05-10 18:58:49 -0700156 // magnitude.
David Tolnayf2d58412020-05-10 23:22:23 -0700157 magnitude: i.wrapping_abs() as u64,
David Tolnayf1715fa2020-05-10 18:58:49 -0700158 }
159 }
David Tolnay17e137f2020-05-08 15:55:28 -0700160}
161
162impl Display for Discriminant {
163 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
David Tolnay69c79602020-05-10 18:53:31 -0700164 if self.negative {
165 f.write_str("-")?;
166 }
David Tolnay17e137f2020-05-08 15:55:28 -0700167 Display::fmt(&self.magnitude, f)
168 }
169}
170
171impl ToTokens for Discriminant {
172 fn to_tokens(&self, tokens: &mut TokenStream) {
David Tolnay69c79602020-05-10 18:53:31 -0700173 if self.negative {
174 Token![-](Span::call_site()).to_tokens(tokens);
175 }
David Tolnayf2d58412020-05-10 23:22:23 -0700176 Literal::u64_unsuffixed(self.magnitude).to_tokens(tokens);
David Tolnay17e137f2020-05-08 15:55:28 -0700177 }
178}
179
180impl FromStr for Discriminant {
181 type Err = Error;
182
David Tolnay69c79602020-05-10 18:53:31 -0700183 fn from_str(mut s: &str) -> Result<Self> {
184 let negative = s.starts_with('-');
185 if negative {
186 s = &s[1..];
187 }
David Tolnayf2d58412020-05-10 23:22:23 -0700188 match s.parse::<u64>() {
David Tolnay69c79602020-05-10 18:53:31 -0700189 Ok(magnitude) => Ok(Discriminant {
190 negative,
191 magnitude,
192 }),
David Tolnay17e137f2020-05-08 15:55:28 -0700193 Err(_) => Err(Error::new(
194 Span::call_site(),
195 "discriminant value outside of supported range",
196 )),
197 }
198 }
199}
David Tolnay9bcb4c62020-05-10 17:42:06 -0700200
David Tolnaye2e303f2020-05-10 20:52:00 -0700201impl Ord for Discriminant {
202 fn cmp(&self, other: &Self) -> Ordering {
203 match (self.negative, other.negative) {
204 (true, true) => self.magnitude.cmp(&other.magnitude).reverse(),
205 (true, false) => Ordering::Less, // negative < positive
206 (false, true) => Ordering::Greater, // positive > negative
207 (false, false) => self.magnitude.cmp(&other.magnitude),
208 }
209 }
210}
211
212impl PartialOrd for Discriminant {
213 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
214 Some(self.cmp(other))
215 }
216}
217
David Tolnay9bcb4c62020-05-10 17:42:06 -0700218fn parse_int_suffix(suffix: &str) -> Result<Option<Atom>> {
219 if suffix.is_empty() {
220 return Ok(None);
221 }
222 if let Some(atom) = Atom::from_str(suffix) {
223 match atom {
224 U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize => return Ok(Some(atom)),
225 _ => {}
226 }
227 }
228 let msg = format!("unrecognized integer suffix: `{}`", suffix);
229 Err(Error::new(Span::call_site(), msg))
230}
David Tolnayf1715fa2020-05-10 18:58:49 -0700231
232struct Bounds {
233 repr: Atom,
234 min: Discriminant,
235 max: Discriminant,
236}
237
David Tolnayf2d58412020-05-10 23:22:23 -0700238const BOUNDS: [Bounds; 8] = [
David Tolnayf1715fa2020-05-10 18:58:49 -0700239 Bounds {
240 repr: U8,
241 min: Discriminant::zero(),
David Tolnayf2d58412020-05-10 23:22:23 -0700242 max: Discriminant::pos(u8::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700243 },
244 Bounds {
245 repr: I8,
David Tolnayf2d58412020-05-10 23:22:23 -0700246 min: Discriminant::neg(i8::MIN as i64),
247 max: Discriminant::pos(i8::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700248 },
249 Bounds {
250 repr: U16,
251 min: Discriminant::zero(),
David Tolnayf2d58412020-05-10 23:22:23 -0700252 max: Discriminant::pos(u16::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700253 },
254 Bounds {
255 repr: I16,
David Tolnayf2d58412020-05-10 23:22:23 -0700256 min: Discriminant::neg(i16::MIN as i64),
257 max: Discriminant::pos(i16::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700258 },
259 Bounds {
260 repr: U32,
261 min: Discriminant::zero(),
David Tolnayf2d58412020-05-10 23:22:23 -0700262 max: Discriminant::pos(u32::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700263 },
264 Bounds {
265 repr: I32,
David Tolnayf2d58412020-05-10 23:22:23 -0700266 min: Discriminant::neg(i32::MIN as i64),
267 max: Discriminant::pos(i32::MAX as u64),
268 },
269 Bounds {
270 repr: U64,
271 min: Discriminant::zero(),
272 max: Discriminant::pos(u64::MAX),
273 },
274 Bounds {
275 repr: I64,
276 min: Discriminant::neg(i64::MIN),
277 max: Discriminant::pos(i64::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700278 },
279];