Repository: gluon-lang/gluon
Pull request #689 (2019-02-13 20:37:58)

fix(check): The alias reduction stack must be cleared between unifyin…
…g function arguments

Otherwise spurious errors about illegal self recursion would be triggered as it looks like we are reducing the same alias twice.

Found in #686

cc @Etherian


Patch:
diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs
index 9b13e0d6..0955f0d0 100644
--- a/check/src/unify_type.rs
+++ b/check/src/unify_type.rs
@@ -1286,27 +1286,45 @@ impl<'a, 'e> UnifierState<'a, Subsume<'e>> {
     }
 
     fn unify_function(&mut self, actual: &RcType) -> (RcType, RcType) {
+        self.remove_aliases_in(actual, |self_, actual| {
+            let subs = self_.state.subs;
+
+            match actual.as_explicit_function() {
+                Some((arg, ret)) => return (arg.clone(), ret.clone()),
+                None => (),
+            }
+
+            let arg = subs.new_var();
+            let ret = subs.new_var();
+            let f = self_.state.subs.function(Some(arg.clone()), ret.clone());
+            if let Err(errors) = unify::unify(subs, self_.state.clone(), &f, &actual) {
+                for err in errors {
+                    self_.report_error(err);
+                }
+            }
+
+            (arg, ret)
+        })
+    }
+
+    fn remove_aliases_in<R>(&mut self, typ: &RcType, f: impl FnOnce(&mut Self, &RcType) -> R) -> R {
         let subs = self.state.subs;
-        let actual = match self.state.remove_aliases(subs, &actual) {
-            Ok(t) => t.map_or_else(|| Cow::Borrowed(actual), Cow::Owned),
+
+        let before = self.state.reduced_aliases.len();
+
+        let typ = match self.state.remove_aliases(subs, &typ) {
+            Ok(t) => t.map_or_else(|| Cow::Borrowed(typ), Cow::Owned),
             Err(err) => {
                 self.report_error(UnifyError::Other(err));
-                Cow::Borrowed(actual)
+                Cow::Borrowed(typ)
             }
         };
-        match actual.as_explicit_function() {
-            Some((arg, ret)) => return (arg.clone(), ret.clone()),
-            None => (),
-        }
-        let arg = subs.new_var();
-        let ret = subs.new_var();
-        let f = self.state.subs.function(Some(arg.clone()), ret.clone());
-        if let Err(errors) = unify::unify(subs, self.state.clone(), &f, &actual) {
-            for err in errors {
-                self.report_error(err);
-            }
-        }
-        (arg, ret)
+
+        let r = f(self, &typ);
+
+        self.state.reduced_aliases.truncate(before);
+
+        r
     }
 }
 


Test patch:
diff --git a/check/tests/pass.rs b/check/tests/pass.rs
index 92ba0e8e..44f8b9fc 100644
--- a/check/tests/pass.rs
+++ b/check/tests/pass.rs
@@ -1033,3 +1033,30 @@ x
     "#,
     "test.Test2 String"
 }
+
+test_check! {
+    alias_reduction_stack_must_be_cleared_between_function_arguments,
+    r#"
+type StateT s m a = s -> m { value : a, state : s }
+
+#[implicit]
+type Alternative f = {
+    or : forall a . f a -> f a -> f a,
+}
+
+let any x = any x
+
+#[infix(left, 4)]
+let (<*>) : f (a -> b) -> f a -> f b = any ()
+
+#[infix(right, 9)]
+let (<<) : (b -> c) -> (a -> b) -> a -> c = any ()
+
+let alternative ?alt : [Alternative m] -> Alternative (StateT s m) = 
+    let or sra srb = alt.or << sra <*> srb
+    { or }
+
+()
+    "#,
+    "()"
+}