use std::sync::Arc;
use crate::{
metadata::{
signatures::{SignatureMethod, SignatureMethodSpec},
tables::MethodSpec,
token::Token,
typesystem::{
CilFlavor, CilModifier, CilPrimitiveKind, CilTypeRc, CilTypeReference,
CompleteTypeSpec, TypeRegistry, TypeSource,
},
},
Error::TypeError,
Result,
};
pub struct TypeBuilder {
registry: Arc<TypeRegistry>,
source: TypeSource,
current_type: Option<CilTypeRc>,
token_init: Option<Token>,
}
impl TypeBuilder {
pub fn new(registry: Arc<TypeRegistry>) -> Self {
let source = registry.current_assembly_source();
TypeBuilder {
registry,
source,
current_type: None,
token_init: None,
}
}
#[must_use]
pub fn with_source(mut self, source: TypeSource) -> Self {
self.source = source;
self
}
#[must_use]
pub fn with_token_init(mut self, token: Token) -> Self {
self.token_init = Some(token);
self
}
pub fn primitive(mut self, primitive: CilPrimitiveKind) -> Result<Self> {
self.current_type = Some(self.registry.get_primitive(primitive)?);
Ok(self)
}
pub fn class(mut self, namespace: &str, name: &str) -> Result<Self> {
self.current_type = Some(self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::Class,
namespace: namespace.to_string(),
name: name.to_string(),
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?);
Ok(self)
}
pub fn value_type(mut self, namespace: &str, name: &str) -> Result<Self> {
self.current_type = Some(self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::ValueType,
namespace: namespace.to_string(),
name: name.to_string(),
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?);
Ok(self)
}
pub fn interface(mut self, namespace: &str, name: &str) -> Result<Self> {
self.current_type = Some(self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::Interface,
namespace: namespace.to_string(),
name: name.to_string(),
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?);
Ok(self)
}
pub fn pointer(mut self) -> Result<Self> {
if let Some(base_type) = self.current_type.take() {
let ptr_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::Pointer,
namespace: base_type.namespace.clone(),
name: format!("{}*", base_type.name),
source: self.source.clone(),
generic_args: None,
base_type: Some(base_type),
flags: None,
})?;
self.current_type = Some(ptr_type);
}
Ok(self)
}
pub fn by_ref(mut self) -> Result<Self> {
if let Some(base_type) = self.current_type.take() {
let name = format!("{}&", base_type.name);
let namespace = base_type.namespace.clone();
let ref_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::ByRef,
namespace: namespace.clone(),
name,
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?;
ref_type
.base
.set(base_type.into())
.map_err(|_| malformed_error!("ByRef type base already set"))?;
self.current_type = Some(ref_type);
}
Ok(self)
}
pub fn pinned(mut self) -> Result<Self> {
if let Some(base_type) = self.current_type.take() {
let name = format!("pinned {}", base_type.name);
let namespace = base_type.namespace.clone();
let pinned_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::Pinned,
namespace: namespace.clone(),
name,
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?;
pinned_type
.base
.set(base_type.into())
.map_err(|_| malformed_error!("Pinned type base already set"))?;
self.current_type = Some(pinned_type);
}
Ok(self)
}
pub fn array(mut self) -> Result<Self> {
if let Some(base_type) = self.current_type.take() {
let name = format!("{}[]", base_type.name);
let namespace = base_type.namespace.clone();
let array_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::Array {
element_type: Box::new(base_type.flavor().clone()),
rank: 1,
dimensions: vec![],
},
namespace: namespace.clone(),
name,
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?;
array_type
.base
.set(base_type.into())
.map_err(|_| malformed_error!("Array type base already set"))?;
self.current_type = Some(array_type);
}
Ok(self)
}
pub fn multi_dimensional_array(mut self, rank: u32) -> Result<Self> {
if let Some(base_type) = self.current_type.take() {
let dimension_part = if rank <= 1 {
"[]".to_string()
} else {
format!("[{}]", ",".repeat(rank as usize - 1))
};
let name = format!("{}{}", base_type.name, dimension_part);
let namespace = base_type.namespace.clone();
let array_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::Array {
element_type: Box::new(base_type.flavor().clone()),
rank,
dimensions: vec![],
},
namespace: namespace.clone(),
name,
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?;
array_type
.base
.set(base_type.into())
.map_err(|_| malformed_error!("Multi-dimensional array type base already set"))?;
self.current_type = Some(array_type);
}
Ok(self)
}
pub fn function_pointer(mut self, signature: SignatureMethod) -> Result<Self> {
let name = Self::format_fnptr_name(&signature);
let fn_ptr_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::FnPtr { signature },
namespace: String::new(),
name,
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?;
self.current_type = Some(fn_ptr_type);
Ok(self)
}
pub fn required_modifier(self, modifier_token: Token) -> Result<Self> {
if let Some(current) = &self.current_type {
if let Some(modifier_type) = self.registry.get(&modifier_token) {
current.modifiers.push(CilModifier {
required: true,
modifier: modifier_type.into(),
});
}
}
Ok(self)
}
pub fn optional_modifier(self, modifier_token: Token) -> Result<Self> {
if let Some(current) = &self.current_type {
if let Some(modifier_type) = self.registry.get(&modifier_token) {
current.modifiers.push(CilModifier {
required: false,
modifier: modifier_type.into(),
});
}
}
Ok(self)
}
pub fn extends(self, base_token: Token) -> Result<Self> {
if let Some(current) = &self.current_type {
if let Some(base_type) = self.registry.get(&base_token) {
current
.base
.set(base_type.into())
.map_err(|_| malformed_error!("Base type already set"))?;
}
}
Ok(self)
}
pub fn generic_instance<F>(mut self, arg_count: usize, arg_builder: F) -> Result<Self>
where
F: FnOnce(Arc<TypeRegistry>) -> Result<Vec<CilTypeRc>>,
{
if let Some(base_type) = self.current_type.take() {
let name = Self::format_generic_name(&base_type.name, arg_count);
let namespace = base_type.namespace.clone();
let generic_type = self.registry.get_or_create_type(&CompleteTypeSpec {
token_init: self.token_init.take(),
flavor: CilFlavor::GenericInstance,
namespace: namespace.clone(),
name,
source: self.source.clone(),
generic_args: None,
base_type: None,
flags: None,
})?;
let args = arg_builder(self.registry.clone())?;
if !args.is_empty() {
for (index, arg) in args.iter().enumerate() {
let rid = u32::try_from(index)
.map_err(|_| malformed_error!("Generic argument index too large"))?
+ 1;
let token_value =
0x2B00_0000_u32
.checked_add(u32::try_from(index).map_err(|_| {
malformed_error!("Generic argument index too large")
})?)
.and_then(|v| v.checked_add(1))
.ok_or_else(|| malformed_error!("Token value overflow"))?;
let method_spec = Arc::new(MethodSpec {
rid,
token: Token::new(token_value),
offset: 0,
method: CilTypeReference::None,
instantiation: SignatureMethodSpec {
generic_args: vec![],
},
custom_attributes: Arc::new(boxcar::Vec::new()),
generic_args: {
let type_ref_list = Arc::new(boxcar::Vec::with_capacity(1));
type_ref_list.push(arg.clone().into());
type_ref_list
},
});
generic_type.generic_args.push(method_spec);
}
}
generic_type
.base
.set(base_type.into())
.map_err(|_| malformed_error!("Generic type base already set"))?;
self.current_type = Some(generic_type);
}
Ok(self)
}
fn format_generic_name(base_name: &str, arg_count: usize) -> String {
if base_name.contains('`') {
base_name.to_string()
} else {
format!("{base_name}`{arg_count}")
}
}
fn format_fnptr_name(signature: &SignatureMethod) -> String {
let calling_convention = if signature.stdcall {
"stdcall"
} else if signature.cdecl {
"cdecl"
} else if signature.thiscall {
"thiscall"
} else if signature.fastcall {
"fastcall"
} else {
"default"
};
let param_count = signature.params.len();
let return_info = format!("{:?}", signature.return_type.base).replace(' ', "");
let return_short = if return_info.len() > 16 {
&return_info[..16]
} else {
&return_info
};
format!("FnPtr_{calling_convention}_{param_count}_{return_short}")
}
pub fn build(self) -> Result<CilTypeRc> {
match self.current_type {
Some(t) => Ok(t),
None => Err(TypeError("Failed to build requested Type".to_string())),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, OnceLock};
use super::*;
use crate::{
metadata::{
identity::AssemblyIdentity,
signatures::{SignatureMethod, SignatureParameter, TypeSignature},
tables::GenericParam,
token::Token,
typesystem::{CilFlavor, CilPrimitiveKind, TypeRegistry, TypeSource},
},
Error,
};
#[test]
fn test_build_primitive() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let int_type = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::I4)
.unwrap()
.build()
.unwrap();
assert_eq!(int_type.name, "Int32");
assert_eq!(int_type.namespace, "System");
assert!(matches!(*int_type.flavor(), CilFlavor::I4));
}
#[test]
fn test_build_pointer() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let int_ptr = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::I4)
.unwrap()
.pointer()
.unwrap()
.build()
.unwrap();
assert_eq!(int_ptr.name, "Int32*");
assert!(matches!(*int_ptr.flavor(), CilFlavor::Pointer));
let base_type = int_ptr.base.get().unwrap().upgrade().unwrap();
assert_eq!(base_type.name, "Int32");
}
#[test]
fn test_build_array() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let string_array = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::String)
.unwrap()
.array()
.unwrap()
.build()
.unwrap();
assert_eq!(string_array.name, "String[]");
assert!(matches!(*string_array.flavor(), CilFlavor::Array { .. }));
let base_type = string_array.base.get().unwrap().upgrade().unwrap();
assert_eq!(base_type.name, "String");
}
#[test]
fn test_build_multidimensional_array() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let int_2d_array = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::I4)
.unwrap()
.multi_dimensional_array(2)
.unwrap()
.build()
.unwrap();
assert_eq!(int_2d_array.name, "Int32[,]");
if let CilFlavor::Array { rank, .. } = *int_2d_array.flavor() {
assert_eq!(rank, 2);
} else {
panic!("Expected Array flavor");
};
}
#[test]
fn test_build_class() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let list_type = TypeBuilder::new(registry.clone())
.class("System.Collections.Generic", "List`1")
.unwrap()
.build()
.unwrap();
assert_eq!(list_type.name, "List`1");
assert_eq!(list_type.namespace, "System.Collections.Generic");
assert!(matches!(*list_type.flavor(), CilFlavor::Class));
}
#[test]
fn test_build_value_type() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let struct_type = TypeBuilder::new(registry.clone())
.value_type("System", "DateTime")
.unwrap()
.build()
.unwrap();
assert_eq!(struct_type.name, "DateTime");
assert_eq!(struct_type.namespace, "System");
assert!(matches!(*struct_type.flavor(), CilFlavor::ValueType));
}
#[test]
fn test_build_interface() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let interface_type = TypeBuilder::new(registry.clone())
.interface("System.Collections.Generic", "IList`1")
.unwrap()
.build()
.unwrap();
assert_eq!(interface_type.name, "IList`1");
assert_eq!(interface_type.namespace, "System.Collections.Generic");
assert!(matches!(*interface_type.flavor(), CilFlavor::Interface));
}
#[test]
fn test_build_generic_instance() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let list_type = TypeBuilder::new(registry.clone())
.class("System.Collections.Generic", "List`1")
.unwrap()
.build()
.unwrap();
let generic_param = Arc::new(GenericParam {
token: Token::new(0x2A000001),
number: 0,
flags: 0,
owner: OnceLock::new(),
name: "T".to_string(),
constraints: Arc::new(boxcar::Vec::new()),
rid: 0,
offset: 0,
custom_attributes: Arc::new(boxcar::Vec::new()),
});
list_type.generic_params.push(generic_param);
let list_int_instance = TypeBuilder::new(registry.clone())
.with_source(TypeSource::Unknown)
.class("System.Collections.Generic", "List`1")
.unwrap()
.generic_instance(1, |registry| {
let int_type = registry.get_primitive(CilPrimitiveKind::I4).unwrap();
Ok(vec![int_type])
})
.unwrap()
.build()
.unwrap();
assert_eq!(list_int_instance.name, "List`1");
assert!(matches!(
*list_int_instance.flavor(),
CilFlavor::GenericInstance
));
assert_eq!(list_int_instance.generic_args.count(), 1);
assert_eq!(
list_int_instance.generic_args[0].generic_args[0]
.name()
.unwrap(),
"Int32"
);
}
#[test]
fn test_build_byref() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let byref_type = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::I4)
.unwrap()
.by_ref()
.unwrap()
.build()
.unwrap();
assert_eq!(byref_type.name, "Int32&");
assert!(matches!(*byref_type.flavor(), CilFlavor::ByRef));
let base_type = byref_type.base.get().unwrap().upgrade().unwrap();
assert_eq!(base_type.name, "Int32");
}
#[test]
fn test_build_pinned() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let pinned_type = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::Object)
.unwrap()
.pinned()
.unwrap()
.build()
.unwrap();
assert_eq!(pinned_type.name, "pinned Object");
assert!(matches!(*pinned_type.flavor(), CilFlavor::Pinned));
let base_type = pinned_type.base.get().unwrap().upgrade().unwrap();
assert_eq!(base_type.name, "Object");
}
#[test]
fn test_build_function_pointer() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let signature = SignatureMethod {
has_this: false,
explicit_this: false,
return_type: SignatureParameter {
modifiers: Vec::new(),
base: TypeSignature::Void,
by_ref: false,
},
params: Vec::new(),
default: false,
vararg: false,
cdecl: false,
stdcall: true,
thiscall: false,
fastcall: false,
param_count_generic: 0,
param_count: 0,
varargs: Vec::new(),
};
let fn_ptr = TypeBuilder::new(registry.clone())
.function_pointer(signature)
.unwrap()
.build()
.unwrap();
assert!(fn_ptr.name.starts_with("FnPtr_"));
if let CilFlavor::FnPtr { signature: sig } = fn_ptr.flavor() {
assert!(!sig.has_this);
assert_eq!(sig.params.len(), 0);
} else {
panic!("Expected FnPtr flavor");
};
}
#[test]
fn test_with_source() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let source = TypeSource::AssemblyRef(Token::new(0x23000001));
let int_type = TypeBuilder::new(registry.clone())
.with_source(source)
.primitive(CilPrimitiveKind::I4)
.unwrap()
.build()
.unwrap();
assert_eq!(int_type.name, "Int32");
}
#[test]
fn test_with_token_init() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let token: Token = Token::new(0x01000999);
let list_type = TypeBuilder::new(registry.clone())
.with_token_init(token)
.class("System.Collections.Generic", "List`1")
.unwrap()
.build()
.unwrap();
assert_eq!(list_type.name, "List`1");
assert_eq!(list_type.namespace, "System.Collections.Generic");
assert_eq!(list_type.token, token);
assert!(matches!(*list_type.flavor(), CilFlavor::Class));
}
#[test]
fn test_modifiers() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let in_attr_token = Token::new(0x01000888);
let _ = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: Some(in_attr_token),
flavor: CilFlavor::Class,
namespace: "System.Runtime.InteropServices".to_string(),
name: "InAttribute".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let int_type = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::I4)
.unwrap()
.required_modifier(in_attr_token)
.unwrap()
.build()
.unwrap();
assert_eq!(int_type.modifiers.count(), 1);
assert!(int_type.modifiers[0].required);
assert_eq!(
int_type.modifiers[0].modifier.name().unwrap(),
"InAttribute"
);
let string_type = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::String)
.unwrap()
.optional_modifier(in_attr_token)
.unwrap()
.build()
.unwrap();
assert_eq!(string_type.modifiers.count(), 1);
assert!(!string_type.modifiers[0].required);
}
#[test]
fn test_extends() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let base_token = Token::new(0x01000777);
let _ = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: Some(base_token),
flavor: CilFlavor::Class,
namespace: "System".to_string(),
name: "Exception".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let derived_type = TypeBuilder::new(registry.clone())
.class("System.IO", "IOException")
.unwrap()
.extends(base_token)
.unwrap()
.build()
.unwrap();
let base_type = derived_type.base.get().unwrap().upgrade();
assert!(base_type.is_some());
let base_type = base_type.unwrap();
assert_eq!(base_type.token, base_token);
assert_eq!(base_type.name, "Exception");
}
#[test]
fn test_build_failure() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let result = TypeBuilder::new(registry.clone()).build();
assert!(result.is_err());
match result {
Err(Error::TypeError(_)) => (), _ => panic!("Expected TypeError"),
}
}
#[test]
fn test_build_complex_chain() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let complex_type = TypeBuilder::new(registry.clone())
.primitive(CilPrimitiveKind::String)
.unwrap()
.array()
.unwrap() .array()
.unwrap() .pointer()
.unwrap() .by_ref()
.unwrap() .build()
.unwrap();
assert_eq!(complex_type.name, "String[][]*&");
assert!(matches!(*complex_type.flavor(), CilFlavor::ByRef));
let pointer_type = complex_type.base.get().unwrap().upgrade().unwrap();
assert_eq!(pointer_type.name, "String[][]*");
assert!(matches!(*pointer_type.flavor(), CilFlavor::Pointer));
let array2d_type = pointer_type.base.get().unwrap().upgrade().unwrap();
assert_eq!(array2d_type.name, "String[][]");
let array_type = array2d_type.base.get().unwrap().upgrade().unwrap();
assert_eq!(array_type.name, "String[]");
let string_type = array_type.base.get().unwrap().upgrade().unwrap();
assert_eq!(string_type.name, "String");
}
#[test]
fn test_generic_instance_with_multiple_args() {
let test_identity = AssemblyIdentity::parse("TestAssembly, Version=1.0.0.0").unwrap();
let registry = Arc::new(TypeRegistry::new(test_identity).unwrap());
let dict_token = Token::new(0x01000555);
let dict_type = registry
.get_or_create_type(&CompleteTypeSpec {
token_init: Some(dict_token),
flavor: CilFlavor::Class,
namespace: "System.Collections.Generic".to_string(),
name: "Dictionary`2".to_string(),
source: TypeSource::Unknown,
generic_args: None,
base_type: None,
flags: None,
})
.unwrap();
let key_param = Arc::new(GenericParam {
token: Token::new(0x2A000002),
number: 0,
flags: 0,
owner: OnceLock::new(),
name: "TKey".to_string(),
constraints: Arc::new(boxcar::Vec::new()),
rid: 0,
offset: 0,
custom_attributes: Arc::new(boxcar::Vec::new()),
});
let value_param = Arc::new(GenericParam {
token: Token::new(0x2A000003),
number: 1,
flags: 0,
owner: OnceLock::new(),
name: "TValue".to_string(),
constraints: Arc::new(boxcar::Vec::new()),
rid: 1,
offset: 1,
custom_attributes: Arc::new(boxcar::Vec::new()),
});
dict_type.generic_params.push(key_param);
dict_type.generic_params.push(value_param);
let dict_instance = TypeBuilder::new(registry.clone())
.with_source(TypeSource::Unknown)
.class("System.Collections.Generic", "Dictionary`2")
.unwrap()
.generic_instance(2, |registry| {
let string_type = registry.get_primitive(CilPrimitiveKind::String).unwrap();
let int_type = registry.get_primitive(CilPrimitiveKind::I4).unwrap();
Ok(vec![string_type, int_type])
})
.unwrap()
.build()
.unwrap();
assert_eq!(dict_instance.name, "Dictionary`2");
assert!(matches!(
*dict_instance.flavor(),
CilFlavor::GenericInstance
));
assert_eq!(dict_instance.generic_args.count(), 2);
assert_eq!(
dict_instance.generic_args[0].generic_args[0]
.name()
.unwrap(),
"String"
);
assert_eq!(
dict_instance.generic_args[1].generic_args[0]
.name()
.unwrap(),
"Int32"
);
assert_eq!(
dict_instance.generic_args[0].generic_args[0]
.name()
.unwrap(),
"String"
); assert_eq!(
dict_instance.generic_args[1].generic_args[0]
.name()
.unwrap(),
"Int32"
); }
}