use crate::ast::{FnResolution, Spanned};
use crate::ir::hir::{
ResolvedCallee, ResolvedCtor, ResolvedExpr, ResolvedFnBody, ResolvedFnDef, ResolvedMatchArm,
ResolvedPattern, ResolvedStmt, ResolvedTopLevel,
};
use super::expr::{
MirBinOp, MirCall, MirCallee, MirConstruct, MirCtor, MirEffectAnnotation, MirExpr, MirLet,
MirMatch, MirMatchArm, MirPattern, MirProject, MirRecordCreate, MirRecordField,
MirRecordUpdate,
};
use super::program::{LocalId, MirFn, MirParam, MirProgram};
use super::stats::SkipReason;
pub fn lower_program(items: &[ResolvedTopLevel]) -> MirProgram {
let mut program = MirProgram::empty();
for item in items {
let ResolvedTopLevel::FnDef(fd) = item else {
continue;
};
match lower_fn(fd, &mut program) {
Ok(mir_fn) => {
program.fns.insert(fd.fn_id, mir_fn);
program.stats.record_lowered();
}
Err(reason) => {
program.stats.record_skip(reason);
}
}
}
program
}
pub fn lower_top_level_value(
value: &Spanned<ResolvedExpr>,
program: &mut MirProgram,
) -> Result<Spanned<MirExpr>, SkipReason> {
lower_expr(value, program)
}
fn lower_fn(fd: &ResolvedFnDef, program: &mut MirProgram) -> Result<MirFn, SkipReason> {
let ResolvedFnBody::Block(stmts) = &*fd.body;
if stmts.is_empty() {
return Err(SkipReason::EmptyBody);
}
let base_local_count = fd
.resolution
.as_ref()
.map_or(fd.params.len() as u32, |r| u32::from(r.local_count));
let mut next_synthetic_local = base_local_count;
let body = match stmts.len() {
1 => {
let ResolvedStmt::Expr(expr) = &stmts[0] else {
return Err(SkipReason::BindingOnlyTail);
};
lower_expr(expr, program)?
}
_ => {
let resolution = fd
.resolution
.as_ref()
.ok_or(SkipReason::MissingResolution)?;
lower_stmt_chain(stmts, resolution, &mut next_synthetic_local, program)?
}
};
let local_count = next_synthetic_local;
let params = fd
.params
.iter()
.enumerate()
.map(|(i, (name, ty))| MirParam {
local: LocalId(i as u32),
name: name.clone(),
ty: format!("{ty:?}"),
})
.collect();
let effects = fd
.effects
.iter()
.map(|e| MirEffectAnnotation {
name: e.node.clone(),
})
.collect();
Ok(MirFn {
fn_id: fd.fn_id,
name: fd.name.clone(),
params,
return_type: format!("{:?}", fd.return_type),
effects,
body,
local_count,
aliased_slots: fd
.resolution
.as_ref()
.map(|r| r.aliased_slots.clone())
.unwrap_or_default(),
repr: super::program::MirFnRepr::default(),
})
}
fn lower_expr(
expr: &Spanned<ResolvedExpr>,
program: &mut MirProgram,
) -> Result<Spanned<MirExpr>, SkipReason> {
let mir = match &expr.node {
ResolvedExpr::Literal(lit) => MirExpr::Literal(wrap(lit.clone(), expr)),
ResolvedExpr::Resolved {
slot,
name,
last_use,
} => {
let local = super::expr::MirLocal {
slot: LocalId(u32::from(*slot)),
last_use: last_use.0,
name: name.clone(),
};
MirExpr::Local(wrap(local, expr))
}
ResolvedExpr::BinOp(op, lhs, rhs) => MirExpr::BinOp(wrap(
MirBinOp {
op: *op,
lhs: Box::new(lower_expr(lhs, program)?),
rhs: Box::new(lower_expr(rhs, program)?),
},
expr,
)),
ResolvedExpr::Neg(inner) => MirExpr::Neg(Box::new(lower_expr(inner, program)?)),
ResolvedExpr::Call(callee, args) => {
let mir_callee = match callee {
ResolvedCallee::Fn(fn_id) => MirCallee::Fn(*fn_id),
ResolvedCallee::Builtin(name) => MirCallee::Builtin(program.intern_builtin(name)),
ResolvedCallee::Intrinsic(intrinsic) => MirCallee::Intrinsic(*intrinsic),
ResolvedCallee::LocalSlot {
slot,
name,
last_use,
} => MirCallee::LocalSlot {
slot: *slot,
name: name.clone(),
last_use: last_use.0,
},
ResolvedCallee::Unresolved { .. } => {
return Err(SkipReason::UnsupportedCallee);
}
};
let mir_args = args
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::Call(wrap(
MirCall {
callee: mir_callee,
args: mir_args,
},
expr,
))
}
ResolvedExpr::Ctor(ResolvedCtor::User { ctor_id, .. }, args) => {
let mir_args = args
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::Construct(wrap(
MirConstruct {
ctor: MirCtor::User(*ctor_id),
args: mir_args,
},
expr,
))
}
ResolvedExpr::Ctor(ResolvedCtor::Builtin(bc), args) => {
let mir_args = args
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::Construct(wrap(
MirConstruct {
ctor: MirCtor::Builtin(*bc),
args: mir_args,
},
expr,
))
}
ResolvedExpr::Ctor(ResolvedCtor::Unresolved { .. }, _) => {
return Err(SkipReason::UnresolvedCtor);
}
ResolvedExpr::RecordCreate {
type_id,
type_name,
fields,
} => {
let mir_fields = fields
.iter()
.map(|(name, value)| {
Ok(MirRecordField {
name: name.clone(),
value: lower_expr(value, program)?,
})
})
.collect::<Result<Vec<_>, SkipReason>>()?;
MirExpr::RecordCreate(wrap(
MirRecordCreate {
type_id: *type_id,
type_name: type_name.clone(),
fields: mir_fields,
},
expr,
))
}
ResolvedExpr::RecordUpdate {
type_id,
type_name,
base,
updates,
} => {
let mir_updates = updates
.iter()
.map(|(name, value)| {
Ok(MirRecordField {
name: name.clone(),
value: lower_expr(value, program)?,
})
})
.collect::<Result<Vec<_>, SkipReason>>()?;
MirExpr::RecordUpdate(wrap(
MirRecordUpdate {
base: Box::new(lower_expr(base, program)?),
type_id: *type_id,
type_name: type_name.clone(),
updates: mir_updates,
},
expr,
))
}
ResolvedExpr::Attr(base, field) => MirExpr::Project(wrap(
MirProject {
base: Box::new(lower_expr(base, program)?),
field: field.clone(),
},
expr,
)),
ResolvedExpr::Match { subject, arms } => {
let mir_subject = lower_expr(subject, program)?;
let mir_arms = arms
.iter()
.map(|a| lower_arm(a, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::Match(wrap(
MirMatch {
subject: Box::new(mir_subject),
arms: mir_arms,
},
expr,
))
}
ResolvedExpr::ErrorProp(inner) => MirExpr::Try(Box::new(lower_expr(inner, program)?)),
ResolvedExpr::TailCall { target, args } => {
let mir_args = args
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::TailCall(wrap(
super::expr::MirTailCall {
target: *target,
args: mir_args,
},
expr,
))
}
ResolvedExpr::List(items) => {
let mir_items = items
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::List(mir_items)
}
ResolvedExpr::Tuple(items) => {
let mir_items = items
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::Tuple(mir_items)
}
ResolvedExpr::MapLiteral(pairs) => {
let mir_pairs = pairs
.iter()
.map(|(k, v)| Ok((lower_expr(k, program)?, lower_expr(v, program)?)))
.collect::<Result<Vec<_>, SkipReason>>()?;
MirExpr::MapLiteral(mir_pairs)
}
ResolvedExpr::InterpolatedStr(parts) => {
let mir_parts = parts
.iter()
.map(|p| match p {
crate::ir::hir::ResolvedStrPart::Literal(s) => {
Ok(super::expr::MirStrPart::Literal(s.clone()))
}
crate::ir::hir::ResolvedStrPart::Parsed(inner) => {
Ok(super::expr::MirStrPart::Expr(lower_expr(inner, program)?))
}
})
.collect::<Result<Vec<_>, SkipReason>>()?;
MirExpr::InterpolatedStr(mir_parts)
}
ResolvedExpr::IndependentProduct(items, unwrap_results) => {
let mir_items = items
.iter()
.map(|e| lower_expr(e, program))
.collect::<Result<Vec<_>, _>>()?;
MirExpr::IndependentProduct(wrap(
super::expr::MirIndependentProduct {
items: mir_items,
unwrap_results: *unwrap_results,
},
expr,
))
}
ResolvedExpr::Ident(name) => MirExpr::FnValue(name.clone()),
};
Ok(wrap(mir, expr))
}
fn lower_arm(arm: &ResolvedMatchArm, program: &mut MirProgram) -> Result<MirMatchArm, SkipReason> {
let slots: &[u16] = arm.binding_slots.get().map(Vec::as_slice).unwrap_or(&[]);
let mut cursor = 0usize;
let pattern = lower_pattern(&arm.pattern, slots, &mut cursor)?;
if cursor != slots.len() {
return Err(SkipReason::PatternSlotShortfall);
}
let body = lower_expr(&arm.body, program)?;
Ok(MirMatchArm { pattern, body })
}
fn lower_pattern(
pattern: &ResolvedPattern,
slots: &[u16],
cursor: &mut usize,
) -> Result<MirPattern, SkipReason> {
Ok(match pattern {
ResolvedPattern::Wildcard => MirPattern::Wildcard,
ResolvedPattern::Literal(lit) => MirPattern::Literal(lit.clone()),
ResolvedPattern::Ident(name) => {
let slot = take_slot(slots, cursor)?;
MirPattern::Bind(LocalId(u32::from(slot)), name.clone())
}
ResolvedPattern::EmptyList => MirPattern::EmptyList,
ResolvedPattern::Cons(head_name, tail_name) => {
let head = take_slot(slots, cursor)?;
let tail = take_slot(slots, cursor)?;
MirPattern::Cons {
head: LocalId(u32::from(head)),
head_name: head_name.clone(),
tail: LocalId(u32::from(tail)),
tail_name: tail_name.clone(),
}
}
ResolvedPattern::Tuple(items) => {
let mir_items = items
.iter()
.map(|p| lower_pattern(p, slots, cursor))
.collect::<Result<Vec<_>, _>>()?;
MirPattern::Tuple(mir_items)
}
ResolvedPattern::Ctor(ResolvedCtor::User { ctor_id, .. }, names) => {
let bindings = take_pattern_bindings(slots, cursor, names.len())?;
MirPattern::Ctor {
ctor: MirCtor::User(*ctor_id),
bindings,
binding_names: names.clone(),
}
}
ResolvedPattern::Ctor(ResolvedCtor::Builtin(bc), names) => {
let bindings = take_pattern_bindings(slots, cursor, names.len())?;
MirPattern::Ctor {
ctor: MirCtor::Builtin(*bc),
bindings,
binding_names: names.clone(),
}
}
ResolvedPattern::Ctor(ResolvedCtor::Unresolved { .. }, _) => {
return Err(SkipReason::UnresolvedCtor);
}
})
}
fn take_pattern_bindings(
slots: &[u16],
cursor: &mut usize,
arity: usize,
) -> Result<Vec<LocalId>, SkipReason> {
let mut bindings = Vec::with_capacity(arity);
for _ in 0..arity {
let slot = take_slot(slots, cursor)?;
bindings.push(LocalId(u32::from(slot)));
}
Ok(bindings)
}
fn take_slot(slots: &[u16], cursor: &mut usize) -> Result<u16, SkipReason> {
let slot = *slots.get(*cursor).ok_or(SkipReason::PatternSlotShortfall)?;
*cursor += 1;
Ok(slot)
}
fn wrap<T, U>(node: T, source: &Spanned<U>) -> Spanned<T> {
let ty = std::sync::OnceLock::new();
if let Some(t) = source.ty() {
let _ = ty.set(t.clone());
}
Spanned {
node,
line: source.line,
ty,
}
}
fn lower_stmt_chain(
stmts: &[ResolvedStmt],
resolution: &FnResolution,
next_synthetic_local: &mut u32,
program: &mut MirProgram,
) -> Result<Spanned<MirExpr>, SkipReason> {
let (last, rest) = stmts.split_last().ok_or(SkipReason::EmptyBody)?;
let ResolvedStmt::Expr(tail_expr) = last else {
return Err(SkipReason::BindingOnlyTail);
};
let mut body = lower_expr(tail_expr, program)?;
for stmt in rest.iter().rev() {
let (binding, binding_name, value_expr) = match stmt {
ResolvedStmt::Binding { name, value, .. } if name == "_" => {
let fresh = LocalId(*next_synthetic_local);
*next_synthetic_local += 1;
(fresh, String::new(), value)
}
ResolvedStmt::Binding { name, value, .. } => {
let slot = *resolution
.local_slots
.get(name)
.ok_or(SkipReason::BindingSlotLookupMissing)?;
(LocalId(u32::from(slot)), name.clone(), value)
}
ResolvedStmt::Expr(expr) => {
let fresh = LocalId(*next_synthetic_local);
*next_synthetic_local += 1;
(fresh, String::new(), expr)
}
};
let mir_value = lower_expr(value_expr, program)?;
let span = mir_value.line;
body = Spanned {
node: MirExpr::Let(Spanned {
node: MirLet {
binding,
binding_name,
value: Box::new(mir_value),
body: Box::new(body),
},
line: span,
ty: std::sync::OnceLock::new(),
}),
line: span,
ty: std::sync::OnceLock::new(),
};
}
Ok(body)
}