use core::num::{NonZeroU32, NonZeroUsize, TryFromIntError};
#[cfg(not(feature = "std"))]
use core::ops::Range;
#[cfg(feature = "std")]
use std::ops::Range;
use crate::error::FormatError;
pub(crate) trait Narrow: Copy {
#[inline]
fn to_usize(self) -> Result<usize, FormatError>
where
usize: TryFrom<Self, Error = TryFromIntError>,
{
self.narrow()
}
#[inline]
fn narrow<T>(self) -> Result<T, FormatError>
where
T: NarrowTarget + TryFrom<Self, Error = TryFromIntError>,
{
self.narrow_or_else(|| FormatError::ValueTooLargeForPlatform {
value: self.to_u64(),
target: T::NAME,
})
}
#[expect(
clippy::map_err_ignore,
reason = "the bound admits only `TryFromIntError`, which carries nothing beyond \
the conversion having failed, and that is what the error `error` builds reports"
)]
#[inline]
fn narrow_or_else<T, E>(self, error: impl FnOnce() -> E) -> Result<T, E>
where
T: TryFrom<Self, Error = TryFromIntError>,
{
T::try_from(self).map_err(|_| error())
}
fn to_u64(self) -> u64;
}
impl Narrow for u64 {
#[inline]
fn to_u64(self) -> u64 {
self
}
}
impl Narrow for u32 {
#[inline]
fn to_u64(self) -> u64 {
u64::from(self)
}
}
impl Narrow for usize {
#[inline]
fn to_u64(self) -> u64 {
self as u64
}
}
impl Narrow for NonZeroU32 {
#[inline]
fn to_u64(self) -> u64 {
u64::from(self.get())
}
}
pub(crate) trait NarrowTarget {
const NAME: &'static str;
}
impl NarrowTarget for usize {
const NAME: &'static str = "usize";
}
impl NarrowTarget for u32 {
const NAME: &'static str = "u32";
}
impl NarrowTarget for NonZeroUsize {
const NAME: &'static str = "usize";
}
#[cfg(test)]
pub(crate) fn nz(value: usize) -> NonZeroUsize {
NonZeroUsize::new(value).expect("a test's element size is non-zero")
}
#[inline]
pub fn slice_range(offset: u64, len: u64) -> Result<Range<usize>, FormatError> {
let end = offset.checked_add(len).ok_or(FormatError::OffsetOverflow {
offset,
length: len,
})?;
Ok(offset.to_usize()?..end.to_usize()?)
}
#[inline]
pub(crate) fn is_undefined_addr(addr: u64, offset_size: u8) -> bool {
match offset_size {
2 => addr == 0xFFFF,
4 => addr == 0xFFFF_FFFF,
8 => addr == 0xFFFF_FFFF_FFFF_FFFF,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_values_round_trip() {
assert_eq!(0u64.to_usize().unwrap(), 0);
assert_eq!(1234u64.to_usize().unwrap(), 1234);
assert_eq!(42u32.to_usize().unwrap(), 42);
assert_eq!(1000u64.narrow::<u32>().unwrap(), 1000);
}
#[test]
fn a_value_past_the_target_width_is_reported_with_the_value() {
let err = 0x1_0000_0000u64.narrow::<u32>().unwrap_err();
let FormatError::ValueTooLargeForPlatform { value, target } = err else {
panic!("expected ValueTooLargeForPlatform, got {err:?}");
};
assert_eq!(value, 0x1_0000_0000);
assert_eq!(target, "u32");
}
#[test]
fn the_callers_error_replaces_the_platform_one() {
assert_eq!(
65_535u32
.narrow_or_else::<u16, _>(|| "index does not fit u16")
.unwrap(),
65_535
);
assert_eq!(
65_536u32
.narrow_or_else::<u16, _>(|| "index does not fit u16")
.unwrap_err(),
"index does not fit u16"
);
}
#[test]
fn a_nonzero_width_survives_the_narrowing() {
let width = NonZeroU32::new(8).unwrap();
assert_eq!(width.narrow::<NonZeroUsize>().unwrap().get(), 8);
let widest = NonZeroU32::new(u32::MAX).unwrap();
assert_eq!(
widest.narrow::<NonZeroUsize>().unwrap().get(),
u32::MAX as usize
);
}
#[test]
fn slice_range_basic() {
let r = slice_range(10, 20).unwrap();
assert_eq!(r, 10..30);
}
#[test]
fn slice_range_addition_overflow_is_caught() {
let err = slice_range(u64::MAX, 1).unwrap_err();
assert!(matches!(err, FormatError::OffsetOverflow { .. }));
}
#[test]
fn large_u64_behaviour_matches_platform_width() {
let big: u64 = u64::from(u32::MAX) + 1; match big.to_usize() {
Ok(v) => assert_eq!(v as u64, big),
Err(e) => assert!(matches!(e, FormatError::ValueTooLargeForPlatform { .. })),
}
}
}