use std::collections::HashMap;
use super::{
EnumMember, NumberFormat, TypeId, TypeMember, TypeShape, TypeTable, TypeValue, ValueRepr,
};
mod scalar_kind {
pub const VOID: u32 = 1;
pub const BOOL: u32 = 2;
pub const INT: u32 = 3;
pub const FLOAT: u32 = 4;
}
fn opt_size(size: u64, has_size: u32) -> Option<u64> {
(has_size != 0).then_some(size)
}
#[derive(Debug)]
pub(crate) struct TypeBuilder {
table: TypeTable,
name2type: HashMap<Box<str>, TypeId>,
pending: HashMap<TypeId, Option<Box<str>>>,
too_wide: Option<u32>,
}
impl TypeBuilder {
pub(crate) fn new() -> Self {
Self {
table: TypeTable::new(),
name2type: HashMap::new(),
pending: HashMap::new(),
too_wide: None,
}
}
pub(crate) fn intern(&mut self, data: TypeValue) -> TypeId {
self.table.intern(data)
}
pub(crate) fn alloc_placeholder(&mut self) -> TypeId {
self.table.alloc_placeholder()
}
pub(crate) fn fill(&mut self, id: TypeId, data: TypeValue) {
self.table.fill(id, data);
}
pub(crate) fn type_size(&self, id: TypeId) -> Option<u64> {
self.table.get(id).size
}
pub(crate) fn scalar(
&mut self,
kind: u32,
bytes: u32,
signed: u32,
size: u64,
has_size: u32,
) -> TypeId {
let width = if let Ok(w) = u8::try_from(bytes) {
w
} else {
self.too_wide.get_or_insert(bytes);
0
};
let shape = match kind {
scalar_kind::VOID => TypeShape::Void,
scalar_kind::BOOL => TypeShape::Bool,
scalar_kind::INT => TypeShape::Int {
bytes: width,
signed: signed != 0,
},
scalar_kind::FLOAT => TypeShape::Float { bytes: width },
_ => TypeShape::Unknown,
};
self.intern(TypeValue {
shape,
size: opt_size(size, has_size),
})
}
pub(crate) fn ptr(&mut self, target: TypeId, size: u64, has_size: u32) -> TypeId {
self.intern(TypeValue {
shape: TypeShape::Ptr(target),
size: opt_size(size, has_size),
})
}
pub(crate) fn array(&mut self, elem: TypeId, nelems: u64, size: u64, has_size: u32) -> TypeId {
self.intern(TypeValue {
shape: TypeShape::Array { elem, len: nelems },
size: opt_size(size, has_size),
})
}
pub(crate) fn function(&mut self, ret: TypeId, params: Vec<TypeId>, vararg: u32) -> TypeId {
self.intern(TypeValue {
shape: TypeShape::Function {
ret,
params,
varargs: vararg != 0,
},
size: None,
})
}
pub(crate) fn opaque(&mut self, name: String) -> TypeId {
self.intern(TypeValue {
shape: TypeShape::Opaque(name),
size: None,
})
}
pub(crate) fn named_ref(&mut self, name: String) -> TypeId {
if let Some(&id) = self.name2type.get(name.as_str()) {
return id;
}
let id = self.alloc_placeholder();
let key: Box<str> = name.into_boxed_str();
self.name2type.insert(key.clone(), id);
self.pending.insert(id, Some(key));
id
}
pub(crate) fn anon(&mut self) -> TypeId {
let id = self.alloc_placeholder();
self.pending.insert(id, None);
id
}
fn take_name(&mut self, id: TypeId) -> Option<String> {
self.pending.remove(&id).flatten().map(String::from)
}
pub(crate) fn fill_struct(
&mut self,
id: TypeId,
is_union: bool,
members: Vec<TypeMember>,
size: u64,
has_size: u32,
) {
let name = self.take_name(id);
let shape = if is_union {
TypeShape::Union { name, members }
} else {
TypeShape::Struct { name, members }
};
self.fill(
id,
TypeValue {
shape,
size: opt_size(size, has_size),
},
);
}
#[expect(
clippy::too_many_arguments,
reason = "mirrors the facade's flat fill_enum callback"
)]
pub(crate) fn fill_enum(
&mut self,
id: TypeId,
underlying: TypeId,
members: Vec<EnumMember>,
size: u64,
has_size: u32,
is_bitmask: bool,
repr_vtype: u32,
repr_signed: bool,
repr_leading_zeros: bool,
) {
let name = self.take_name(id);
let repr = NumberFormat::try_from(repr_vtype)
.ok()
.map(|format| ValueRepr {
format,
signed: repr_signed,
leading_zeros: repr_leading_zeros,
});
self.fill(
id,
TypeValue {
shape: TypeShape::Enum {
name,
underlying,
members,
is_bitmask,
repr,
},
size: opt_size(size, has_size),
},
);
}
pub(crate) fn fill_typedef(&mut self, id: TypeId, underlying: TypeId) {
let name = self.take_name(id).unwrap_or_default();
let size = self.type_size(underlying);
self.fill(
id,
TypeValue {
shape: TypeShape::Typedef { name, underlying },
size,
},
);
}
pub(crate) fn too_wide(&self) -> Option<u32> {
self.too_wide
}
pub(crate) fn unfilled(&self) -> usize {
self.pending.len()
}
pub(crate) fn into_table(self) -> TypeTable {
self.table
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
#[test]
fn scalar_kinds_map_to_their_shapes() {
let mut b = TypeBuilder::new();
let void = b.scalar(scalar_kind::VOID, 0, 0, 0, 0);
let boolean = b.scalar(scalar_kind::BOOL, 1, 0, 0, 0);
let int = b.scalar(scalar_kind::INT, 4, 1, 0, 0);
let float = b.scalar(scalar_kind::FLOAT, 4, 0, 0, 0);
let unknown = b.scalar(u32::MAX, 0, 0, 0, 0);
let table = b.into_table();
assert!(matches!(table.get(void).shape, TypeShape::Void));
assert!(matches!(table.get(boolean).shape, TypeShape::Bool));
assert!(matches!(
table.get(int).shape,
TypeShape::Int {
bytes: 4,
signed: true
}
));
assert!(matches!(
table.get(float).shape,
TypeShape::Float { bytes: 4 }
));
assert!(matches!(table.get(unknown).shape, TypeShape::Unknown));
}
#[test]
fn over_wide_scalar_is_recorded() {
let mut b = TypeBuilder::new();
let id = b.scalar(scalar_kind::INT, 300, 0, 0, 0);
assert!(b.too_wide() == Some(300));
assert!(matches!(
b.into_table().get(id).shape,
TypeShape::Int { bytes: 0, .. }
));
}
#[test]
fn a_reserved_placeholder_counts_as_unfilled() {
let mut b = TypeBuilder::new();
assert!(b.unfilled() == 0);
b.anon();
assert!(b.unfilled() == 1);
}
}