| // 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) |
| } |