use smallvec::SmallVec;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum FilterOp {
Eq,
Neq,
Gt,
Gte,
Lt,
Lte,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FilterLiteral {
Int(i64),
Float(f64),
String(String),
Bool(bool),
Null,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FilterNode {
Compare {
column: String,
op: FilterOp,
value: FilterLiteral,
},
And(Box<FilterNode>, Box<FilterNode>),
Or(Box<FilterNode>, Box<FilterNode>),
Not(Box<FilterNode>),
}
#[derive(Debug, Clone)]
pub struct VTable {
pub trait_names: Vec<String>,
pub concrete_type_id: u32,
pub methods: HashMap<String, VTableEntry>,
}
#[derive(Debug, Clone)]
pub enum VTableEntry {
Direct { function_id: u16 },
Closure { function_id: u32, type_id: u32 },
BoxedReturn {
thunk_id: u16,
wrap_targets: SmallVec<[WrapTarget; 2]>,
},
SelfArg {
thunk_id: u16,
self_arg_positions: SmallVec<[u8; 4]>,
},
Generic {
thunk_id: u16,
type_param_count: u8,
},
Compound {
thunk_id: u16,
flags: VTableEntryFlags,
wrap_targets: SmallVec<[WrapTarget; 2]>,
self_arg_positions: SmallVec<[u8; 4]>,
type_param_count: u8,
},
}
#[derive(Debug, Clone)]
pub struct WrapTarget {
pub path: SmallVec<[u8; 4]>,
pub wrap_as_trait_id: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct VTableEntryFlags(pub u8);
impl VTableEntryFlags {
pub const BOXED_RETURN: u8 = 0b0000_0001;
pub const SELF_ARG: u8 = 0b0000_0010;
pub const GENERIC: u8 = 0b0000_0100;
#[inline]
pub const fn empty() -> Self {
Self(0)
}
#[inline]
pub const fn from_bits(bits: u8) -> Self {
Self(bits)
}
#[inline]
pub const fn bits(self) -> u8 {
self.0
}
#[inline]
pub const fn is_boxed_return(self) -> bool {
self.0 & Self::BOXED_RETURN != 0
}
#[inline]
pub const fn is_self_arg(self) -> bool {
self.0 & Self::SELF_ARG != 0
}
#[inline]
pub const fn is_generic(self) -> bool {
self.0 & Self::GENERIC != 0
}
#[inline]
pub fn set(&mut self, flag: u8) {
self.0 |= flag;
}
}
#[derive(Debug, Clone)]
pub struct TypeInfo {
pub concrete_type_id: u32,
pub vtable_for_bound: Option<std::sync::Arc<VTable>>,
pub size_align: (u32, u32),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErasureType {
SelfType,
SelfRef,
SelfRefMut,
SelfAssoc {
assoc_name: String,
bound_trait_id: Option<u32>,
},
Generic {
name: String,
args: Vec<ErasureType>,
},
Reference {
mutable: bool,
inner: Box<ErasureType>,
},
MethodGeneric { name: String },
Concrete { type_token: u32 },
}
impl ErasureType {
pub fn rewrite(&self, trait_id: u32) -> Result<RewriteResult, ErasureError> {
let mut wrap_targets = SmallVec::new();
let out = Self::rewrite_inner(self, trait_id, &mut wrap_targets, &mut SmallVec::new())?;
Ok(RewriteResult {
erased: out,
wrap_targets,
})
}
fn rewrite_inner(
ty: &ErasureType,
trait_id: u32,
wrap_targets: &mut SmallVec<[WrapTarget; 2]>,
path: &mut SmallVec<[u8; 4]>,
) -> Result<ErasureType, ErasureError> {
match ty {
ErasureType::SelfType => {
wrap_targets.push(WrapTarget {
path: path.clone(),
wrap_as_trait_id: trait_id,
});
Ok(ErasureType::Concrete { type_token: trait_id })
}
ErasureType::SelfRef => {
wrap_targets.push(WrapTarget {
path: path.clone(),
wrap_as_trait_id: trait_id,
});
Ok(ErasureType::Reference {
mutable: false,
inner: Box::new(ErasureType::Concrete { type_token: trait_id }),
})
}
ErasureType::SelfRefMut => {
wrap_targets.push(WrapTarget {
path: path.clone(),
wrap_as_trait_id: trait_id,
});
Ok(ErasureType::Reference {
mutable: true,
inner: Box::new(ErasureType::Concrete { type_token: trait_id }),
})
}
ErasureType::SelfAssoc { assoc_name, bound_trait_id } => match bound_trait_id {
Some(bound) => {
wrap_targets.push(WrapTarget {
path: path.clone(),
wrap_as_trait_id: *bound,
});
Ok(ErasureType::Concrete { type_token: *bound })
}
None => Err(ErasureError::Eto001UnboundedAssoc {
assoc_name: assoc_name.clone(),
}),
},
ErasureType::Generic { name, args } => {
let mut new_args = Vec::with_capacity(args.len());
for (i, arg) in args.iter().enumerate() {
path.push(i as u8);
new_args.push(Self::rewrite_inner(arg, trait_id, wrap_targets, path)?);
path.pop();
}
Ok(ErasureType::Generic {
name: name.clone(),
args: new_args,
})
}
ErasureType::Reference { mutable, inner } => {
let new_inner =
Self::rewrite_inner(inner.as_ref(), trait_id, wrap_targets, path)?;
Ok(ErasureType::Reference {
mutable: *mutable,
inner: Box::new(new_inner),
})
}
ErasureType::MethodGeneric { .. } | ErasureType::Concrete { .. } => Ok(ty.clone()),
}
}
}
#[derive(Debug, Clone)]
pub struct RewriteResult {
pub erased: ErasureType,
pub wrap_targets: SmallVec<[WrapTarget; 2]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErasureError {
Eto001UnboundedAssoc { assoc_name: String },
Eto002StaticOnly { method_name: String },
}
#[derive(Debug, Clone)]
pub struct ThunkSignature {
pub impl_type_id: u32,
pub trait_id: u32,
pub method_name: String,
pub flags: VTableEntryFlags,
pub wrap_targets: SmallVec<[WrapTarget; 2]>,
pub self_arg_positions: SmallVec<[u8; 4]>,
pub type_param_count: u8,
}
impl ThunkSignature {
pub fn build(
impl_type_id: u32,
trait_id: u32,
method_name: String,
return_wrap_targets: SmallVec<[WrapTarget; 2]>,
self_arg_positions: SmallVec<[u8; 4]>,
type_param_count: u8,
) -> Self {
let mut flags = VTableEntryFlags::empty();
if !return_wrap_targets.is_empty() {
flags.set(VTableEntryFlags::BOXED_RETURN);
}
if !self_arg_positions.is_empty() {
flags.set(VTableEntryFlags::SELF_ARG);
}
if type_param_count > 0 {
flags.set(VTableEntryFlags::GENERIC);
}
Self {
impl_type_id,
trait_id,
method_name,
flags,
wrap_targets: return_wrap_targets,
self_arg_positions,
type_param_count,
}
}
pub fn is_direct(&self) -> bool {
self.flags.bits() == 0
}
pub fn to_vtable_entry(&self, function_or_thunk_id: u16) -> VTableEntry {
let bits = self.flags.bits();
if bits == 0 {
return VTableEntry::Direct {
function_id: function_or_thunk_id,
};
}
let one_bit_set = bits.count_ones() == 1;
if one_bit_set {
if self.flags.is_boxed_return() {
return VTableEntry::BoxedReturn {
thunk_id: function_or_thunk_id,
wrap_targets: self.wrap_targets.clone(),
};
}
if self.flags.is_self_arg() {
return VTableEntry::SelfArg {
thunk_id: function_or_thunk_id,
self_arg_positions: self.self_arg_positions.clone(),
};
}
if self.flags.is_generic() {
return VTableEntry::Generic {
thunk_id: function_or_thunk_id,
type_param_count: self.type_param_count,
};
}
}
VTableEntry::Compound {
thunk_id: function_or_thunk_id,
flags: self.flags,
wrap_targets: self.wrap_targets.clone(),
self_arg_positions: self.self_arg_positions.clone(),
type_param_count: self.type_param_count,
}
}
}
#[cfg(test)]
mod erase_t_tests {
use super::*;
fn concrete(token: u32) -> ErasureType {
ErasureType::Concrete { type_token: token }
}
#[test]
fn erase_self_at_top_level_pushes_wrap_target_with_empty_path() {
let r = ErasureType::SelfType.rewrite(42).unwrap();
assert_eq!(r.wrap_targets.len(), 1);
assert_eq!(r.wrap_targets[0].path.as_slice(), &[] as &[u8]);
assert_eq!(r.wrap_targets[0].wrap_as_trait_id, 42);
}
#[test]
fn erase_result_of_self_pushes_path_zero() {
let ty = ErasureType::Generic {
name: "Result".to_string(),
args: vec![ErasureType::SelfType, concrete(99)],
};
let r = ty.rewrite(7).unwrap();
assert_eq!(r.wrap_targets.len(), 1);
assert_eq!(r.wrap_targets[0].path.as_slice(), &[0u8] as &[u8]);
assert_eq!(r.wrap_targets[0].wrap_as_trait_id, 7);
}
#[test]
fn erase_tuple_of_self_self_pushes_two_targets() {
let ty = ErasureType::Generic {
name: "tuple".to_string(),
args: vec![ErasureType::SelfType, ErasureType::SelfType],
};
let r = ty.rewrite(11).unwrap();
assert_eq!(r.wrap_targets.len(), 2);
assert_eq!(r.wrap_targets[0].path.as_slice(), &[0u8] as &[u8]);
assert_eq!(r.wrap_targets[1].path.as_slice(), &[1u8] as &[u8]);
}
#[test]
fn erase_nested_option_result_self_pushes_deep_path() {
let ty = ErasureType::Generic {
name: "Option".to_string(),
args: vec![ErasureType::Generic {
name: "Result".to_string(),
args: vec![ErasureType::SelfType, concrete(50)],
}],
};
let r = ty.rewrite(33).unwrap();
assert_eq!(r.wrap_targets.len(), 1);
assert_eq!(r.wrap_targets[0].path.as_slice(), &[0u8, 0u8] as &[u8]);
}
#[test]
fn erase_unbounded_assoc_returns_eto_001() {
let ty = ErasureType::SelfAssoc {
assoc_name: "Item".to_string(),
bound_trait_id: None,
};
let err = ty.rewrite(1).unwrap_err();
assert!(matches!(err, ErasureError::Eto001UnboundedAssoc { .. }));
}
#[test]
fn erase_bounded_assoc_erases_to_bound_trait() {
let ty = ErasureType::SelfAssoc {
assoc_name: "Iter".to_string(),
bound_trait_id: Some(77),
};
let r = ty.rewrite(1).unwrap();
assert_eq!(r.wrap_targets.len(), 1);
assert_eq!(r.wrap_targets[0].wrap_as_trait_id, 77);
}
#[test]
fn erase_concrete_type_is_identity_no_wrap_targets() {
let r = concrete(42).rewrite(1).unwrap();
assert_eq!(r.wrap_targets.len(), 0);
assert_eq!(r.erased, concrete(42));
}
#[test]
fn thunk_signature_direct_when_no_rewriting() {
let sig = ThunkSignature::build(
1,
2,
"name".to_string(),
SmallVec::new(),
SmallVec::new(),
0,
);
assert!(sig.is_direct());
match sig.to_vtable_entry(7) {
VTableEntry::Direct { function_id } => assert_eq!(function_id, 7),
_ => panic!("expected Direct"),
}
}
#[test]
fn thunk_signature_boxed_return_only() {
let mut wts: SmallVec<[WrapTarget; 2]> = SmallVec::new();
wts.push(WrapTarget {
path: SmallVec::new(),
wrap_as_trait_id: 1,
});
let sig = ThunkSignature::build(
1,
2,
"clone".to_string(),
wts,
SmallVec::new(),
0,
);
assert!(!sig.is_direct());
match sig.to_vtable_entry(9) {
VTableEntry::BoxedReturn { thunk_id, wrap_targets } => {
assert_eq!(thunk_id, 9);
assert_eq!(wrap_targets.len(), 1);
}
_ => panic!("expected BoxedReturn"),
}
}
#[test]
fn thunk_signature_compound_when_two_flags() {
let mut wts: SmallVec<[WrapTarget; 2]> = SmallVec::new();
wts.push(WrapTarget {
path: SmallVec::new(),
wrap_as_trait_id: 5,
});
let sig = ThunkSignature::build(
1,
5,
"compound".to_string(),
wts,
SmallVec::new(),
1,
);
match sig.to_vtable_entry(11) {
VTableEntry::Compound { thunk_id, flags, type_param_count, .. } => {
assert_eq!(thunk_id, 11);
assert!(flags.is_boxed_return());
assert!(flags.is_generic());
assert!(!flags.is_self_arg());
assert_eq!(type_param_count, 1);
}
_ => panic!("expected Compound"),
}
}
}