better type assertion ergonomic

This commit is contained in:
2026-04-20 17:57:54 +08:00
parent 581c333070
commit 11b0b8a78e
13 changed files with 399 additions and 258 deletions
+135 -45
View File
@@ -23,10 +23,10 @@ mod private {
/// # Safety
///
/// 1. TAG must be unique among all implementors.
/// 2. TAG must be within 1..=7
/// TAG must be unique among all implementors.
#[allow(private_interfaces)]
pub unsafe trait Storable: private::Cealed {
const TAG: (bool, u8);
const TAG: RawTag;
}
#[allow(private_bounds)]
pub trait InlineStorable: Storable + RawStore {}
@@ -38,15 +38,17 @@ macro_rules! define_value_types {
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
) => {
$(
#[allow(private_interfaces)]
unsafe impl Storable for $itype {
const TAG: (bool, u8) = $itag;
const TAG: RawTag = $itag;
}
impl InlineStorable for $itype {}
impl private::Cealed for $itype {}
)*
$(
#[allow(private_interfaces)]
unsafe impl Storable for $gtype {
const TAG: (bool, u8) = $gtag;
const TAG: RawTag = $gtag;
}
impl GcStorable for $gtype {}
impl private::Cealed for $gtype {}
@@ -54,11 +56,9 @@ macro_rules! define_value_types {
const _: () = assert!(size_of::<Value<'static>>() == 8);
$(const _: () = assert!(size_of::<$itype>() <= 6);)*
$(const _: () = { let (_, val) = $itag; assert!(val >= 1 && val <= 7); };)*
$(const _: () = { let (_, val) = $gtag; assert!(val >= 1 && val <= 7); };)*
const _: () = {
let tags: &[(bool, u8)] = &[$($itag),*, $($gtag),*];
let tags: &[(bool, u8)] = &[$(RawTag::neg_val($itag)),*, $(RawTag::neg_val($gtag)),*];
let mut mask_false: u8 = 0;
let mut mask_true: u8 = 0;
let mut i = 0;
@@ -80,12 +80,12 @@ macro_rules! define_value_types {
const NEEDS_TRACE: bool = true;
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
let Some(tag) = self.raw.tag() else { return };
match tag.neg_val() {
match tag {
$(<$gtype as Storable>::TAG => unsafe {
self.load_gc::<$gtype>().trace(cc)
},)*
$(<$itype as Storable>::TAG => (),)*
(neg, val) => unreachable!("invalid tag: neg={neg}, val={val}"),
_ => unreachable!("invalid value tag"),
}
}
}
@@ -101,7 +101,7 @@ macro_rules! define_value_types {
}),)*
$(Some(<$gtype as Storable>::TAG) =>
write!(f, "{}(..)", $gname),)*
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
_ => unreachable!("invalid value tag"),
}
}
}
@@ -110,20 +110,20 @@ macro_rules! define_value_types {
define_value_types! {
inline {
i32 => (false, 1), "SmallInt";
bool => (false, 2), "Bool";
Null => (false, 3), "Null";
StringId => (false, 4), "SmallString";
PrimOp => (false, 5), "PrimOp";
i32 => RawTag::P1, "SmallInt";
bool => RawTag::P2, "Bool";
Null => RawTag::P3, "Null";
StringId => RawTag::P4, "SmallString";
PrimOp => RawTag::P5, "PrimOp";
}
gc {
i64 => (false, 6), "BigInt";
NixString => (false, 7), "String";
AttrSet<'_> => (true, 1), "AttrSet";
List<'_> => (true, 2), "List";
Thunk<'_> => (true, 3), "Thunk";
Closure<'_> => (true, 4), "Closure";
PrimOpApp<'_> => (true, 5), "PrimOpApp";
i64 => RawTag::P6, "BigInt";
NixString => RawTag::P7, "String";
AttrSet<'_> => RawTag::N1, "AttrSet";
List<'_> => RawTag::N2, "List";
Thunk<'_> => RawTag::N3, "Thunk";
Closure<'_> => RawTag::N4, "Closure";
PrimOpApp<'_> => RawTag::N5, "PrimOpApp";
}
}
@@ -132,7 +132,7 @@ define_value_types! {
/// NaN-boxed value fitting in 8 bytes.
#[derive(Copy, Clone)]
#[repr(transparent)]
pub(crate) struct Value<'gc> {
pub struct Value<'gc> {
raw: RawBox,
_marker: PhantomData<Gc<'gc, ()>>,
}
@@ -167,16 +167,15 @@ impl<'gc> Value<'gc> {
}
}
/// Returns the `(negative, val)` tag, or `None` for a float.
#[inline(always)]
fn tag(self) -> Option<(bool, u8)> {
self.raw.tag().map(|t| t.neg_val())
const fn tag(self) -> Option<RawTag> {
self.raw.tag()
}
}
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn new_float(val: f64) -> Self {
pub fn new_float(val: f64) -> Self {
Self {
raw: RawBox::from_float(val),
_marker: PhantomData,
@@ -184,24 +183,24 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
pub fn new_inline<T: InlineStorable>(val: T) -> Self {
Self::from_raw_value(RawValue::store(
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
T::TAG,
val,
))
}
#[inline]
pub(crate) fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
pub fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
let ptr = Gc::as_ptr(gc);
Self::from_raw_value(RawValue::store(
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
T::TAG,
ptr,
))
}
#[inline]
pub(crate) fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
pub fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
Value::new_inline(val as i32)
} else {
@@ -212,24 +211,24 @@ impl<'gc> Value<'gc> {
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn is_float(self) -> bool {
pub fn is_float(self) -> bool {
self.raw.is_float()
}
#[inline]
pub(crate) fn is<T: Storable>(self) -> bool {
pub fn is<T: Storable>(self) -> bool {
self.tag() == Some(T::TAG)
}
}
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn as_float(self) -> Option<f64> {
pub fn as_float(self) -> Option<f64> {
self.raw.float().copied()
}
#[inline]
pub(crate) fn as_inline<T: InlineStorable>(self) -> Option<T> {
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
if self.is::<T>() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
@@ -241,7 +240,7 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub(crate) fn as_gc<T: GcStorable>(self) -> Option<Gc<'gc, T>> {
pub fn as_gc<T: GcStorable>(self) -> Option<Gc<'gc, T>> {
if self.is::<T>() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
@@ -254,7 +253,7 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub(crate) fn as_num(self) -> Option<NixNum> {
pub fn as_num(self) -> Option<NixNum> {
if let Some(i) = self.as_inline::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = self.as_gc::<i64>() {
@@ -265,13 +264,69 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub(crate) fn restrict(self) -> Result<StrictValue<'gc>, Gc<'gc, Thunk<'gc>>> {
pub fn restrict(self) -> Result<StrictValue<'gc>, Gc<'gc, Thunk<'gc>>> {
if let Some(thunk) = self.as_gc::<Thunk<'gc>>() {
Err(thunk)
} else {
Ok(StrictValue(self))
}
}
#[inline]
pub fn ty(self) -> NixType {
if self.is_float() {
NixType::Float
} else if self.is::<i32>() || self.is::<i64>() {
NixType::Int
} else if self.is::<bool>() {
NixType::Bool
} else if self.is::<Null>() {
NixType::Null
} else if self.is::<StringId>() {
NixType::String
} else if self.is::<PrimOp>() {
NixType::PrimOp
} else if self.is::<NixString>() {
NixType::String
} else if self.is::<AttrSet>() {
NixType::AttrSet
} else if self.is::<List>() {
NixType::List
} else if self.is::<Thunk>() {
NixType::Thunk
} else if self.is::<Closure>() {
NixType::Closure
} else if self.is::<PrimOpApp>() {
NixType::PrimOpApp
} else {
unreachable!("value has no recognized type tag")
}
}
#[inline]
pub(crate) fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
self.as_inline::<T>().ok_or_else(|| self.ty())
}
#[inline]
pub(crate) fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
self.as_gc::<T>().ok_or_else(|| self.ty())
}
#[inline]
pub(crate) fn expect_num(self) -> Result<NixNum, NixType> {
self.as_num().ok_or_else(|| self.ty())
}
#[inline]
pub(crate) fn expect_bool(self) -> Result<bool, NixType> {
self.as_inline::<bool>().ok_or_else(|| self.ty())
}
#[inline]
pub(crate) fn expect_float(self) -> Result<f64, NixType> {
self.as_float().ok_or_else(|| self.ty())
}
}
#[derive(Copy, Clone, Default)]
@@ -550,8 +605,9 @@ pub(crate) struct PrimOpApp<'gc> {
pub(crate) args: SmallVec<[Value<'gc>; 2]>,
}
#[derive(Copy, Clone, Default)]
#[derive(Copy, Clone, Default, Collect)]
#[repr(transparent)]
#[collect(no_drop)]
pub(crate) struct StrictValue<'gc>(Value<'gc>);
impl<'gc> StrictValue<'gc> {
@@ -575,9 +631,43 @@ impl fmt::Debug for StrictValue<'_> {
}
}
unsafe impl<'gc> Collect<'gc> for StrictValue<'gc> {
const NEEDS_TRACE: bool = true;
fn trace<T: gc_arena::collect::Trace<'gc>>(&self, cc: &mut T) {
self.0.trace(cc);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Collect)]
#[collect(require_static)]
pub(crate) enum NixType {
Int,
Float,
Bool,
Null,
String,
AttrSet,
List,
Thunk,
Closure,
PrimOp,
PrimOpApp,
}
impl NixType {
pub fn display(self) -> &'static str {
use NixType::*;
match self {
Int => "an integer",
Float => "a float",
Bool => "a boolean",
Null => "null",
String => "a string",
AttrSet => "a set",
List => "a list",
Thunk => "a thunk",
Closure => "a function",
PrimOp => "a built-in function",
PrimOpApp => "a partially applied built-in function",
}
}
}
impl std::fmt::Display for NixType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display())
}
}