make Fixnum::build_with harder to accidentally misuse

change trait bound order for better
This commit is contained in:
Bennet Bleßmann
2025-01-22 21:10:44 +01:00
committed by Mark Thom
parent 87ca083cfa
commit cbdd0fbf15
19 changed files with 422 additions and 223 deletions

View File

@@ -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()) }
}
}