yulang-runtime 0.1.0

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

#[derive(Clone)]
pub(super) struct FunctionParts {
    pub(super) param: RuntimeType,
    pub(super) ret: RuntimeType,
}

pub(super) fn function_parts(ty: &RuntimeType) -> RuntimeResult<FunctionParts> {
    match ty {
        RuntimeType::Fun { param, ret } => Ok(FunctionParts {
            param: (**param).clone(),
            ret: (**ret).clone(),
        }),
        RuntimeType::Core(core @ typed_ir::Type::Fun { .. }) => {
            let ty = project_runtime_hir_type_with_vars(core, &BTreeSet::new());
            function_parts(&ty)
        }
        RuntimeType::Thunk { value, .. } => function_parts(value),
        other => Err(RuntimeError::NonFunctionCallee {
            ty: diagnostic_core_type(other),
        }),
    }
}

pub(super) fn bind_here_if_thunk(expr: Expr, value_ty: RuntimeType) -> Expr {
    if matches!(expr.ty, RuntimeType::Thunk { .. }) {
        Expr::typed(
            ExprKind::BindHere {
                expr: Box::new(expr),
            },
            value_ty,
        )
    } else {
        expr
    }
}

pub(super) fn erased_fun_type(param: RuntimeType, ret: RuntimeType) -> RuntimeType {
    RuntimeType::fun(param, ret)
}

pub(super) fn runtime_bounds_type(bounds: &typed_ir::TypeBounds) -> Option<typed_ir::Type> {
    project_runtime_bounds(bounds)
}

pub(super) fn record_field_expected(
    expected: Option<&typed_ir::Type>,
    name: &typed_ir::Name,
) -> Option<typed_ir::Type> {
    match expected {
        Some(typed_ir::Type::Record(record)) => record
            .fields
            .iter()
            .find(|field| field.name == *name)
            .map(|field| field.value.clone()),
        _ => None,
    }
}

pub(super) fn variant_payload_expected(
    expected: Option<&typed_ir::Type>,
    tag: &typed_ir::Name,
) -> Option<typed_ir::Type> {
    match expected {
        Some(typed_ir::Type::Variant(variant)) => {
            variant_payload_expected_from_variant(variant, tag)
        }
        Some(typed_ir::Type::Union(items) | typed_ir::Type::Inter(items)) => items
            .iter()
            .find_map(|item| variant_payload_expected(Some(item), tag)),
        _ => None,
    }
}

fn variant_payload_expected_from_variant(
    variant: &typed_ir::VariantType,
    tag: &typed_ir::Name,
) -> Option<typed_ir::Type> {
    variant
        .cases
        .iter()
        .find(|case| case.name == *tag)
        .and_then(|case| variant_case_payload_value_type(&case.payloads))
}

fn variant_case_payload_value_type(payloads: &[typed_ir::Type]) -> Option<typed_ir::Type> {
    match payloads {
        [] => None,
        [payload] => Some(payload.clone()),
        payloads => Some(typed_ir::Type::Tuple(payloads.to_vec())),
    }
}

pub(super) fn select_field_type(
    ty: &typed_ir::Type,
    field: &typed_ir::Name,
) -> RuntimeResult<typed_ir::Type> {
    match ty {
        typed_ir::Type::Any => Ok(typed_ir::Type::Any),
        typed_ir::Type::Record(record) => record
            .fields
            .iter()
            .find(|candidate| candidate.name == *field)
            .map(|candidate| candidate.value.clone())
            .ok_or_else(|| RuntimeError::UnsupportedSelectBase {
                field: field.clone(),
                ty: ty.clone(),
            }),
        _ => Err(RuntimeError::UnsupportedSelectBase {
            field: field.clone(),
            ty: ty.clone(),
        }),
    }
}

pub(super) fn unary_runtime_container_item_type(ty: &typed_ir::Type) -> Option<typed_ir::Type> {
    match ty {
        typed_ir::Type::Named { args, .. } if args.len() == 1 => {
            args.first().and_then(|arg| match arg {
                typed_ir::TypeArg::Type(ty) => Some(ty.clone()),
                typed_ir::TypeArg::Bounds(bounds) => runtime_bounds_type(bounds),
            })
        }
        _ => None,
    }
}

pub(super) fn require_same_type(
    expected: &typed_ir::Type,
    actual: &typed_ir::Type,
    source: TypeSource,
) -> RuntimeResult<()> {
    if core_types_compatible(expected, actual)
        || effect_compatible(expected, actual)
        || effect_compatible(actual, expected)
    {
        return Ok(());
    }
    if std::env::var_os("YULANG_DEBUG_RUNTIME_TYPE").is_some() {
        eprintln!("lower require_same_type {source:?}: {expected:?} / {actual:?}");
    }
    Err(RuntimeError::TypeMismatch {
        expected: expected.clone(),
        actual: actual.clone(),
        source,
        context: None,
    })
}

