Do not permit inferred-open-in-output-position extension variables to grow

Closes #4852
Closes #4845
This commit is contained in:
Ayaz Hafiz 2023-01-13 11:53:30 -06:00
parent c9460ecf3f
commit 2f74d0b1b9
No known key found for this signature in database
GPG Key ID: 0E2A37416A25EF58
2 changed files with 197 additions and 10 deletions

View File

@ -2405,14 +2405,22 @@ fn close_uninhabited_extended_union(subs: &mut Subs, mut var: Variable) {
}
}
enum UnifySides<T, U> {
Left(T, U),
Right(U, T),
}
#[must_use]
fn unify_tag_ext<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ext: TagExt,
var: Variable,
vars: UnifySides<TagExt, Variable>,
mode: Mode,
) -> Outcome<M> {
let (ext, var, flip_for_unify) = match vars {
UnifySides::Left(ext, var) => (ext, var, false),
UnifySides::Right(var, ext) => (ext, var, true),
};
let legal_unification = match ext {
TagExt::Openness(_) => {
// Openness extensions can either unify with empty tag unions (marking them as closed),
@ -2434,12 +2442,24 @@ fn unify_tag_ext<M: MetaCollector>(
TagExt::Any(_) => true,
};
if legal_unification {
unify_pool(env, pool, ext.var(), var, mode)
if flip_for_unify {
unify_pool(env, pool, var, ext.var(), mode)
} else {
unify_pool(env, pool, ext.var(), var, mode)
}
} else {
mismatch!()
}
}
#[must_use]
fn merge_tag_exts(ext1: TagExt, ext2: TagExt) -> TagExt {
match (ext1, ext2) {
(_, TagExt::Openness(v)) | (TagExt::Openness(v), _) => TagExt::Openness(v),
(TagExt::Any(v), TagExt::Any(_)) => TagExt::Any(v),
}
}
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_tag_unions<M: MetaCollector>(
@ -2509,7 +2529,7 @@ fn unify_tag_unions<M: MetaCollector>(
ctx,
shared_tags,
OtherTags2::Empty,
ext1,
merge_tag_exts(ext1, ext2),
recursion_var,
);
@ -2538,7 +2558,8 @@ fn unify_tag_unions<M: MetaCollector>(
ext1 = new_ext;
}
let ext_outcome = unify_tag_ext(env, pool, ext1, extra_tags_in_2, ctx.mode);
let ext_outcome =
unify_tag_ext(env, pool, UnifySides::Left(ext1, extra_tags_in_2), ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
@ -2590,7 +2611,12 @@ fn unify_tag_unions<M: MetaCollector>(
ext2 = new_ext;
}
let ext_outcome = unify_tag_ext(env, pool, ext2, extra_tags_in_1, ctx.mode);
let ext_outcome = unify_tag_ext(
env,
pool,
UnifySides::Right(extra_tags_in_1, ext2),
ctx.mode,
);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
@ -2659,7 +2685,7 @@ fn unify_tag_unions<M: MetaCollector>(
let mut total_outcome = Outcome::default();
let snapshot = env.subs.snapshot();
let ext1_outcome = unify_tag_ext(env, pool, ext1, sub2, ctx.mode);
let ext1_outcome = unify_tag_ext(env, pool, UnifySides::Left(ext1, sub2), ctx.mode);
if !ext1_outcome.mismatches.is_empty() {
env.subs.rollback_to(snapshot);
return ext1_outcome;
@ -2667,7 +2693,7 @@ fn unify_tag_unions<M: MetaCollector>(
total_outcome.union(ext1_outcome);
if ctx.mode.is_eq() {
let ext2_outcome = unify_tag_ext(env, pool, ext2, sub1, ctx.mode);
let ext2_outcome = unify_tag_ext(env, pool, UnifySides::Right(sub1, ext2), ctx.mode);
if !ext2_outcome.mismatches.is_empty() {
env.subs.rollback_to(snapshot);
return ext2_outcome;

View File

@ -12848,8 +12848,7 @@ I recommend using camelCase. It's the standard style in Roc code!
str = List.concat ["blah"] empty
{one, str}
"#
),
"#),
@r###"
TYPE MISMATCH /code/proj/Main.roc
@ -12867,4 +12866,166 @@ I recommend using camelCase. It's the standard style in Roc code!
List Str
"###
);
test_report!(
implicit_inferred_open_in_output_position_cannot_grow,
indoc!(
r#"
app "test" provides [main] to "./platform"
main : {} -> [One]
main = \{} ->
if Bool.true
then One
else Two
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
Something is off with the `else` branch of this `if` expression:
3 main : {} -> [One]
4 main = \{} ->
5 if Bool.true
6 then One
7 else Two
^^^
This `Two` tag has the type:
[Two]
But the type annotation on `main` says it should be:
[One]
"###
);
test_report!(
implicit_inferred_open_in_output_position_cannot_grow_alias,
indoc!(
r#"
app "test" provides [main] to "./platform"
R : [One]
main : {} -> R
main = \{} ->
if Bool.true
then One
else Two
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
Something is off with the `else` branch of this `if` expression:
5 main : {} -> R
6 main = \{} ->
7 if Bool.true
8 then One
9 else Two
^^^
This `Two` tag has the type:
[Two]
But the type annotation on `main` says it should be:
[One]
"###
);
test_report!(
implicit_inferred_open_in_output_position_cannot_grow_nested,
indoc!(
r#"
app "test" provides [main] to "./platform"
main : List [One, Two] -> List [One]
main = \tags ->
List.map tags \tag ->
when tag is
One -> One
Two -> Two
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
Something is off with the body of the `main` definition:
3 main : List [One, Two] -> List [One]
4 main = \tags ->
5> List.map tags \tag ->
6> when tag is
7> One -> One
8> Two -> Two
This `map` call produces:
List [One, Two]
But the type annotation on `main` says it should be:
List [One]
"###
);
test_report!(
implicit_inferred_open_in_output_position_cannot_grow_nested_alias,
indoc!(
r#"
app "test" provides [main] to "./platform"
R : [One]
main : List [One, Two] -> List R
main = \tags ->
List.map tags \tag ->
when tag is
One -> One
Two -> Two
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
Something is off with the body of the `main` definition:
5 main : List [One, Two] -> List R
6 main = \tags ->
7> List.map tags \tag ->
8> when tag is
9> One -> One
10> Two -> Two
This `map` call produces:
List [One, Two]
But the type annotation on `main` says it should be:
List [One]
"###
);
test_no_problem!(
explicit_inferred_open_in_output_position_can_grow,
indoc!(
r#"
app "test" provides [main] to "./platform"
main : List [One, Two] -> List [One]_
main = \tags ->
List.map tags \tag ->
when tag is
One -> One
Two -> Two
"#
)
);
}