make Fixnum::build_with harder to accidentally misuse
change trait bound order for better
This commit is contained in:
committed by
Mark Thom
parent
87ca083cfa
commit
cbdd0fbf15
@@ -11,7 +11,10 @@ use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::i64;
|
||||
use std::io::{Error as IOError, ErrorKind};
|
||||
use std::ops::Not;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::ops::{Deref, Neg};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -550,9 +553,90 @@ pub struct Fixnum {
|
||||
tag: B6,
|
||||
}
|
||||
|
||||
mod private {
|
||||
use dashu::Integer;
|
||||
|
||||
pub(crate) trait FitsInFixnumSeal {}
|
||||
pub(crate) trait MightNotFitInFixnumSeal {}
|
||||
|
||||
macro_rules! impl_fits_in_fixnum {
|
||||
($t:ty) => {
|
||||
impl $crate::parser::ast::private::FitsInFixnumSeal for $t {}
|
||||
|
||||
impl $crate::parser::ast::FitsInFixnum for $t {
|
||||
fn into_i56(self) -> i64 {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_fits_in_fixnum!(u8);
|
||||
impl_fits_in_fixnum!(i8);
|
||||
impl_fits_in_fixnum!(u16);
|
||||
impl_fits_in_fixnum!(i16);
|
||||
impl_fits_in_fixnum!(u32);
|
||||
impl_fits_in_fixnum!(i32);
|
||||
|
||||
impl FitsInFixnumSeal for char {}
|
||||
impl super::FitsInFixnum for char {
|
||||
fn into_i56(self) -> i64 {
|
||||
u32::from(self) as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl MightNotFitInFixnumSeal for i64 {}
|
||||
impl MightNotFitInFixnumSeal for &Integer {}
|
||||
impl MightNotFitInFixnumSeal for Integer {}
|
||||
impl MightNotFitInFixnumSeal for usize {}
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
pub trait FitsInFixnum: private::FitsInFixnumSeal {
|
||||
fn into_i56(self) -> i64;
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
pub trait MightNotFitInFixnum: private::MightNotFitInFixnumSeal {
|
||||
fn try_into_i56(self) -> Option<i64>;
|
||||
}
|
||||
|
||||
impl<T> MightNotFitInFixnum for T
|
||||
where
|
||||
T: private::MightNotFitInFixnumSeal + TryInto<i64>,
|
||||
{
|
||||
fn try_into_i56(self) -> Option<i64> {
|
||||
let val = self.try_into().ok()?;
|
||||
if Fixnum::RANGE.contains(&val) {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fixnum {
|
||||
pub(crate) const MIN: i64 = -(1 << 55);
|
||||
pub(crate) const MAX: i64 = (1 << 55) - 1;
|
||||
const RANGE: RangeInclusive<i64> = Self::MIN..=Self::MAX;
|
||||
|
||||
// if you have a type that is not guaranteed to fit use `Fixnum::build_with_checked` or `Fixnum::build_with_unchecked` instead
|
||||
#[inline]
|
||||
pub fn build_with(num: i64) -> Self {
|
||||
pub fn build_with(num: impl FitsInFixnum) -> Self {
|
||||
// Safety: FitsInFixnum is only implemented by types that only have valid values
|
||||
// and FitsInFixnumSeal ensures no one outside this crate can violate that
|
||||
unsafe { Self::build_with_unchecked(num.into_i56()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn build_with_unchecked(num: i64) -> Self {
|
||||
debug_assert!(
|
||||
Self::RANGE.contains(&num),
|
||||
"{num} should be in the range {}..={}",
|
||||
Self::MIN,
|
||||
Self::MAX
|
||||
);
|
||||
|
||||
Fixnum::new()
|
||||
.with_num(u64::from_ne_bytes(num.to_ne_bytes()) & ((1 << 56) - 1))
|
||||
.with_tag(HeapCellValueTag::Fixnum as u8)
|
||||
@@ -561,12 +645,8 @@ impl Fixnum {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_cutpoint(num: i64) -> Self {
|
||||
Fixnum::new()
|
||||
.with_num(u64::from_ne_bytes(num.to_ne_bytes()) & ((1 << 56) - 1))
|
||||
.with_tag(HeapCellValueTag::CutPoint as u8)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
pub fn as_cutpoint(self) -> Self {
|
||||
self.with_tag(HeapCellValueTag::CutPoint as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -575,20 +655,14 @@ impl Fixnum {
|
||||
HeapCellValueTag::from_bytes(self.tag()).unwrap()
|
||||
}
|
||||
|
||||
// if you have a type that is guaranteed to fit use `Fixnum::build_with` instead
|
||||
#[inline]
|
||||
pub fn build_with_checked(num: i64) -> Result<Self, OutOfBounds> {
|
||||
const UPPER_BOUND: i64 = (1 << 55) - 1;
|
||||
const LOWER_BOUND: i64 = -(1 << 55);
|
||||
|
||||
if (LOWER_BOUND..=UPPER_BOUND).contains(&num) {
|
||||
Ok(Fixnum::new()
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_tag(HeapCellValueTag::Fixnum as u8)
|
||||
.with_num(u64::from_ne_bytes(num.to_ne_bytes()) & ((1 << 56) - 1)))
|
||||
} else {
|
||||
Err(OutOfBounds {})
|
||||
}
|
||||
pub fn build_with_checked(num: impl MightNotFitInFixnum) -> Result<Self, OutOfBounds> {
|
||||
Ok(unsafe {
|
||||
// Safety: all MightNotFitInFixnum impls return None when the value is out-of-bounds
|
||||
// and MightNotFitInFixnumSeal ensures no one outside this crate can violate that
|
||||
Self::build_with_unchecked(num.try_into_i56().ok_or(OutOfBounds {})?)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -598,6 +672,10 @@ impl Fixnum {
|
||||
debug_assert!(!overflowed);
|
||||
n
|
||||
}
|
||||
|
||||
pub fn checked_abs(self) -> Option<Self> {
|
||||
Self::build_with_checked(self.get_num().abs()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Fixnum {
|
||||
@@ -605,7 +683,18 @@ impl Neg for Fixnum {
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Fixnum::build_with(-self.get_num())
|
||||
// Safety: the truncating behaviour is correct
|
||||
unsafe { Self::build_with_unchecked(-self.get_num()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Fixnum {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn not(self) -> Self::Output {
|
||||
// Safety: the truncating behaviour is correct
|
||||
unsafe { Self::build_with_unchecked(!self.get_num()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user