fix infinite unfolding of recursive tag union

This commit is contained in:
Folkert 2020-06-30 10:53:00 +02:00
parent b4480e212a
commit 5483ec819f
3 changed files with 56 additions and 25 deletions

View file

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

View file

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

View file

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