blob: 80f7c0bef6ea259009c0116c0ad8a31e981cd969 [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 Tolnay17451de2020-05-10 23:49:12 -07008use std::u64;
David Tolnay69c79602020-05-10 18:53:31 -07009use syn::{Error, Expr, Lit, Result, Token, UnOp};
David Tolnay17e137f2020-05-08 15:55:28 -070010
11pub struct DiscriminantSet {
David Tolnay9bcb4c62020-05-10 17:42:06 -070012 repr: Option<Atom>,
David Tolnaye2e303f2020-05-10 20:52:00 -070013 values: BTreeSet<Discriminant>,
David Tolnay17e137f2020-05-08 15:55:28 -070014 previous: Option<Discriminant>,
15}
16
David Tolnaye2e303f2020-05-10 20:52:00 -070017#[derive(Copy, Clone, Eq, PartialEq)]
David Tolnay17e137f2020-05-08 15:55:28 -070018pub struct Discriminant {
David Tolnaya8abe462020-11-20 20:32:13 -080019 sign: Sign,
David Tolnayf2d58412020-05-10 23:22:23 -070020 magnitude: u64,
David Tolnay17e137f2020-05-08 15:55:28 -070021}
22
David Tolnaya8abe462020-11-20 20:32:13 -080023#[derive(Copy, Clone, Eq, PartialEq)]
24enum Sign {
25 Negative,
26 Positive,
27}
28
David Tolnay17e137f2020-05-08 15:55:28 -070029impl DiscriminantSet {
David Tolnayddf69e22020-05-10 22:08:20 -070030 pub fn new(repr: Option<Atom>) -> Self {
David Tolnay17e137f2020-05-08 15:55:28 -070031 DiscriminantSet {
David Tolnayddf69e22020-05-10 22:08:20 -070032 repr,
David Tolnaye2e303f2020-05-10 20:52:00 -070033 values: BTreeSet::new(),
David Tolnay17e137f2020-05-08 15:55:28 -070034 previous: None,
35 }
36 }
37
38 pub fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
David Tolnay9bcb4c62020-05-10 17:42:06 -070039 let (discriminant, repr) = expr_to_discriminant(expr)?;
David Tolnay94cce002020-05-10 23:19:39 -070040 match (self.repr, repr) {
David Tolnay65bc8e62020-05-11 00:24:31 -070041 (None, Some(new_repr)) => {
42 if let Some(limits) = Limits::of(new_repr) {
43 for &past in &self.values {
44 if limits.min <= past && past <= limits.max {
45 continue;
46 }
47 let msg = format!(
48 "discriminant value `{}` is outside the limits of {}",
49 past, new_repr,
50 );
51 return Err(Error::new(Span::call_site(), msg));
52 }
53 }
54 self.repr = Some(new_repr);
55 }
David Tolnay94cce002020-05-10 23:19:39 -070056 (Some(prev), Some(repr)) if prev != repr => {
57 let msg = format!("expected {}, found {}", prev, repr);
58 return Err(Error::new(Span::call_site(), msg));
59 }
60 _ => {}
61 }
David Tolnay17e137f2020-05-08 15:55:28 -070062 insert(self, discriminant)
63 }
64
65 pub fn insert_next(&mut self) -> Result<Discriminant> {
66 let discriminant = match self.previous {
67 None => Discriminant::zero(),
David Tolnaya8abe462020-11-20 20:32:13 -080068 Some(mut discriminant) => match discriminant.sign {
69 Sign::Negative => {
70 discriminant.magnitude -= 1;
71 if discriminant.magnitude == 0 {
72 discriminant.sign = Sign::Positive;
73 }
74 discriminant
David Tolnay69c79602020-05-10 18:53:31 -070075 }
David Tolnaya8abe462020-11-20 20:32:13 -080076 Sign::Positive => {
77 if discriminant.magnitude == u64::MAX {
78 let msg = format!("discriminant overflow on value after {}", u64::MAX);
79 return Err(Error::new(Span::call_site(), msg));
80 }
81 discriminant.magnitude += 1;
82 discriminant
David Tolnay17e137f2020-05-08 15:55:28 -070083 }
David Tolnaya8abe462020-11-20 20:32:13 -080084 },
David Tolnay17e137f2020-05-08 15:55:28 -070085 };
86 insert(self, discriminant)
87 }
David Tolnaye2e303f2020-05-10 20:52:00 -070088
89 pub fn inferred_repr(&self) -> Result<Atom> {
90 if let Some(repr) = self.repr {
91 return Ok(repr);
92 }
93 if self.values.is_empty() {
94 return Ok(U8);
95 }
96 let min = *self.values.iter().next().unwrap();
97 let max = *self.values.iter().next_back().unwrap();
David Tolnay9f7c55a2020-05-11 00:19:01 -070098 for limits in &LIMITS {
99 if limits.min <= min && max <= limits.max {
100 return Ok(limits.repr);
David Tolnaye2e303f2020-05-10 20:52:00 -0700101 }
102 }
103 let msg = "these discriminant values do not fit in any supported enum repr type";
104 Err(Error::new(Span::call_site(), msg))
105 }
David Tolnay17e137f2020-05-08 15:55:28 -0700106}
107
David Tolnay9bcb4c62020-05-10 17:42:06 -0700108fn expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)> {
David Tolnay69c79602020-05-10 18:53:31 -0700109 match expr {
110 Expr::Lit(expr) => {
111 if let Lit::Int(lit) = &expr.lit {
David Tolnay9bcb4c62020-05-10 17:42:06 -0700112 let discriminant = lit.base10_parse::<Discriminant>()?;
113 let repr = parse_int_suffix(lit.suffix())?;
114 return Ok((discriminant, repr));
David Tolnay69c79602020-05-10 18:53:31 -0700115 }
David Tolnay17e137f2020-05-08 15:55:28 -0700116 }
David Tolnay69c79602020-05-10 18:53:31 -0700117 Expr::Unary(unary) => {
118 if let UnOp::Neg(_) = unary.op {
David Tolnay9bcb4c62020-05-10 17:42:06 -0700119 let (mut discriminant, repr) = expr_to_discriminant(&unary.expr)?;
David Tolnaya8abe462020-11-20 20:32:13 -0800120 discriminant.sign = match discriminant.sign {
121 Sign::Positive => Sign::Negative,
122 Sign::Negative => Sign::Positive,
123 };
David Tolnay9bcb4c62020-05-10 17:42:06 -0700124 return Ok((discriminant, repr));
David Tolnay69c79602020-05-10 18:53:31 -0700125 }
126 }
127 _ => {}
David Tolnay17e137f2020-05-08 15:55:28 -0700128 }
129 Err(Error::new_spanned(
130 expr,
131 "enums with non-integer literal discriminants are not supported yet",
132 ))
133}
134
135fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
David Tolnay5966f7b2020-05-10 22:53:24 -0700136 if let Some(expected_repr) = set.repr {
David Tolnay65bc8e62020-05-11 00:24:31 -0700137 if let Some(limits) = Limits::of(expected_repr) {
138 if discriminant < limits.min || limits.max < discriminant {
139 let msg = format!(
140 "discriminant value `{}` is outside the limits of {}",
141 discriminant, expected_repr,
142 );
143 return Err(Error::new(Span::call_site(), msg));
David Tolnay5966f7b2020-05-10 22:53:24 -0700144 }
David Tolnay5966f7b2020-05-10 22:53:24 -0700145 }
146 }
David Tolnayced2adc2020-11-19 19:52:54 -0800147 set.values.insert(discriminant);
148 set.previous = Some(discriminant);
149 Ok(discriminant)
David Tolnay17e137f2020-05-08 15:55:28 -0700150}
151
152impl Discriminant {
David Tolnayf1715fa2020-05-10 18:58:49 -0700153 const fn zero() -> Self {
David Tolnay69c79602020-05-10 18:53:31 -0700154 Discriminant {
David Tolnaya8abe462020-11-20 20:32:13 -0800155 sign: Sign::Positive,
David Tolnay69c79602020-05-10 18:53:31 -0700156 magnitude: 0,
157 }
David Tolnay17e137f2020-05-08 15:55:28 -0700158 }
David Tolnayf1715fa2020-05-10 18:58:49 -0700159
David Tolnayf2d58412020-05-10 23:22:23 -0700160 const fn pos(u: u64) -> Self {
David Tolnayf1715fa2020-05-10 18:58:49 -0700161 Discriminant {
David Tolnaya8abe462020-11-20 20:32:13 -0800162 sign: Sign::Positive,
David Tolnayf1715fa2020-05-10 18:58:49 -0700163 magnitude: u,
164 }
165 }
166
David Tolnayf2d58412020-05-10 23:22:23 -0700167 const fn neg(i: i64) -> Self {
David Tolnayf1715fa2020-05-10 18:58:49 -0700168 Discriminant {
David Tolnaya8abe462020-11-20 20:32:13 -0800169 sign: if i < 0 {
170 Sign::Negative
171 } else {
172 Sign::Positive
173 },
David Tolnayf2d58412020-05-10 23:22:23 -0700174 // This is `i.abs() as u64` but without overflow on MIN. Uses the
David Tolnayf1715fa2020-05-10 18:58:49 -0700175 // fact that MIN.wrapping_abs() wraps back to MIN whose binary
David Tolnayf2d58412020-05-10 23:22:23 -0700176 // representation is 1<<63, and thus the `as u64` conversion
177 // produces 1<<63 too which happens to be the correct unsigned
David Tolnayf1715fa2020-05-10 18:58:49 -0700178 // magnitude.
David Tolnayf2d58412020-05-10 23:22:23 -0700179 magnitude: i.wrapping_abs() as u64,
David Tolnayf1715fa2020-05-10 18:58:49 -0700180 }
181 }
David Tolnay17e137f2020-05-08 15:55:28 -0700182}
183
184impl Display for Discriminant {
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
David Tolnaya8abe462020-11-20 20:32:13 -0800186 if self.sign == Sign::Negative {
David Tolnay69c79602020-05-10 18:53:31 -0700187 f.write_str("-")?;
188 }
David Tolnay17e137f2020-05-08 15:55:28 -0700189 Display::fmt(&self.magnitude, f)
190 }
191}
192
193impl ToTokens for Discriminant {
194 fn to_tokens(&self, tokens: &mut TokenStream) {
David Tolnaya8abe462020-11-20 20:32:13 -0800195 if self.sign == Sign::Negative {
David Tolnay69c79602020-05-10 18:53:31 -0700196 Token![-](Span::call_site()).to_tokens(tokens);
197 }
David Tolnayf2d58412020-05-10 23:22:23 -0700198 Literal::u64_unsuffixed(self.magnitude).to_tokens(tokens);
David Tolnay17e137f2020-05-08 15:55:28 -0700199 }
200}
201
202impl FromStr for Discriminant {
203 type Err = Error;
204
David Tolnay69c79602020-05-10 18:53:31 -0700205 fn from_str(mut s: &str) -> Result<Self> {
David Tolnaya8abe462020-11-20 20:32:13 -0800206 let sign = if s.starts_with('-') {
David Tolnay69c79602020-05-10 18:53:31 -0700207 s = &s[1..];
David Tolnaya8abe462020-11-20 20:32:13 -0800208 Sign::Negative
209 } else {
210 Sign::Positive
211 };
David Tolnayf2d58412020-05-10 23:22:23 -0700212 match s.parse::<u64>() {
David Tolnaya8abe462020-11-20 20:32:13 -0800213 Ok(magnitude) => Ok(Discriminant { sign, magnitude }),
David Tolnay17e137f2020-05-08 15:55:28 -0700214 Err(_) => Err(Error::new(
215 Span::call_site(),
216 "discriminant value outside of supported range",
217 )),
218 }
219 }
220}
David Tolnay9bcb4c62020-05-10 17:42:06 -0700221
David Tolnaye2e303f2020-05-10 20:52:00 -0700222impl Ord for Discriminant {
223 fn cmp(&self, other: &Self) -> Ordering {
David Tolnaya8abe462020-11-20 20:32:13 -0800224 use self::Sign::{Negative, Positive};
225 match (self.sign, other.sign) {
226 (Negative, Negative) => self.magnitude.cmp(&other.magnitude).reverse(),
227 (Negative, Positive) => Ordering::Less, // negative < positive
228 (Positive, Negative) => Ordering::Greater, // positive > negative
229 (Positive, Positive) => self.magnitude.cmp(&other.magnitude),
David Tolnaye2e303f2020-05-10 20:52:00 -0700230 }
231 }
232}
233
234impl PartialOrd for Discriminant {
235 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
236 Some(self.cmp(other))
237 }
238}
239
David Tolnay9bcb4c62020-05-10 17:42:06 -0700240fn parse_int_suffix(suffix: &str) -> Result<Option<Atom>> {
241 if suffix.is_empty() {
242 return Ok(None);
243 }
244 if let Some(atom) = Atom::from_str(suffix) {
245 match atom {
246 U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize => return Ok(Some(atom)),
247 _ => {}
248 }
249 }
250 let msg = format!("unrecognized integer suffix: `{}`", suffix);
251 Err(Error::new(Span::call_site(), msg))
252}
David Tolnayf1715fa2020-05-10 18:58:49 -0700253
David Tolnay65bc8e62020-05-11 00:24:31 -0700254#[derive(Copy, Clone)]
David Tolnay9f7c55a2020-05-11 00:19:01 -0700255struct Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700256 repr: Atom,
257 min: Discriminant,
258 max: Discriminant,
259}
260
David Tolnay65bc8e62020-05-11 00:24:31 -0700261impl Limits {
262 fn of(repr: Atom) -> Option<Limits> {
263 for limits in &LIMITS {
264 if limits.repr == repr {
265 return Some(*limits);
266 }
267 }
268 None
269 }
270}
271
David Tolnay9f7c55a2020-05-11 00:19:01 -0700272const LIMITS: [Limits; 8] = [
273 Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700274 repr: U8,
275 min: Discriminant::zero(),
David Tolnay17451de2020-05-10 23:49:12 -0700276 max: Discriminant::pos(std::u8::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700277 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700278 Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700279 repr: I8,
David Tolnay17451de2020-05-10 23:49:12 -0700280 min: Discriminant::neg(std::i8::MIN as i64),
281 max: Discriminant::pos(std::i8::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700282 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700283 Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700284 repr: U16,
285 min: Discriminant::zero(),
David Tolnay17451de2020-05-10 23:49:12 -0700286 max: Discriminant::pos(std::u16::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700287 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700288 Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700289 repr: I16,
David Tolnay17451de2020-05-10 23:49:12 -0700290 min: Discriminant::neg(std::i16::MIN as i64),
291 max: Discriminant::pos(std::i16::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700292 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700293 Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700294 repr: U32,
295 min: Discriminant::zero(),
David Tolnay17451de2020-05-10 23:49:12 -0700296 max: Discriminant::pos(std::u32::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700297 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700298 Limits {
David Tolnayf1715fa2020-05-10 18:58:49 -0700299 repr: I32,
David Tolnay17451de2020-05-10 23:49:12 -0700300 min: Discriminant::neg(std::i32::MIN as i64),
301 max: Discriminant::pos(std::i32::MAX as u64),
David Tolnayf2d58412020-05-10 23:22:23 -0700302 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700303 Limits {
David Tolnayf2d58412020-05-10 23:22:23 -0700304 repr: U64,
305 min: Discriminant::zero(),
David Tolnay17451de2020-05-10 23:49:12 -0700306 max: Discriminant::pos(std::u64::MAX),
David Tolnayf2d58412020-05-10 23:22:23 -0700307 },
David Tolnay9f7c55a2020-05-11 00:19:01 -0700308 Limits {
David Tolnayf2d58412020-05-10 23:22:23 -0700309 repr: I64,
David Tolnay17451de2020-05-10 23:49:12 -0700310 min: Discriminant::neg(std::i64::MIN),
311 max: Discriminant::pos(std::i64::MAX as u64),
David Tolnayf1715fa2020-05-10 18:58:49 -0700312 },
313];