use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{CodedIndex, CodedIndexType, GenericParamRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub use super::GenericParamAttributes;
pub struct GenericParamBuilder {
name: Option<String>,
number: Option<u32>,
flags: Option<u32>,
owner: Option<CodedIndex>,
}
impl Default for GenericParamBuilder {
fn default() -> Self {
Self::new()
}
}
impl GenericParamBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
number: None,
flags: None,
owner: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn number(mut self, number: u32) -> Self {
self.number = Some(number);
self
}
#[must_use]
pub fn flags(mut self, flags: u32) -> Self {
self.flags = Some(flags);
self
}
#[must_use]
pub fn owner(mut self, owner: CodedIndex) -> Self {
self.owner = Some(owner);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self.name.ok_or_else(|| {
Error::ModificationInvalid("GenericParam name is required".to_string())
})?;
let number = self.number.ok_or_else(|| {
Error::ModificationInvalid("GenericParam number is required".to_string())
})?;
let owner = self.owner.ok_or_else(|| {
Error::ModificationInvalid("GenericParam owner is required".to_string())
})?;
let flags = self.flags.unwrap_or(0);
let valid_owner_tables = CodedIndexType::TypeOrMethodDef.tables();
if !valid_owner_tables.contains(&owner.tag) {
return Err(Error::ModificationInvalid(format!(
"Owner must be a TypeOrMethodDef coded index (TypeDef/MethodDef), got {:?}",
owner.tag
)));
}
if number > 65535 {
return Err(Error::ModificationInvalid(format!(
"GenericParam number {number} is too large (maximum 65535)"
)));
}
let valid_flags_mask =
GenericParamAttributes::VARIANCE_MASK | GenericParamAttributes::SPECIAL_CONSTRAINT_MASK;
if flags & !valid_flags_mask != 0 {
return Err(Error::ModificationInvalid(format!(
"Invalid GenericParam flags: 0x{flags:04X}. Unsupported flags detected"
)));
}
let name_index = assembly.string_get_or_add(&name)?.placeholder();
let rid = assembly.next_rid(TableId::GenericParam)?;
let token = Token::from_parts(TableId::GenericParam, rid);
let generic_param_raw = GenericParamRaw {
rid,
token,
offset: 0, number,
flags,
owner,
name: name_index,
};
assembly.table_row_add(
TableId::GenericParam,
TableDataOwned::GenericParam(generic_param_raw),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::cilassemblyview::CilAssemblyView,
};
use std::path::PathBuf;
#[test]
fn test_generic_param_builder_basic() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let ref_ = GenericParamBuilder::new()
.name("T")
.number(0)
.owner(generic_type)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
}
}
#[test]
fn test_generic_param_builder_with_flags() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let constraint_flags = GenericParamAttributes::REFERENCE_TYPE_CONSTRAINT
| GenericParamAttributes::DEFAULT_CONSTRUCTOR_CONSTRAINT;
let ref_ = GenericParamBuilder::new()
.name("TEntity")
.number(0)
.flags(constraint_flags)
.owner(generic_type)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
}
}
#[test]
fn test_generic_param_builder_covariant() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_interface =
CodedIndex::new(TableId::TypeDef, 2, CodedIndexType::TypeOrMethodDef);
let ref_ = GenericParamBuilder::new()
.name("TResult")
.number(0)
.flags(GenericParamAttributes::COVARIANT)
.owner(generic_interface)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
}
}
#[test]
fn test_generic_param_builder_method_parameter() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_method =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::TypeOrMethodDef);
let ref_ = GenericParamBuilder::new()
.name("U")
.number(0)
.owner(generic_method)
.build(&mut assembly)
.unwrap();
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
}
}
#[test]
fn test_generic_param_builder_missing_name() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let result = GenericParamBuilder::new()
.number(0)
.owner(generic_type)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_generic_param_builder_missing_number() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let result = GenericParamBuilder::new()
.name("T")
.owner(generic_type)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_generic_param_builder_missing_owner() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let result = GenericParamBuilder::new()
.name("T")
.number(0)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_generic_param_builder_invalid_owner_type() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let invalid_owner = CodedIndex::new(TableId::Field, 1, CodedIndexType::TypeOrMethodDef);
let result = GenericParamBuilder::new()
.name("T")
.number(0)
.owner(invalid_owner)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_generic_param_builder_invalid_number() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let result = GenericParamBuilder::new()
.name("T")
.number(100000) .owner(generic_type)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_generic_param_builder_invalid_flags() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let result = GenericParamBuilder::new()
.name("T")
.number(0)
.flags(0xFFFF) .owner(generic_type)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_generic_param_builder_multiple_parameters() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let generic_method =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::TypeOrMethodDef);
let ref1 = GenericParamBuilder::new()
.name("T")
.number(0)
.owner(generic_type.clone())
.build(&mut assembly)
.unwrap();
let ref2 = GenericParamBuilder::new()
.name("U")
.number(1)
.flags(GenericParamAttributes::REFERENCE_TYPE_CONSTRAINT)
.owner(generic_type.clone())
.build(&mut assembly)
.unwrap();
let ref3 = GenericParamBuilder::new()
.name("V")
.number(0)
.flags(GenericParamAttributes::COVARIANT)
.owner(generic_method)
.build(&mut assembly)
.unwrap();
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref3));
assert!(!std::sync::Arc::ptr_eq(&ref2, &ref3));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
assert_eq!(ref3.kind(), ChangeRefKind::TableRow(TableId::GenericParam));
}
}
#[test]
fn test_generic_param_builder_all_constraint_types() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let generic_type =
CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::TypeOrMethodDef);
let constraints = [
(
"TClass",
0,
GenericParamAttributes::REFERENCE_TYPE_CONSTRAINT,
),
(
"TStruct",
1,
GenericParamAttributes::NOT_NULLABLE_VALUE_TYPE_CONSTRAINT,
),
(
"TNew",
2,
GenericParamAttributes::DEFAULT_CONSTRUCTOR_CONSTRAINT,
),
("TOut", 3, GenericParamAttributes::COVARIANT),
("TIn", 4, GenericParamAttributes::CONTRAVARIANT),
(
"TComplex",
5,
GenericParamAttributes::REFERENCE_TYPE_CONSTRAINT
| GenericParamAttributes::DEFAULT_CONSTRUCTOR_CONSTRAINT,
),
];
for (name, number, flags) in constraints.iter() {
let _param = GenericParamBuilder::new()
.name(*name)
.number(*number)
.flags(*flags)
.owner(generic_type.clone())
.build(&mut assembly)
.unwrap();
}
}
}
}