- -- Here, we try to match "t" with "A t", and succeed
- -- because the unifier looks through synonyms. The occurs
- -- check doesn't kick in because we are "really" binding "t" to "()",
- -- but we *actually* bind "t" to "A t" if we store ps_ty2.
- -- That leads the typechecker into an infinite loop later.
-
- returnTc ()
- where
- occur_check ty = mapTc occur_check_tv (varSetElems (tyVarsOfType ty)) `thenTc_`
- returnTc ()
-
- occur_check_tv tv2
- | tv1 == tv2 -- Same tyvar; fail
- = zonkTcType ps_ty2 `thenNF_Tc` \ zonked_ty2 ->
- failWithTcM (unifyOccurCheck tv1 zonked_ty2)
+ -- In the application (p x), we try to match "t" with "A t". If we go
+ -- ahead and bind t to A t (= ps_ty2), we'll lead the type checker into
+ -- an infinite loop later.
+ -- But we should not reject the program, because A t = ().
+ -- Rather, we should bind t to () (= non_var_ty2).
+ --
+ -- That's why we have this two-state occurs-check
+ zonkTcType ps_ty2 `thenNF_Tc` \ ps_ty2' ->
+ if not (tv1 `elemVarSet` tyVarsOfType ps_ty2') then
+ tcPutTyVar tv1 ps_ty2' `thenNF_Tc_`
+ returnTc ()
+ else
+ zonkTcType non_var_ty2 `thenNF_Tc` \ non_var_ty2' ->
+ if not (tv1 `elemVarSet` tyVarsOfType non_var_ty2') then
+ -- This branch rarely succeeds, except in strange cases
+ -- like that in the example above
+ tcPutTyVar tv1 non_var_ty2' `thenNF_Tc_`
+ returnTc ()
+ else
+ failWithTcM (unifyOccurCheck tv1 ps_ty2')