use crate::ast::{AnnotBool, BinOp, FnResolution, Literal, Module, Spanned, Type};
use crate::ir::identity::{CtorId, FnId, TypeId};
pub mod classify;
pub mod dump;
pub mod resolve;
pub use classify::{
ForwardSlot, ResolvedBodyBindingPlan, ResolvedBodyExprPlan, ResolvedBodyPlan,
ResolvedBoolSubjectPlan, ResolvedForwardCallPlan, ResolvedLeafOp, ResolvedThinBodyPlan,
ThinKind, call_plan_from_resolved_callee, classify_body_expr_plan_resolved,
classify_body_plan_resolved, classify_bool_match_shape_resolved,
classify_bool_subject_plan_resolved, classify_dispatch_pattern_resolved,
classify_dispatch_table_shape_resolved, classify_forward_call_resolved,
classify_leaf_op_resolved, classify_list_match_shape_resolved,
classify_match_dispatch_plan_resolved, classify_thin_fn_def_resolved, resolved_to_dotted,
semantic_constructor_from_resolved_ctor,
};
pub use dump::{dump_resolved_expr, dump_resolved_program};
pub use resolve::{ResolveCtx, resolve_fn_def_external, resolve_program, resolve_top_level};
pub fn count_unresolved_in_fn(fd: &ResolvedFnDef) -> usize {
let mut count = 0;
count_unresolved_in_body(&fd.body, &mut count);
count
}
fn count_unresolved_in_body(body: &ResolvedFnBody, out: &mut usize) {
match body {
ResolvedFnBody::Block(stmts) => {
for stmt in stmts {
count_unresolved_in_stmt(stmt, out);
}
}
}
}
fn count_unresolved_in_stmt(stmt: &ResolvedStmt, out: &mut usize) {
match stmt {
ResolvedStmt::Binding { value, .. } | ResolvedStmt::Expr(value) => {
count_unresolved_in_expr(&value.node, out)
}
}
}
fn count_unresolved_in_expr(expr: &ResolvedExpr, out: &mut usize) {
match expr {
ResolvedExpr::Literal(_) | ResolvedExpr::Ident(_) | ResolvedExpr::Resolved { .. } => {}
ResolvedExpr::Attr(obj, _) => count_unresolved_in_expr(&obj.node, out),
ResolvedExpr::Call(callee, args) => {
if let ResolvedCallee::Unresolved { callee } = callee {
*out += 1;
count_unresolved_in_expr(&callee.node, out);
}
for a in args {
count_unresolved_in_expr(&a.node, out);
}
}
ResolvedExpr::BinOp(_, l, r) => {
count_unresolved_in_expr(&l.node, out);
count_unresolved_in_expr(&r.node, out);
}
ResolvedExpr::Neg(inner) | ResolvedExpr::ErrorProp(inner) => {
count_unresolved_in_expr(&inner.node, out)
}
ResolvedExpr::Match { subject, arms } => {
count_unresolved_in_expr(&subject.node, out);
for arm in arms {
count_unresolved_in_pattern(&arm.pattern, out);
count_unresolved_in_expr(&arm.body.node, out);
}
}
ResolvedExpr::Ctor(ctor, args) => {
if matches!(ctor, ResolvedCtor::Unresolved { .. }) {
*out += 1;
}
for a in args {
count_unresolved_in_expr(&a.node, out);
}
}
ResolvedExpr::InterpolatedStr(parts) => {
for p in parts {
if let ResolvedStrPart::Parsed(inner) = p {
count_unresolved_in_expr(&inner.node, out);
}
}
}
ResolvedExpr::List(items)
| ResolvedExpr::Tuple(items)
| ResolvedExpr::IndependentProduct(items, _) => {
for i in items {
count_unresolved_in_expr(&i.node, out);
}
}
ResolvedExpr::MapLiteral(pairs) => {
for (k, v) in pairs {
count_unresolved_in_expr(&k.node, out);
count_unresolved_in_expr(&v.node, out);
}
}
ResolvedExpr::RecordCreate { fields, .. } => {
for (_, e) in fields {
count_unresolved_in_expr(&e.node, out);
}
}
ResolvedExpr::RecordUpdate { base, updates, .. } => {
count_unresolved_in_expr(&base.node, out);
for (_, e) in updates {
count_unresolved_in_expr(&e.node, out);
}
}
ResolvedExpr::TailCall { args, .. } => {
for a in args {
count_unresolved_in_expr(&a.node, out);
}
}
}
}
fn count_unresolved_in_pattern(pat: &ResolvedPattern, out: &mut usize) {
match pat {
ResolvedPattern::Wildcard
| ResolvedPattern::Literal(_)
| ResolvedPattern::Ident(_)
| ResolvedPattern::EmptyList
| ResolvedPattern::Cons(_, _) => {}
ResolvedPattern::Tuple(items) => {
for p in items {
count_unresolved_in_pattern(p, out);
}
}
ResolvedPattern::Ctor(ctor, _) => {
if matches!(ctor, ResolvedCtor::Unresolved { .. }) {
*out += 1;
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedExpr {
Literal(Literal),
Ident(String),
Resolved {
slot: u16,
name: String,
last_use: AnnotBool,
},
Attr(Box<Spanned<ResolvedExpr>>, String),
Call(ResolvedCallee, Vec<Spanned<ResolvedExpr>>),
BinOp(
BinOp,
Box<Spanned<ResolvedExpr>>,
Box<Spanned<ResolvedExpr>>,
),
Neg(Box<Spanned<ResolvedExpr>>),
Match {
subject: Box<Spanned<ResolvedExpr>>,
arms: Vec<ResolvedMatchArm>,
},
Ctor(ResolvedCtor, Vec<Spanned<ResolvedExpr>>),
ErrorProp(Box<Spanned<ResolvedExpr>>),
InterpolatedStr(Vec<ResolvedStrPart>),
List(Vec<Spanned<ResolvedExpr>>),
Tuple(Vec<Spanned<ResolvedExpr>>),
MapLiteral(Vec<(Spanned<ResolvedExpr>, Spanned<ResolvedExpr>)>),
RecordCreate {
type_id: Option<TypeId>,
type_name: String,
fields: Vec<(String, Spanned<ResolvedExpr>)>,
},
RecordUpdate {
type_id: Option<TypeId>,
type_name: String,
base: Box<Spanned<ResolvedExpr>>,
updates: Vec<(String, Spanned<ResolvedExpr>)>,
},
TailCall {
target: FnId,
args: Vec<Spanned<ResolvedExpr>>,
},
IndependentProduct(Vec<Spanned<ResolvedExpr>>, bool),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedCallee {
Fn(FnId),
Builtin(String),
Intrinsic(BuiltinIntrinsic),
LocalSlot {
slot: u16,
name: String,
last_use: AnnotBool,
},
Unresolved {
callee: Box<Spanned<ResolvedExpr>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinIntrinsic {
BufNew,
BufAppend,
BufAppendSepUnlessFirst,
BufFinalize,
ToStr,
}
impl BuiltinIntrinsic {
pub const fn name(self) -> &'static str {
match self {
Self::BufNew => "__buf_new",
Self::BufAppend => "__buf_append",
Self::BufAppendSepUnlessFirst => "__buf_append_sep_unless_first",
Self::BufFinalize => "__buf_finalize",
Self::ToStr => "__to_str",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"__buf_new" => Some(Self::BufNew),
"__buf_append" => Some(Self::BufAppend),
"__buf_append_sep_unless_first" => Some(Self::BufAppendSepUnlessFirst),
"__buf_finalize" => Some(Self::BufFinalize),
"__to_str" => Some(Self::ToStr),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedCtor {
User {
ctor_id: CtorId,
type_id: TypeId,
name: String,
},
Builtin(BuiltinCtor),
Unresolved {
name: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinCtor {
ResultOk,
ResultErr,
OptionSome,
OptionNone,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedStrPart {
Literal(String),
Parsed(Box<Spanned<ResolvedExpr>>),
}
#[derive(Debug)]
pub struct ResolvedMatchArm {
pub pattern: ResolvedPattern,
pub body: Box<Spanned<ResolvedExpr>>,
pub binding_slots: std::sync::OnceLock<Vec<u16>>,
}
impl Clone for ResolvedMatchArm {
fn clone(&self) -> Self {
let binding_slots = std::sync::OnceLock::new();
if let Some(v) = self.binding_slots.get() {
let _ = binding_slots.set(v.clone());
}
Self {
pattern: self.pattern.clone(),
body: self.body.clone(),
binding_slots,
}
}
}
impl PartialEq for ResolvedMatchArm {
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern && self.body == other.body
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedPattern {
Wildcard,
Literal(Literal),
Ident(String),
EmptyList,
Cons(String, String),
Tuple(Vec<ResolvedPattern>),
Ctor(ResolvedCtor, Vec<String>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedStmt {
Binding {
name: String,
ty_ann: Option<Type>,
value: Spanned<ResolvedExpr>,
},
Expr(Spanned<ResolvedExpr>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedFnBody {
Block(Vec<ResolvedStmt>),
}
impl ResolvedFnBody {
pub fn stmts(&self) -> &[ResolvedStmt] {
match self {
Self::Block(stmts) => stmts,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedFnDef {
pub fn_id: FnId,
pub name: String,
pub line: usize,
pub params: Vec<(String, Type)>,
pub return_type: Type,
pub effects: Vec<Spanned<String>>,
pub desc: Option<String>,
pub body: std::sync::Arc<ResolvedFnBody>,
pub resolution: Option<FnResolution>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedTopLevel {
Module(Module),
FnDef(ResolvedFnDef),
Passthrough(crate::ast::TopLevel),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::identity::TypeId;
#[test]
fn resolved_callee_user_fn_carries_typed_identity() {
let callee = ResolvedCallee::Fn(FnId(7));
let call = ResolvedExpr::Call(
callee,
vec![Spanned::new(ResolvedExpr::Literal(Literal::Int(42)), 3)],
);
let clone = call.clone();
assert_eq!(clone, call);
match clone {
ResolvedExpr::Call(ResolvedCallee::Fn(id), args) => {
assert_eq!(id, FnId(7));
assert_eq!(args.len(), 1);
}
_ => panic!("expected ResolvedExpr::Call(Fn, _)"),
}
}
#[test]
fn resolved_ctor_user_carries_ctor_and_type_id() {
let ctor = ResolvedCtor::User {
ctor_id: CtorId(2),
type_id: TypeId(5),
name: "Circle".to_string(),
};
let expr = ResolvedExpr::Ctor(
ctor,
vec![Spanned::new(ResolvedExpr::Literal(Literal::Float(1.0)), 1)],
);
let ResolvedExpr::Ctor(
ResolvedCtor::User {
ctor_id,
type_id,
name,
},
..,
) = expr
else {
panic!("expected User ctor variant")
};
assert_eq!(ctor_id, CtorId(2));
assert_eq!(type_id, TypeId(5));
assert_eq!(name, "Circle");
}
#[test]
fn resolved_ctor_builtin_variants_distinguish() {
let variants = [
BuiltinCtor::ResultOk,
BuiltinCtor::ResultErr,
BuiltinCtor::OptionSome,
BuiltinCtor::OptionNone,
];
for v in variants {
let c = ResolvedCtor::Builtin(v);
match c {
ResolvedCtor::Builtin(got) => assert_eq!(got, v),
_ => panic!("builtin ctor lost variant kind"),
}
}
}
#[test]
fn resolved_pattern_ctor_round_trips_through_clone() {
let pat = ResolvedPattern::Ctor(
ResolvedCtor::User {
ctor_id: CtorId(11),
type_id: TypeId(3),
name: "Square".to_string(),
},
vec!["side".to_string()],
);
assert_eq!(pat.clone(), pat);
}
#[test]
fn resolved_match_arm_equality_ignores_binding_slots() {
let body = Box::new(Spanned::new(ResolvedExpr::Literal(Literal::Int(0)), 1));
let a = ResolvedMatchArm {
pattern: ResolvedPattern::Wildcard,
body: body.clone(),
binding_slots: std::sync::OnceLock::new(),
};
let b = ResolvedMatchArm {
pattern: ResolvedPattern::Wildcard,
body,
binding_slots: {
let lock = std::sync::OnceLock::new();
let _ = lock.set(vec![3, 4]);
lock
},
};
assert_eq!(a, b);
}
#[test]
fn resolved_fn_def_carries_fn_id_and_resolved_param_types() {
let body = ResolvedFnBody::Block(vec![ResolvedStmt::Expr(Spanned::new(
ResolvedExpr::Literal(Literal::Int(1)),
1,
))]);
let def = ResolvedFnDef {
fn_id: FnId(0),
name: "id".to_string(),
line: 1,
params: vec![("x".to_string(), Type::Int)],
return_type: Type::Int,
effects: vec![],
desc: None,
body: std::sync::Arc::new(body),
resolution: None,
};
assert_eq!(def.fn_id, FnId(0));
assert_eq!(def.params[0].1, Type::Int);
assert_eq!(def.return_type, Type::Int);
assert_eq!(def.body.stmts().len(), 1);
}
#[test]
fn resolved_stmt_binding_threads_optional_resolved_type_ann() {
let stmt_with = ResolvedStmt::Binding {
name: "n".to_string(),
ty_ann: Some(Type::Int),
value: Spanned::new(ResolvedExpr::Literal(Literal::Int(0)), 1),
};
let stmt_without = ResolvedStmt::Binding {
name: "n".to_string(),
ty_ann: None,
value: Spanned::new(ResolvedExpr::Literal(Literal::Int(0)), 1),
};
assert_ne!(stmt_with, stmt_without);
}
#[test]
fn resolved_callee_unresolved_passes_through_inner_expr() {
let inner = Spanned::new(ResolvedExpr::Ident("dynamic".to_string()), 1);
let call = ResolvedExpr::Call(
ResolvedCallee::Unresolved {
callee: Box::new(inner.clone()),
},
vec![],
);
let clone = call.clone();
assert_eq!(clone, call);
}
#[test]
fn variant_coverage_matches_expr() {
use crate::ast::Expr;
fn cover(expr: &Expr) -> &'static str {
match expr {
Expr::Literal(_) => "Literal → Literal",
Expr::Ident(_) => "Ident → Ident",
Expr::Resolved { .. } => "Resolved → Resolved",
Expr::Attr(_, _) => "Attr → Attr",
Expr::FnCall(_, _) => "FnCall → Call",
Expr::BinOp(_, _, _) => "BinOp → BinOp",
Expr::Neg(_) => "Neg → Neg",
Expr::Match { .. } => "Match → Match",
Expr::Constructor(_, _) => "Constructor → Ctor",
Expr::ErrorProp(_) => "ErrorProp → ErrorProp",
Expr::InterpolatedStr(_) => "InterpolatedStr → InterpolatedStr",
Expr::List(_) => "List → List",
Expr::Tuple(_) => "Tuple → Tuple",
Expr::MapLiteral(_) => "MapLiteral → MapLiteral",
Expr::RecordCreate { .. } => "RecordCreate → RecordCreate",
Expr::RecordUpdate { .. } => "RecordUpdate → RecordUpdate",
Expr::TailCall(_) => "TailCall → TailCall",
Expr::IndependentProduct(_, _) => "IndependentProduct → IndependentProduct",
}
}
let probe = Expr::Literal(Literal::Int(0));
assert!(cover(&probe).contains("Literal"));
let _: ResolvedExpr = ResolvedExpr::Literal(Literal::Int(0));
let _: ResolvedExpr = ResolvedExpr::Ident(String::new());
let _: ResolvedExpr = ResolvedExpr::Resolved {
slot: 0,
name: String::new(),
last_use: AnnotBool(false),
};
let dummy = || Box::new(Spanned::new(ResolvedExpr::Literal(Literal::Int(0)), 1));
let _: ResolvedExpr = ResolvedExpr::Attr(dummy(), String::new());
let _: ResolvedExpr = ResolvedExpr::Call(ResolvedCallee::Fn(FnId(0)), vec![]);
let _: ResolvedExpr = ResolvedExpr::BinOp(BinOp::Add, dummy(), dummy());
let _: ResolvedExpr = ResolvedExpr::Neg(dummy());
let _: ResolvedExpr = ResolvedExpr::Match {
subject: dummy(),
arms: vec![],
};
let _: ResolvedExpr =
ResolvedExpr::Ctor(ResolvedCtor::Builtin(BuiltinCtor::OptionNone), vec![]);
let _: ResolvedExpr = ResolvedExpr::ErrorProp(dummy());
let _: ResolvedExpr = ResolvedExpr::InterpolatedStr(vec![]);
let _: ResolvedExpr = ResolvedExpr::List(vec![]);
let _: ResolvedExpr = ResolvedExpr::Tuple(vec![]);
let _: ResolvedExpr = ResolvedExpr::MapLiteral(vec![]);
let _: ResolvedExpr = ResolvedExpr::RecordCreate {
type_id: None,
type_name: String::new(),
fields: vec![],
};
let _: ResolvedExpr = ResolvedExpr::RecordUpdate {
type_id: None,
type_name: String::new(),
base: dummy(),
updates: vec![],
};
let _: ResolvedExpr = ResolvedExpr::TailCall {
target: FnId(0),
args: vec![],
};
let _: ResolvedExpr = ResolvedExpr::IndependentProduct(vec![], false);
}
}