blob: 3ff8f58d56dc0fc0edeed84be069de6daa1b0e8e [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::discriminant::Discriminant;
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use std::ops::RangeInclusive;
use syn::{parse::Parse, Error};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Repr {
I8,
U8,
U16,
I16,
U32,
I32,
U64,
I64,
Usize,
Isize,
#[cfg(feature = "repr_c")]
C,
}
fn range_contains(x: &RangeInclusive<i128>, y: &RangeInclusive<i128>) -> bool {
x.contains(y.start()) && x.contains(y.end())
}
impl Repr {
const REPR_RANGES: &'static [(Repr, RangeInclusive<i128>)] = &[
(Repr::I8, (i8::MIN as i128)..=(i8::MAX as i128)),
(Repr::U8, (u8::MIN as i128)..=(u8::MAX as i128)),
(Repr::I16, (i16::MIN as i128)..=(i16::MAX as i128)),
(Repr::U16, (u16::MIN as i128)..=(u16::MAX as i128)),
(Repr::I32, (i32::MIN as i128)..=(i32::MAX as i128)),
(Repr::U32, (u32::MIN as i128)..=(u32::MAX as i128)),
(Repr::I64, (i64::MIN as i128)..=(i64::MAX as i128)),
(Repr::U64, (u64::MIN as i128)..=(u64::MAX as i128)),
(Repr::Isize, (isize::MIN as i128)..=(isize::MAX as i128)),
(Repr::Usize, (usize::MIN as i128)..=(usize::MAX as i128)),
];
/// Finds the smallest repr that can fit this range, if any.
fn smallest_fitting_repr(range: RangeInclusive<i128>) -> Option<Self> {
// TODO: perhaps check this logic matches current rustc behavior?
for (repr, repr_range) in Self::REPR_RANGES {
if range_contains(repr_range, &range) {
return Some(*repr);
}
}
None
}
fn name(self) -> &'static str {
match self {
Repr::I8 => "i8",
Repr::U8 => "u8",
Repr::U16 => "u16",
Repr::I16 => "i16",
Repr::U32 => "u32",
Repr::I32 => "i32",
Repr::U64 => "u64",
Repr::I64 => "i64",
Repr::Usize => "usize",
Repr::Isize => "isize",
#[cfg(feature = "repr_c")]
Repr::C => "C",
}
}
}
impl ToTokens for Repr {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend([match self {
// Technically speaking, #[repr(C)] on an enum isn't always `c_int`,
// but those who care can fix it if they need.
#[cfg(feature = "repr_c")]
Repr::C => quote::quote!(::open_enum::__private::c_int),
x => x.name().parse::<TokenStream>().unwrap(),
}])
}
}
impl Parse for Repr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
Ok(match ident.to_string().as_str() {
"i8" => Repr::I8,
"u8" => Repr::U8,
"i16" => Repr::I16,
"u16" => Repr::U16,
"i32" => Repr::I32,
"u32" => Repr::U32,
"i64" => Repr::I64,
"u64" => Repr::U64,
"usize" => Repr::Usize,
"isize" => Repr::Isize,
#[cfg(feature = "repr_c")]
"C" => Repr::C,
#[cfg(not(feature = "repr_c"))]
"C" => {
return Err(Error::new(
ident.span(),
"#[repr(C)] requires either the `std` or `libc_` feature",
))
}
_ => {
return Err(Error::new(
ident.span(),
format!("unsupported repr `{ident}`"),
))
}
})
}
}
/// Figure out what the internal representation of the enum should be given its variants.
///
/// If we don't have sufficient info to auto-shrink the internal repr, fallback to isize.
pub fn autodetect_inner_repr<'a>(variants: impl Iterator<Item = &'a Discriminant>) -> Repr {
let mut variants = variants.peekable();
if variants.peek().is_none() {
// TODO: maybe use the unit type for a fieldless open enum without a #[repr]?
return Repr::Isize;
}
let mut min = i128::MAX;
let mut max = i128::MIN;
for value in variants {
match value {
&Discriminant::Literal(value) => {
min = min.min(value);
max = max.max(value);
}
Discriminant::Nonliteral { .. } => {
// No way to do fancy sizing here, fall back to isize.
return Repr::Isize;
}
}
}
Repr::smallest_fitting_repr(min..=max).unwrap_or(Repr::Isize)
}