mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Basic type inference and solving for abilities
Note that is still pretty limited. We only permit opaque types to implement abilities, abilities cannot have type arguments, and also no other functions may depend on abilities
This commit is contained in:
parent
913d97cab1
commit
15a040ec87
25 changed files with 1808 additions and 477 deletions
|
@ -1,4 +1,5 @@
|
|||
use bitflags::bitflags;
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Content::{self, *};
|
||||
|
@ -6,7 +7,7 @@ use roc_types::subs::{
|
|||
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
|
||||
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
use roc_types::types::{AliasKind, ErrorType, Mismatch, RecordField};
|
||||
use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField};
|
||||
|
||||
macro_rules! mismatch {
|
||||
() => {{
|
||||
|
@ -19,7 +20,10 @@ macro_rules! mismatch {
|
|||
);
|
||||
}
|
||||
|
||||
vec![Mismatch::TypeMismatch]
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch],
|
||||
..Outcome::default()
|
||||
}
|
||||
}};
|
||||
($msg:expr) => {{
|
||||
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
|
||||
|
@ -34,7 +38,10 @@ macro_rules! mismatch {
|
|||
}
|
||||
|
||||
|
||||
vec![Mismatch::TypeMismatch]
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch],
|
||||
..Outcome::default()
|
||||
}
|
||||
}};
|
||||
($msg:expr,) => {{
|
||||
mismatch!($msg)
|
||||
|
@ -51,8 +58,28 @@ macro_rules! mismatch {
|
|||
println!("");
|
||||
}
|
||||
|
||||
vec![Mismatch::TypeMismatch]
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch],
|
||||
..Outcome::default()
|
||||
}
|
||||
}};
|
||||
(%not_able, $var:expr, $ability:expr, $msg:expr, $($arg:tt)*) => {{
|
||||
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
|
||||
println!(
|
||||
"Mismatch in {} Line {} Column {}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
println!($msg, $($arg)*);
|
||||
println!("");
|
||||
}
|
||||
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch, Mismatch::DoesNotImplementAbiity($var, $ability)],
|
||||
..Outcome::default()
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
type Pool = Vec<Variable>;
|
||||
|
@ -105,20 +132,52 @@ pub struct Context {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum Unified {
|
||||
Success(Pool),
|
||||
Failure(Pool, ErrorType, ErrorType),
|
||||
Success {
|
||||
vars: Pool,
|
||||
must_implement_ability: Vec<MustImplementAbility>,
|
||||
},
|
||||
Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility),
|
||||
BadType(Pool, roc_types::types::Problem),
|
||||
}
|
||||
|
||||
type Outcome = Vec<Mismatch>;
|
||||
/// Specifies that `type` must implement the ability `ability`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MustImplementAbility {
|
||||
// This only points to opaque type names currently.
|
||||
// TODO(abilities) support structural types in general
|
||||
pub typ: Symbol,
|
||||
pub ability: Symbol,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Outcome {
|
||||
mismatches: Vec<Mismatch>,
|
||||
/// We defer these checks until the end of a solving phase.
|
||||
/// NOTE: this vector is almost always empty!
|
||||
must_implement_ability: Vec<MustImplementAbility>,
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
fn union(&mut self, other: Self) {
|
||||
self.mismatches.extend(other.mismatches);
|
||||
self.must_implement_ability
|
||||
.extend(other.must_implement_ability);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Unified {
|
||||
let mut vars = Vec::new();
|
||||
let mismatches = unify_pool(subs, &mut vars, var1, var2, mode);
|
||||
let Outcome {
|
||||
mismatches,
|
||||
must_implement_ability,
|
||||
} = unify_pool(subs, &mut vars, var1, var2, mode);
|
||||
|
||||
if mismatches.is_empty() {
|
||||
Unified::Success(vars)
|
||||
Unified::Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
}
|
||||
} else {
|
||||
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
|
||||
ErrorTypeContext::ExpandRanges
|
||||
|
@ -136,7 +195,19 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni
|
|||
if !problems.is_empty() {
|
||||
Unified::BadType(vars, problems.remove(0))
|
||||
} else {
|
||||
Unified::Failure(vars, type1, type2)
|
||||
let do_not_implement_ability = mismatches
|
||||
.into_iter()
|
||||
.filter_map(|mismatch| match mismatch {
|
||||
Mismatch::DoesNotImplementAbiity(var, ab) => {
|
||||
let (err_type, _new_problems) =
|
||||
subs.var_to_error_type_contextual(var, error_context);
|
||||
Some((err_type, ab))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Unified::Failure(vars, type1, type2, do_not_implement_ability)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +221,7 @@ pub fn unify_pool(
|
|||
mode: Mode,
|
||||
) -> Outcome {
|
||||
if subs.equivalent(var1, var2) {
|
||||
Vec::new()
|
||||
Outcome::default()
|
||||
} else {
|
||||
let ctx = Context {
|
||||
first: var1,
|
||||
|
@ -191,7 +262,14 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
|||
);
|
||||
}
|
||||
match &ctx.first_desc.content {
|
||||
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content),
|
||||
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content),
|
||||
FlexAbleVar(opt_name, ability) => unify_flex(
|
||||
subs,
|
||||
&ctx,
|
||||
opt_name,
|
||||
Some(*ability),
|
||||
&ctx.second_desc.content,
|
||||
),
|
||||
RecursionVar {
|
||||
opt_name,
|
||||
structure,
|
||||
|
@ -203,7 +281,10 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
|||
*structure,
|
||||
&ctx.second_desc.content,
|
||||
),
|
||||
RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content),
|
||||
RigidVar(name) => unify_rigid(subs, &ctx, name, None, &ctx.second_desc.content),
|
||||
RigidAbleVar(name, ability) => {
|
||||
unify_rigid(subs, &ctx, name, Some(*ability), &ctx.second_desc.content)
|
||||
}
|
||||
Structure(flat_type) => {
|
||||
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
|
||||
}
|
||||
|
@ -238,7 +319,7 @@ fn unify_ranged_number(
|
|||
}
|
||||
&RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode)
|
||||
} else {
|
||||
outcome
|
||||
|
@ -246,9 +327,12 @@ fn unify_ranged_number(
|
|||
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
FlexAbleVar(..) | RigidAbleVar(..) => {
|
||||
todo_abilities!("I don't think this can be reached yet")
|
||||
}
|
||||
};
|
||||
|
||||
if !outcome.is_empty() {
|
||||
if !outcome.mismatches.is_empty() {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
|
@ -269,11 +353,11 @@ fn check_valid_range(
|
|||
let snapshot = subs.snapshot();
|
||||
let old_pool = pool.clone();
|
||||
let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
// Okay, we matched some type in the range.
|
||||
subs.rollback_to(snapshot);
|
||||
*pool = old_pool;
|
||||
return vec![];
|
||||
return Outcome::default();
|
||||
} else if it.peek().is_some() {
|
||||
// We failed to match something in the range, but there are still things we can try.
|
||||
subs.rollback_to(snapshot);
|
||||
|
@ -283,7 +367,10 @@ fn check_valid_range(
|
|||
}
|
||||
}
|
||||
|
||||
return vec![Mismatch::TypeNotInRange];
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeNotInRange],
|
||||
..Outcome::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -310,13 +397,19 @@ fn unify_alias(
|
|||
unify_pool(subs, pool, real_var, *structure, ctx.mode)
|
||||
}
|
||||
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => {
|
||||
// Opaque type wins
|
||||
let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind));
|
||||
outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability });
|
||||
outcome
|
||||
}
|
||||
Alias(other_symbol, other_args, other_real_var, _)
|
||||
// Opaques types are only equal if the opaque symbols are equal!
|
||||
if !either_is_opaque || symbol == *other_symbol =>
|
||||
{
|
||||
if symbol == *other_symbol {
|
||||
if args.len() == other_args.len() {
|
||||
let mut problems = Vec::new();
|
||||
let mut outcome = Outcome::default();
|
||||
let it = args
|
||||
.all_variables()
|
||||
.into_iter()
|
||||
|
@ -327,23 +420,23 @@ fn unify_alias(
|
|||
for (l, r) in it {
|
||||
let l_var = subs[l];
|
||||
let r_var = subs[r];
|
||||
problems.extend(unify_pool(subs, pool, l_var, r_var, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode));
|
||||
}
|
||||
|
||||
if problems.is_empty() {
|
||||
problems.extend(merge(subs, ctx, *other_content));
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(merge(subs, ctx, *other_content));
|
||||
}
|
||||
|
||||
let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty();
|
||||
subs.commit_snapshot(args_unification_snapshot);
|
||||
|
||||
if !args.is_empty() && args_unification_had_changes && problems.is_empty() {
|
||||
if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() {
|
||||
// We need to unify the real vars because unification of type variables
|
||||
// may have made them larger, which then needs to be reflected in the `real_var`.
|
||||
problems.extend(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode));
|
||||
}
|
||||
|
||||
problems
|
||||
outcome
|
||||
} else {
|
||||
dbg!(args.len(), other_args.len());
|
||||
mismatch!("{:?}", symbol)
|
||||
|
@ -355,7 +448,7 @@ fn unify_alias(
|
|||
Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => {
|
||||
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode)
|
||||
} else {
|
||||
outcome
|
||||
|
@ -448,13 +541,31 @@ fn unify_structure(
|
|||
},
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode)
|
||||
} else {
|
||||
outcome
|
||||
}
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
|
||||
FlexAbleVar(_, ability) => {
|
||||
// TODO(abilities) support structural types in ability bounds
|
||||
mismatch!(
|
||||
%not_able, ctx.first, *ability,
|
||||
"trying to unify {:?} with FlexAble {:?}",
|
||||
&flat_type,
|
||||
&other
|
||||
)
|
||||
}
|
||||
RigidAbleVar(_, ability) => {
|
||||
mismatch!(
|
||||
%not_able, ctx.first, *ability,
|
||||
"trying to unify {:?} with RigidAble {:?}",
|
||||
&flat_type,
|
||||
&other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,29 +585,29 @@ fn unify_record(
|
|||
if separate.only_in_1.is_empty() {
|
||||
if separate.only_in_2.is_empty() {
|
||||
// these variable will be the empty record, but we must still unify them
|
||||
let ext_problems = unify_pool(subs, pool, ext1, ext2, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, ext1, ext2, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems =
|
||||
let mut field_outcome =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, ext1);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
field_outcome.union(ext_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
} else {
|
||||
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
|
||||
let flat_type = FlatType::Record(only_in_2, ext2);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems = unify_shared_fields(
|
||||
let mut field_outcome = unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
|
@ -505,21 +616,21 @@ fn unify_record(
|
|||
sub_record,
|
||||
);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
field_outcome.union(ext_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
}
|
||||
} else if separate.only_in_2.is_empty() {
|
||||
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
|
||||
let flat_type = FlatType::Record(only_in_1, ext1);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems = unify_shared_fields(
|
||||
let mut field_outcome = unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
|
@ -528,9 +639,9 @@ fn unify_record(
|
|||
sub_record,
|
||||
);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
field_outcome.union(ext_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
} else {
|
||||
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
|
||||
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
|
||||
|
@ -544,24 +655,26 @@ fn unify_record(
|
|||
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
|
||||
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
|
||||
|
||||
let rec1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !rec1_problems.is_empty() {
|
||||
return rec1_problems;
|
||||
let rec1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !rec1_outcome.mismatches.is_empty() {
|
||||
return rec1_outcome;
|
||||
}
|
||||
|
||||
let rec2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !rec2_problems.is_empty() {
|
||||
return rec2_problems;
|
||||
let rec2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !rec2_outcome.mismatches.is_empty() {
|
||||
return rec2_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems =
|
||||
let mut field_outcome =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext);
|
||||
|
||||
field_problems.reserve(rec1_problems.len() + rec2_problems.len());
|
||||
field_problems.extend(rec1_problems);
|
||||
field_problems.extend(rec2_problems);
|
||||
field_outcome
|
||||
.mismatches
|
||||
.reserve(rec1_outcome.mismatches.len() + rec2_outcome.mismatches.len());
|
||||
field_outcome.union(rec1_outcome);
|
||||
field_outcome.union(rec2_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -584,7 +697,7 @@ fn unify_shared_fields(
|
|||
let num_shared_fields = shared_fields.len();
|
||||
|
||||
for (name, (actual, expected)) in shared_fields {
|
||||
let local_problems = unify_pool(
|
||||
let local_outcome = unify_pool(
|
||||
subs,
|
||||
pool,
|
||||
actual.into_inner(),
|
||||
|
@ -592,7 +705,7 @@ fn unify_shared_fields(
|
|||
ctx.mode,
|
||||
);
|
||||
|
||||
if local_problems.is_empty() {
|
||||
if local_outcome.mismatches.is_empty() {
|
||||
use RecordField::*;
|
||||
|
||||
// Unification of optional fields
|
||||
|
@ -856,18 +969,18 @@ fn unify_tag_union_new(
|
|||
|
||||
if separate.only_in_1.is_empty() {
|
||||
if separate.only_in_2.is_empty() {
|
||||
let ext_problems = if ctx.mode.is_eq() {
|
||||
let ext_outcome = if ctx.mode.is_eq() {
|
||||
unify_pool(subs, pool, ext1, ext2, ctx.mode)
|
||||
} else {
|
||||
// In a presence context, we don't care about ext2 being equal to ext1
|
||||
vec![]
|
||||
Outcome::default()
|
||||
};
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut tag_problems = unify_shared_tags_new(
|
||||
let mut shared_tags_outcome = unify_shared_tags_new(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
|
@ -877,20 +990,20 @@ fn unify_tag_union_new(
|
|||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
shared_tags_outcome.union(ext_outcome);
|
||||
|
||||
tag_problems
|
||||
shared_tags_outcome
|
||||
} else {
|
||||
let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2);
|
||||
let flat_type = FlatType::TagUnion(unique_tags2, ext2);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut tag_problems = unify_shared_tags_new(
|
||||
let mut shared_tags_outcome = unify_shared_tags_new(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
|
@ -900,9 +1013,9 @@ fn unify_tag_union_new(
|
|||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
shared_tags_outcome.union(ext_outcome);
|
||||
|
||||
tag_problems
|
||||
shared_tags_outcome
|
||||
}
|
||||
} else if separate.only_in_2.is_empty() {
|
||||
let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1);
|
||||
|
@ -911,10 +1024,10 @@ fn unify_tag_union_new(
|
|||
|
||||
// In a presence context, we don't care about ext2 being equal to tags1
|
||||
if ctx.mode.is_eq() {
|
||||
let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -961,17 +1074,17 @@ fn unify_tag_union_new(
|
|||
|
||||
let snapshot = subs.snapshot();
|
||||
|
||||
let ext1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !ext1_problems.is_empty() {
|
||||
let ext1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !ext1_outcome.mismatches.is_empty() {
|
||||
subs.rollback_to(snapshot);
|
||||
return ext1_problems;
|
||||
return ext1_outcome;
|
||||
}
|
||||
|
||||
if ctx.mode.is_eq() {
|
||||
let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !ext2_problems.is_empty() {
|
||||
let ext2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !ext2_outcome.mismatches.is_empty() {
|
||||
subs.rollback_to(snapshot);
|
||||
return ext2_problems;
|
||||
return ext2_outcome;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1063,17 +1176,17 @@ fn unify_shared_tags_new(
|
|||
maybe_mark_tag_union_recursive(subs, actual);
|
||||
maybe_mark_tag_union_recursive(subs, expected);
|
||||
|
||||
let mut problems = Vec::new();
|
||||
let mut outcome = Outcome::default();
|
||||
|
||||
problems.extend(unify_pool(subs, pool, actual, expected, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, actual, expected, ctx.mode));
|
||||
|
||||
// clearly, this is very suspicious: these variables have just been unified. And yet,
|
||||
// not doing this leads to stack overflows
|
||||
if let Rec::Right(_) = recursion_var {
|
||||
if problems.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
matching_vars.push(expected);
|
||||
}
|
||||
} else if problems.is_empty() {
|
||||
} else if outcome.mismatches.is_empty() {
|
||||
matching_vars.push(actual);
|
||||
}
|
||||
}
|
||||
|
@ -1215,39 +1328,43 @@ fn unify_flat_type(
|
|||
debug_assert!(is_recursion_var(subs, *rec2));
|
||||
|
||||
let rec = Rec::Both(*rec1, *rec2);
|
||||
let mut problems =
|
||||
let mut outcome =
|
||||
unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec);
|
||||
problems.extend(unify_pool(subs, pool, *rec1, *rec2, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, *rec1, *rec2, ctx.mode));
|
||||
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
|
||||
(Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
|
||||
let problems = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
let mut outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
|
||||
if problems.is_empty() {
|
||||
merge(subs, ctx, Structure(Apply(*r_symbol, *r_args)))
|
||||
} else {
|
||||
problems
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))));
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
(Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret))
|
||||
if l_args.len() == r_args.len() =>
|
||||
{
|
||||
let arg_problems = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
|
||||
let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode);
|
||||
let arg_outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
let ret_outcome = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
|
||||
let closure_outcome = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode);
|
||||
|
||||
if arg_problems.is_empty() && closure_problems.is_empty() && ret_problems.is_empty() {
|
||||
merge(subs, ctx, Structure(Func(*r_args, *r_closure, *r_ret)))
|
||||
} else {
|
||||
let mut problems = ret_problems;
|
||||
let mut outcome = ret_outcome;
|
||||
|
||||
problems.extend(closure_problems);
|
||||
problems.extend(arg_problems);
|
||||
outcome.union(closure_outcome);
|
||||
outcome.union(arg_outcome);
|
||||
|
||||
problems
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(merge(
|
||||
subs,
|
||||
ctx,
|
||||
Structure(Func(*r_args, *r_closure, *r_ret)),
|
||||
));
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
(FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => {
|
||||
unify_function_or_tag_union_and_func(
|
||||
|
@ -1282,12 +1399,12 @@ fn unify_flat_type(
|
|||
let tag_name_2_ref = &subs[*tag_name_2];
|
||||
|
||||
if tag_name_1_ref == tag_name_2_ref {
|
||||
let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
|
||||
if problems.is_empty() {
|
||||
let outcome = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
|
||||
if outcome.mismatches.is_empty() {
|
||||
let content = *subs.get_content_without_compacting(ctx.second);
|
||||
merge(subs, ctx, content)
|
||||
} else {
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
} else {
|
||||
let tags1 = UnionTags::from_tag_name_index(*tag_name_1);
|
||||
|
@ -1343,7 +1460,7 @@ fn unify_zip_slices(
|
|||
left: SubsSlice<Variable>,
|
||||
right: SubsSlice<Variable>,
|
||||
) -> Outcome {
|
||||
let mut problems = Vec::new();
|
||||
let mut outcome = Outcome::default();
|
||||
|
||||
let it = left.into_iter().zip(right.into_iter());
|
||||
|
||||
|
@ -1351,10 +1468,10 @@ fn unify_zip_slices(
|
|||
let l_var = subs[l_index];
|
||||
let r_var = subs[r_index];
|
||||
|
||||
problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::EQ));
|
||||
outcome.union(unify_pool(subs, pool, l_var, r_var, Mode::EQ));
|
||||
}
|
||||
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -1362,6 +1479,7 @@ fn unify_rigid(
|
|||
subs: &mut Subs,
|
||||
ctx: &Context,
|
||||
name: &SubsIndex<Lowercase>,
|
||||
opt_able_bound: Option<Symbol>,
|
||||
other: &Content,
|
||||
) -> Outcome {
|
||||
match other {
|
||||
|
@ -1369,16 +1487,76 @@ fn unify_rigid(
|
|||
// If the other is flex, rigid wins!
|
||||
merge(subs, ctx, RigidVar(*name))
|
||||
}
|
||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
|
||||
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
|
||||
} else {
|
||||
// We are treating rigid vars as flex vars; admit this
|
||||
merge(subs, ctx, *other)
|
||||
FlexAbleVar(_, other_ability) => {
|
||||
match opt_able_bound {
|
||||
Some(ability) => {
|
||||
if ability == *other_ability {
|
||||
// The ability bounds are the same, so rigid wins!
|
||||
merge(subs, ctx, RigidAbleVar(*name, ability))
|
||||
} else {
|
||||
// Mismatch for now.
|
||||
// TODO check ability hierarchies.
|
||||
mismatch!(
|
||||
%not_able, ctx.second, ability,
|
||||
"RigidAble {:?} with ability {:?} not compatible with ability {:?}",
|
||||
ctx.first,
|
||||
ability,
|
||||
other_ability
|
||||
)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Mismatch - Rigid can unify with FlexAble only when the Rigid has an ability
|
||||
// bound as well, otherwise the user failed to correctly annotate the bound.
|
||||
mismatch!(
|
||||
%not_able, ctx.first, *other_ability,
|
||||
"Rigid {:?} with FlexAble {:?}", ctx.first, other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..)
|
||||
if ctx.mode.contains(Mode::RIGID_AS_FLEX) =>
|
||||
{
|
||||
// Usually rigids can only unify with flex, but the mode indicates we are treating
|
||||
// rigid vars as flex, so admit this.
|
||||
match (opt_able_bound, other) {
|
||||
(None, other) => merge(subs, ctx, *other),
|
||||
(Some(ability), Alias(opaque_name, vars, _real_var, AliasKind::Opaque))
|
||||
if vars.is_empty() =>
|
||||
{
|
||||
let mut output = merge(subs, ctx, *other);
|
||||
let must_implement_ability = MustImplementAbility {
|
||||
typ: *opaque_name,
|
||||
ability,
|
||||
};
|
||||
output.must_implement_ability.push(must_implement_ability);
|
||||
output
|
||||
}
|
||||
(Some(ability), other) => {
|
||||
// For now, only allow opaque types with no type variables to implement abilities.
|
||||
mismatch!(
|
||||
%not_able, ctx.second, ability,
|
||||
"RigidAble {:?} with non-opaque or opaque with type variables {:?}",
|
||||
ctx.first,
|
||||
&other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RigidVar(_)
|
||||
| RigidAbleVar(..)
|
||||
| RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(..)
|
||||
| RangedNumber(..) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
Error => {
|
||||
// Error propagates.
|
||||
merge(subs, ctx, Error)
|
||||
|
@ -1391,16 +1569,49 @@ fn unify_flex(
|
|||
subs: &mut Subs,
|
||||
ctx: &Context,
|
||||
opt_name: &Option<SubsIndex<Lowercase>>,
|
||||
opt_able_bound: Option<Symbol>,
|
||||
other: &Content,
|
||||
) -> Outcome {
|
||||
match other {
|
||||
FlexVar(None) => {
|
||||
// If both are flex, and only left has a name, keep the name around.
|
||||
merge(subs, ctx, FlexVar(*opt_name))
|
||||
match opt_able_bound {
|
||||
Some(ability) => merge(subs, ctx, FlexAbleVar(*opt_name, ability)),
|
||||
None => merge(subs, ctx, FlexVar(*opt_name)),
|
||||
}
|
||||
}
|
||||
|
||||
FlexAbleVar(opt_other_name, other_ability) => {
|
||||
// Prefer the right's name when possible.
|
||||
let opt_name = (opt_other_name).or(*opt_name);
|
||||
|
||||
match opt_able_bound {
|
||||
Some(ability) => {
|
||||
if ability == *other_ability {
|
||||
// The ability bounds are the same! Keep the name around if it exists.
|
||||
merge(subs, ctx, FlexAbleVar(opt_name, ability))
|
||||
} else {
|
||||
// Ability names differ; mismatch for now.
|
||||
// TODO check ability hierarchies.
|
||||
mismatch!(
|
||||
%not_able, ctx.second, ability,
|
||||
"FlexAble {:?} with ability {:?} not compatible with ability {:?}",
|
||||
ctx.first,
|
||||
ability,
|
||||
other_ability
|
||||
)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Right has an ability bound, but left might have the name. Combine them.
|
||||
merge(subs, ctx, FlexAbleVar(opt_name, *other_ability))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlexVar(Some(_))
|
||||
| RigidVar(_)
|
||||
| RigidAbleVar(_, _)
|
||||
| RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(_, _, _, _)
|
||||
|
@ -1446,7 +1657,13 @@ fn unify_recursion(
|
|||
// unify the structure variable with this Structure
|
||||
unify_pool(subs, pool, structure, ctx.second, ctx.mode)
|
||||
}
|
||||
RigidVar(_) => mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other),
|
||||
RigidVar(_) => {
|
||||
mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexAbleVar(..) | RigidAbleVar(..) => {
|
||||
mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexVar(_) => merge(
|
||||
subs,
|
||||
|
@ -1492,7 +1709,7 @@ pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
|
|||
|
||||
subs.union(ctx.first, ctx.second, desc);
|
||||
|
||||
Vec::new()
|
||||
Outcome::default()
|
||||
}
|
||||
|
||||
fn register(subs: &mut Subs, desc: Descriptor, pool: &mut Pool) -> Variable {
|
||||
|
@ -1543,7 +1760,7 @@ fn unify_function_or_tag_union_and_func(
|
|||
|
||||
let new_tag_union_var = fresh(subs, pool, ctx, content);
|
||||
|
||||
let mut problems = if left {
|
||||
let mut outcome = if left {
|
||||
unify_pool(subs, pool, new_tag_union_var, function_return, ctx.mode)
|
||||
} else {
|
||||
unify_pool(subs, pool, function_return, new_tag_union_var, ctx.mode)
|
||||
|
@ -1567,16 +1784,16 @@ fn unify_function_or_tag_union_and_func(
|
|||
pool,
|
||||
);
|
||||
|
||||
let closure_problems = if left {
|
||||
let closure_outcome = if left {
|
||||
unify_pool(subs, pool, tag_lambda_set, function_lambda_set, ctx.mode)
|
||||
} else {
|
||||
unify_pool(subs, pool, function_lambda_set, tag_lambda_set, ctx.mode)
|
||||
};
|
||||
|
||||
problems.extend(closure_problems);
|
||||
outcome.union(closure_outcome);
|
||||
}
|
||||
|
||||
if problems.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
let desc = if left {
|
||||
subs.get(ctx.second)
|
||||
} else {
|
||||
|
@ -1586,5 +1803,5 @@ fn unify_function_or_tag_union_and_func(
|
|||
subs.union(ctx.first, ctx.second, desc);
|
||||
}
|
||||
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue