use std::fmt;
use crate::metadata::{
cilobject::CilObject,
method::Method,
signatures::{
CustomModifiers, SignatureArray, SignatureLocalVariable, SignatureMethod,
SignatureParameter, SignaturePointer, SignatureSzArray, TypeSignature,
},
tables::{MemberRefSignature, StandAloneSigRaw, StandAloneSignature},
token::Token,
typesystem::{ArrayDimensions, CilFlavor},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeRef(pub Token);
impl TypeRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for TypeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TypeRef({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MethodRef(pub Token);
impl MethodRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for MethodRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MethodRef({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FieldRef(pub Token);
impl FieldRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for FieldRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FieldRef({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SigRef(pub Token);
impl SigRef {
#[must_use]
pub const fn new(token: Token) -> Self {
Self(token)
}
#[must_use]
pub const fn token(&self) -> Token {
self.0
}
}
impl fmt::Display for SigRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SigRef({})", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub enum SsaType {
Void,
Bool,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
NativeInt,
NativeUInt,
F32,
F64,
Char,
Object,
String,
Class(TypeRef),
ValueType(TypeRef),
Array(Box<SsaType>, u32),
Pointer(Box<SsaType>),
ByRef(Box<SsaType>),
TypedReference,
GenericParam(u32),
MethodGenericParam(u32),
FnPtr(Box<FnPtrSig>),
RuntimeTypeHandle,
RuntimeMethodHandle,
RuntimeFieldHandle,
Null,
#[default]
Unknown,
Varying,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FnPtrSig {
pub ret: SsaType,
pub params: Vec<SsaType>,
pub call_conv: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TypeClass {
Int32,
Int64,
Float32,
Float64,
Reference,
NativeInt,
Other,
}
impl SsaType {
#[must_use]
pub const fn is_primitive(&self) -> bool {
matches!(
self,
Self::Bool
| Self::I8
| Self::U8
| Self::I16
| Self::U16
| Self::I32
| Self::U32
| Self::I64
| Self::U64
| Self::NativeInt
| Self::NativeUInt
| Self::F32
| Self::F64
| Self::Char
)
}
#[must_use]
pub const fn is_integer(&self) -> bool {
matches!(
self,
Self::I8
| Self::U8
| Self::I16
| Self::U16
| Self::I32
| Self::U32
| Self::I64
| Self::U64
| Self::NativeInt
| Self::NativeUInt
)
}
#[must_use]
pub const fn is_float(&self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
#[must_use]
pub fn is_reference(&self) -> bool {
matches!(
self,
Self::Object | Self::String | Self::Class(_) | Self::Array(_, _) | Self::Null
)
}
#[must_use]
pub const fn is_value_type(&self) -> bool {
matches!(self, Self::ValueType(_)) || self.is_primitive()
}
#[must_use]
pub const fn is_array(&self) -> bool {
matches!(self, Self::Array(_, _))
}
#[must_use]
pub const fn is_pointer(&self) -> bool {
matches!(self, Self::Pointer(_) | Self::ByRef(_))
}
#[must_use]
pub const fn is_void(&self) -> bool {
matches!(self, Self::Void)
}
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown | Self::Varying)
}
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub const fn is_generic_param(&self) -> bool {
matches!(self, Self::GenericParam(_) | Self::MethodGenericParam(_))
}
#[must_use]
pub fn array_element_type(&self) -> Option<&SsaType> {
match self {
Self::Array(elem, _) => Some(elem),
_ => None,
}
}
#[must_use]
pub const fn array_rank(&self) -> Option<u32> {
match self {
Self::Array(_, rank) => Some(*rank),
_ => None,
}
}
#[must_use]
pub fn pointee_type(&self) -> Option<&SsaType> {
match self {
Self::Pointer(inner) | Self::ByRef(inner) => Some(inner),
_ => None,
}
}
#[must_use]
pub const fn size_bytes(&self) -> Option<u32> {
match self {
Self::Bool | Self::I8 | Self::U8 => Some(1),
Self::I16 | Self::U16 | Self::Char => Some(2),
Self::I32 | Self::U32 | Self::F32 => Some(4),
Self::I64 | Self::U64 | Self::F64 => Some(8),
_ => None,
}
}
#[must_use]
pub fn stack_type(&self) -> SsaType {
match self {
Self::Bool | Self::I8 | Self::U8 | Self::I16 | Self::U16 | Self::Char | Self::I32 => {
Self::I32
}
Self::U32 => Self::I32, Self::I64 | Self::U64 => Self::I64,
Self::NativeInt | Self::NativeUInt => Self::NativeInt,
Self::F32 | Self::F64 => Self::F64, _ => self.clone(),
}
}
#[must_use]
pub fn storage_class(&self) -> TypeClass {
match self {
Self::I32
| Self::U32
| Self::Bool
| Self::Char
| Self::I16
| Self::U16
| Self::I8
| Self::U8 => TypeClass::Int32,
Self::I64 | Self::U64 => TypeClass::Int64,
Self::F32 => TypeClass::Float32,
Self::F64 => TypeClass::Float64,
Self::NativeInt | Self::NativeUInt => TypeClass::NativeInt,
Self::Object
| Self::String
| Self::Class(_)
| Self::ValueType(_)
| Self::Array(_, _)
| Self::ByRef(_)
| Self::Pointer(_)
| Self::TypedReference
| Self::RuntimeTypeHandle
| Self::RuntimeMethodHandle
| Self::RuntimeFieldHandle => TypeClass::Reference,
Self::Void
| Self::Unknown
| Self::Varying
| Self::Null
| Self::GenericParam(_)
| Self::MethodGenericParam(_)
| Self::FnPtr(_) => TypeClass::Other,
}
}
#[must_use]
pub fn is_compatible_for_storage(&self, other: &SsaType) -> bool {
if self == other {
return true;
}
if matches!(self, Self::Unknown) || matches!(other, Self::Unknown) {
return true;
}
matches!(
(self.storage_class(), other.storage_class()),
(TypeClass::Int32, TypeClass::Int32)
| (TypeClass::Int64, TypeClass::Int64)
| (TypeClass::Float32, TypeClass::Float32)
| (TypeClass::Float64, TypeClass::Float64)
| (TypeClass::Reference, TypeClass::Reference)
| (TypeClass::NativeInt, TypeClass::NativeInt)
)
}
#[must_use]
pub fn merge(&self, other: &SsaType) -> SsaType {
if self == other {
return self.clone();
}
if matches!(self, Self::Unknown) {
return other.clone();
}
if matches!(other, Self::Unknown) {
return self.clone();
}
if matches!(self, Self::Null) && other.is_reference() {
return other.clone();
}
if matches!(other, Self::Null) && self.is_reference() {
return self.clone();
}
Self::Varying
}
#[must_use]
pub fn to_type_signature(&self) -> TypeSignature {
match self {
Self::Void => TypeSignature::Void,
Self::Bool => TypeSignature::Boolean,
Self::I8 => TypeSignature::I1,
Self::U8 => TypeSignature::U1,
Self::I16 => TypeSignature::I2,
Self::U16 => TypeSignature::U2,
Self::I32 => TypeSignature::I4,
Self::U32 => TypeSignature::U4,
Self::I64 => TypeSignature::I8,
Self::U64 => TypeSignature::U8,
Self::NativeInt => TypeSignature::I,
Self::NativeUInt => TypeSignature::U,
Self::F32 => TypeSignature::R4,
Self::F64 => TypeSignature::R8,
Self::Char => TypeSignature::Char,
Self::String => TypeSignature::String,
Self::Class(type_ref) => TypeSignature::Class(type_ref.token()),
Self::ValueType(type_ref) => TypeSignature::ValueType(type_ref.token()),
Self::Array(elem, 1) => TypeSignature::SzArray(SignatureSzArray {
modifiers: CustomModifiers::default(),
base: Box::new(elem.to_type_signature()),
}),
Self::Array(elem, rank) => TypeSignature::Array(SignatureArray {
base: Box::new(elem.to_type_signature()),
rank: *rank,
dimensions: (0..*rank)
.map(|_| ArrayDimensions {
size: None,
lower_bound: None,
})
.collect(),
}),
Self::Pointer(inner) => TypeSignature::Ptr(SignaturePointer {
modifiers: CustomModifiers::default(),
base: Box::new(inner.to_type_signature()),
}),
Self::ByRef(inner) => TypeSignature::ByRef(Box::new(inner.to_type_signature())),
Self::TypedReference => TypeSignature::TypedByRef,
Self::GenericParam(idx) => TypeSignature::GenericParamType(*idx),
Self::MethodGenericParam(idx) => TypeSignature::GenericParamMethod(*idx),
Self::FnPtr(sig) => TypeSignature::FnPtr(Box::new(SignatureMethod {
has_this: false,
explicit_this: false,
default: true,
vararg: false,
cdecl: false,
stdcall: false,
thiscall: false,
fastcall: false,
param_count_generic: 0,
#[allow(clippy::cast_possible_truncation)]
param_count: sig.params.len() as u32,
return_type: SignatureParameter {
modifiers: CustomModifiers::default(),
by_ref: false,
base: sig.ret.to_type_signature(),
},
params: sig
.params
.iter()
.map(|p| SignatureParameter {
modifiers: CustomModifiers::default(),
by_ref: false,
base: p.to_type_signature(),
})
.collect(),
varargs: Vec::new(),
})),
Self::Object
| Self::Null
| Self::Unknown
| Self::Varying
| Self::RuntimeTypeHandle
| Self::RuntimeMethodHandle
| Self::RuntimeFieldHandle => TypeSignature::Object,
}
}
#[must_use]
pub fn from_cil_flavor(flavor: &CilFlavor, token: Token) -> Self {
match flavor {
CilFlavor::Void => Self::Void,
CilFlavor::Boolean => Self::Bool,
CilFlavor::Char => Self::Char,
CilFlavor::I1 => Self::I8,
CilFlavor::U1 => Self::U8,
CilFlavor::I2 => Self::I16,
CilFlavor::U2 => Self::U16,
CilFlavor::I4 => Self::I32,
CilFlavor::U4 => Self::U32,
CilFlavor::I8 => Self::I64,
CilFlavor::U8 => Self::U64,
CilFlavor::R4 => Self::F32,
CilFlavor::R8 => Self::F64,
CilFlavor::I => Self::NativeInt,
CilFlavor::U => Self::NativeUInt,
CilFlavor::Object => Self::Object,
CilFlavor::String => Self::String,
CilFlavor::Array { rank, .. } => Self::Array(Box::new(Self::Unknown), *rank),
CilFlavor::Pointer => Self::Pointer(Box::new(Self::Unknown)),
CilFlavor::ByRef => Self::ByRef(Box::new(Self::Unknown)),
CilFlavor::GenericParameter { index, method } => {
if *method {
Self::MethodGenericParam(*index)
} else {
Self::GenericParam(*index)
}
}
CilFlavor::GenericInstance | CilFlavor::Class | CilFlavor::Interface => {
Self::Class(TypeRef::new(token))
}
CilFlavor::ValueType | CilFlavor::TypedRef { .. } => {
Self::ValueType(TypeRef::new(token))
}
CilFlavor::Pinned | CilFlavor::FnPtr { .. } | CilFlavor::Unknown => Self::Unknown,
}
}
#[must_use]
pub fn from_type_signature(signature: &TypeSignature, assembly: &CilObject) -> Self {
match signature {
TypeSignature::Void => Self::Void,
TypeSignature::Boolean => Self::Bool,
TypeSignature::Char => Self::Char,
TypeSignature::I1 => Self::I8,
TypeSignature::U1 => Self::U8,
TypeSignature::I2 => Self::I16,
TypeSignature::U2 => Self::U16,
TypeSignature::I4 => Self::I32,
TypeSignature::U4 => Self::U32,
TypeSignature::I8 => Self::I64,
TypeSignature::U8 => Self::U64,
TypeSignature::R4 => Self::F32,
TypeSignature::R8 => Self::F64,
TypeSignature::I => Self::NativeInt,
TypeSignature::U => Self::NativeUInt,
TypeSignature::String => Self::String,
TypeSignature::Object | TypeSignature::Type | TypeSignature::Boxed => Self::Object,
TypeSignature::TypedByRef => Self::TypedReference,
TypeSignature::Class(token) | TypeSignature::ValueType(token) => {
Self::from_type_token(*token, assembly)
}
TypeSignature::SzArray(sz_array) => {
let elem_type = Self::from_type_signature(&sz_array.base, assembly);
Self::Array(Box::new(elem_type), 1)
}
TypeSignature::Array(array) => {
let elem_type = Self::from_type_signature(&array.base, assembly);
Self::Array(Box::new(elem_type), array.rank)
}
TypeSignature::Ptr(ptr) => {
let inner = Self::from_type_signature(&ptr.base, assembly);
Self::Pointer(Box::new(inner))
}
TypeSignature::ByRef(inner) => {
let inner_type = Self::from_type_signature(inner, assembly);
Self::ByRef(Box::new(inner_type))
}
TypeSignature::GenericParamType(index) => Self::GenericParam(*index),
TypeSignature::GenericParamMethod(index) => Self::MethodGenericParam(*index),
TypeSignature::GenericInst(base_type, _type_args) => {
Self::from_type_signature(base_type, assembly)
}
TypeSignature::Pinned(inner) => Self::from_type_signature(inner, assembly),
TypeSignature::FnPtr(_)
| TypeSignature::ModifiedRequired(_)
| TypeSignature::ModifiedOptional(_)
| TypeSignature::Sentinel
| TypeSignature::Unknown
| TypeSignature::Internal
| TypeSignature::Modifier
| TypeSignature::Reserved
| TypeSignature::Field => Self::Unknown,
}
}
#[must_use]
pub fn from_type_token(token: Token, assembly: &CilObject) -> Self {
let table_id = token.table();
match table_id {
0x02 | 0x01 => {
let Some(cil_type) = assembly.types().get(&token) else {
return Self::Unknown;
};
let name = &cil_type.name;
let namespace = &cil_type.namespace;
if namespace == "System" {
if let Some(primitive) = Self::match_system_type(name) {
return primitive;
}
}
if cil_type.flavor().is_value_type() {
Self::ValueType(TypeRef::new(token))
} else {
Self::Class(TypeRef::new(token))
}
}
0x1B => {
let Some(typespec) = assembly.types().get(&token) else {
return Self::Unknown;
};
Self::from_cil_flavor(typespec.flavor(), token)
}
_ => Self::Unknown,
}
}
fn match_system_type(name: &str) -> Option<Self> {
match name {
"Void" => Some(Self::Void),
"Boolean" => Some(Self::Bool),
"Char" => Some(Self::Char),
"SByte" => Some(Self::I8),
"Byte" => Some(Self::U8),
"Int16" => Some(Self::I16),
"UInt16" => Some(Self::U16),
"Int32" => Some(Self::I32),
"UInt32" => Some(Self::U32),
"Int64" => Some(Self::I64),
"UInt64" => Some(Self::U64),
"Single" => Some(Self::F32),
"Double" => Some(Self::F64),
"IntPtr" => Some(Self::NativeInt),
"UIntPtr" => Some(Self::NativeUInt),
"String" => Some(Self::String),
"Object" => Some(Self::Object),
"TypedReference" => Some(Self::TypedReference),
_ => None,
}
}
}
impl fmt::Display for SsaType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Void => write!(f, "void"),
Self::Bool => write!(f, "bool"),
Self::I8 => write!(f, "i8"),
Self::U8 => write!(f, "u8"),
Self::I16 => write!(f, "i16"),
Self::U16 => write!(f, "u16"),
Self::I32 => write!(f, "i32"),
Self::U32 => write!(f, "u32"),
Self::I64 => write!(f, "i64"),
Self::U64 => write!(f, "u64"),
Self::NativeInt => write!(f, "nint"),
Self::NativeUInt => write!(f, "nuint"),
Self::F32 => write!(f, "f32"),
Self::F64 => write!(f, "f64"),
Self::Char => write!(f, "char"),
Self::Object => write!(f, "object"),
Self::String => write!(f, "string"),
Self::Class(t) => write!(f, "class {t}"),
Self::ValueType(t) => write!(f, "valuetype {t}"),
Self::Array(elem, 1) => write!(f, "{elem}[]"),
Self::Array(elem, rank) => write!(f, "{elem}[{rank}d]"),
Self::Pointer(inner) => write!(f, "{inner}*"),
Self::ByRef(inner) => write!(f, "{inner}&"),
Self::TypedReference => write!(f, "typedref"),
Self::GenericParam(idx) => write!(f, "!{idx}"),
Self::MethodGenericParam(idx) => write!(f, "!!{idx}"),
Self::FnPtr(_) => write!(f, "fnptr"),
Self::RuntimeTypeHandle => write!(f, "RuntimeTypeHandle"),
Self::RuntimeMethodHandle => write!(f, "RuntimeMethodHandle"),
Self::RuntimeFieldHandle => write!(f, "RuntimeFieldHandle"),
Self::Null => write!(f, "null"),
Self::Unknown => write!(f, "?"),
Self::Varying => write!(f, "varying"),
}
}
}
pub struct TypeContext<'a> {
method: &'a Method,
assembly: &'a CilObject,
}
impl<'a> TypeContext<'a> {
#[must_use]
pub fn new(method: &'a Method, assembly: &'a CilObject) -> Self {
Self { method, assembly }
}
#[must_use]
pub fn assembly(&self) -> &'a CilObject {
self.assembly
}
#[must_use]
pub fn arg_type(&self, idx: u16) -> SsaType {
let idx = idx as usize;
if self.method.signature.has_this {
if idx == 0 {
return self
.method
.declaring_type_rc()
.map_or(SsaType::Object, |dt| SsaType::Class(TypeRef::new(dt.token)));
}
if let Some(param) = self.method.signature.params.get(idx - 1) {
return SsaType::from_type_signature(¶m.base, self.assembly);
}
} else if let Some(param) = self.method.signature.params.get(idx) {
return SsaType::from_type_signature(¶m.base, self.assembly);
}
SsaType::Unknown
}
#[must_use]
pub fn local_type(&self, idx: u16) -> SsaType {
self.method
.get_local_type_signatures()
.and_then(|types| types.into_iter().nth(idx as usize))
.map_or(SsaType::Unknown, |sig| {
SsaType::from_type_signature(&sig.base, self.assembly)
})
}
#[must_use]
pub fn call_return_type(&self, token: Token) -> SsaType {
let table_id = token.table();
match table_id {
0x06 => self
.assembly
.methods()
.get(&token)
.map_or(SsaType::Unknown, |entry| {
let method = entry.value();
SsaType::from_type_signature(&method.signature.return_type.base, self.assembly)
}),
0x0A => self
.assembly
.refs_members()
.get(&token)
.and_then(|entry| {
let member_ref = entry.value();
match &member_ref.signature {
MemberRefSignature::Method(sig) => Some(SsaType::from_type_signature(
&sig.return_type.base,
self.assembly,
)),
MemberRefSignature::Field(_) => None,
}
})
.unwrap_or(SsaType::Unknown),
0x2B => self
.assembly
.method_specs()
.get(&token)
.and_then(|entry| entry.value().method.token())
.map_or(SsaType::Unknown, |method_token| {
self.call_return_type(method_token)
}),
_ => SsaType::Unknown,
}
}
#[must_use]
pub fn newobj_type(&self, ctor_token: Token) -> SsaType {
let table_id = ctor_token.table();
match table_id {
0x06 => self
.assembly
.methods()
.get(&ctor_token)
.and_then(|entry| entry.value().declaring_type_rc().map(|dt| dt.token))
.map_or(SsaType::Object, |type_token| {
SsaType::Class(TypeRef::new(type_token))
}),
0x0A => self
.assembly
.refs_members()
.get(&ctor_token)
.and_then(|entry| entry.value().declaredby.token())
.map_or(SsaType::Object, |class_token| {
SsaType::from_type_token(class_token, self.assembly)
}),
0x2B => self
.assembly
.method_specs()
.get(&ctor_token)
.and_then(|entry| entry.value().method.token())
.map_or(SsaType::Object, |method_token| {
self.newobj_type(method_token)
}),
_ => SsaType::Object,
}
}
#[must_use]
pub fn field_type(&self, field_token: Token) -> SsaType {
let table_id = field_token.table();
match table_id {
0x04 => self
.assembly
.types()
.get_field_signature(&field_token)
.map_or(SsaType::Unknown, |sig| {
SsaType::from_type_signature(&sig, self.assembly)
}),
0x0A => self
.assembly
.refs_members()
.get(&field_token)
.and_then(|entry| {
if let MemberRefSignature::Field(field_sig) = &entry.value().signature {
Some(SsaType::from_type_signature(&field_sig.base, self.assembly))
} else {
None
}
})
.unwrap_or(SsaType::Unknown),
_ => SsaType::Unknown,
}
}
#[must_use]
pub fn call_indirect_return_type(&self, sig_token: Token) -> SsaType {
if sig_token.table() != 0x11 {
return SsaType::Unknown;
}
let Some(tables) = self.assembly.tables() else {
return SsaType::Unknown;
};
let Some(table) = tables.table::<StandAloneSigRaw>() else {
return SsaType::Unknown;
};
let Some(raw) = table.get(sig_token.row()) else {
return SsaType::Unknown;
};
let Some(blob) = self.assembly.blob() else {
return SsaType::Unknown;
};
let Ok(owned) = raw.to_owned(blob) else {
return SsaType::Unknown;
};
match &owned.parsed_signature {
StandAloneSignature::Method(sig) => {
SsaType::from_type_signature(&sig.return_type.base, self.assembly)
}
_ => SsaType::Unknown,
}
}
#[must_use]
pub fn local_type_signatures(&self) -> Option<Vec<SignatureLocalVariable>> {
self.method.get_local_type_signatures()
}
}
#[cfg(test)]
mod tests {
use crate::analysis::ssa::types::{SsaType, TypeClass};
#[test]
fn test_primitive_types() {
assert!(SsaType::I32.is_primitive());
assert!(SsaType::F64.is_primitive());
assert!(SsaType::Bool.is_primitive());
assert!(!SsaType::Object.is_primitive());
assert!(!SsaType::String.is_primitive());
}
#[test]
fn test_integer_types() {
assert!(SsaType::I32.is_integer());
assert!(SsaType::U64.is_integer());
assert!(SsaType::NativeInt.is_integer());
assert!(!SsaType::F32.is_integer());
assert!(!SsaType::Bool.is_integer());
}
#[test]
fn test_float_types() {
assert!(SsaType::F32.is_float());
assert!(SsaType::F64.is_float());
assert!(!SsaType::I32.is_float());
}
#[test]
fn test_reference_types() {
assert!(SsaType::Object.is_reference());
assert!(SsaType::String.is_reference());
assert!(SsaType::Null.is_reference());
let array_type = SsaType::Array(Box::new(SsaType::I32), 1);
assert!(array_type.is_reference());
assert!(!SsaType::I32.is_reference());
}
#[test]
fn test_array_type() {
let array = SsaType::Array(Box::new(SsaType::I32), 1);
assert!(array.is_array());
assert_eq!(array.array_element_type(), Some(&SsaType::I32));
assert_eq!(array.array_rank(), Some(1));
let multi_dim = SsaType::Array(Box::new(SsaType::F64), 3);
assert_eq!(multi_dim.array_rank(), Some(3));
}
#[test]
fn test_pointer_types() {
let ptr = SsaType::Pointer(Box::new(SsaType::I32));
assert!(ptr.is_pointer());
assert_eq!(ptr.pointee_type(), Some(&SsaType::I32));
let byref = SsaType::ByRef(Box::new(SsaType::Object));
assert!(byref.is_pointer());
assert_eq!(byref.pointee_type(), Some(&SsaType::Object));
}
#[test]
fn test_size_bytes() {
assert_eq!(SsaType::I8.size_bytes(), Some(1));
assert_eq!(SsaType::I16.size_bytes(), Some(2));
assert_eq!(SsaType::I32.size_bytes(), Some(4));
assert_eq!(SsaType::I64.size_bytes(), Some(8));
assert_eq!(SsaType::F32.size_bytes(), Some(4));
assert_eq!(SsaType::F64.size_bytes(), Some(8));
assert_eq!(SsaType::NativeInt.size_bytes(), None); assert_eq!(SsaType::Object.size_bytes(), None);
}
#[test]
fn test_stack_type() {
assert_eq!(SsaType::I8.stack_type(), SsaType::I32);
assert_eq!(SsaType::I16.stack_type(), SsaType::I32);
assert_eq!(SsaType::I32.stack_type(), SsaType::I32);
assert_eq!(SsaType::I64.stack_type(), SsaType::I64);
assert_eq!(SsaType::F32.stack_type(), SsaType::F64);
}
#[test]
fn test_type_merge() {
assert_eq!(SsaType::I32.merge(&SsaType::I32), SsaType::I32);
assert_eq!(SsaType::Unknown.merge(&SsaType::I32), SsaType::I32);
assert_eq!(SsaType::I32.merge(&SsaType::Unknown), SsaType::I32);
assert_eq!(SsaType::Null.merge(&SsaType::Object), SsaType::Object);
assert_eq!(SsaType::Object.merge(&SsaType::Null), SsaType::Object);
assert_eq!(SsaType::I32.merge(&SsaType::I64), SsaType::Varying);
assert_eq!(SsaType::Object.merge(&SsaType::I32), SsaType::Varying);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", SsaType::I32), "i32");
assert_eq!(format!("{}", SsaType::Object), "object");
assert_eq!(
format!("{}", SsaType::Array(Box::new(SsaType::I32), 1)),
"i32[]"
);
assert_eq!(
format!("{}", SsaType::Pointer(Box::new(SsaType::I32))),
"i32*"
);
assert_eq!(
format!("{}", SsaType::ByRef(Box::new(SsaType::I32))),
"i32&"
);
assert_eq!(format!("{}", SsaType::GenericParam(0)), "!0");
assert_eq!(format!("{}", SsaType::MethodGenericParam(1)), "!!1");
}
#[test]
fn test_storage_class() {
assert_eq!(SsaType::I32.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::U32.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::Bool.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::Char.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::I8.storage_class(), TypeClass::Int32);
assert_eq!(SsaType::I64.storage_class(), TypeClass::Int64);
assert_eq!(SsaType::U64.storage_class(), TypeClass::Int64);
assert_eq!(SsaType::F32.storage_class(), TypeClass::Float32);
assert_eq!(SsaType::F64.storage_class(), TypeClass::Float64);
assert_eq!(SsaType::NativeInt.storage_class(), TypeClass::NativeInt);
assert_eq!(SsaType::NativeUInt.storage_class(), TypeClass::NativeInt);
assert_eq!(SsaType::Object.storage_class(), TypeClass::Reference);
assert_eq!(SsaType::String.storage_class(), TypeClass::Reference);
assert_eq!(
SsaType::Array(Box::new(SsaType::I32), 1).storage_class(),
TypeClass::Reference
);
assert_eq!(SsaType::Void.storage_class(), TypeClass::Other);
assert_eq!(SsaType::Unknown.storage_class(), TypeClass::Other);
}
#[test]
fn test_is_compatible_for_storage() {
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::I32));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::U32));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::Bool));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::Char));
assert!(SsaType::I64.is_compatible_for_storage(&SsaType::U64));
assert!(SsaType::Object.is_compatible_for_storage(&SsaType::String));
assert!(SsaType::Unknown.is_compatible_for_storage(&SsaType::I32));
assert!(SsaType::I32.is_compatible_for_storage(&SsaType::Unknown));
assert!(!SsaType::I32.is_compatible_for_storage(&SsaType::I64));
assert!(!SsaType::I32.is_compatible_for_storage(&SsaType::Object));
assert!(!SsaType::F32.is_compatible_for_storage(&SsaType::F64));
}
}