• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use crate::discriminant::Discriminant;
16 use proc_macro2::{Ident, TokenStream};
17 use quote::ToTokens;
18 use std::ops::RangeInclusive;
19 use syn::{parse::Parse, Error};
20 
21 #[derive(Clone, Copy, PartialEq, Eq)]
22 pub enum Repr {
23     I8,
24     U8,
25     U16,
26     I16,
27     U32,
28     I32,
29     U64,
30     I64,
31     Usize,
32     Isize,
33     #[cfg(feature = "repr_c")]
34     C,
35 }
36 
range_contains(x: &RangeInclusive<i128>, y: &RangeInclusive<i128>) -> bool37 fn range_contains(x: &RangeInclusive<i128>, y: &RangeInclusive<i128>) -> bool {
38     x.contains(y.start()) && x.contains(y.end())
39 }
40 
41 impl Repr {
42     const REPR_RANGES: &'static [(Repr, RangeInclusive<i128>)] = &[
43         (Repr::I8, (i8::MIN as i128)..=(i8::MAX as i128)),
44         (Repr::U8, (u8::MIN as i128)..=(u8::MAX as i128)),
45         (Repr::I16, (i16::MIN as i128)..=(i16::MAX as i128)),
46         (Repr::U16, (u16::MIN as i128)..=(u16::MAX as i128)),
47         (Repr::I32, (i32::MIN as i128)..=(i32::MAX as i128)),
48         (Repr::U32, (u32::MIN as i128)..=(u32::MAX as i128)),
49         (Repr::I64, (i64::MIN as i128)..=(i64::MAX as i128)),
50         (Repr::U64, (u64::MIN as i128)..=(u64::MAX as i128)),
51         (Repr::Isize, (isize::MIN as i128)..=(isize::MAX as i128)),
52         (Repr::Usize, (usize::MIN as i128)..=(usize::MAX as i128)),
53     ];
54 
55     /// Finds the smallest repr that can fit this range, if any.
smallest_fitting_repr(range: RangeInclusive<i128>) -> Option<Self>56     fn smallest_fitting_repr(range: RangeInclusive<i128>) -> Option<Self> {
57         // TODO: perhaps check this logic matches current rustc behavior?
58         for (repr, repr_range) in Self::REPR_RANGES {
59             if range_contains(repr_range, &range) {
60                 return Some(*repr);
61             }
62         }
63         None
64     }
65 
name(self) -> &'static str66     fn name(self) -> &'static str {
67         match self {
68             Repr::I8 => "i8",
69             Repr::U8 => "u8",
70             Repr::U16 => "u16",
71             Repr::I16 => "i16",
72             Repr::U32 => "u32",
73             Repr::I32 => "i32",
74             Repr::U64 => "u64",
75             Repr::I64 => "i64",
76             Repr::Usize => "usize",
77             Repr::Isize => "isize",
78             #[cfg(feature = "repr_c")]
79             Repr::C => "C",
80         }
81     }
82 }
83 
84 impl ToTokens for Repr {
to_tokens(&self, tokens: &mut TokenStream)85     fn to_tokens(&self, tokens: &mut TokenStream) {
86         tokens.extend([match self {
87             // Technically speaking, #[repr(C)] on an enum isn't always `c_int`,
88             // but those who care can fix it if they need.
89             #[cfg(feature = "repr_c")]
90             Repr::C => quote::quote!(::open_enum::__private::c_int),
91             x => x.name().parse::<TokenStream>().unwrap(),
92         }])
93     }
94 }
95 
96 impl Parse for Repr {
parse(input: syn::parse::ParseStream) -> syn::Result<Self>97     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
98         let ident: Ident = input.parse()?;
99         Ok(match ident.to_string().as_str() {
100             "i8" => Repr::I8,
101             "u8" => Repr::U8,
102             "i16" => Repr::I16,
103             "u16" => Repr::U16,
104             "i32" => Repr::I32,
105             "u32" => Repr::U32,
106             "i64" => Repr::I64,
107             "u64" => Repr::U64,
108             "usize" => Repr::Usize,
109             "isize" => Repr::Isize,
110             #[cfg(feature = "repr_c")]
111             "C" => Repr::C,
112             #[cfg(not(feature = "repr_c"))]
113             "C" => {
114                 return Err(Error::new(
115                     ident.span(),
116                     "#[repr(C)] requires either the `std` or `libc_` feature",
117                 ))
118             }
119             _ => {
120                 return Err(Error::new(
121                     ident.span(),
122                     format!("unsupported repr `{ident}`"),
123                 ))
124             }
125         })
126     }
127 }
128 
129 /// Figure out what the internal representation of the enum should be given its variants.
130 ///
131 /// If we don't have sufficient info to auto-shrink the internal repr, fallback to isize.
autodetect_inner_repr<'a>(variants: impl Iterator<Item = &'a Discriminant>) -> Repr132 pub fn autodetect_inner_repr<'a>(variants: impl Iterator<Item = &'a Discriminant>) -> Repr {
133     let mut variants = variants.peekable();
134     if variants.peek().is_none() {
135         // TODO: maybe use the unit type for a fieldless open enum without a #[repr]?
136         return Repr::Isize;
137     }
138     let mut min = i128::MAX;
139     let mut max = i128::MIN;
140     for value in variants {
141         match value {
142             &Discriminant::Literal(value) => {
143                 min = min.min(value);
144                 max = max.max(value);
145             }
146             Discriminant::Nonliteral { .. } => {
147                 // No way to do fancy sizing here, fall back to isize.
148                 return Repr::Isize;
149             }
150         }
151     }
152     Repr::smallest_fitting_repr(min..=max).unwrap_or(Repr::Isize)
153 }
154