use super::*;
use crate::ast::Literal;
use crate::compile_to_ir;
use crate::ir::IrExpr;
#[test]
fn test_eliminate_constant_true_branch() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
struct Config { value: I32 = if true { 1 } else { 2 } }
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let optimized = eliminate_dead_code(&module, false);
let struct_def = optimized
.structs
.iter()
.find(|s| s.name == "Config")
.ok_or("expected Config struct")?;
let field = struct_def
.fields
.first()
.ok_or("expected at least one field")?;
let expr = field.default.as_ref().ok_or("expected default expr")?;
if let IrExpr::Literal {
value: Literal::Number(n),
..
} = expr
{
if (n.value.as_f64() - 1.0).abs() >= f64::EPSILON {
return Err(format!("Expected 1, got {}", n.value.as_f64()).into());
}
} else {
return Err(format!("Expected literal 1, got {expr:?}").into());
}
Ok(())
}
#[test]
fn test_eliminate_constant_false_branch() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
struct Config { value: I32 = if false { 1 } else { 2 } }
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let optimized = eliminate_dead_code(&module, false);
let struct_def = optimized
.structs
.iter()
.find(|s| s.name == "Config")
.ok_or("expected Config struct")?;
let field = struct_def
.fields
.first()
.ok_or("expected at least one field")?;
let expr = field.default.as_ref().ok_or("expected default expr")?;
if let IrExpr::Literal {
value: Literal::Number(n),
..
} = expr
{
if (n.value.as_f64() - 2.0).abs() >= f64::EPSILON {
return Err(format!("Expected 2, got {}", n.value.as_f64()).into());
}
} else {
return Err(format!("Expected literal 2, got {expr:?}").into());
}
Ok(())
}
#[test]
fn test_no_elimination_non_constant_condition() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
let flag: Boolean = true
let value: I32 = if flag { 1 } else { 2 }
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let optimized = eliminate_dead_code(&module, false);
let let_binding = optimized
.lets
.iter()
.find(|l| l.name == "value")
.ok_or("expected value let binding")?;
let expr = &let_binding.value;
if let IrExpr::If { .. } = expr {
} else if let IrExpr::Literal { .. } = expr {
} else {
return Err(format!("Expected If or Literal, got {expr:?}").into());
}
Ok(())
}
#[test]
fn test_analyze_used_structs() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
struct Used { value: I32 = 1 }
struct Unused { data: String }
impl Used {}
pub fn take(u: Used) -> I32 { u.value }
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let mut eliminator = DeadCodeEliminator::new(&module);
eliminator.analyze();
let used_id = module.struct_id("Used").ok_or("Used struct not found")?;
if !eliminator.is_struct_used(used_id) {
return Err("Used struct should be marked as used".into());
}
let unused_id = module
.struct_id("Unused")
.ok_or("Unused struct not found")?;
if eliminator.is_struct_used(unused_id) {
return Err("Unused struct should not be marked as used".into());
}
Ok(())
}
#[test]
fn test_analyze_struct_referenced_in_field() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
struct Inner { value: I32 = 1 }
struct Outer { inner: Inner = Inner(value: 1) }
impl Outer {}
pub fn show(o: Outer) -> I32 { o.inner.value }
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let mut eliminator = DeadCodeEliminator::new(&module);
eliminator.analyze();
let inner_id = module.struct_id("Inner").ok_or("Inner struct not found")?;
let outer_id = module.struct_id("Outer").ok_or("Outer struct not found")?;
if !eliminator.is_struct_used(inner_id) {
return Err("Inner struct should be used (referenced by Outer)".into());
}
if !eliminator.is_struct_used(outer_id) {
return Err("Outer struct should be used (referenced by `show`)".into());
}
Ok(())
}
#[test]
fn test_nested_dead_code_elimination() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
struct Config { value: I32 = if true { if false { 1 } else { 2 } } else { 3 } }
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let optimized = eliminate_dead_code(&module, false);
let struct_def = optimized
.structs
.iter()
.find(|s| s.name == "Config")
.ok_or("expected Config struct")?;
let field = struct_def
.fields
.first()
.ok_or("expected at least one field")?;
let expr = field.default.as_ref().ok_or("expected default expr")?;
if let IrExpr::Literal {
value: Literal::Number(n),
..
} = expr
{
if (n.value.as_f64() - 2.0).abs() >= f64::EPSILON {
return Err(format!("Expected 2, got {}", n.value.as_f64()).into());
}
} else {
return Err(format!("Expected literal 2, got {expr:?}").into());
}
Ok(())
}
#[test]
fn test_analyze_trait_constraint_kept_alive() -> Result<(), Box<dyn std::error::Error>> {
let source = r"
pub trait Container { size: I32 }
pub struct Box<T: Container> { value: T }
impl Box {}
";
let module = compile_to_ir(source).map_err(|e| format!("{e:?}"))?;
let mut eliminator = DeadCodeEliminator::new(&module);
eliminator.analyze();
let trait_id = module
.trait_id("Container")
.ok_or("Container trait not found")?;
if !eliminator.is_trait_used(trait_id) {
return Err("Container trait should be marked as used because it is a bound".into());
}
Ok(())
}
mod removal_tests {
#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
use super::*;
#[test]
fn test_removal_drops_unused_struct() {
let source = r"
pub struct Used { value: I32 }
pub struct Unused { data: String }
impl Used { fn get(self) -> I32 { self.value } }
pub fn run(u: Used) -> I32 { u.get() }
";
let module = compile_to_ir(source).unwrap();
let before = module.structs.len();
assert!(before >= 2, "expected both structs in IR before DCE");
let optimized = eliminate_dead_code(&module, true);
assert!(
optimized.structs.iter().any(|s| s.name == "Used"),
"Used should survive"
);
assert!(
!optimized.structs.iter().any(|s| s.name == "Unused"),
"Unused should be removed"
);
}
#[test]
fn test_removal_preserves_remaining_struct_ids() {
let source = r"
pub struct Unused { data: String }
pub struct Used { value: I32 }
pub fn run(u: Used) -> I32 { u.value }
";
let module = compile_to_ir(source).unwrap();
let optimized = eliminate_dead_code(&module, true);
let used_id = optimized.struct_id("Used").unwrap();
let used = optimized.get_struct(used_id).unwrap();
assert_eq!(used.name, "Used");
assert_eq!(used.fields.len(), 1);
}
#[test]
fn test_removal_drops_impl_for_removed_enum() {
let source = r"
pub enum Used { a, b }
pub enum Unused { x, y }
impl Unused { fn describe(self) -> I32 { 0 } }
pub fn run(u: Used) -> Used { u }
";
let module = compile_to_ir(source).unwrap();
let before_impls = module.impls.len();
assert!(before_impls >= 1, "expected Unused impl in IR before DCE");
let optimized = eliminate_dead_code(&module, true);
assert!(
!optimized.enums.iter().any(|e| e.name == "Unused"),
"Unused enum should be removed"
);
for impl_block in &optimized.impls {
match impl_block.target {
crate::ir::ImplTarget::Enum(id) => {
let e = optimized.get_enum(id).unwrap();
assert_ne!(e.name, "Unused");
}
crate::ir::ImplTarget::Struct(_) | crate::ir::ImplTarget::Primitive(_) => {}
}
}
}
}