mod expr;
mod filtering;
mod reachability;
mod remap;
#[cfg(test)]
mod tests;
use crate::ir::{EnumId, IrModule, StructId, TraitId};
use std::collections::HashSet;
pub use expr::eliminate_dead_code_expr;
use remap::remove_unused_definitions;
#[derive(Debug)]
pub struct DeadCodeEliminator<'a> {
pub(super) module: &'a IrModule,
pub(super) used_structs: HashSet<StructId>,
pub(super) used_traits: HashSet<TraitId>,
pub(super) used_enums: HashSet<EnumId>,
}
impl<'a> DeadCodeEliminator<'a> {
#[must_use]
pub fn new(module: &'a IrModule) -> Self {
Self {
module,
used_structs: HashSet::new(),
used_traits: HashSet::new(),
used_enums: HashSet::new(),
}
}
pub fn analyze(&mut self) {
for impl_block in &self.module.impls {
for func in &impl_block.functions {
if let Some(body) = &func.body {
self.mark_used_in_expr(body);
}
for p in &func.params {
if let Some(ty) = &p.ty {
self.mark_used_in_type(ty);
}
}
if let Some(ret) = &func.return_type {
self.mark_used_in_type(ret);
}
}
}
for func in &self.module.functions {
if let Some(body) = &func.body {
self.mark_used_in_expr(body);
}
for gp in &func.params {
if let Some(ty) = &gp.ty {
self.mark_used_in_type(ty);
}
}
}
for let_binding in &self.module.lets {
self.mark_used_in_expr(&let_binding.value);
self.mark_used_in_type(&let_binding.ty);
}
for s in &self.module.structs {
for field in &s.fields {
self.mark_used_in_type(&field.ty);
}
for trait_ref in &s.traits {
self.used_traits.insert(trait_ref.trait_id);
for arg in &trait_ref.args {
self.mark_used_in_type(arg);
}
}
for gp in &s.generic_params {
for constraint in &gp.constraints {
self.used_traits.insert(constraint.trait_id);
for arg in &constraint.args {
self.mark_used_in_type(arg);
}
}
}
}
for t in &self.module.traits {
for composed in &t.composed_traits {
self.used_traits.insert(*composed);
}
for gp in &t.generic_params {
for constraint in &gp.constraints {
self.used_traits.insert(constraint.trait_id);
for arg in &constraint.args {
self.mark_used_in_type(arg);
}
}
}
for field in &t.fields {
self.mark_used_in_type(&field.ty);
}
for method in &t.methods {
for p in &method.params {
if let Some(ty) = &p.ty {
self.mark_used_in_type(ty);
}
}
if let Some(ret) = &method.return_type {
self.mark_used_in_type(ret);
}
}
}
for f in &self.module.functions {
for p in &f.params {
if let Some(ty) = &p.ty {
self.mark_used_in_type(ty);
}
}
if let Some(ret) = &f.return_type {
self.mark_used_in_type(ret);
}
}
for e in &self.module.enums {
for variant in &e.variants {
for field in &variant.fields {
self.mark_used_in_type(&field.ty);
}
}
for gp in &e.generic_params {
for constraint in &gp.constraints {
self.used_traits.insert(constraint.trait_id);
for arg in &constraint.args {
self.mark_used_in_type(arg);
}
}
}
}
}
#[must_use]
pub fn is_struct_used(&self, id: StructId) -> bool {
self.used_structs.contains(&id)
}
#[must_use]
pub fn is_trait_used(&self, id: TraitId) -> bool {
self.used_traits.contains(&id)
}
#[must_use]
pub const fn used_structs(&self) -> &HashSet<StructId> {
&self.used_structs
}
#[must_use]
pub const fn used_traits(&self) -> &HashSet<TraitId> {
&self.used_traits
}
#[must_use]
pub const fn used_enums_set(&self) -> &HashSet<EnumId> {
&self.used_enums
}
#[must_use]
pub fn is_enum_used(&self, id: EnumId) -> bool {
self.used_enums.contains(&id)
}
}
#[must_use]
pub fn eliminate_dead_code(module: &IrModule, remove_unused_structs: bool) -> IrModule {
let mut result = module.clone();
for impl_block in &mut result.impls {
for func in &mut impl_block.functions {
func.body = func.body.take().map(eliminate_dead_code_expr);
}
}
for func in &mut result.functions {
func.body = func.body.take().map(eliminate_dead_code_expr);
}
for let_binding in &mut result.lets {
let_binding.value = eliminate_dead_code_expr(let_binding.value.clone());
}
for struct_def in &mut result.structs {
for field in &mut struct_def.fields {
if let Some(default) = &mut field.default {
*default = eliminate_dead_code_expr(default.clone());
}
}
}
if remove_unused_structs {
let mut eliminator = DeadCodeEliminator::new(&result);
eliminator.analyze();
let used_structs = eliminator.used_structs.clone();
let used_traits = eliminator.used_traits.clone();
let used_enums = eliminator.used_enums.clone();
drop(eliminator);
remove_unused_definitions(&mut result, &used_structs, &used_traits, &used_enums);
}
result
}
#[derive(Debug)]
#[expect(
clippy::exhaustive_structs,
reason = "IR types are constructed directly by consumer code"
)]
pub struct DeadCodeEliminationPass {
pub remove_unused_structs: bool,
}
impl DeadCodeEliminationPass {
#[must_use]
pub const fn new() -> Self {
Self {
remove_unused_structs: true,
}
}
}
impl Default for DeadCodeEliminationPass {
fn default() -> Self {
Self::new()
}
}
impl crate::pipeline::IrPass for DeadCodeEliminationPass {
fn name(&self) -> &'static str {
"dead-code-elimination"
}
fn run(&mut self, module: IrModule) -> Result<IrModule, Vec<crate::error::CompilerError>> {
Ok(eliminate_dead_code(&module, self.remove_unused_structs))
}
}