use std::collections::HashSet;
use crate::ast::Type;
use crate::ir::hir::BuiltinCtor;
use super::expr::{MirCtor, MirExpr};
use super::program::MirProgram;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct InstantiationRegistry {
pub lists: Vec<Type>,
pub maps: Vec<(Type, Type)>,
pub vectors: Vec<Type>,
pub tuples: Vec<Vec<Type>>,
pub options: Vec<Type>,
pub results: Vec<(Type, Type)>,
}
impl InstantiationRegistry {
pub fn total(&self) -> usize {
self.lists.len()
+ self.maps.len()
+ self.vectors.len()
+ self.tuples.len()
+ self.options.len()
+ self.results.len()
}
}
pub fn discover_instantiations(program: &MirProgram) -> InstantiationRegistry {
let mut walker = Walker::default();
let mut ordered: Vec<_> = program.iter().collect();
ordered.sort_by_key(|(fn_id, _)| fn_id.0);
for (_, mir_fn) in ordered {
walker.register_ty(mir_fn.body.ty());
walker.visit_expr(&mir_fn.body.node);
}
walker.registry
}
#[derive(Default)]
struct Walker {
registry: InstantiationRegistry,
seen_lists: HashSet<String>,
seen_maps: HashSet<String>,
seen_vectors: HashSet<String>,
seen_tuples: HashSet<String>,
seen_options: HashSet<String>,
seen_results: HashSet<String>,
}
impl Walker {
fn visit_expr(&mut self, expr: &MirExpr) {
match expr {
MirExpr::Literal(_) | MirExpr::Local(_) | MirExpr::FnValue(_) => {}
MirExpr::Neg(inner) => {
self.register_ty(inner.ty());
self.visit_expr(&inner.node);
}
MirExpr::BinOp(spanned_bop) => {
let bop = &spanned_bop.node;
self.register_ty(bop.lhs.ty());
self.visit_expr(&bop.lhs.node);
self.register_ty(bop.rhs.ty());
self.visit_expr(&bop.rhs.node);
}
MirExpr::Let(spanned_let) => {
let let_node = &spanned_let.node;
self.register_ty(let_node.value.ty());
self.visit_expr(&let_node.value.node);
self.register_ty(let_node.body.ty());
self.visit_expr(&let_node.body.node);
}
MirExpr::Call(spanned_call) => {
for arg in &spanned_call.node.args {
self.register_ty(arg.ty());
self.visit_expr(&arg.node);
}
}
MirExpr::TailCall(spanned_tc) => {
for arg in &spanned_tc.node.args {
self.register_ty(arg.ty());
self.visit_expr(&arg.node);
}
}
MirExpr::Match(spanned_match) => {
let m = &spanned_match.node;
self.register_ty(m.subject.ty());
self.visit_expr(&m.subject.node);
for arm in &m.arms {
self.register_ty(arm.body.ty());
self.visit_expr(&arm.body.node);
}
}
MirExpr::IfThenElse(spanned_ite) => {
let ite = &spanned_ite.node;
self.register_ty(ite.cond.ty());
self.visit_expr(&ite.cond.node);
self.register_ty(ite.then_branch.ty());
self.visit_expr(&ite.then_branch.node);
self.register_ty(ite.else_branch.ty());
self.visit_expr(&ite.else_branch.node);
}
MirExpr::Construct(spanned_ctor) => {
let con = &spanned_ctor.node;
if let MirCtor::Builtin(bc) = con.ctor {
self.seed_builtin_ctor(bc, &con.args);
}
for arg in &con.args {
self.register_ty(arg.ty());
self.visit_expr(&arg.node);
}
}
MirExpr::RecordCreate(spanned_rec) => {
for f in &spanned_rec.node.fields {
self.register_ty(f.value.ty());
self.visit_expr(&f.value.node);
}
}
MirExpr::RecordUpdate(spanned_upd) => {
self.register_ty(spanned_upd.node.base.ty());
self.visit_expr(&spanned_upd.node.base.node);
for f in &spanned_upd.node.updates {
self.register_ty(f.value.ty());
self.visit_expr(&f.value.node);
}
}
MirExpr::Project(spanned_proj) => {
self.register_ty(spanned_proj.node.base.ty());
self.visit_expr(&spanned_proj.node.base.node);
}
MirExpr::Try(inner)
| MirExpr::Return(inner)
| MirExpr::Box(inner)
| MirExpr::Unbox(inner) => {
self.register_ty(inner.ty());
self.visit_expr(&inner.node);
}
MirExpr::List(items) => {
if let Some(first) = items.first().and_then(|i| i.ty()) {
self.register_list_t(first);
}
for item in items {
self.register_ty(item.ty());
self.visit_expr(&item.node);
}
}
MirExpr::Tuple(items) => {
let tys: Vec<Type> = items.iter().filter_map(|i| i.ty().cloned()).collect();
if tys.len() == items.len() && !tys.is_empty() {
self.register_tuple(tys);
}
for item in items {
self.register_ty(item.ty());
self.visit_expr(&item.node);
}
}
MirExpr::MapLiteral(entries) => {
if let Some((k_first, v_first)) = entries.first()
&& let (Some(k_ty), Some(v_ty)) = (k_first.ty(), v_first.ty())
{
self.register_map_kv(k_ty, v_ty);
}
for (k, v) in entries {
self.register_ty(k.ty());
self.visit_expr(&k.node);
self.register_ty(v.ty());
self.visit_expr(&v.node);
}
}
MirExpr::InterpolatedStr(parts) => {
for part in parts {
if let super::expr::MirStrPart::Expr(e) = part {
self.register_ty(e.ty());
self.visit_expr(&e.node);
}
}
}
MirExpr::IndependentProduct(spanned_ip) => {
for item in &spanned_ip.node.items {
self.register_ty(item.ty());
self.visit_expr(&item.node);
}
}
}
}
fn seed_builtin_ctor(&mut self, ctor: BuiltinCtor, args: &[crate::ast::Spanned<MirExpr>]) {
match ctor {
BuiltinCtor::OptionSome => {
if let Some(first) = args.first().and_then(|a| a.ty()) {
self.register_option_t(first);
}
}
BuiltinCtor::OptionNone => {}
BuiltinCtor::ResultOk => {
if let Some(t) = args.first().and_then(|a| a.ty()) {
self.register_result_te(t, &Type::Invalid);
}
}
BuiltinCtor::ResultErr => {
if let Some(e) = args.first().and_then(|a| a.ty()) {
self.register_result_te(&Type::Invalid, e);
}
}
}
}
fn register_ty(&mut self, ty: Option<&Type>) {
let Some(ty) = ty else { return };
match ty {
Type::List(inner) => {
self.register_list_t(inner);
}
Type::Vector(inner) => {
self.register_vector_t(inner);
}
Type::Option(inner) => {
self.register_option_t(inner);
}
Type::Map(k, v) => {
self.register_map_kv(k, v);
}
Type::Result(t, e) => {
self.register_result_te(t, e);
}
Type::Tuple(items) => {
self.register_tuple(items.clone());
}
Type::Fn(args, ret, _effects) => {
for a in args {
self.register_ty(Some(a));
}
self.register_ty(Some(ret));
}
Type::Int
| Type::Float
| Type::Str
| Type::Bool
| Type::Unit
| Type::Var(_)
| Type::Invalid
| Type::Named { .. } => {}
}
}
fn register_list_t(&mut self, t: &Type) {
let key = format!("{t:?}");
if self.seen_lists.insert(key) {
self.registry.lists.push(t.clone());
}
self.register_ty(Some(t));
}
fn register_vector_t(&mut self, t: &Type) {
let key = format!("{t:?}");
if self.seen_vectors.insert(key) {
self.registry.vectors.push(t.clone());
}
self.register_ty(Some(t));
}
fn register_option_t(&mut self, t: &Type) {
let key = format!("{t:?}");
if self.seen_options.insert(key) {
self.registry.options.push(t.clone());
}
self.register_ty(Some(t));
}
fn register_map_kv(&mut self, k: &Type, v: &Type) {
let key = format!("({k:?},{v:?})");
if self.seen_maps.insert(key) {
self.registry.maps.push((k.clone(), v.clone()));
}
self.register_ty(Some(k));
self.register_ty(Some(v));
}
fn register_result_te(&mut self, t: &Type, e: &Type) {
let key = format!("({t:?},{e:?})");
if self.seen_results.insert(key) {
self.registry.results.push((t.clone(), e.clone()));
}
self.register_ty(Some(t));
self.register_ty(Some(e));
}
fn register_tuple(&mut self, items: Vec<Type>) {
let key = format!("{items:?}");
if self.seen_tuples.insert(key) {
for item in &items {
self.register_ty(Some(item));
}
self.registry.tuples.push(items);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Literal, Spanned};
use crate::ir::FnId;
use crate::ir::mir::{MirExpr, MirFn};
fn span_with_ty<T>(node: T, ty: Type) -> Spanned<T> {
let cell = std::sync::OnceLock::new();
let _ = cell.set(ty);
Spanned {
node,
line: 0,
ty: cell,
}
}
fn span<T>(node: T) -> Spanned<T> {
Spanned {
node,
line: 0,
ty: std::sync::OnceLock::new(),
}
}
fn one_fn_program(body: Spanned<MirExpr>) -> MirProgram {
let mut p = MirProgram::empty();
p.fns.insert(
FnId(0),
MirFn {
fn_id: FnId(0),
name: "f".to_string(),
params: vec![],
return_type: "Int".to_string(),
effects: vec![],
body,
local_count: 0,
aliased_slots: std::sync::Arc::new(Vec::new()),
repr: crate::ir::mir::MirFnRepr::default(),
},
);
p
}
#[test]
fn empty_program_yields_empty_registry() {
let registry = discover_instantiations(&MirProgram::empty());
assert_eq!(registry.total(), 0);
}
#[test]
fn list_int_stamp_seeds_list_t() {
let body = span_with_ty(
MirExpr::Literal(span(Literal::Int(0))),
Type::List(Box::new(Type::Int)),
);
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.lists, vec![Type::Int]);
}
#[test]
fn nested_list_map_str_int_registers_every_layer() {
let inner = Type::Map(Box::new(Type::Str), Box::new(Type::Int));
let outer = Type::List(Box::new(inner.clone()));
let body = span_with_ty(MirExpr::Literal(span(Literal::Int(0))), outer);
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.lists, vec![inner.clone()]);
assert_eq!(registry.maps, vec![(Type::Str, Type::Int)]);
}
#[test]
fn deeply_nested_user_example_registers_all_layers() {
let user_type = Type::named("UserType");
let inny = Type::named("InnyTyp");
let tuple = Type::Tuple(vec![inny.clone(), Type::Int]);
let map = Type::Map(Box::new(user_type.clone()), Box::new(tuple.clone()));
let list = Type::List(Box::new(map.clone()));
let body = span_with_ty(MirExpr::Literal(span(Literal::Int(0))), list);
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.lists, vec![map.clone()]);
assert_eq!(registry.maps, vec![(user_type, tuple.clone())]);
let Type::Tuple(items) = &tuple else {
unreachable!()
};
assert_eq!(registry.tuples, vec![items.clone()]);
}
#[test]
fn duplicates_collapse_within_kind() {
let l_int = Type::List(Box::new(Type::Int));
let bop = super::super::expr::MirBinOp {
op: crate::ast::BinOp::Add,
lhs: Box::new(span_with_ty(
MirExpr::Literal(span(Literal::Int(1))),
l_int.clone(),
)),
rhs: Box::new(span_with_ty(
MirExpr::Literal(span(Literal::Int(2))),
l_int.clone(),
)),
};
let body = span(MirExpr::BinOp(span(bop)));
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.lists.len(), 1);
}
#[test]
fn distinct_list_t_kinds_register_separately() {
let bop = super::super::expr::MirBinOp {
op: crate::ast::BinOp::Add,
lhs: Box::new(span_with_ty(
MirExpr::Literal(span(Literal::Int(1))),
Type::List(Box::new(Type::Int)),
)),
rhs: Box::new(span_with_ty(
MirExpr::Literal(span(Literal::Int(2))),
Type::List(Box::new(Type::Str)),
)),
};
let body = span(MirExpr::BinOp(span(bop)));
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.lists.len(), 2);
assert!(registry.lists.contains(&Type::Int));
assert!(registry.lists.contains(&Type::Str));
}
#[test]
fn vector_and_option_kinds_route_to_their_own_buckets() {
let bop = super::super::expr::MirBinOp {
op: crate::ast::BinOp::Add,
lhs: Box::new(span_with_ty(
MirExpr::Literal(span(Literal::Int(0))),
Type::Vector(Box::new(Type::Bool)),
)),
rhs: Box::new(span_with_ty(
MirExpr::Literal(span(Literal::Int(0))),
Type::Option(Box::new(Type::Float)),
)),
};
let body = span(MirExpr::BinOp(span(bop)));
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.vectors, vec![Type::Bool]);
assert_eq!(registry.options, vec![Type::Float]);
}
#[test]
fn result_pair_registers_under_results() {
let body = span_with_ty(
MirExpr::Literal(span(Literal::Int(0))),
Type::Result(Box::new(Type::Int), Box::new(Type::Str)),
);
let registry = discover_instantiations(&one_fn_program(body));
assert_eq!(registry.results, vec![(Type::Int, Type::Str)]);
}
}