mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 22:09:09 +00:00
Move exhaustiveness checking to type checking
This commit is contained in:
parent
f7e04490c0
commit
85e3373d8b
15 changed files with 346 additions and 94 deletions
|
@ -4727,6 +4727,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
|
@ -4824,6 +4825,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
|
@ -4887,6 +4889,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
|
@ -4964,6 +4967,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
|
@ -5041,6 +5045,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
|
@ -5133,6 +5138,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
defn(
|
defn(
|
||||||
|
|
|
@ -574,7 +574,7 @@ impl Constraints {
|
||||||
Constraint::IsOpenType(_) => false,
|
Constraint::IsOpenType(_) => false,
|
||||||
Constraint::IncludesTag(_) => false,
|
Constraint::IncludesTag(_) => false,
|
||||||
Constraint::PatternPresence(_, _, _, _) => false,
|
Constraint::PatternPresence(_, _, _, _) => false,
|
||||||
Constraint::Exhaustive(_, _) => false,
|
Constraint::Exhaustive { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,10 +603,27 @@ impl Constraints {
|
||||||
Constraint::Store(type_index, variable, string_index, line_number)
|
Constraint::Store(type_index, variable, string_index, line_number)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exhaustive(&mut self, rows: SketchedRows, context: ExhaustiveContext) -> Constraint {
|
pub fn exhaustive(
|
||||||
let rows_index = Index::push_new(&mut self.sketched_rows, rows);
|
&mut self,
|
||||||
|
real_var: Variable,
|
||||||
|
real_region: Region,
|
||||||
|
real_category: Category,
|
||||||
|
expected_branches: Expected<Type>,
|
||||||
|
sketched_rows: SketchedRows,
|
||||||
|
context: ExhaustiveContext,
|
||||||
|
) -> Constraint {
|
||||||
|
let real_category = Index::push_new(&mut self.categories, real_category);
|
||||||
|
let expected_branches = Index::push_new(&mut self.expectations, expected_branches);
|
||||||
|
let sketched_rows = Index::push_new(&mut self.sketched_rows, sketched_rows);
|
||||||
|
|
||||||
Constraint::Exhaustive(rows_index, context)
|
Constraint::Exhaustive {
|
||||||
|
real_var,
|
||||||
|
real_region,
|
||||||
|
real_category,
|
||||||
|
expected_branches,
|
||||||
|
sketched_rows,
|
||||||
|
context,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,7 +669,14 @@ pub enum Constraint {
|
||||||
Index<PatternCategory>,
|
Index<PatternCategory>,
|
||||||
Region,
|
Region,
|
||||||
),
|
),
|
||||||
Exhaustive(Index<SketchedRows>, ExhaustiveContext),
|
Exhaustive {
|
||||||
|
real_var: Variable,
|
||||||
|
real_region: Region,
|
||||||
|
real_category: Index<Category>,
|
||||||
|
expected_branches: Index<Expected<Type>>,
|
||||||
|
sketched_rows: Index<SketchedRows>,
|
||||||
|
context: ExhaustiveContext,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
@ -707,8 +731,19 @@ impl std::fmt::Debug for Constraint {
|
||||||
arg0, arg1, arg2, arg3
|
arg0, arg1, arg2, arg3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::Exhaustive(arg0, arg1) => {
|
Self::Exhaustive {
|
||||||
write!(f, "Exhaustive({:?}, {:?})", arg0, arg1)
|
real_var: arg0,
|
||||||
|
real_region: arg1,
|
||||||
|
real_category: arg2,
|
||||||
|
expected_branches: arg3,
|
||||||
|
sketched_rows: arg4,
|
||||||
|
context: arg5,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Exhaustive({:?}, {:?}, {:?}, {:?}, {:?}, {:?})",
|
||||||
|
arg0, arg1, arg2, arg3, arg4, arg5
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1366,6 +1366,7 @@ fn build_effect_loop_inner_body(
|
||||||
region: Region::zero(),
|
region: Region::zero(),
|
||||||
loc_cond: Box::new(force_thunk_call),
|
loc_cond: Box::new(force_thunk_call),
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Expr::LetNonRec(
|
Expr::LetNonRec(
|
||||||
|
|
|
@ -6,6 +6,7 @@ use roc_exhaustive::{is_useful, Ctor, Error, Guard, Literal, Pattern, RenderAs,
|
||||||
use roc_module::ident::{TagIdIntType, TagName};
|
use roc_module::ident::{TagIdIntType, TagName};
|
||||||
use roc_region::all::{Loc, Region};
|
use roc_region::all::{Loc, Region};
|
||||||
use roc_types::subs::{Content, FlatType, Subs, SubsFmtContent, Variable};
|
use roc_types::subs::{Content, FlatType, Subs, SubsFmtContent, Variable};
|
||||||
|
use roc_types::types::AliasKind;
|
||||||
|
|
||||||
pub use roc_exhaustive::Context as ExhaustiveContext;
|
pub use roc_exhaustive::Context as ExhaustiveContext;
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ pub fn check(
|
||||||
roc_exhaustive::check(overall_region, context, matrix)
|
roc_exhaustive::check(overall_region, context, matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
enum SketchedPattern {
|
enum SketchedPattern {
|
||||||
Anything,
|
Anything,
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
|
@ -52,14 +53,14 @@ impl SketchedPattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct SketchedRow {
|
struct SketchedRow {
|
||||||
patterns: Vec<SketchedPattern>,
|
patterns: Vec<SketchedPattern>,
|
||||||
region: Region,
|
region: Region,
|
||||||
guard: Guard,
|
guard: Guard,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SketchedRows {
|
pub struct SketchedRows {
|
||||||
rows: Vec<SketchedRow>,
|
rows: Vec<SketchedRow>,
|
||||||
overall_region: Region,
|
overall_region: Region,
|
||||||
|
@ -155,13 +156,15 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Treat this like a literal so we mark it as non-exhaustive
|
||||||
|
MalformedPattern(..) => SP::Literal(Literal::Byte(1)),
|
||||||
|
|
||||||
Underscore
|
Underscore
|
||||||
| Identifier(_)
|
| Identifier(_)
|
||||||
| AbilityMemberSpecialization { .. }
|
| AbilityMemberSpecialization { .. }
|
||||||
| Shadowed(..)
|
| Shadowed(..)
|
||||||
| OpaqueNotInScope(..)
|
| OpaqueNotInScope(..)
|
||||||
| UnsupportedPattern(..)
|
| UnsupportedPattern(..) => SP::Anything,
|
||||||
| MalformedPattern(..) => SP::Anything,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +235,7 @@ pub fn sketch_rows(target_var: Variable, region: Region, patterns: &[WhenBranch]
|
||||||
|
|
||||||
let row = SketchedRow {
|
let row = SketchedRow {
|
||||||
patterns,
|
patterns,
|
||||||
region,
|
region: loc_pat.region,
|
||||||
guard,
|
guard,
|
||||||
};
|
};
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
|
@ -285,7 +288,7 @@ fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union,
|
||||||
|
|
||||||
use {Content::*, FlatType::*};
|
use {Content::*, FlatType::*};
|
||||||
|
|
||||||
match content {
|
match dealias_tag(subs, content) {
|
||||||
Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => {
|
Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => {
|
||||||
let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext);
|
let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext);
|
||||||
|
|
||||||
|
@ -335,3 +338,18 @@ fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content {
|
||||||
|
use Content::*;
|
||||||
|
let mut result = content;
|
||||||
|
loop {
|
||||||
|
match result {
|
||||||
|
Alias(_, _, real_var, AliasKind::Structural)
|
||||||
|
| RecursionVar {
|
||||||
|
structure: real_var,
|
||||||
|
..
|
||||||
|
} => result = subs.get_content_without_compacting(*real_var),
|
||||||
|
_ => return result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use roc_parse::pattern::PatternType::*;
|
||||||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||||
use roc_region::all::{Loc, Region};
|
use roc_region::all::{Loc, Region};
|
||||||
use roc_types::subs::{VarStore, Variable};
|
use roc_types::subs::{VarStore, Variable};
|
||||||
use roc_types::types::{Alias, LambdaSet, Type};
|
use roc_types::types::{Alias, Category, LambdaSet, Type};
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::{char, u32};
|
use std::{char, u32};
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ pub enum Expr {
|
||||||
region: Region,
|
region: Region,
|
||||||
loc_cond: Box<Loc<Expr>>,
|
loc_cond: Box<Loc<Expr>>,
|
||||||
branches: Vec<WhenBranch>,
|
branches: Vec<WhenBranch>,
|
||||||
|
branches_cond_var: Variable,
|
||||||
},
|
},
|
||||||
If {
|
If {
|
||||||
cond_var: Variable,
|
cond_var: Variable,
|
||||||
|
@ -193,6 +194,47 @@ pub enum Expr {
|
||||||
// Compiles, but will crash if reached
|
// Compiles, but will crash if reached
|
||||||
RuntimeError(RuntimeError),
|
RuntimeError(RuntimeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Expr {
|
||||||
|
pub fn category(&self) -> Category {
|
||||||
|
match self {
|
||||||
|
Self::Num(..) => Category::Num,
|
||||||
|
Self::Int(..) => Category::Int,
|
||||||
|
Self::Float(..) => Category::Float,
|
||||||
|
Self::Str(..) => Category::Str,
|
||||||
|
Self::SingleQuote(..) => Category::Character,
|
||||||
|
Self::List { .. } => Category::List,
|
||||||
|
&Self::Var(sym) => Category::Lookup(sym),
|
||||||
|
Self::When { .. } => Category::When,
|
||||||
|
Self::If { .. } => Category::If,
|
||||||
|
Self::LetRec(_, expr, _) => expr.value.category(),
|
||||||
|
Self::LetNonRec(_, expr, _) => expr.value.category(),
|
||||||
|
&Self::Call(_, _, called_via) => Category::CallResult(None, called_via),
|
||||||
|
&Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op),
|
||||||
|
Self::ForeignCall { .. } => Category::ForeignCall,
|
||||||
|
Self::Closure(..) => Category::Lambda,
|
||||||
|
Self::Record { .. } => Category::Record,
|
||||||
|
Self::EmptyRecord => Category::Record,
|
||||||
|
Self::Access { field, .. } => Category::Access(field.clone()),
|
||||||
|
Self::Accessor(data) => Category::Accessor(data.field.clone()),
|
||||||
|
Self::Update { .. } => Category::Record,
|
||||||
|
Self::Tag {
|
||||||
|
name, arguments, ..
|
||||||
|
} => Category::TagApply {
|
||||||
|
tag_name: name.clone(),
|
||||||
|
args_count: arguments.len(),
|
||||||
|
},
|
||||||
|
Self::ZeroArgumentTag { name, .. } => Category::TagApply {
|
||||||
|
tag_name: name.clone(),
|
||||||
|
args_count: 0,
|
||||||
|
},
|
||||||
|
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
|
||||||
|
Self::Expect(..) => Category::Expect,
|
||||||
|
Self::RuntimeError(..) => Category::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClosureData {
|
pub struct ClosureData {
|
||||||
pub function_type: Variable,
|
pub function_type: Variable,
|
||||||
|
@ -782,6 +824,7 @@ pub fn canonicalize_expr<'a>(
|
||||||
region,
|
region,
|
||||||
loc_cond: Box::new(can_cond),
|
loc_cond: Box::new(can_cond),
|
||||||
branches: can_branches,
|
branches: can_branches,
|
||||||
|
branches_cond_var: var_store.fresh(),
|
||||||
};
|
};
|
||||||
|
|
||||||
(expr, output)
|
(expr, output)
|
||||||
|
@ -1298,6 +1341,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
region,
|
region,
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var,
|
||||||
} => {
|
} => {
|
||||||
let loc_cond = Box::new(Loc {
|
let loc_cond = Box::new(Loc {
|
||||||
region: loc_cond.region,
|
region: loc_cond.region,
|
||||||
|
@ -1333,6 +1377,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
region,
|
region,
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches: new_branches,
|
branches: new_branches,
|
||||||
|
branches_cond_var,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
If {
|
If {
|
||||||
|
|
|
@ -65,6 +65,7 @@ fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr) {
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
region: _,
|
region: _,
|
||||||
|
branches_cond_var: _,
|
||||||
} => {
|
} => {
|
||||||
walk_when(visitor, *cond_var, *expr_var, loc_cond, branches);
|
walk_when(visitor, *cond_var, *expr_var, loc_cond, branches);
|
||||||
}
|
}
|
||||||
|
|
|
@ -580,17 +580,18 @@ pub fn constrain_expr(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
When {
|
When {
|
||||||
cond_var,
|
cond_var: real_cond_var,
|
||||||
expr_var,
|
expr_var,
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let cond_var = *cond_var;
|
let branches_cond_var = *branches_cond_var;
|
||||||
let cond_type = Variable(cond_var);
|
let branches_cond_type = Variable(branches_cond_var);
|
||||||
|
|
||||||
let branch_var = *expr_var;
|
let body_var = *expr_var;
|
||||||
let branch_type = Variable(branch_var);
|
let body_type = Variable(body_var);
|
||||||
|
|
||||||
let branches_region = {
|
let branches_region = {
|
||||||
debug_assert!(!branches.is_empty());
|
debug_assert!(!branches.is_empty());
|
||||||
|
@ -615,13 +616,13 @@ pub fn constrain_expr(
|
||||||
index,
|
index,
|
||||||
region: ann_source.region(),
|
region: ann_source.region(),
|
||||||
},
|
},
|
||||||
branch_type.clone(),
|
body_type.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => ForReason(
|
_ => ForReason(
|
||||||
Reason::WhenBranch { index },
|
Reason::WhenBranch { index },
|
||||||
branch_type.clone(),
|
body_type.clone(),
|
||||||
branch_region,
|
branch_region,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -659,7 +660,7 @@ pub fn constrain_expr(
|
||||||
index: HumanIndex::zero_based(index),
|
index: HumanIndex::zero_based(index),
|
||||||
sub_pattern,
|
sub_pattern,
|
||||||
},
|
},
|
||||||
cond_type.clone(),
|
branches_cond_type.clone(),
|
||||||
sub_region,
|
sub_region,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -703,15 +704,16 @@ pub fn constrain_expr(
|
||||||
|
|
||||||
// After solving the condition variable with what's expected from the branch patterns,
|
// After solving the condition variable with what's expected from the branch patterns,
|
||||||
// check it against the condition expression.
|
// check it against the condition expression.
|
||||||
// TODO: when we have exhaustiveness checking during the typechecking phase, perform
|
//
|
||||||
// exhaustiveness checking when this expectation fails. That will produce better error
|
// First, solve the condition type.
|
||||||
// messages.
|
let real_cond_var = *real_cond_var;
|
||||||
let expr_con = constrain_expr(
|
let real_cond_type = Type::Variable(real_cond_var);
|
||||||
|
let cond_constraint = constrain_expr(
|
||||||
constraints,
|
constraints,
|
||||||
env,
|
env,
|
||||||
loc_cond.region,
|
loc_cond.region,
|
||||||
&loc_cond.value,
|
&loc_cond.value,
|
||||||
Expected::ForReason(Reason::WhenBranches, cond_type, branches_region),
|
Expected::NoExpectation(real_cond_type),
|
||||||
);
|
);
|
||||||
// branch_cons.extend(pattern_cons);
|
// branch_cons.extend(pattern_cons);
|
||||||
// branch_constraints.push(constraints.and_constraint(pattern_cons));
|
// branch_constraints.push(constraints.and_constraint(pattern_cons));
|
||||||
|
@ -719,10 +721,18 @@ pub fn constrain_expr(
|
||||||
// total_cons.push(expr_con);
|
// total_cons.push(expr_con);
|
||||||
pattern_cons.push(expr_con);
|
pattern_cons.push(expr_con);
|
||||||
|
|
||||||
let sketched_rows = sketch_rows(cond_var, branches_region, &branches);
|
// Now check the condition against the type expected by the branches.
|
||||||
let exhaustiveness_constraint =
|
let sketched_rows = sketch_rows(real_cond_var, branches_region, &branches);
|
||||||
constraints.exhaustive(sketched_rows, ExhaustiveContext::BadCase);
|
// let exhaustive_reason = Reason::Exhaustive(sketched_rows, ExhaustiveContext::BadCase);
|
||||||
pattern_cons.push(exhaustiveness_constraint);
|
let cond_matches_branches_constraint = constraints.exhaustive(
|
||||||
|
real_cond_var,
|
||||||
|
loc_cond.region,
|
||||||
|
loc_cond.value.category(),
|
||||||
|
Expected::ForReason(Reason::WhenBranches, branches_cond_type, branches_region),
|
||||||
|
sketched_rows,
|
||||||
|
ExhaustiveContext::BadCase,
|
||||||
|
);
|
||||||
|
pattern_cons.push(cond_matches_branches_constraint);
|
||||||
|
|
||||||
// Solve all the pattern constraints together, introducing variables in the pattern as
|
// Solve all the pattern constraints together, introducing variables in the pattern as
|
||||||
// need be before solving the bodies.
|
// need be before solving the bodies.
|
||||||
|
@ -742,7 +752,10 @@ pub fn constrain_expr(
|
||||||
let total_cons = [when_body_con, result_con];
|
let total_cons = [when_body_con, result_con];
|
||||||
let branch_constraints = constraints.and_constraint(total_cons);
|
let branch_constraints = constraints.and_constraint(total_cons);
|
||||||
|
|
||||||
constraints.exists([cond_var, *expr_var], branch_constraints)
|
constraints.exists(
|
||||||
|
[branches_cond_var, real_cond_var, *expr_var],
|
||||||
|
branch_constraints,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Access {
|
Access {
|
||||||
record_var,
|
record_var,
|
||||||
|
|
|
@ -72,7 +72,7 @@ pub enum Pattern {
|
||||||
Ctor(Union, TagId, std::vec::Vec<Pattern>),
|
Ctor(Union, TagId, std::vec::Vec<Pattern>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Literal {
|
pub enum Literal {
|
||||||
Int(i128),
|
Int(i128),
|
||||||
U128(u128),
|
U128(u128),
|
||||||
|
@ -96,14 +96,14 @@ pub enum Error {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum Context {
|
pub enum Context {
|
||||||
BadArg,
|
BadArg,
|
||||||
BadDestruct,
|
BadDestruct,
|
||||||
BadCase,
|
BadCase,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum Guard {
|
pub enum Guard {
|
||||||
HasGuard,
|
HasGuard,
|
||||||
NoGuard,
|
NoGuard,
|
||||||
|
|
|
@ -2091,6 +2091,7 @@ fn pattern_to_when<'a>(
|
||||||
value: body,
|
value: body,
|
||||||
guard: None,
|
guard: None,
|
||||||
}],
|
}],
|
||||||
|
branches_cond_var: pattern_var,
|
||||||
};
|
};
|
||||||
|
|
||||||
(symbol, Loc::at_zero(wrapped_body))
|
(symbol, Loc::at_zero(wrapped_body))
|
||||||
|
@ -3753,6 +3754,7 @@ pub fn with_hole<'a>(
|
||||||
region,
|
region,
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: _,
|
||||||
} => {
|
} => {
|
||||||
let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value);
|
let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value);
|
||||||
|
|
||||||
|
@ -5443,6 +5445,7 @@ pub fn from_can<'a>(
|
||||||
region,
|
region,
|
||||||
loc_cond,
|
loc_cond,
|
||||||
branches,
|
branches,
|
||||||
|
branches_cond_var: _,
|
||||||
} => {
|
} => {
|
||||||
let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value);
|
let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value);
|
||||||
|
|
||||||
|
|
|
@ -1197,12 +1197,116 @@ fn solve(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Exhaustive(rows_index, context) => {
|
&Exhaustive {
|
||||||
let sketched_rows = constraints.sketched_rows[rows_index.index()].clone();
|
real_var,
|
||||||
|
real_region,
|
||||||
|
real_category,
|
||||||
|
expected_branches,
|
||||||
|
sketched_rows,
|
||||||
|
context,
|
||||||
|
} => {
|
||||||
|
// A few cases:
|
||||||
|
// 1. Either condition or branch types already have a type error. In this case just
|
||||||
|
// propogate 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 checked = roc_can::exhaustive::check(&subs, sketched_rows, *context);
|
let expected_branches = &constraints.expectations[expected_branches.index()];
|
||||||
if let Err(errors) = checked {
|
let branches_var =
|
||||||
problems.extend(errors.into_iter().map(TypeError::Exhaustive));
|
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 = match (real_content, branches_content) {
|
||||||
|
(Content::Error | Content::Structure(FlatType::Erroneous(_)), _) => true,
|
||||||
|
(_, Content::Error | Content::Structure(FlatType::Erroneous(_))) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -1213,6 +1317,35 @@ fn solve(
|
||||||
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
|
/// If a symbol claims to specialize an ability member, check that its solved type in fact
|
||||||
/// does specialize the ability, and record the specialization.
|
/// does specialize the ability, and record the specialization.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|
|
@ -6163,7 +6163,7 @@ mod solve_expr {
|
||||||
),
|
),
|
||||||
&[
|
&[
|
||||||
"ob : Bool",
|
"ob : Bool",
|
||||||
"ob : [ False, True ]",
|
"ob : Bool",
|
||||||
"True : [ False, True ]",
|
"True : [ False, True ]",
|
||||||
"False : [ False, True ]",
|
"False : [ False, True ]",
|
||||||
],
|
],
|
||||||
|
|
|
@ -2118,6 +2118,18 @@ impl Content {
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unroll_structural_alias<'a>(&'a self, subs: &'a Subs) -> &'a Self {
|
||||||
|
let mut result = self;
|
||||||
|
loop {
|
||||||
|
match result {
|
||||||
|
Self::Alias(_, _, real_var, AliasKind::Structural) => {
|
||||||
|
result = subs.get_content_without_compacting(*real_var)
|
||||||
|
}
|
||||||
|
_ => return result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|
|
@ -1815,6 +1815,9 @@ pub enum Category {
|
||||||
DefaultValue(Lowercase), // for setting optional fields
|
DefaultValue(Lowercase), // for setting optional fields
|
||||||
|
|
||||||
AbilityMemberSpecialization(Symbol),
|
AbilityMemberSpecialization(Symbol),
|
||||||
|
|
||||||
|
Expect,
|
||||||
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
|
@ -1528,7 +1528,7 @@ fn format_category<'b>(
|
||||||
alloc.concat([this_is, alloc.text(" an uniqueness attribute")]),
|
alloc.concat([this_is, alloc.text(" an uniqueness attribute")]),
|
||||||
alloc.text(" of type:"),
|
alloc.text(" of type:"),
|
||||||
),
|
),
|
||||||
Storage(_file, _line) => (
|
Storage(..) | Unknown => (
|
||||||
alloc.concat([this_is, alloc.text(" a value")]),
|
alloc.concat([this_is, alloc.text(" a value")]),
|
||||||
alloc.text(" of type:"),
|
alloc.text(" of type:"),
|
||||||
),
|
),
|
||||||
|
@ -1540,6 +1540,10 @@ fn format_category<'b>(
|
||||||
alloc.concat([this_is, alloc.text(" a declared specialization")]),
|
alloc.concat([this_is, alloc.text(" a declared specialization")]),
|
||||||
alloc.text(" of type:"),
|
alloc.text(" of type:"),
|
||||||
),
|
),
|
||||||
|
Expect => (
|
||||||
|
alloc.concat([this_is, alloc.text(" an expectation")]),
|
||||||
|
alloc.text(" of type:"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1197,6 +1197,7 @@ mod test_reporting {
|
||||||
when 1 is
|
when 1 is
|
||||||
2 -> "foo"
|
2 -> "foo"
|
||||||
3 -> {}
|
3 -> {}
|
||||||
|
_ -> ""
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -1205,10 +1206,10 @@ mod test_reporting {
|
||||||
|
|
||||||
The 2nd branch of this `when` does not match all the previous branches:
|
The 2nd branch of this `when` does not match all the previous branches:
|
||||||
|
|
||||||
1│ when 1 is
|
1│ when 1 is
|
||||||
2│ 2 -> "foo"
|
2│ 2 -> "foo"
|
||||||
3│ 3 -> {}
|
3│> 3 -> {}
|
||||||
^^
|
4│ _ -> ""
|
||||||
|
|
||||||
The 2nd branch is a record of type:
|
The 2nd branch is a record of type:
|
||||||
|
|
||||||
|
@ -1788,7 +1789,7 @@ mod test_reporting {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
when { foo: 1 } is
|
when { foo: 1 } is
|
||||||
{ foo: 2 } -> foo
|
{ foo: _ } -> foo
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -1797,7 +1798,7 @@ mod test_reporting {
|
||||||
|
|
||||||
I cannot find a `foo` value
|
I cannot find a `foo` value
|
||||||
|
|
||||||
2│ { foo: 2 } -> foo
|
2│ { foo: _ } -> foo
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
Did you mean one of these?
|
Did you mean one of these?
|
||||||
|
@ -2791,26 +2792,18 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
The branches of this `when` expression don't match the condition:
|
This `when` does not cover all the possibilities:
|
||||||
|
|
||||||
4│> when x is
|
4│> when x is
|
||||||
5│ Red -> 3
|
5│> Red -> 3
|
||||||
|
|
||||||
This `x` value is a:
|
Other possibilities include:
|
||||||
|
|
||||||
[ Green, Red ]
|
Green
|
||||||
|
|
||||||
But the branch patterns have type:
|
I would have to crash if I saw one of those! Add branches for them!
|
||||||
|
|
||||||
[ Red ]
|
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
|
||||||
|
|
||||||
Tip: Looks like the branches are missing coverage of the `Green` tag.
|
|
||||||
|
|
||||||
Tip: Maybe you need to add a catch-all branch, like `_`?
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2831,27 +2824,19 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
The branches of this `when` expression don't match the condition:
|
This `when` does not cover all the possibilities:
|
||||||
|
|
||||||
4│> when x is
|
4│> when x is
|
||||||
5│ Red -> 0
|
5│> Red -> 0
|
||||||
6│ Green -> 1
|
6│> Green -> 1
|
||||||
|
|
||||||
This `x` value is a:
|
Other possibilities include:
|
||||||
|
|
||||||
[ Blue, Green, Red ]
|
Blue
|
||||||
|
|
||||||
But the branch patterns have type:
|
I would have to crash if I saw one of those! Add branches for them!
|
||||||
|
|
||||||
[ Green, Red ]
|
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
|
||||||
|
|
||||||
Tip: Looks like the branches are missing coverage of the `Blue` tag.
|
|
||||||
|
|
||||||
Tip: Maybe you need to add a catch-all branch, like `_`?
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2872,27 +2857,20 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
The branches of this `when` expression don't match the condition:
|
This `when` does not cover all the possibilities:
|
||||||
|
|
||||||
5│> when x is
|
5│> when x is
|
||||||
6│ NotAsked -> 3
|
6│> NotAsked -> 3
|
||||||
|
|
||||||
This `x` value is a:
|
Other possibilities include:
|
||||||
|
|
||||||
[ Failure I64, Loading, NotAsked, Success Str ]
|
Failure _
|
||||||
|
Loading
|
||||||
|
Success _
|
||||||
|
|
||||||
But the branch patterns have type:
|
I would have to crash if I saw one of those! Add branches for them!
|
||||||
|
|
||||||
[ NotAsked ]
|
|
||||||
|
|
||||||
The branches must be cases of the `when` condition's type!
|
|
||||||
|
|
||||||
Tip: Looks like the branches are missing coverage of the
|
|
||||||
`Success`, `Failure` and `Loading` tags.
|
|
||||||
|
|
||||||
Tip: Maybe you need to add a catch-all branch, like `_`?
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2955,7 +2933,7 @@ mod test_reporting {
|
||||||
|
|
||||||
Other possibilities include:
|
Other possibilities include:
|
||||||
|
|
||||||
{ a: Just _, b }
|
{ a: Just _ }
|
||||||
|
|
||||||
I would have to crash if I saw one of those! Add branches for them!
|
I would have to crash if I saw one of those! Add branches for them!
|
||||||
"#
|
"#
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue