yulang-runtime 0.1.0

Runtime IR, validation, monomorphization, and VM support for Yulang
Documentation
use super::*;

fn reachable_expr_binding_paths(
    bindings: &[Binding],
    root_exprs: &[Expr],
    roots: &[Root],
) -> HashSet<typed_ir::Path> {
    let bindings_by_path = bindings
        .iter()
        .map(|binding| (binding.name.clone(), binding))
        .collect::<HashMap<_, _>>();
    let mut reachable = HashSet::new();
    let mut stack = Vec::new();
    for expr in root_exprs {
        let mut vars = HashSet::new();
        collect_expr_vars(expr, &mut vars);
        stack.extend(vars);
    }
    for root in roots {
        match root {
            Root::Binding(path) => stack.push(path.clone()),
            Root::Expr(index) => {
                if let Some(expr) = root_exprs.get(*index) {
                    let mut vars = HashSet::new();
                    collect_expr_vars(expr, &mut vars);
                    stack.extend(vars);
                }
            }
        }
    }
    while let Some(path) = stack.pop() {
        if !reachable.insert(path.clone()) {
            continue;
        }
        let Some(binding) = bindings_by_path.get(&path) else {
            continue;
        };
        let mut vars = HashSet::new();
        collect_expr_vars(&binding.body, &mut vars);
        stack.extend(vars);
    }
    reachable
}

pub(super) fn final_reachable_binding_paths(module: &Module) -> HashSet<typed_ir::Path> {
    module
        .bindings
        .iter()
        .map(|binding| binding.name.clone())
        .collect()
}

pub(super) fn root_reachable_binding_paths(module: &Module) -> HashSet<typed_ir::Path> {
    reachable_expr_binding_paths(&module.bindings, &module.root_exprs, &module.roots)
}

pub(super) fn prune_unreachable_bindings(module: Module) -> Module {
    let reachable =
        reachable_expr_binding_paths(&module.bindings, &module.root_exprs, &module.roots);
    let bindings = module
        .bindings
        .into_iter()
        .filter(|binding| reachable.contains(&binding.name))
        .collect::<Vec<_>>();
    let binding_names = bindings
        .iter()
        .map(|binding| binding.name.clone())
        .collect::<HashSet<_>>();
    let roots = module
        .roots
        .into_iter()
        .filter(|root| match root {
            Root::Binding(path) => binding_names.contains(path),
            Root::Expr(_) => true,
        })
        .collect();
    Module {
        path: module.path,
        bindings,
        root_exprs: module.root_exprs,
        roots,
        role_impls: module.role_impls,
    }
}

pub(super) fn prune_unreachable_specializations(module: Module) -> Module {
    let reachable =
        reachable_expr_binding_paths(&module.bindings, &module.root_exprs, &module.roots);
    let bindings = module
        .bindings
        .into_iter()
        .filter(|binding| {
            !is_demand_specialization_path(&binding.name) || reachable.contains(&binding.name)
        })
        .collect::<Vec<_>>();
    Module {
        path: module.path,
        bindings,
        root_exprs: module.root_exprs,
        roots: module.roots,
        role_impls: module.role_impls,
    }
}

fn is_demand_specialization_path(path: &typed_ir::Path) -> bool {
    path.segments
        .last()
        .and_then(|name| name.0.rsplit_once("__ddmono"))
        .is_some_and(|(_, suffix)| {
            !suffix.is_empty() && suffix.bytes().all(|byte| byte.is_ascii_digit())
        })
}

pub(super) fn ensure_monomorphic_bindings(module: &Module) -> RuntimeResult<()> {
    for binding in &module.bindings {
        if !binding.type_params.is_empty() {
            if std::env::var_os("YULANG_DEBUG_MONO_PIPELINE").is_some() {
                let mut referrers = Vec::new();
                for candidate in &module.bindings {
                    let mut vars = HashSet::new();
                    collect_expr_vars(&candidate.body, &mut vars);
                    if vars.contains(&binding.name) {
                        referrers.push(candidate.name.clone());
                    }
                }
                let mut root_refs = Vec::new();
                for (index, root) in module.root_exprs.iter().enumerate() {
                    let mut vars = HashSet::new();
                    collect_expr_vars(root, &mut vars);
                    if vars.contains(&binding.name) {
                        root_refs.push(index);
                    }
                }
                eprintln!(
                    "residual polymorphic binding {:?} is referenced by {:?}; root refs {:?}",
                    binding.name, referrers, root_refs
                );
            }
            return Err(RuntimeError::ResidualPolymorphicBinding {
                path: binding.name.clone(),
                vars: binding.type_params.clone(),
                source: crate::diagnostic::ResidualPolymorphicSource::TypeParams,
            });
        }
        let mut vars = BTreeSet::new();
        collect_expr_type_vars(&binding.body, &mut vars);
        collect_core_type_vars(&binding.scheme.body, &mut vars);
        for requirement in &binding.scheme.requirements {
            for arg in &requirement.args {
                match arg {
                    typed_ir::RoleRequirementArg::Input(bounds)
                    | typed_ir::RoleRequirementArg::Associated { bounds, .. } => {
                        if let Some(lower) = bounds.lower.as_deref() {
                            collect_core_type_vars(lower, &mut vars);
                        }
                        if let Some(upper) = bounds.upper.as_deref() {
                            collect_core_type_vars(upper, &mut vars);
                        }
                    }
                }
            }
        }
        if !vars.is_empty() {
            return Err(RuntimeError::ResidualPolymorphicBinding {
                path: binding.name.clone(),
                vars: vars.into_iter().collect(),
                source: crate::diagnostic::ResidualPolymorphicSource::RuntimeTypes,
            });
        }
    }
    Ok(())
}