diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 90df5d3bac..a3df595f68 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1758,10 +1758,20 @@ impl Subs { self.utable.is_redirect(var) } + /// Determines if there is any variable in [var] that occurs recursively. + /// + /// The [Err] variant returns the occuring variable and the chain of variables that led + /// to a recursive occurence, in order of proximity. For example, if the type "r" has a + /// reference chain r -> t1 -> t2 -> r, [occurs] will return `Err(r, [t2, t1, r])`. + /// + /// This ignores [Content::RecursionVar]s that occur recursively, because those are + /// already priced in and expected to occur. Use [Subs::occurs_including_recursion_vars] if you + /// need to check for recursion var occurence. pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { occurs(self, &[], var, false) } + /// Like [Subs::occurs], but also errors when recursion vars occur. pub fn occurs_including_recursion_vars( &self, var: Variable, diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 51a4252e6e..d01f6b5ba1 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1528,50 +1528,37 @@ enum OtherTags2 { /// Promotes a non-recursive tag union or lambda set to its recursive variant, if it is found to be /// recursive. fn maybe_mark_union_recursive(subs: &mut Subs, union_var: Variable) { - 'outer: while let Err((recursive, chain)) = subs.occurs(union_var) { - let description = subs.get(recursive); - match description.content { - Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - subs.mark_tag_union_recursive(recursive, tags, ext_var); - } - LambdaSet(self::LambdaSet { - solved, - recursion_var: OptVariable::NONE, - }) => { - subs.mark_lambda_set_recursive(recursive, solved); - } - _ => { - // walk the chain till we find a tag union or lambda set - for v in &chain[..chain.len() - 1] { - let description = subs.get(*v); - match description.content { - Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - subs.mark_tag_union_recursive(*v, tags, ext_var); - continue 'outer; - } - LambdaSet(self::LambdaSet { - solved, - recursion_var: OptVariable::NONE, - }) => { - subs.mark_lambda_set_recursive(*v, solved); - continue 'outer; - } - _ => { /* fall through */ } - } + 'outer: while let Err((_, chain)) = subs.occurs(union_var) { + // walk the chain till we find a tag union or lambda set, starting from the variable that + // occurred recursively, which is always at the end of the chain. + for &v in chain.iter().rev() { + let description = subs.get(v); + match description.content { + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + subs.mark_tag_union_recursive(v, tags, ext_var); + continue 'outer; } + LambdaSet(self::LambdaSet { + solved, + recursion_var: OptVariable::NONE, + }) => { + subs.mark_lambda_set_recursive(v, solved); + continue 'outer; + } + _ => { /* fall through */ } + } + } - // Might not be any tag union if we only pass through `Apply`s. Otherwise, we have a bug! - if chain.iter().all(|&v| { - matches!( - subs.get_content_without_compacting(v), - Content::Structure(FlatType::Apply(..)) - ) - }) { - return; - } else { - internal_error!("recursive loop does not contain a tag union") - } - } + // Might not be any tag union if we only pass through `Apply`s. Otherwise, we have a bug! + if chain.iter().all(|&v| { + matches!( + subs.get_content_without_compacting(v), + Content::Structure(FlatType::Apply(..)) + ) + }) { + return; + } else { + internal_error!("recursive loop does not contain a tag union") } } }