yulang-runtime-refine 0.1.0

Runtime type refinement, validation, invariant checks, and hygiene printing for Yulang.
Documentation
use super::*;

pub(super) fn validate_pattern(
    pattern: &Pattern,
    expected: &typed_ir::Type,
    type_arg_kinds: &TypeArgKinds,
    locals: &mut HashMap<typed_ir::Path, RuntimeType>,
) -> RuntimeResult<()> {
    require_same_type(
        expected,
        hir_value_core_type(pattern_ty(pattern)).as_ref(),
        TypeSource::Validation,
    )?;
    match pattern {
        Pattern::Wildcard { .. } | Pattern::Lit { .. } => {}
        Pattern::Bind { name, ty } => {
            locals.insert(typed_ir::Path::from_name(name.clone()), ty.clone());
        }
        Pattern::Tuple { items, .. } => {
            let erased_items;
            let item_tys = match expected {
                typed_ir::Type::Tuple(item_tys) => item_tys.as_slice(),
                typed_ir::Type::Any => {
                    erased_items = vec![typed_ir::Type::Unknown; items.len()];
                    erased_items.as_slice()
                }
                _ => {
                    return Err(RuntimeError::UnsupportedPatternShape {
                        pattern: "tuple",
                        ty: expected.clone(),
                    });
                }
            };
            for (item, item_ty) in items.iter().zip(item_tys) {
                validate_pattern(item, item_ty, type_arg_kinds, locals)?;
            }
        }
        Pattern::List {
            prefix,
            spread,
            suffix,
            ..
        } => {
            for item in prefix.iter().chain(suffix) {
                let erased_item_ty;
                let inferred_item_ty;
                let item_ty = if matches!(expected, typed_ir::Type::Any) {
                    erased_item_ty = typed_ir::Type::Unknown;
                    &erased_item_ty
                } else {
                    inferred_item_ty = hir_value_core_type(pattern_ty(item));
                    inferred_item_ty.as_ref()
                };
                validate_pattern(item, item_ty, type_arg_kinds, locals)?;
            }
            if let Some(spread) = spread {
                validate_pattern(spread, expected, type_arg_kinds, locals)?;
            }
        }
        Pattern::Record { fields, spread, .. } => {
            for field in fields {
                let erased_field_ty;
                let field_ty = match expected {
                    typed_ir::Type::Record(record) => match record
                        .fields
                        .iter()
                        .find(|candidate| candidate.name == field.name)
                        .map(|field| &field.value)
                    {
                        Some(field_ty) => field_ty,
                        None if field.default.is_some() => core_type(pattern_ty(&field.pattern)),
                        None => {
                            return Err(RuntimeError::UnsupportedPatternShape {
                                pattern: "record field",
                                ty: expected.clone(),
                            });
                        }
                    },
                    typed_ir::Type::Any => {
                        erased_field_ty = typed_ir::Type::Unknown;
                        &erased_field_ty
                    }
                    typed_ir::Type::Named { .. } => core_type(pattern_ty(&field.pattern)),
                    _ => {
                        return Err(RuntimeError::UnsupportedPatternShape {
                            pattern: "record",
                            ty: expected.clone(),
                        });
                    }
                };
                validate_pattern(&field.pattern, field_ty, type_arg_kinds, locals)?;
                if let Some(default) = &field.default {
                    require_same_type(field_ty, core_type(&default.ty), TypeSource::Expected)?;
                }
            }
            validate_record_spread_pattern(spread, expected, type_arg_kinds, locals)?;
        }
        Pattern::Variant { value, .. } => {
            if let Some(value) = value {
                validate_pattern(value, core_type(pattern_ty(value)), type_arg_kinds, locals)?;
            }
        }
        Pattern::Or { left, right, .. } => {
            validate_pattern(left, expected, type_arg_kinds, locals)?;
            validate_pattern(right, expected, type_arg_kinds, locals)?;
        }
        Pattern::As { pattern, name, ty } => {
            validate_pattern(pattern, expected, type_arg_kinds, locals)?;
            locals.insert(typed_ir::Path::from_name(name.clone()), ty.clone());
        }
    }
    Ok(())
}

