use super::*;
pub(crate) fn type_compatible(expected: &typed_ir::Type, actual: &typed_ir::Type) -> bool {
type_compatible_inner(expected, actual, 128)
}
pub(crate) fn needs_runtime_coercion(expected: &typed_ir::Type, actual: &typed_ir::Type) -> bool {
expected != actual && can_widen_runtime_value(actual, expected)
}
pub(super) fn type_compatible_inner(
expected: &typed_ir::Type,
actual: &typed_ir::Type,
depth: usize,
) -> bool {
if expected == actual || core_type_is_never(expected) && core_type_is_never(actual) {
return true;
}
if depth == 0 {
return false;
}
if core_type_is_never(actual) {
return true;
}
match (expected, actual) {
(expected, _) if core_type_is_top(expected) || core_type_is_inference_var(expected) => true,
(_, actual) if core_type_is_top(actual) || core_type_is_inference_var(actual) => true,
(
typed_ir::Type::Named { path, args },
typed_ir::Type::Named {
path: actual_path,
args: actual_args,
},
) if path == actual_path && args.len() == actual_args.len() => args
.iter()
.zip(actual_args)
.all(|(left, right)| type_arg_compatible(left, right, depth - 1)),
(
typed_ir::Type::Named { path, args },
typed_ir::Type::Named {
path: actual_path,
args: actual_args,
},
) if args.is_empty() && actual_args.is_empty() => {
typed_ir::can_widen_named_paths(actual_path, path)
}
(
typed_ir::Type::Fun {
param,
param_effect,
ret_effect,
ret,
},
typed_ir::Type::Fun {
param: actual_param,
param_effect: actual_param_effect,
ret_effect: actual_ret_effect,
ret: actual_ret,
},
) => {
type_compatible_inner(param, actual_param, depth - 1)
&& effect_compatible(param_effect, actual_param_effect)
&& effect_compatible(ret_effect, actual_ret_effect)
&& type_compatible_inner(ret, actual_ret, depth - 1)
}
(typed_ir::Type::Tuple(items), typed_ir::Type::Tuple(actual_items))
if items.len() == actual_items.len() =>
{
items
.iter()
.zip(actual_items)
.all(|(left, right)| type_compatible_inner(left, right, depth - 1))
}
(typed_ir::Type::Record(record), typed_ir::Type::Record(actual_record)) => {
record.fields.iter().all(|field| {
match actual_record
.fields
.iter()
.find(|actual| actual.name == field.name)
{
Some(actual) => type_compatible_inner(&field.value, &actual.value, depth - 1),
None => field.optional,
}
})
}
(typed_ir::Type::Variant(variant), typed_ir::Type::Variant(actual_variant)) => {
actual_variant.cases.iter().all(|actual| {
variant
.cases
.iter()
.find(|case| case.name == actual.name)
.is_some_and(|case| {
variant_payloads_compatible(&case.payloads, &actual.payloads, depth - 1)
})
})
}
(typed_ir::Type::Union(items), _) => items
.iter()
.any(|item| type_compatible_inner(item, actual, depth - 1)),
(_, typed_ir::Type::Union(items)) => items
.iter()
.any(|item| type_compatible_inner(expected, item, depth - 1)),
(typed_ir::Type::Inter(items), _) => items
.iter()
.all(|item| type_compatible_inner(item, actual, depth - 1)),
(_, typed_ir::Type::Inter(items)) => items
.iter()
.all(|item| type_compatible_inner(expected, item, depth - 1)),
(
typed_ir::Type::Row { items, tail },
typed_ir::Type::Row {
items: actual_items,
tail: actual_tail,
},
) if items.len() == actual_items.len() => {
row_items_compatible_unordered(items, actual_items, depth - 1)
&& type_compatible_inner(tail, actual_tail, depth - 1)
}
(
typed_ir::Type::Recursive { var, body },
typed_ir::Type::Recursive {
var: actual_var,
body: actual_body,
},
) if var == actual_var => type_compatible_inner(body, actual_body, depth - 1),
_ => false,
}
}
fn row_items_compatible_unordered(
expected: &[typed_ir::Type],
actual: &[typed_ir::Type],
depth: usize,
) -> bool {
if expected.len() != actual.len() {
return false;
}
let mut matched_expected = vec![false; expected.len()];
for actual_item in actual {
let Some(index) = expected
.iter()
.enumerate()
.find_map(|(index, expected_item)| {
(!matched_expected[index] && row_item_compatible(expected_item, actual_item, depth))
.then_some(index)
})
else {
return false;
};
matched_expected[index] = true;
}
true
}
fn row_item_compatible(expected: &typed_ir::Type, actual: &typed_ir::Type, depth: usize) -> bool {
type_compatible_inner(expected, actual, depth)
|| effect_path(expected).is_some()
&& effect_path(actual).is_some()
&& effect_compatible(expected, actual)
}
fn variant_payloads_compatible(
expected: &[typed_ir::Type],
actual: &[typed_ir::Type],
depth: usize,
) -> bool {
if expected.len() == actual.len() {
return actual
.iter()
.zip(expected)
.all(|(left, right)| type_compatible_inner(right, left, depth));
}
if expected.len() > 1 && actual.len() == 1 {
return type_compatible_inner(&typed_ir::Type::Tuple(expected.to_vec()), &actual[0], depth);
}
if expected.len() == 1 && actual.len() > 1 {
return type_compatible_inner(&expected[0], &typed_ir::Type::Tuple(actual.to_vec()), depth);
}
false
}
pub(super) fn can_widen_runtime_value(actual: &typed_ir::Type, expected: &typed_ir::Type) -> bool {
match (actual, expected) {
(
typed_ir::Type::Named {
path: actual_path,
args: actual_args,
},
typed_ir::Type::Named {
path: expected_path,
args: expected_args,
},
) if actual_args.is_empty() && expected_args.is_empty() => {
typed_ir::can_widen_named_paths(actual_path, expected_path)
}
(typed_ir::Type::Record(_), typed_ir::Type::Named { .. })
| (typed_ir::Type::Named { .. }, typed_ir::Type::Record(_)) => true,
_ => false,
}
}
pub(super) fn type_arg_compatible(
expected: &typed_ir::TypeArg,
actual: &typed_ir::TypeArg,
depth: usize,
) -> bool {
match (expected, actual) {
(typed_ir::TypeArg::Type(left), typed_ir::TypeArg::Type(right)) => {
type_compatible_inner(left, right, depth)
}
(typed_ir::TypeArg::Type(left), typed_ir::TypeArg::Bounds(bounds)) => {
bounds_admits_type(bounds, left, depth)
}
(typed_ir::TypeArg::Bounds(bounds), typed_ir::TypeArg::Type(right)) => {
bounds_admits_type(bounds, right, depth)
}
(typed_ir::TypeArg::Bounds(left), typed_ir::TypeArg::Bounds(right)) => {
match (&left.lower, &right.lower) {
(Some(left), Some(right)) if !type_compatible_inner(left, right, depth) => {
return false;
}
_ => {}
}
match (&left.upper, &right.upper) {
(Some(left), Some(right)) => type_compatible_inner(left, right, depth),
_ => true,
}
}
}
}
pub(super) fn bounds_admits_type(
bounds: &typed_ir::TypeBounds,
ty: &typed_ir::Type,
depth: usize,
) -> bool {
bounds
.lower
.as_deref()
.is_none_or(|lower| type_compatible_inner(lower, ty, depth))
&& bounds
.upper
.as_deref()
.is_none_or(|upper| type_compatible_inner(upper, ty, depth))
}