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

View File

@@ -1,9 +1,7 @@
use crate::arena::F64Ptr;
use crate::arena::TypedArenaPtr;
use crate::arena::*;
use crate::atom_table::*;
pub use crate::machine::machine_state::*;
use crate::offset_table::F64Ptr;
use crate::parser::ast::*;
use crate::parser::char_reader::*;
use crate::parser::dashu::Integer;
@@ -662,15 +660,15 @@ impl<'a, R: CharRead> Lexer<'a, R> {
}
}
fn vacate_with_float(&mut self, mut token: String) -> Result<Token, ParserError> {
fn vacate_with_float(&mut self, mut token: String) -> Result<Number, ParserError> {
self.return_char(token.pop().unwrap());
let n = parse_float_lossy(&token)?;
Ok(Token::Literal(Literal::from(float_alloc!(
Ok(Number::Float(float_alloc!(
n,
self.machine_st.arena
))))
)))
}
fn skip_underscore_in_number(&mut self) -> Result<char, ParserError> {
@@ -797,7 +795,8 @@ impl<'a, R: CharRead> Lexer<'a, R> {
}
let n = parse_float_lossy(&token)?;
Ok(Token::Literal(Literal::from(float_alloc!(
Ok(NumberToken::Number(Number::Float(float_alloc!(
n,
self.machine_st.arena
))))
@@ -806,7 +805,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
}
} else {
let n = parse_float_lossy(&token)?;
Ok(Token::Literal(Literal::from(float_alloc!(
Ok(NumberToken::Number(Number::Float(float_alloc!(
n,
self.machine_st.arena
))))
@@ -859,7 +858,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
}
self.get_single_quoted_char()
.map(|c| NumberToken::Number(Number::Fixnum(Fixnum::build_with(c as i64))))
.map(|c| NumberToken::Number(Number::Fixnum(Fixnum::build_with(c))))
.or_else(|err| {
match err {
ParserError::UnexpectedChar('\'', ..) => {}

View File

@@ -389,7 +389,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
Cell::default(),
Box::new(Term::Literal(
Cell::default(),
Literal::Fixnum(Fixnum::build_with(c as i64)),
Literal::Fixnum(Fixnum::build_with(c)),
)),
Box::new(list),
);
@@ -963,12 +963,12 @@ impl<'a, R: CharRead> Parser<'a, R> {
Token::Literal(Literal::Rational(n)) => {
self.negate_number(n, negate_rat_rc, |r, _| Literal::Rational(r))
}
Token::Literal(Literal::Float(n)) if n.as_ptr().is_infinite() => {
Token::Literal(Literal::Float(n)) if n.as_ptr().is_infinite() => {
return Err(ParserError::InfiniteFloat(
self.lexer.line_num,
self.lexer.col_num,
));
}
}
Token::Literal(Literal::Float(n)) => self.negate_number(
**n.as_ptr(),
|n, _| -n,