mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
Merge pull request #2917 from rtfeldman/move-exhaustiveness-checking
Moves exhaustiveness checking to type solving phase
This commit is contained in:
commit
6da39aa296
21 changed files with 1067 additions and 177 deletions
|
@ -8,6 +8,7 @@ edition = "2018"
|
|||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_exhaustive = { path = "../exhaustive" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
|
|
|
@ -100,6 +100,7 @@ pub enum TypeError {
|
|||
ErrorType,
|
||||
Vec<IncompleteAbilityImplementation>,
|
||||
),
|
||||
Exhaustive(roc_exhaustive::Error),
|
||||
}
|
||||
|
||||
use roc_types::types::Alias;
|
||||
|
@ -775,7 +776,7 @@ fn solve(
|
|||
|
||||
copy
|
||||
}
|
||||
Eq(type_index, expectation_index, category_index, region) => {
|
||||
Eq(roc_can::constraint::Eq(type_index, expectation_index, category_index, region)) => {
|
||||
let category = &constraints.categories[category_index.index()];
|
||||
|
||||
let actual =
|
||||
|
@ -1104,32 +1105,7 @@ fn solve(
|
|||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
|
||||
let mut stack = vec![actual];
|
||||
while let Some(var) = stack.pop() {
|
||||
use {Content::*, FlatType::*};
|
||||
|
||||
let mut desc = subs.get(var);
|
||||
if let Structure(TagUnion(tags, ext)) = desc.content {
|
||||
if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext) {
|
||||
let new_ext = subs.fresh_unnamed_flex_var();
|
||||
subs.set_rank(new_ext, desc.rank);
|
||||
let new_union = Structure(TagUnion(tags, new_ext));
|
||||
desc.content = new_union;
|
||||
subs.set(var, desc);
|
||||
}
|
||||
|
||||
// Also open up all nested tag unions.
|
||||
let all_vars = tags.variables().into_iter();
|
||||
stack.extend(all_vars.flat_map(|slice| subs[slice]).map(|var| subs[var]));
|
||||
}
|
||||
|
||||
// Today, an "open" constraint doesn't affect any types
|
||||
// other than tag unions. Recursive tag unions are constructed
|
||||
// at a later time (during occurs checks after tag unions are
|
||||
// resolved), so that's not handled here either.
|
||||
// NB: Handle record types here if we add presence constraints
|
||||
// to their type inference as well.
|
||||
}
|
||||
open_tag_union(subs, actual);
|
||||
|
||||
state
|
||||
}
|
||||
|
@ -1196,12 +1172,163 @@ fn solve(
|
|||
}
|
||||
}
|
||||
}
|
||||
&Exhaustive(eq, sketched_rows, context) => {
|
||||
// A few cases:
|
||||
// 1. Either condition or branch types already have a type error. In this case just
|
||||
// propagate it.
|
||||
// 2. Types are correct, but there are redundancies. In this case we want
|
||||
// exhaustiveness checking to pull those out.
|
||||
// 3. Condition and branch types are "almost equal", that is one or the other is
|
||||
// only missing a few more tags. In this case we want to run
|
||||
// exhaustiveness checking both ways, to see which one is missing tags.
|
||||
// 4. Condition and branch types aren't "almost equal", this is just a normal type
|
||||
// error.
|
||||
|
||||
let roc_can::constraint::Eq(
|
||||
real_var,
|
||||
expected_branches,
|
||||
real_category,
|
||||
real_region,
|
||||
) = constraints.eq[eq.index()];
|
||||
|
||||
let real_var =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, real_var);
|
||||
|
||||
let expected_branches = &constraints.expectations[expected_branches.index()];
|
||||
let branches_var =
|
||||
type_to_var(subs, rank, pools, aliases, expected_branches.get_type_ref());
|
||||
|
||||
let real_content = subs.get_content_without_compacting(real_var);
|
||||
let branches_content = subs.get_content_without_compacting(branches_var);
|
||||
let already_have_error = matches!(
|
||||
(real_content, branches_content),
|
||||
(
|
||||
Content::Error | Content::Structure(FlatType::Erroneous(_)),
|
||||
_
|
||||
) | (
|
||||
_,
|
||||
Content::Error | Content::Structure(FlatType::Erroneous(_))
|
||||
)
|
||||
);
|
||||
|
||||
let snapshot = subs.snapshot();
|
||||
let outcome = unify(subs, real_var, branches_var, Mode::EQ);
|
||||
|
||||
let should_check_exhaustiveness;
|
||||
match outcome {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
} => {
|
||||
subs.commit_snapshot(snapshot);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
internal_error!("Didn't expect ability vars to land here");
|
||||
}
|
||||
|
||||
// Case 1: unify error types, but don't check exhaustiveness.
|
||||
// Case 2: run exhaustiveness to check for redundant branches.
|
||||
should_check_exhaustiveness = !already_have_error;
|
||||
}
|
||||
Failure(..) => {
|
||||
// Rollback and check for almost-equality.
|
||||
subs.rollback_to(snapshot);
|
||||
|
||||
let almost_eq_snapshot = subs.snapshot();
|
||||
// TODO: turn this on for bidirectional exhaustiveness checking
|
||||
// open_tag_union(subs, real_var);
|
||||
open_tag_union(subs, branches_var);
|
||||
let almost_eq = matches!(
|
||||
unify(subs, real_var, branches_var, Mode::EQ),
|
||||
Success { .. }
|
||||
);
|
||||
|
||||
subs.rollback_to(almost_eq_snapshot);
|
||||
|
||||
if almost_eq {
|
||||
// Case 3: almost equal, check exhaustiveness.
|
||||
should_check_exhaustiveness = true;
|
||||
} else {
|
||||
// Case 4: incompatible types, report type error.
|
||||
// Re-run first failed unification to get the type diff.
|
||||
match unify(subs, real_var, branches_var, Mode::EQ) {
|
||||
Failure(vars, actual_type, expected_type, _bad_impls) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let real_category =
|
||||
constraints.categories[real_category.index()].clone();
|
||||
let problem = TypeError::BadExpr(
|
||||
real_region,
|
||||
real_category,
|
||||
actual_type,
|
||||
expected_branches.replace_ref(expected_type),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
should_check_exhaustiveness = false;
|
||||
}
|
||||
_ => internal_error!("Must be failure"),
|
||||
}
|
||||
}
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
subs.commit_snapshot(snapshot);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
should_check_exhaustiveness = false;
|
||||
}
|
||||
}
|
||||
|
||||
let sketched_rows = constraints.sketched_rows[sketched_rows.index()].clone();
|
||||
|
||||
if should_check_exhaustiveness {
|
||||
let checked = roc_can::exhaustive::check(subs, sketched_rows, context);
|
||||
if let Err(errors) = checked {
|
||||
problems.extend(errors.into_iter().map(TypeError::Exhaustive));
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn open_tag_union(subs: &mut Subs, var: Variable) {
|
||||
let mut stack = vec![var];
|
||||
while let Some(var) = stack.pop() {
|
||||
use {Content::*, FlatType::*};
|
||||
|
||||
let mut desc = subs.get(var);
|
||||
if let Structure(TagUnion(tags, ext)) = desc.content {
|
||||
if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext) {
|
||||
let new_ext = subs.fresh_unnamed_flex_var();
|
||||
subs.set_rank(new_ext, desc.rank);
|
||||
let new_union = Structure(TagUnion(tags, new_ext));
|
||||
desc.content = new_union;
|
||||
subs.set(var, desc);
|
||||
}
|
||||
|
||||
// Also open up all nested tag unions.
|
||||
let all_vars = tags.variables().into_iter();
|
||||
stack.extend(all_vars.flat_map(|slice| subs[slice]).map(|var| subs[var]));
|
||||
}
|
||||
|
||||
// Today, an "open" constraint doesn't affect any types
|
||||
// other than tag unions. Recursive tag unions are constructed
|
||||
// at a later time (during occurs checks after tag unions are
|
||||
// resolved), so that's not handled here either.
|
||||
// NB: Handle record types here if we add presence constraints
|
||||
// to their type inference as well.
|
||||
}
|
||||
}
|
||||
|
||||
/// If a symbol claims to specialize an ability member, check that its solved type in fact
|
||||
/// does specialize the ability, and record the specialization.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
|
@ -2560,11 +2560,10 @@ mod solve_expr {
|
|||
}
|
||||
|
||||
// this test is related to a bug where ext_var would have an incorrect rank.
|
||||
// This match has duplicate cases, but that's not important because exhaustiveness happens
|
||||
// after inference.
|
||||
// This match has duplicate cases, but we ignore that.
|
||||
#[test]
|
||||
fn to_bit_record() {
|
||||
infer_eq_without_problem(
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo = \rec ->
|
||||
|
@ -5261,95 +5260,123 @@ mod solve_expr {
|
|||
{
|
||||
u8: (\n ->
|
||||
when n is
|
||||
123u8 -> n),
|
||||
123u8 -> n
|
||||
_ -> n),
|
||||
u16: (\n ->
|
||||
when n is
|
||||
123u16 -> n),
|
||||
123u16 -> n
|
||||
_ -> n),
|
||||
u32: (\n ->
|
||||
when n is
|
||||
123u32 -> n),
|
||||
123u32 -> n
|
||||
_ -> n),
|
||||
u64: (\n ->
|
||||
when n is
|
||||
123u64 -> n),
|
||||
123u64 -> n
|
||||
_ -> n),
|
||||
u128: (\n ->
|
||||
when n is
|
||||
123u128 -> n),
|
||||
123u128 -> n
|
||||
_ -> n),
|
||||
|
||||
i8: (\n ->
|
||||
when n is
|
||||
123i8 -> n),
|
||||
123i8 -> n
|
||||
_ -> n),
|
||||
i16: (\n ->
|
||||
when n is
|
||||
123i16 -> n),
|
||||
123i16 -> n
|
||||
_ -> n),
|
||||
i32: (\n ->
|
||||
when n is
|
||||
123i32 -> n),
|
||||
123i32 -> n
|
||||
_ -> n),
|
||||
i64: (\n ->
|
||||
when n is
|
||||
123i64 -> n),
|
||||
123i64 -> n
|
||||
_ -> n),
|
||||
i128: (\n ->
|
||||
when n is
|
||||
123i128 -> n),
|
||||
123i128 -> n
|
||||
_ -> n),
|
||||
|
||||
nat: (\n ->
|
||||
when n is
|
||||
123nat -> n),
|
||||
123nat -> n
|
||||
_ -> n),
|
||||
|
||||
bu8: (\n ->
|
||||
when n is
|
||||
0b11u8 -> n),
|
||||
0b11u8 -> n
|
||||
_ -> n),
|
||||
bu16: (\n ->
|
||||
when n is
|
||||
0b11u16 -> n),
|
||||
0b11u16 -> n
|
||||
_ -> n),
|
||||
bu32: (\n ->
|
||||
when n is
|
||||
0b11u32 -> n),
|
||||
0b11u32 -> n
|
||||
_ -> n),
|
||||
bu64: (\n ->
|
||||
when n is
|
||||
0b11u64 -> n),
|
||||
0b11u64 -> n
|
||||
_ -> n),
|
||||
bu128: (\n ->
|
||||
when n is
|
||||
0b11u128 -> n),
|
||||
0b11u128 -> n
|
||||
_ -> n),
|
||||
|
||||
bi8: (\n ->
|
||||
when n is
|
||||
0b11i8 -> n),
|
||||
0b11i8 -> n
|
||||
_ -> n),
|
||||
bi16: (\n ->
|
||||
when n is
|
||||
0b11i16 -> n),
|
||||
0b11i16 -> n
|
||||
_ -> n),
|
||||
bi32: (\n ->
|
||||
when n is
|
||||
0b11i32 -> n),
|
||||
0b11i32 -> n
|
||||
_ -> n),
|
||||
bi64: (\n ->
|
||||
when n is
|
||||
0b11i64 -> n),
|
||||
0b11i64 -> n
|
||||
_ -> n),
|
||||
bi128: (\n ->
|
||||
when n is
|
||||
0b11i128 -> n),
|
||||
0b11i128 -> n
|
||||
_ -> n),
|
||||
|
||||
bnat: (\n ->
|
||||
when n is
|
||||
0b11nat -> n),
|
||||
0b11nat -> n
|
||||
_ -> n),
|
||||
|
||||
dec: (\n ->
|
||||
when n is
|
||||
123.0dec -> n),
|
||||
123.0dec -> n
|
||||
_ -> n),
|
||||
f32: (\n ->
|
||||
when n is
|
||||
123.0f32 -> n),
|
||||
123.0f32 -> n
|
||||
_ -> n),
|
||||
f64: (\n ->
|
||||
when n is
|
||||
123.0f64 -> n),
|
||||
123.0f64 -> n
|
||||
_ -> n),
|
||||
|
||||
fdec: (\n ->
|
||||
when n is
|
||||
123dec -> n),
|
||||
123dec -> n
|
||||
_ -> n),
|
||||
ff32: (\n ->
|
||||
when n is
|
||||
123f32 -> n),
|
||||
123f32 -> n
|
||||
_ -> n),
|
||||
ff64: (\n ->
|
||||
when n is
|
||||
123f64 -> n),
|
||||
123f64 -> n
|
||||
_ -> n),
|
||||
}
|
||||
"#
|
||||
),
|
||||
|
@ -5681,6 +5708,7 @@ mod solve_expr {
|
|||
@Id (Id _ A) -> ""
|
||||
@Id (Id _ B) -> ""
|
||||
@Id (Id _ (C { a: "" })) -> ""
|
||||
@Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness
|
||||
"#
|
||||
),
|
||||
r#"Id [ A, B, C { a : Str }* ] -> Str"#,
|
||||
|
@ -5700,6 +5728,7 @@ mod solve_expr {
|
|||
@Id (Id _ A) -> ""
|
||||
@Id (Id _ B) -> ""
|
||||
@Id (Id _ (C { a: "" })) -> ""
|
||||
@Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness
|
||||
|
||||
f
|
||||
"#
|
||||
|
@ -6134,7 +6163,7 @@ mod solve_expr {
|
|||
),
|
||||
&[
|
||||
"ob : Bool",
|
||||
"ob : [ False, True ]",
|
||||
"ob : Bool",
|
||||
"True : [ False, True ]",
|
||||
"False : [ False, True ]",
|
||||
],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue