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