pub(super) fn should_use_visible_root_type(
    graph: &typed_ir::Type,
    visible: &typed_ir::Type,
) -> bool {
    (matches!(graph, typed_ir::Type::Any) && !matches!(visible, typed_ir::Type::Any))
        || matches!(
            (graph, visible),
            (typed_ir::Type::Tuple(graph_items), typed_ir::Type::Tuple(visible_items))
                if graph_items.len() != visible_items.len()
        )
}

pub(super) fn can_use_visible_root_type_without_graph(
    expr: &typed_ir::Expr,
    visible: &typed_ir::Type,
) -> bool {
    matches!(expr, typed_ir::Expr::Var(_) | typed_ir::Expr::Tuple(_))
        || (matches!(expr, typed_ir::Expr::Apply { .. }) && is_concrete_visible_root_type(visible))
}

pub(super) fn is_concrete_visible_root_type(ty: &typed_ir::Type) -> bool {
    if matches!(ty, typed_ir::Type::Any) {
        return false;
    }
    let mut vars = BTreeSet::new();
    collect_type_vars(ty, &mut vars);
    vars.is_empty()
}

pub(super) fn require_same_hir_type(
    expected: &RuntimeType,
    actual: &RuntimeType,
    source: TypeSource,
) -> RuntimeResult<()> {
    if runtime_type_contains_unknown(expected) || runtime_type_contains_unknown(actual) {
        return Ok(());
    }
    match (expected, actual) {
        (RuntimeType::Core(expected), RuntimeType::Core(actual)) => {
            require_same_type(expected, actual, source)
        }
        (RuntimeType::Core(expected), actual @ RuntimeType::Fun { .. })
        | (actual @ RuntimeType::Fun { .. }, RuntimeType::Core(expected)) => {
            require_same_type(expected, &diagnostic_core_type(actual), source)
        }
        (RuntimeType::Core(expected), RuntimeType::Thunk { value, .. }) => {
            require_same_type(expected, &diagnostic_core_type(value), source)
        }
        (
            RuntimeType::Fun {
                param: expected_param,
                ret: expected_ret,
            },
            RuntimeType::Fun {
                param: actual_param,
                ret: actual_ret,
            },
        ) => {
            require_same_hir_type(expected_param, actual_param, source)?;
            require_same_hir_type(expected_ret, actual_ret, source)
        }
        (
            RuntimeType::Thunk {
                effect: expected_effect,
                value: expected_value,
            },
            RuntimeType::Thunk {
                effect: actual_effect,
                value: actual_value,
            },
        ) => {
            if !effect_compatible(expected_effect, actual_effect)
                && !effect_compatible(actual_effect, expected_effect)
            {
                return Err(RuntimeError::TypeMismatch {
                    expected: expected_effect.clone(),
                    actual: actual_effect.clone(),
                    source,
                    context: None,
                });
            }
            require_same_hir_type(expected_value, actual_value, source)
        }
        (RuntimeType::Thunk { value, .. }, actual) => require_same_hir_type(value, actual, source),
        (expected, actual)
            if core_types_compatible(
                &diagnostic_core_type(expected),
                &diagnostic_core_type(actual),
            ) =>
        {
            Ok(())
        }
        (expected, actual) => Err(RuntimeError::TypeMismatch {
            expected: diagnostic_core_type(expected),
            actual: diagnostic_core_type(actual),
            source,
            context: None,
        }),
    }
}

pub(super) fn require_apply_arg_compatible(
    expected: &RuntimeType,
    actual: &RuntimeType,
    source: TypeSource,
) -> RuntimeResult<()> {
    match (expected, actual) {
        (
            RuntimeType::Thunk {
                effect: expected_effect,
                value: expected_value,
            },
            RuntimeType::Thunk {
                effect: actual_effect,
                value: actual_value,
            },
        ) => {
            if !effect_compatible(expected_effect, actual_effect)
                && !effect_compatible(actual_effect, expected_effect)
            {
                return Err(RuntimeError::TypeMismatch {
                    expected: expected_effect.clone(),
                    actual: actual_effect.clone(),
                    source,
                    context: None,
                });
            }
            require_same_hir_type(expected_value, actual_value, source)
        }
        _ => require_same_hir_type(expected, actual, source),
    }
}