Move exhaustiveness checking to type checking

This commit is contained in:
Ayaz Hafiz 2022-04-22 15:23:56 -04:00
parent f7e04490c0
commit 85e3373d8b
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
15 changed files with 346 additions and 94 deletions

View file

@ -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(

View file

@ -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
)
} }
} }
} }

View file

@ -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(

View file

@ -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,
}
}
}

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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,

View file

@ -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,

View file

@ -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);

View file

@ -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)]

View file

@ -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 ]",
], ],

View file

@ -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)]

View file

@ -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)]

View file

@ -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:"),
),
} }
} }

View file

@ -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!
"# "#