mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
fix infinite unfolding of recursive tag union
This commit is contained in:
parent
b4480e212a
commit
5483ec819f
3 changed files with 56 additions and 25 deletions
|
@ -1929,6 +1929,8 @@ fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap<Symbol,
|
||||||
_ => unreachable!("`annotation_to_attr_type` always gives back an Attr"),
|
_ => unreachable!("`annotation_to_attr_type` always gives back an Attr"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO can we "just" fix this in alias instantiation?
|
||||||
|
// e.g. does this work for a triple-mutually-recursive alias?
|
||||||
if let Some(b) = &alias.uniqueness {
|
if let Some(b) = &alias.uniqueness {
|
||||||
fix_mutual_recursive_alias(&mut alias.typ, b);
|
fix_mutual_recursive_alias(&mut alias.typ, b);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1923,30 +1923,25 @@ mod test_uniq_solve {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn assoc_list_map() {
|
fn assoc_list_map() {
|
||||||
// infer_eq(
|
infer_eq(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// ConsList a : [ Cons a (ConsList a), Nil ]
|
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||||
// AssocList a b : ConsList { key: a, value : b }
|
|
||||||
//
|
map : ConsList a -> ConsList a
|
||||||
// map : AssocList k v -> AssocList k v
|
map = \list ->
|
||||||
// map = \list ->
|
when list is
|
||||||
// when list is
|
Cons r xs -> Cons r xs
|
||||||
// Cons { key, value } xs ->
|
Nil -> Nil
|
||||||
// Cons {key: key, value: value } xs
|
|
||||||
//
|
map
|
||||||
// Nil ->
|
"#
|
||||||
// Nil
|
),
|
||||||
//
|
"Attr * (Attr (b | c) (ConsList (Attr c a)) -> Attr (b | c) (ConsList (Attr c a)))",
|
||||||
// map
|
);
|
||||||
// "#
|
}
|
||||||
// ),
|
|
||||||
// // "Attr Shared (Attr Shared (Attr Shared k, Attr a v -> Attr b v2), Attr (c | d | e) (AssocList (Attr Shared k) (Attr a v)) -> Attr (c | d | e) (AssocList (Attr Shared k) (Attr b v2)))"
|
|
||||||
// "Attr Shared (Attr Shared (Attr a p -> Attr b q), Attr (* | a) (ConsList (Attr a p)) -> Attr * (ConsList (Attr b q)))",
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn same_uniqueness_builtin_list() {
|
fn same_uniqueness_builtin_list() {
|
||||||
|
|
|
@ -515,6 +515,17 @@ fn unify_tag_union(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is the given variable a structure. Does not consider Attr itself a structure, and instead looks
|
||||||
|
/// into it.
|
||||||
|
fn is_structure(var: Variable, subs: &mut Subs) -> bool {
|
||||||
|
match subs.get(var).content {
|
||||||
|
Content::Alias(_, _, actual) => is_structure(actual, subs),
|
||||||
|
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => is_structure(args[1], subs),
|
||||||
|
Content::Structure(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn unify_shared_tags(
|
fn unify_shared_tags(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
pool: &mut Pool,
|
pool: &mut Pool,
|
||||||
|
@ -543,12 +554,35 @@ fn unify_shared_tags(
|
||||||
//
|
//
|
||||||
// This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964
|
// This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964
|
||||||
// Polymorphic recursion is now a type error.
|
// Polymorphic recursion is now a type error.
|
||||||
|
//
|
||||||
|
// The strategy is to expand the recursive tag union as deeply as the non-recursive one
|
||||||
|
// is.
|
||||||
|
//
|
||||||
|
// > RecursiveTagUnion(rvar, [ Cons a rvar, Nil ], ext)
|
||||||
|
//
|
||||||
|
// Conceptually becomes
|
||||||
|
//
|
||||||
|
// > RecursiveTagUnion(rvar, [ Cons a [ Cons a rvar, Nil ], Nil ], ext)
|
||||||
|
//
|
||||||
|
// and so on until the whole non-recursive tag union can be unified with it.
|
||||||
let problems = if let Some(rvar) = recursion_var {
|
let problems = if let Some(rvar) = recursion_var {
|
||||||
if expected == rvar {
|
if expected == rvar {
|
||||||
unify_pool(subs, pool, actual, ctx.second)
|
unify_pool(subs, pool, actual, ctx.second)
|
||||||
} else {
|
} else if is_structure(actual, subs) {
|
||||||
|
// the recursion variable is hidden behind some structure (commonly an Attr
|
||||||
|
// with uniqueness inference). Thus we must expand the recursive tag union to
|
||||||
|
// unify if with the non-recursive one. Thus:
|
||||||
|
|
||||||
// replace the rvar with ctx.second in expected
|
// replace the rvar with ctx.second in expected
|
||||||
subs.explicit_substitute(rvar, ctx.second, expected);
|
subs.explicit_substitute(rvar, ctx.second, expected);
|
||||||
|
|
||||||
|
// but, by the `is_structure` condition above, only if we're unifying with a structure!
|
||||||
|
// when `actual` is just a flex/rigid variable, the substitution will expand a
|
||||||
|
// recursive tag union infinitely!
|
||||||
|
|
||||||
|
unify_pool(subs, pool, actual, expected)
|
||||||
|
} else {
|
||||||
|
// unification with a non-structure is trivial
|
||||||
unify_pool(subs, pool, actual, expected)
|
unify_pool(subs, pool, actual, expected)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue