use std::collections::HashMap;
use crate::error::CompilerError;
use crate::ir::{BindingId, IrExpr, IrFunction, IrLet, IrModule};
use crate::pipeline::IrPass;
#[derive(Debug, Default, Clone, Copy)]
#[expect(
clippy::exhaustive_structs,
reason = "stateless pass marker — constructed directly"
)]
pub struct ResolveReferencesPass;
impl ResolveReferencesPass {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl IrPass for ResolveReferencesPass {
fn name(&self) -> &'static str {
"resolve-references"
}
fn run(&mut self, mut module: IrModule) -> Result<IrModule, Vec<CompilerError>> {
let symbols = ModuleSymbols::build(&module);
let mut errors: Vec<CompilerError> = Vec::new();
let mut functions = std::mem::take(&mut module.functions);
let mut lets = std::mem::take(&mut module.lets);
for func in &mut functions {
resolve_function(func, &symbols, &module, &mut errors);
}
module.functions = functions;
#[expect(
clippy::indexing_slicing,
reason = "indices come from the bounds of the just-read .len() calls"
)]
for impl_idx in 0..module.impls.len() {
for fn_idx in 0..module.impls[impl_idx].functions.len() {
let mut taken = std::mem::replace(
&mut module.impls[impl_idx].functions[fn_idx],
placeholder_function(),
);
resolve_function(&mut taken, &symbols, &module, &mut errors);
module.impls[impl_idx].functions[fn_idx] = taken;
}
}
for l in &mut lets {
resolve_module_let(l, &symbols, &module, &mut errors);
}
module.lets = lets;
substitute_forward_ref_defaults(&mut module);
if errors.is_empty() {
Ok(module)
} else {
Err(errors)
}
}
}
fn substitute_forward_ref_defaults(module: &mut IrModule) {
let snapshot: Vec<Vec<Option<IrExpr>>> = module
.functions
.iter()
.map(|f| {
f.params
.iter()
.filter(|p| p.name != "self")
.map(|p| p.default.clone())
.collect()
})
.collect();
let mut functions = std::mem::take(&mut module.functions);
for func in &mut functions {
if let Some(body) = &mut func.body {
substitute_in_expr(body, &snapshot);
}
}
module.functions = functions;
let mut lets = std::mem::take(&mut module.lets);
for l in &mut lets {
substitute_in_expr(&mut l.value, &snapshot);
}
module.lets = lets;
for impl_block in &mut module.impls {
for func in &mut impl_block.functions {
if let Some(body) = &mut func.body {
substitute_in_expr(body, &snapshot);
}
}
}
}
fn substitute_in_expr(expr: &mut IrExpr, default_snapshot: &[Vec<Option<IrExpr>>]) {
if let IrExpr::FunctionCall {
function_id: Some(id),
args,
..
} = expr
{
if let Some(defaults) = default_snapshot.get(id.0 as usize) {
let any_labeled = args.iter().any(|(l, _)| l.is_some());
if !any_labeled && args.len() < defaults.len() {
for default in defaults.iter().skip(args.len()) {
let Some(d) = default else { break };
args.push((None, d.clone()));
}
}
}
}
crate::ir::walk_expr_children_mut(expr, &mut |child| {
substitute_in_expr(child, default_snapshot);
});
}
mod expr;
mod lookups;
mod symbols;
mod walkers;
use expr::resolve_expr;
use symbols::ModuleSymbols;
use walkers::module_prefix_of;
#[derive(Copy, Clone)]
enum BindingKind {
Param,
Local,
}
struct FnResolver<'a> {
symbols: &'a ModuleSymbols,
module: &'a IrModule,
errors: &'a mut Vec<CompilerError>,
module_prefix: String,
next_id: u32,
scopes: Vec<HashMap<String, (BindingId, BindingKind)>>,
}
impl<'a> FnResolver<'a> {
fn new(
symbols: &'a ModuleSymbols,
module: &'a IrModule,
errors: &'a mut Vec<CompilerError>,
module_prefix: String,
) -> Self {
Self {
symbols,
module,
errors,
module_prefix,
next_id: 0,
scopes: vec![HashMap::new()],
}
}
const fn fresh(&mut self) -> BindingId {
let id = BindingId(self.next_id);
self.next_id = self.next_id.saturating_add(1);
id
}
fn bind(&mut self, name: String, id: BindingId, kind: BindingKind) {
if let Some(frame) = self.scopes.last_mut() {
frame.insert(name, (id, kind));
}
}
fn lookup(&self, name: &str) -> Option<(BindingId, BindingKind)> {
self.scopes
.iter()
.rev()
.find_map(|frame| frame.get(name).copied())
}
fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
}
fn pop_scope(&mut self) {
self.scopes.pop();
}
}
fn placeholder_function() -> IrFunction {
IrFunction {
name: String::new(),
generic_params: Vec::new(),
params: Vec::new(),
return_type: None,
body: None,
extern_abi: None,
attributes: Vec::new(),
doc: None,
span: crate::ir::IrSpan::default(),
}
}
fn resolve_function(
func: &mut IrFunction,
symbols: &ModuleSymbols,
module: &IrModule,
errors: &mut Vec<CompilerError>,
) {
let prefix = module_prefix_of(&func.name);
let mut r = FnResolver::new(symbols, module, errors, prefix);
for param in &mut func.params {
let id = r.fresh();
param.binding_id = id;
r.bind(param.name.clone(), id, BindingKind::Param);
if let Some(default) = param.default.as_mut() {
resolve_expr(default, &mut r);
}
}
if let Some(body) = func.body.as_mut() {
resolve_expr(body, &mut r);
}
}
fn resolve_module_let(
l: &mut IrLet,
symbols: &ModuleSymbols,
module: &IrModule,
errors: &mut Vec<CompilerError>,
) {
let prefix = module_prefix_of(&l.name);
let mut r = FnResolver::new(symbols, module, errors, prefix);
resolve_expr(&mut l.value, &mut r);
}