mod capture_rewrite;
mod env_synthesis;
mod lifting;
mod state;
#[cfg(test)]
mod tests;
use crate::error::CompilerError;
use crate::ir::{IrBlockStatement, IrExpr, IrModule};
use crate::location::Span;
use crate::pipeline::IrPass;
use self::capture_rewrite::CaptureCtx;
use self::state::ConversionState;
const ENV_STRUCT_PREFIX: &str = "__ClosureEnv";
const LIFTED_FN_PREFIX: &str = "__closure";
const ENV_PARAM_NAME: &str = "__env";
#[expect(
clippy::exhaustive_structs,
reason = "no fields planned; flag-typed knobs would be added explicitly later"
)]
#[derive(Debug, Clone, Default)]
pub struct ClosureConversionPass;
impl ClosureConversionPass {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl IrPass for ClosureConversionPass {
fn name(&self) -> &'static str {
"closure-conversion"
}
fn run(&mut self, module: IrModule) -> Result<IrModule, Vec<CompilerError>> {
let mut module = crate::ir::ResolveReferencesPass::new().run(module)?;
let mut state = ConversionState::new(&module);
let lets = std::mem::take(&mut module.lets);
module.lets = lets
.into_iter()
.map(|mut l| {
l.value = state.process(l.value, &CaptureCtx::module_level());
l
})
.collect();
let functions = std::mem::take(&mut module.functions);
module.functions = functions
.into_iter()
.map(|mut f| {
f.body = f
.body
.map(|b| state.process(b, &CaptureCtx::module_level()));
f.params = state.process_param_defaults(f.params);
f
})
.collect();
let impls = std::mem::take(&mut module.impls);
module.impls = impls
.into_iter()
.map(|mut i| {
let methods = std::mem::take(&mut i.functions);
i.functions = methods
.into_iter()
.map(|mut f| {
f.body = f
.body
.map(|b| state.process(b, &CaptureCtx::module_level()));
f.params = state.process_param_defaults(f.params);
f
})
.collect();
i
})
.collect();
let structs = std::mem::take(&mut module.structs);
module.structs = structs
.into_iter()
.map(|mut s| {
let fields = std::mem::take(&mut s.fields);
s.fields = fields
.into_iter()
.map(|mut field| {
field.default = field
.default
.map(|d| state.process(d, &CaptureCtx::module_level()));
field
})
.collect();
s
})
.collect();
let enums = std::mem::take(&mut module.enums);
module.enums = enums
.into_iter()
.map(|mut e| {
for variant in &mut e.variants {
let fields = std::mem::take(&mut variant.fields);
variant.fields = fields
.into_iter()
.map(|mut field| {
field.default = field
.default
.map(|d| state.process(d, &CaptureCtx::module_level()));
field
})
.collect();
}
e
})
.collect();
let (envs, lifted) = state.into_outputs();
module.structs.extend(envs);
module.functions.extend(lifted);
module.rebuild_indices();
let residuals = find_residual_closures(&module);
if !residuals.is_empty() {
return Err(residuals
.into_iter()
.map(|location| CompilerError::InternalError {
detail: format!(
"closure-conversion: residual IrExpr::Closure remains in {location}"
),
span: Span::default(),
})
.collect());
}
crate::ir::ResolveReferencesPass::new().run(module)
}
}
fn find_residual_closures(module: &IrModule) -> Vec<String> {
let mut hits: Vec<String> = Vec::new();
for l in &module.lets {
if expr_has_closure(&l.value) {
hits.push(format!("module-level let `{}`", l.name));
}
}
for f in &module.functions {
if let Some(body) = &f.body {
if expr_has_closure(body) {
hits.push(format!("function `{}` body", f.name));
}
}
for p in &f.params {
if let Some(default) = &p.default {
if expr_has_closure(default) {
hits.push(format!(
"default of parameter `{}` on function `{}`",
p.name, f.name
));
}
}
}
}
for i in &module.impls {
for f in &i.functions {
if let Some(body) = &f.body {
if expr_has_closure(body) {
hits.push(format!("impl method `{}` body", f.name));
}
}
for p in &f.params {
if let Some(default) = &p.default {
if expr_has_closure(default) {
hits.push(format!(
"default of parameter `{}` on impl method `{}`",
p.name, f.name
));
}
}
}
}
}
for s in &module.structs {
for field in &s.fields {
if let Some(default) = &field.default {
if expr_has_closure(default) {
hits.push(format!(
"default of field `{}` on struct `{}`",
field.name, s.name
));
}
}
}
}
for e in &module.enums {
for variant in &e.variants {
for field in &variant.fields {
if let Some(default) = &field.default {
if expr_has_closure(default) {
hits.push(format!(
"default of field `{}` on enum `{}::{}`",
field.name, e.name, variant.name
));
}
}
}
}
}
hits
}
fn expr_has_closure(expr: &IrExpr) -> bool {
match expr {
IrExpr::Closure { .. } => true,
IrExpr::Literal { .. }
| IrExpr::Reference { .. }
| IrExpr::SelfFieldRef { .. }
| IrExpr::LetRef { .. } => false,
IrExpr::Tuple { fields, .. } => fields.iter().any(|(_, v)| expr_has_closure(v)),
IrExpr::StructInst { fields, .. } | IrExpr::EnumInst { fields, .. } => {
fields.iter().any(|(_, _, v)| expr_has_closure(v))
}
IrExpr::Array { elements, .. } => elements.iter().any(expr_has_closure),
IrExpr::FieldAccess { object, .. } => expr_has_closure(object),
IrExpr::BinaryOp { left, right, .. } => expr_has_closure(left) || expr_has_closure(right),
IrExpr::UnaryOp { operand, .. } => expr_has_closure(operand),
IrExpr::If {
condition,
then_branch,
else_branch,
..
} => {
expr_has_closure(condition)
|| expr_has_closure(then_branch)
|| else_branch.as_ref().is_some_and(|e| expr_has_closure(e))
}
IrExpr::For {
collection, body, ..
} => expr_has_closure(collection) || expr_has_closure(body),
IrExpr::Match {
scrutinee, arms, ..
} => expr_has_closure(scrutinee) || arms.iter().any(|arm| expr_has_closure(&arm.body)),
IrExpr::FunctionCall { args, .. } => args.iter().any(|(_, v)| expr_has_closure(v)),
IrExpr::CallClosure { closure, args, .. } => {
expr_has_closure(closure) || args.iter().any(|(_, v)| expr_has_closure(v))
}
IrExpr::MethodCall { receiver, args, .. } => {
expr_has_closure(receiver) || args.iter().any(|(_, v)| expr_has_closure(v))
}
IrExpr::DictLiteral { entries, .. } => entries
.iter()
.any(|(k, v)| expr_has_closure(k) || expr_has_closure(v)),
IrExpr::DictAccess { dict, key, .. } => expr_has_closure(dict) || expr_has_closure(key),
IrExpr::Block {
statements, result, ..
} => {
statements.iter().any(|stmt| match stmt {
IrBlockStatement::Let { value, .. } => expr_has_closure(value),
IrBlockStatement::Assign { target, value, .. } => {
expr_has_closure(target) || expr_has_closure(value)
}
IrBlockStatement::Expr(e) => expr_has_closure(e),
}) || expr_has_closure(result)
}
IrExpr::ClosureRef { env_struct, .. } => expr_has_closure(env_struct),
}
}