yulang-runtime 0.1.1

Runtime IR, validation, monomorphization, and VM support for Yulang
Documentation
use std::collections::HashMap;

use yulang_typed_ir as typed_ir;

use crate::ir::{Expr, ExprKind, Module, Stmt};
use crate::types::effect_paths;

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct DemandSemantics {
    handler_bindings: HashMap<typed_ir::Path, Vec<typed_ir::Path>>,
}

impl DemandSemantics {
    pub(super) fn from_module(module: &Module) -> Self {
        let handler_bindings = module
            .bindings
            .iter()
            .filter_map(|binding| {
                expr_handler_consumes(&binding.body)
                    .map(|consumes| (binding.name.clone(), consumes))
            })
            .collect();
        Self { handler_bindings }
    }

    pub(super) fn is_effect_polymorphic_higher_order_target(
        &self,
        _target: &typed_ir::Path,
    ) -> bool {
        false
    }
}

pub(super) fn pure_handler_bindings(
    module: &Module,
) -> HashMap<typed_ir::Path, Vec<typed_ir::Path>> {
    module
        .bindings
        .iter()
        .filter_map(|binding| {
            expr_pure_handler_consumes(&binding.body)
                .map(|consumes| (binding.name.clone(), consumes))
        })
        .collect()
}

pub(super) fn consumed_effects_for_target(
    _semantics: &DemandSemantics,
    pure_handlers: &HashMap<typed_ir::Path, Vec<typed_ir::Path>>,
    target: &typed_ir::Path,
) -> Vec<typed_ir::Path> {
    pure_handlers.get(target).cloned().unwrap_or_default()
}

pub(super) fn known_consumed_effects_for_target(
    semantics: &DemandSemantics,
    target: &typed_ir::Path,
) -> Vec<typed_ir::Path> {
    semantics
        .handler_bindings
        .get(target)
        .cloned()
        .unwrap_or_default()
}

fn expr_handler_consumes(expr: &Expr) -> Option<Vec<typed_ir::Path>> {
    match &expr.kind {
        ExprKind::Handle { handler, .. } => Some(handler.consumes.clone()),
        ExprKind::Lambda { body, .. }
        | ExprKind::BindHere { expr: body }
        | ExprKind::Thunk { expr: body, .. }
        | ExprKind::LocalPushId { body, .. }
        | ExprKind::AddId { thunk: body, .. }
        | ExprKind::Coerce { expr: body, .. }
        | ExprKind::Pack { expr: body, .. } => expr_handler_consumes(body),
        ExprKind::Block {
            tail: Some(tail), ..
        } => expr_handler_consumes(tail),
        ExprKind::Block { stmts, tail: None } => match stmts.last() {
            Some(Stmt::Expr(expr)) => expr_handler_consumes(expr),
            _ => None,
        },
        _ => None,
    }
}

fn expr_pure_handler_consumes(expr: &Expr) -> Option<Vec<typed_ir::Path>> {
    match &expr.kind {
        ExprKind::Handle { handler, .. }
            if handler
                .residual_after
                .as_ref()
                .is_some_and(core_effect_is_empty) =>
        {
            Some(handler.consumes.clone())
        }
        ExprKind::Lambda { body, .. }
        | ExprKind::BindHere { expr: body }
        | ExprKind::Thunk { expr: body, .. }
        | ExprKind::LocalPushId { body, .. }
        | ExprKind::AddId { thunk: body, .. }
        | ExprKind::Coerce { expr: body, .. }
        | ExprKind::Pack { expr: body, .. } => expr_pure_handler_consumes(body),
        ExprKind::Block {
            tail: Some(tail), ..
        } => expr_pure_handler_consumes(tail),
        ExprKind::Block { stmts, tail: None } => match stmts.last() {
            Some(Stmt::Expr(expr)) => expr_pure_handler_consumes(expr),
            _ => None,
        },
        _ => None,
    }
}

fn core_effect_is_empty(effect: &typed_ir::Type) -> bool {
    matches!(effect, typed_ir::Type::Never)
        || matches!(
            effect,
            typed_ir::Type::Row { items, tail }
                if items.is_empty() && matches!(tail.as_ref(), typed_ir::Type::Never)
        )
        || effect_paths(effect).is_empty()
}