use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{LocalVariableRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone)]
pub struct LocalVariableBuilder {
attributes: Option<u16>,
index: Option<u16>,
name: Option<String>,
}
impl LocalVariableBuilder {
#[must_use]
pub fn new() -> Self {
Self {
attributes: None,
index: None,
name: None,
}
}
#[must_use]
pub fn attributes(mut self, attributes: u16) -> Self {
self.attributes = Some(attributes);
self
}
#[must_use]
pub fn index(mut self, index: u16) -> Self {
self.index = Some(index);
self
}
#[must_use]
pub fn name<T: Into<String>>(mut self, name: T) -> Self {
self.name = Some(name.into());
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let index = self.index.ok_or_else(|| {
Error::ModificationInvalid("Variable index is required for LocalVariable".to_string())
})?;
let name = self.name.ok_or_else(|| {
Error::ModificationInvalid(
"Variable name is required for LocalVariable (use empty string for anonymous)"
.to_string(),
)
})?;
let name_index = if name.is_empty() {
0
} else {
assembly.string_add(&name)?.placeholder()
};
let local_variable = LocalVariableRaw {
rid: 0,
token: Token::new(0),
offset: 0,
attributes: self.attributes.unwrap_or(0),
index,
name: name_index,
};
assembly.table_row_add(
TableId::LocalVariable,
TableDataOwned::LocalVariable(local_variable),
)
}
}
impl Default for LocalVariableBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_localvariable_builder_new() {
let builder = LocalVariableBuilder::new();
assert!(builder.attributes.is_none());
assert!(builder.index.is_none());
assert!(builder.name.is_none());
}
#[test]
fn test_localvariable_builder_default() {
let builder = LocalVariableBuilder::default();
assert!(builder.attributes.is_none());
assert!(builder.index.is_none());
assert!(builder.name.is_none());
}
#[test]
fn test_localvariable_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = LocalVariableBuilder::new()
.index(0)
.name("testVar")
.build(&mut assembly)
.expect("Should build successfully");
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::LocalVariable));
Ok(())
}
#[test]
fn test_localvariable_builder_with_all_fields() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = LocalVariableBuilder::new()
.attributes(0x0001)
.index(2)
.name("myVariable")
.build(&mut assembly)
.expect("Should build successfully");
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::LocalVariable));
Ok(())
}
#[test]
fn test_localvariable_builder_anonymous_variable() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = LocalVariableBuilder::new()
.index(1)
.name("") .build(&mut assembly)
.expect("Should build successfully");
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::LocalVariable));
Ok(())
}
#[test]
fn test_localvariable_builder_missing_index() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalVariableBuilder::new()
.name("testVar")
.build(&mut assembly);
assert!(result.is_err());
match result.unwrap_err() {
Error::ModificationInvalid(details) => {
assert!(details.contains("Variable index is required"));
}
_ => panic!("Expected ModificationInvalid error"),
}
Ok(())
}
#[test]
fn test_localvariable_builder_missing_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = LocalVariableBuilder::new().index(0).build(&mut assembly);
assert!(result.is_err());
match result.unwrap_err() {
Error::ModificationInvalid(details) => {
assert!(details.contains("Variable name is required"));
}
_ => panic!("Expected ModificationInvalid error"),
}
Ok(())
}
#[test]
fn test_localvariable_builder_clone() {
let builder = LocalVariableBuilder::new()
.attributes(0x01)
.index(0)
.name("testVar");
let cloned = builder.clone();
assert_eq!(builder.attributes, cloned.attributes);
assert_eq!(builder.index, cloned.index);
assert_eq!(builder.name, cloned.name);
}
#[test]
fn test_localvariable_builder_debug() {
let builder = LocalVariableBuilder::new()
.attributes(0x01)
.index(0)
.name("testVar");
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("LocalVariableBuilder"));
assert!(debug_str.contains("attributes"));
assert!(debug_str.contains("index"));
assert!(debug_str.contains("name"));
}
#[test]
fn test_localvariable_builder_fluent_interface() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = LocalVariableBuilder::new()
.attributes(0x0002)
.index(3)
.name("chainedVar")
.build(&mut assembly)
.expect("Should build successfully");
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::LocalVariable));
Ok(())
}
#[test]
fn test_localvariable_builder_multiple_builds() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref1 = LocalVariableBuilder::new()
.index(0)
.name("var1")
.build(&mut assembly)
.expect("Should build first variable");
let ref2 = LocalVariableBuilder::new()
.index(1)
.name("var2")
.build(&mut assembly)
.expect("Should build second variable");
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::LocalVariable));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::LocalVariable));
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
Ok(())
}
}