pub(super) fn validate_hir_pattern(
    pattern: &Pattern,
    expected: &RuntimeType,
    type_arg_kinds: &TypeArgKinds,
    locals: &mut HashMap<typed_ir::Path, RuntimeType>,
) -> RuntimeResult<()> {
    require_same_hir_type(expected, pattern_ty(pattern), TypeSource::Validation)?;
    match pattern {
        Pattern::Wildcard { .. } => Ok(()),
        Pattern::Bind { name, ty } => {
            locals.insert(typed_ir::Path::from_name(name.clone()), ty.clone());
            Ok(())
        }
        Pattern::Or { left, right, .. } => {
            validate_hir_pattern(left, expected, type_arg_kinds, locals)?;
            validate_hir_pattern(right, expected, type_arg_kinds, locals)
        }
        Pattern::As { pattern, name, ty } => {
            validate_hir_pattern(pattern, expected, type_arg_kinds, locals)?;
            locals.insert(typed_ir::Path::from_name(name.clone()), ty.clone());
            Ok(())
        }
        pattern => {
            let Some(expected) = expected.as_value() else {
                return Err(RuntimeError::UnsupportedPatternShape {
                    pattern: pattern_shape_name(pattern),
                    ty: diagnostic_core_type(expected),
                });
            };
            validate_pattern(pattern, expected, type_arg_kinds, locals)
        }
    }
}

pub(super) fn validate_record_spread_expr(
    spread: &Option<RecordSpreadExpr>,
    bindings: &HashMap<typed_ir::Path, BindingInfo>,
    type_arg_kinds: &TypeArgKinds,
    locals: &mut HashMap<typed_ir::Path, RuntimeType>,
) -> RuntimeResult<()> {
    match spread {
        Some(RecordSpreadExpr::Head(expr)) | Some(RecordSpreadExpr::Tail(expr)) => {
            validate_expr(expr, bindings, type_arg_kinds, locals)
        }
        None => Ok(()),
    }
}

pub(super) fn validate_record_spread_pattern(
    spread: &Option<RecordSpreadPattern>,
    expected: &typed_ir::Type,
    type_arg_kinds: &TypeArgKinds,
    locals: &mut HashMap<typed_ir::Path, RuntimeType>,
) -> RuntimeResult<()> {
    match spread {
        Some(RecordSpreadPattern::Head(pattern)) | Some(RecordSpreadPattern::Tail(pattern)) => {
            validate_pattern(pattern, expected, type_arg_kinds, locals)
        }
        None => Ok(()),
    }
}

pub(super) fn pattern_ty(pattern: &Pattern) -> &RuntimeType {
    match pattern {
        Pattern::Wildcard { ty }
        | Pattern::Bind { ty, .. }
        | Pattern::Lit { ty, .. }
        | Pattern::Tuple { ty, .. }
        | Pattern::List { ty, .. }
        | Pattern::Record { ty, .. }
        | Pattern::Variant { ty, .. }
        | Pattern::Or { ty, .. }
        | Pattern::As { ty, .. } => ty,
    }
}

pub(super) fn pattern_shape_name(pattern: &Pattern) -> &'static str {
    match pattern {
        Pattern::Wildcard { .. } => "wildcard",
        Pattern::Bind { .. } => "bind",
        Pattern::Lit { .. } => "literal",
        Pattern::Tuple { .. } => "tuple",
        Pattern::List { .. } => "list",
        Pattern::Record { .. } => "record",
        Pattern::Variant { .. } => "variant",
        Pattern::Or { .. } => "or",
        Pattern::As { .. } => "as",
    }
}

pub(super) fn restore_local(
    locals: &mut HashMap<typed_ir::Path, RuntimeType>,
    local: typed_ir::Path,
    previous: Option<RuntimeType>,
) {
    match previous {
        Some(previous) => {
            locals.insert(local, previous);
        }
        None => {
            locals.remove(&local);
        }
    }
}