mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Make sure to apply "is-open" constraints at the very end of pattern constraining
Closes #3298
This commit is contained in:
parent
613606a67d
commit
13b0ce7ca0
5 changed files with 85 additions and 62 deletions
|
@ -735,6 +735,7 @@ 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 delayed_is_open_constraints = Vec::with_capacity(2);
|
||||
let mut branch_cons = Vec::with_capacity(branches.len());
|
||||
|
||||
for (index, when_branch) in branches.iter().enumerate() {
|
||||
|
@ -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 (
|
||||
new_pattern_vars,
|
||||
new_pattern_headers,
|
||||
pattern_con,
|
||||
partial_delayed_is_open_constraints,
|
||||
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,
|
||||
),
|
||||
);
|
||||
|
||||
pattern_vars.extend(new_pattern_vars);
|
||||
|
||||
|
@ -780,6 +786,7 @@ pub fn constrain_expr(
|
|||
|
||||
pattern_headers.extend(new_pattern_headers);
|
||||
pattern_cons.push(pattern_con);
|
||||
delayed_is_open_constraints.extend(partial_delayed_is_open_constraints);
|
||||
|
||||
branch_cons.push(branch_con);
|
||||
}
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -1804,6 +1816,7 @@ fn constrain_when_branch_help(
|
|||
Vec<Variable>,
|
||||
VecMap<Symbol, Loc<Type>>,
|
||||
Constraint,
|
||||
Vec<Constraint>,
|
||||
Constraint,
|
||||
) {
|
||||
let ret_constraint = constrain_expr(
|
||||
|
@ -1869,39 +1882,41 @@ 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,
|
||||
pattern_constraints,
|
||||
delayed_is_open_constraints,
|
||||
body_constraints,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -7409,4 +7409,22 @@ mod solve_expr {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -838,26 +838,7 @@ fn unify_structure<M: MetaCollector>(
|
|||
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
|
||||
|
|
|
@ -9875,4 +9875,16 @@ 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 -> ""
|
||||
"#
|
||||
),
|
||||
@""
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue