diff --git a/crates/ast/src/constrain.rs b/crates/ast/src/constrain.rs index 066c967c5c..6f52e9506c 100644 --- a/crates/ast/src/constrain.rs +++ b/crates/ast/src/constrain.rs @@ -2705,7 +2705,7 @@ pub mod test_constrain { A _ -> Z "# ), - "[A [M, N]*] -> [X, Y, Z]*", + "[A [M, N]] -> [X, Y, Z]*", ) } diff --git a/crates/ast/src/solve_type.rs b/crates/ast/src/solve_type.rs index 10db23ea45..d976a85b65 100644 --- a/crates/ast/src/solve_type.rs +++ b/crates/ast/src/solve_type.rs @@ -1084,7 +1084,8 @@ fn type_to_union_tags<'a>( let ext = { let (it, ext) = - roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var) + .expect("not a tag union"); tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 7d350ee5bd..4bcade1dd2 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -735,7 +735,8 @@ pub fn constrain_expr( let mut pattern_vars = Vec::with_capacity(branches.len()); let mut pattern_headers = SendMap::default(); let mut pattern_cons = Vec::with_capacity(branches.len() + 2); - let mut branch_cons = Vec::with_capacity(branches.len()); + let mut delayed_is_open_constraints = Vec::with_capacity(2); + let mut body_cons = Vec::with_capacity(branches.len()); for (index, when_branch) in branches.iter().enumerate() { let expected_pattern = |sub_pattern, sub_region| { @@ -749,19 +750,24 @@ pub fn constrain_expr( ) }; - let (new_pattern_vars, new_pattern_headers, pattern_con, branch_con) = - constrain_when_branch_help( - constraints, - env, - region, - when_branch, - expected_pattern, - branch_expr_reason( - &expected, - HumanIndex::zero_based(index), - when_branch.value.region, - ), - ); + let ConstrainedBranch { + vars: new_pattern_vars, + headers: new_pattern_headers, + pattern_constraints, + is_open_constrains, + body_constraints, + } = constrain_when_branch_help( + constraints, + env, + region, + when_branch, + expected_pattern, + branch_expr_reason( + &expected, + HumanIndex::zero_based(index), + when_branch.value.region, + ), + ); pattern_vars.extend(new_pattern_vars); @@ -779,9 +785,10 @@ pub fn constrain_expr( } pattern_headers.extend(new_pattern_headers); - pattern_cons.push(pattern_con); + pattern_cons.push(pattern_constraints); + delayed_is_open_constraints.extend(is_open_constrains); - branch_cons.push(branch_con); + body_cons.push(body_constraints); } // Deviation: elm adds another layer of And nesting @@ -793,6 +800,11 @@ pub fn constrain_expr( // The return type of each branch must equal the return type of // the entire when-expression. + // Layer on the "is-open" constraints at the very end, after we know what the branch + // types are supposed to look like without open-ness. + let is_open_constr = constraints.and_constraint(delayed_is_open_constraints); + pattern_cons.push(is_open_constr); + // After solving the condition variable with what's expected from the branch patterns, // check it against the condition expression. // @@ -826,7 +838,7 @@ pub fn constrain_expr( // Solve all the pattern constraints together, introducing variables in the pattern as // need be before solving the bodies. let pattern_constraints = constraints.and_constraint(pattern_cons); - let body_constraints = constraints.and_constraint(branch_cons); + let body_constraints = constraints.and_constraint(body_cons); let when_body_con = constraints.let_constraint( [], pattern_vars, @@ -1790,6 +1802,14 @@ fn constrain_value_def( } } +struct ConstrainedBranch { + vars: Vec, + headers: VecMap>, + pattern_constraints: Constraint, + is_open_constrains: Vec, + body_constraints: Constraint, +} + /// Constrain a when branch, returning (variables in pattern, symbols introduced in pattern, pattern constraint, body constraint). /// We want to constraint all pattern constraints in a "when" before body constraints. #[inline(always)] @@ -1800,12 +1820,7 @@ fn constrain_when_branch_help( when_branch: &WhenBranch, pattern_expected: impl Fn(HumanIndex, Region) -> PExpected, expr_expected: Expected, -) -> ( - Vec, - VecMap>, - Constraint, - Constraint, -) { +) -> ConstrainedBranch { let ret_constraint = constrain_expr( constraints, env, @@ -1869,41 +1884,43 @@ fn constrain_when_branch_help( } } - let (pattern_constraints, body_constraints) = if let Some(loc_guard) = &when_branch.guard { - let guard_constraint = constrain_expr( - constraints, - env, - region, - &loc_guard.value, - Expected::ForReason( - Reason::WhenGuard, - Type::Variable(Variable::BOOL), - loc_guard.region, - ), - ); + let (pattern_constraints, delayed_is_open_constraints, body_constraints) = + if let Some(loc_guard) = &when_branch.guard { + let guard_constraint = constrain_expr( + constraints, + env, + region, + &loc_guard.value, + Expected::ForReason( + Reason::WhenGuard, + Type::Variable(Variable::BOOL), + loc_guard.region, + ), + ); - // must introduce the headers from the pattern before constraining the guard - state - .constraints - .append(&mut state.delayed_is_open_constraints); - let state_constraints = constraints.and_constraint(state.constraints); - let inner = constraints.let_constraint([], [], [], guard_constraint, ret_constraint); + // must introduce the headers from the pattern before constraining the guard + let delayed_is_open_constraints = state.delayed_is_open_constraints; + let state_constraints = constraints.and_constraint(state.constraints); + let inner = constraints.let_constraint([], [], [], guard_constraint, ret_constraint); - (state_constraints, inner) - } else { - state - .constraints - .append(&mut state.delayed_is_open_constraints); - let state_constraints = constraints.and_constraint(state.constraints); - (state_constraints, ret_constraint) - }; + (state_constraints, delayed_is_open_constraints, inner) + } else { + let delayed_is_open_constraints = state.delayed_is_open_constraints; + let state_constraints = constraints.and_constraint(state.constraints); + ( + state_constraints, + delayed_is_open_constraints, + ret_constraint, + ) + }; - ( - state.vars, - state.headers, + ConstrainedBranch { + vars: state.vars, + headers: state.headers, pattern_constraints, + is_open_constrains: delayed_is_open_constraints, body_constraints, - ) + } } fn constrain_field( diff --git a/crates/compiler/constrain/src/pattern.rs b/crates/compiler/constrain/src/pattern.rs index bad0df0fc5..31ff602be5 100644 --- a/crates/compiler/constrain/src/pattern.rs +++ b/crates/compiler/constrain/src/pattern.rs @@ -498,9 +498,6 @@ pub fn constrain_pattern( state.vars.push(*ext_var); state.constraints.push(whole_con); state.constraints.push(tag_con); - state - .constraints - .append(&mut state.delayed_is_open_constraints); } UnwrappedOpaque { diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index eada1b5cec..99cc4a3ccf 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -633,6 +633,23 @@ impl<'a> UnionLayout<'a> { round_up_to_alignment(data_width, data_align) } + pub fn tag_id_offset(&self, target_info: TargetInfo) -> Option { + use UnionLayout::*; + + if let NonNullableUnwrapped(_) | NullableUnwrapped { .. } = self { + return None; + } + + let data_width = self.data_size_and_alignment_help_match(None, target_info).0; + + // current, broken logic + if data_width > 8 { + Some(round_up_to_alignment(data_width, 8)) + } else { + Some(data_width) + } + } + /// Very important to use this when doing a memcpy! fn stack_size_without_alignment(&self, target_info: TargetInfo) -> u32 { match self { @@ -3055,7 +3072,9 @@ pub fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_) | Content::RigidVar(_))) => ext_fields.is_empty(), + Ok(()) | Err((_, Content::FlexVar(_) | Content::RigidVar(_) | Content::Error)) => { + ext_fields.is_empty() + } Err(content) => panic!("invalid content in ext_var: {:?}", content), } } diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index dbea87d931..bdeaf945e5 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -1653,25 +1653,35 @@ fn open_tag_union(subs: &mut Subs, var: Variable) { use {Content::*, FlatType::*}; let 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)); - subs.set_content(var, new_union); + match desc.content { + Structure(TagUnion(tags, ext)) => { + 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)); + subs.set_content(var, new_union); + } + + // 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])); } - // 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])); + Structure(Record(fields, _)) => { + // Open up all nested tag unions. + stack.extend(subs.get_subs_slice(fields.variables())); + } + + _ => { + // Everything else is not a structural type that can be opened + // (i.e. cannot be matched in a pattern-match) + } } // 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. } } @@ -2807,7 +2817,8 @@ fn type_to_variable<'a>( subs, UnionTags::default(), temp_ext_var, - ); + ) + .expect("extension var could not be seen as a tag union"); for _ in it { unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); @@ -3351,7 +3362,8 @@ fn type_to_union_tags<'a>( subs, UnionTags::default(), temp_ext_var, - ); + ) + .expect("extension var could not be seen as tag union"); tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 0c0b6a2c1f..f113c86872 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -245,7 +245,13 @@ mod solve_expr { assert_eq!(actual, expected.to_string()); } - fn infer_queries_help(src: &str, expected: impl FnOnce(&str), print_only_under_alias: bool) { + #[derive(Default)] + struct InferOptions { + print_only_under_alias: bool, + allow_errors: bool, + } + + fn infer_queries_help(src: &str, expected: impl FnOnce(&str), options: InferOptions) { let ( LoadedModule { module_id: home, @@ -269,12 +275,14 @@ mod solve_expr { let (can_problems, type_problems) = format_problems(&src, home, &interns, can_problems, type_problems); - assert!( - can_problems.is_empty(), - "Canonicalization problems: {}", - can_problems - ); - assert!(type_problems.is_empty(), "Type problems: {}", type_problems); + if !options.allow_errors { + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); + assert!(type_problems.is_empty(), "Type problems: {}", type_problems); + } let queries = parse_queries(&src); assert!(!queries.is_empty(), "No queries provided!"); @@ -295,7 +303,7 @@ mod solve_expr { &interns, DebugPrint { print_lambda_sets: true, - print_only_under_alias, + print_only_under_alias: options.print_only_under_alias, }, ); subs.rollback_to(snapshot); @@ -325,11 +333,10 @@ mod solve_expr { } macro_rules! infer_queries { - ($program:expr, @$queries:literal $(,)?) => { - infer_queries_help($program, |golden| insta::assert_snapshot!(golden, @$queries), false) - }; - ($program:expr, @$queries:literal, print_only_under_alias=true $(,)?) => { - infer_queries_help($program, |golden| insta::assert_snapshot!(golden, @$queries), true) + ($program:expr, @$queries:literal $($option:ident: $value:expr)*) => { + infer_queries_help($program, |golden| insta::assert_snapshot!(golden, @$queries), InferOptions { + $($option: $value,)* ..InferOptions::default() + }) }; } @@ -6662,7 +6669,7 @@ mod solve_expr { A#id(4) : A -[[id(4)]]-> A idChoice : a -[[] + a:id(2):1]-> a | a has Id idChoice : A -[[id(4)]]-> A - "#, + "# ) } @@ -6696,8 +6703,8 @@ mod solve_expr { A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {}) Id#id(3) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {}) alias : {} -[[id(5)]]-> ({} -[[8(8)]]-> {}) - "#, - print_only_under_alias = true, + "# + print_only_under_alias: true ) } @@ -6726,8 +6733,8 @@ mod solve_expr { @r#" A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {}) it : {} -[[8(8)]]-> {} - "#, - print_only_under_alias = true, + "# + print_only_under_alias: true ) } @@ -6757,8 +6764,8 @@ mod solve_expr { @r#" A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {}) A#id(5) : {} -[[id(5)]]-> ({} -[[8(8)]]-> {}) - "#, - print_only_under_alias = true, + "# + print_only_under_alias: true ) } @@ -6895,8 +6902,8 @@ mod solve_expr { Named name outerList : [Named Str (List a)] as a name : Str outerList : List ([Named Str (List a)] as a) - "#, - print_only_under_alias = true + "# + print_only_under_alias: true ) } @@ -7012,8 +7019,8 @@ mod solve_expr { #^^^{-1} "# ), - @r#"fun : {} -[[thunk(9) (({} -[[15(15)]]-> { s1 : Str })) ({ s1 : Str } -[[g(4)]]-> ({} -[[13(13) Str]]-> Str)), thunk(9) (({} -[[14(14)]]-> Str)) (Str -[[f(3)]]-> ({} -[[11(11)]]-> Str))]]-> Str"#, - print_only_under_alias = true, + @r#"fun : {} -[[thunk(9) (({} -[[15(15)]]-> { s1 : Str })) ({ s1 : Str } -[[g(4)]]-> ({} -[[13(13) Str]]-> Str)), thunk(9) (({} -[[14(14)]]-> Str)) (Str -[[f(3)]]-> ({} -[[11(11)]]-> Str))]]-> Str"# + print_only_under_alias: true ); } @@ -7357,4 +7364,80 @@ mod solve_expr { "List (A U8)", ); } + + #[test] + fn shared_pattern_variable_in_when_patterns() { + infer_queries!( + indoc!( + r#" + when A "" is + # ^^^^ + A x | B x -> x + # ^ ^ ^ + "# + ), + @r###" + A "" : [A Str, B Str] + x : Str + x : Str + x : Str + "### + ); + } + + #[test] + fn shared_pattern_variable_in_multiple_branch_when_patterns() { + infer_queries!( + indoc!( + r#" + when A "" is + # ^^^^ + A x | B x -> x + # ^ ^ ^ + C x | D x -> x + # ^ ^ ^ + "# + ), + @r###" + A "" : [A Str, B Str, C Str, D Str] + x : Str + x : Str + x : Str + x : Str + x : Str + x : Str + "### + ); + } + + #[test] + fn catchall_branch_for_pattern_not_last() { + infer_queries!( + indoc!( + r#" + \x -> when x is + #^ + A B _ -> "" + A _ C -> "" + "# + ), + @r#"x : [A [B]* [C]*]"# + allow_errors: true + ); + } + + #[test] + fn catchall_branch_walk_into_nested_types() { + infer_queries!( + indoc!( + r#" + \x -> when x is + #^ + { a: A { b: B } } -> "" + _ -> "" + "# + ), + @r#"x : { a : [A { b : [B]* }*]* }*"# + ) + } } diff --git a/crates/compiler/test_gen/src/gen_tags.rs b/crates/compiler/test_gen/src/gen_tags.rs index 4d5401c1bc..d7c3c4fe2a 100644 --- a/crates/compiler/test_gen/src/gen_tags.rs +++ b/crates/compiler/test_gen/src/gen_tags.rs @@ -1879,3 +1879,27 @@ fn alignment_i128() { ((i128, bool), [u8; 8], u8, [u8; 15]) ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[should_panic(expected = r#"Roc failed with message: "Erroneous: Expr::Closure""#)] +fn error_type_in_tag_union_payload() { + assert_evals_to!( + indoc!( + r#" + f : ([] -> Bool) -> Bool + f = \fun -> + if True then + fun 42 + else + False + + f (\x -> x) + "# + ), + 0, + u8, + |x| x, + true // ignore type errors + ) +} diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index 5a527251c5..5fbcc6b3db 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -2702,7 +2702,8 @@ impl UnionTags { subs: &'a Subs, ext: Variable, ) -> impl Iterator + 'a { - let (it, _) = crate::types::gather_tags_unsorted_iter(subs, *self, ext); + let (it, _) = + crate::types::gather_tags_unsorted_iter(subs, *self, ext).expect("not a tag union"); let f = move |(label, slice): (_, SubsSlice)| (label, subs.get_subs_slice(slice)); @@ -2715,7 +2716,8 @@ impl UnionTags { subs: &'a Subs, ext: Variable, ) -> (UnsortedUnionLabels<'a, TagName>, Variable) { - let (it, ext) = crate::types::gather_tags_unsorted_iter(subs, *self, ext); + let (it, ext) = + crate::types::gather_tags_unsorted_iter(subs, *self, ext).expect("not a tag union"); let f = move |(label, slice): (_, SubsSlice)| (label, subs.get_subs_slice(slice)); let it = it.map(f); @@ -2741,7 +2743,8 @@ impl UnionTags { ext, ) } else { - let union_structure = crate::types::gather_tags(subs, *self, ext); + let union_structure = + crate::types::gather_tags(subs, *self, ext).expect("not a tag union"); ( Box::new(union_structure.fields.into_iter()), @@ -2767,7 +2770,8 @@ impl UnionTags { ext, ) } else { - let (fields, ext) = crate::types::gather_tags_slices(subs, *self, ext); + let (fields, ext) = + crate::types::gather_tags_slices(subs, *self, ext).expect("not a tag union"); (Box::new(fields.into_iter()), ext) } diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index 6c2e5ca4b3..2d3b4ef74a 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -2674,14 +2674,28 @@ pub fn gather_fields( }) } +#[derive(Debug)] +pub enum GatherTagsError { + NotATagUnion(Variable), +} + +/// Gathers tag payloads of a type, assuming it is a tag. +/// +/// If the given type is unbound or an error, no payloads are returned. +/// +/// If the given type cannot be seen as a tag, unbound type, or error, this +/// function returns an error. pub fn gather_tags_unsorted_iter( subs: &Subs, other_fields: UnionTags, mut var: Variable, -) -> ( - impl Iterator + '_, - Variable, -) { +) -> Result< + ( + impl Iterator + '_, + Variable, + ), + GatherTagsError, +> { use crate::subs::Content::*; use crate::subs::FlatType::*; @@ -2697,34 +2711,32 @@ pub fn gather_tags_unsorted_iter( Structure(FunctionOrTagUnion(_tag_name_index, _, _sub_ext)) => { todo!("this variant does not use SOA yet, and therefore this case is unreachable right now") - // let sub_fields: UnionTags = (*tag_name_index).into(); - // stack.push(sub_fields); + // let sub_fields: UnionTags = (*tag_name_index).into(); + // stack.push(sub_fields); // - // var = *sub_ext; + // var = *sub_ext; } Structure(RecursiveTagUnion(_, _sub_fields, _sub_ext)) => { todo!("this variant does not use SOA yet, and therefore this case is unreachable right now") - // stack.push(*sub_fields); + // stack.push(*sub_fields); // - // var = *sub_ext; + // var = *sub_ext; } Alias(_, _, actual_var, _) => { - // TODO according to elm/compiler: "TODO may be dropping useful alias info here" var = *actual_var; } Structure(EmptyTagUnion) => break, FlexVar(_) => break, - // TODO investigate this likely can happen when there is a type error + // TODO investigate, this likely can happen when there is a type error RigidVar(_) => break, - other => unreachable!( - "something weird ended up in a tag union type: {:?} at {:?}", - other, var - ), + Error => break, + + _ => return Err(GatherTagsError::NotATagUnion(var)), } } @@ -2738,15 +2750,15 @@ pub fn gather_tags_unsorted_iter( (tag_name, subs_slice) }); - (it, var) + Ok((it, var)) } pub fn gather_tags_slices( subs: &Subs, other_fields: UnionTags, var: Variable, -) -> (Vec<(TagName, VariableSubsSlice)>, Variable) { - let (it, ext) = gather_tags_unsorted_iter(subs, other_fields, var); +) -> Result<(Vec<(TagName, VariableSubsSlice)>, Variable), GatherTagsError> { + let (it, ext) = gather_tags_unsorted_iter(subs, other_fields, var)?; let mut result: Vec<_> = it .map(|(ref_label, field): (_, VariableSubsSlice)| (ref_label.clone(), field)) @@ -2754,11 +2766,15 @@ pub fn gather_tags_slices( result.sort_by(|(a, _), (b, _)| a.cmp(b)); - (result, ext) + Ok((result, ext)) } -pub fn gather_tags(subs: &Subs, other_fields: UnionTags, var: Variable) -> TagUnionStructure { - let (it, ext) = gather_tags_unsorted_iter(subs, other_fields, var); +pub fn gather_tags( + subs: &Subs, + other_fields: UnionTags, + var: Variable, +) -> Result { + let (it, ext) = gather_tags_unsorted_iter(subs, other_fields, var)?; let mut result: Vec<_> = it .map(|(ref_label, field): (_, VariableSubsSlice)| { @@ -2768,10 +2784,10 @@ pub fn gather_tags(subs: &Subs, other_fields: UnionTags, var: Variable) -> TagUn result.sort_by(|(a, _), (b, _)| a.cmp(b)); - TagUnionStructure { + Ok(TagUnionStructure { fields: result, ext, - } + }) } fn instantiate_lambda_sets_as_unspecialized( diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index f186398413..60729c7e57 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -838,26 +838,7 @@ fn unify_structure( match other { FlexVar(_) => { // If the other is flex, Structure wins! - let outcome = merge(subs, ctx, Structure(*flat_type)); - - // And if we see a flex variable on the right hand side of a presence - // constraint, we know we need to open up the structure we're trying to unify with. - match (ctx.mode.is_present(), flat_type) { - (true, FlatType::TagUnion(tags, _ext)) => { - let new_ext = subs.fresh_unnamed_flex_var(); - let mut new_desc = ctx.first_desc; - new_desc.content = Structure(FlatType::TagUnion(*tags, new_ext)); - subs.set(ctx.first, new_desc); - } - (true, FlatType::FunctionOrTagUnion(tn, sym, _ext)) => { - let new_ext = subs.fresh_unnamed_flex_var(); - let mut new_desc = ctx.first_desc; - new_desc.content = Structure(FlatType::FunctionOrTagUnion(*tn, *sym, new_ext)); - subs.set(ctx.first, new_desc); - } - _ => {} - } - outcome + merge(subs, ctx, Structure(*flat_type)) } FlexAbleVar(_, ability) => { // Structure wins diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 08217b35ac..02a9ef053c 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -7727,7 +7727,7 @@ All branches in an `if` must have the same type! But all the previous branches match: - F [A]a + F [A] "### ); @@ -9875,4 +9875,30 @@ All branches in an `if` must have the same type! your code don't wonder why it is there. "### ); + + test_report!( + flip_flop_catch_all_branches_not_exhaustive, + indoc!( + r#" + \x -> when x is + A B _ -> "" + A _ C -> "" + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 4│> \x -> when x is + 5│> A B _ -> "" + 6│> A _ C -> "" + + Other possibilities include: + + A _ _ + + I would have to crash if I saw one of those! Add branches for them! + "### + ); }