disco-0.1.6: src/Disco/Typecheck/Unify.hs
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- |
-- Module : Disco.Typecheck.Unify
-- Copyright : disco team and contributors
-- Maintainer : byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Unification.
module Disco.Typecheck.Unify where
import Unbound.Generics.LocallyNameless (Name, fv)
import Control.Lens (anyOf)
import Control.Monad.State
import qualified Data.Map as M
import Data.Set (Set)
import qualified Data.Set as S
import Disco.Subst
import Disco.Types
-- XXX todo: might be better if unification took sorts into account
-- directly. As it is, however, I think it works properly;
-- e.g. suppose we have a with sort {sub} and we unify it with Bool.
-- unify will just return a substitution [a |-> Bool]. But then when
-- we call extendSubst, and in particular substSortMap, the sort {sub}
-- will be applied to Bool and decomposed which will throw an error.
-- | Given a list of equations between types, return a substitution
-- which makes all the equations satisfied (or fail if it is not
-- possible).
--
-- This is not the most efficient way to implement unification but
-- it is simple.
unify :: TyDefCtx -> [(Type, Type)] -> Maybe S
unify = unify' (==)
-- | Given a list of equations between types, return a substitution
-- which makes all the equations equal *up to* identifying all base
-- types. So, for example, Int = Nat weakly unifies but Int = (Int
-- -> Int) does not. This is used to check whether subtyping
-- constraints are structurally sound before doing constraint
-- simplification/solving, to ensure termination.
weakUnify :: TyDefCtx -> [(Type, Type)] -> Maybe S
weakUnify = unify' (\_ _ -> True)
-- | Given a list of equations between types, return a substitution
-- which makes all the equations satisfied (or fail if it is not
-- possible), up to the given comparison on base types.
unify' ::
(BaseTy -> BaseTy -> Bool) ->
TyDefCtx ->
[(Type, Type)] ->
Maybe S
unify' baseEq tyDefns eqs = evalStateT (go eqs) S.empty
where
go :: [(Type, Type)] -> StateT (Set (Type, Type)) Maybe S
go [] = return idS
go (e : es) = do
u <- unifyOne e
case u of
Left sub -> (@@ sub) <$> go (applySubst sub es)
Right newEs -> go (newEs ++ es)
unifyOne :: (Type, Type) -> StateT (Set (Type, Type)) Maybe (Either S [(Type, Type)])
unifyOne pair = do
seen <- get
case pair `S.member` seen of
True -> return $ Left idS
False -> unifyOne' pair
unifyOne' :: (Type, Type) -> StateT (Set (Type, Type)) Maybe (Either S [(Type, Type)])
unifyOne' (ty1, ty2)
| ty1 == ty2 = return $ Left idS
unifyOne' (TyVar x, ty2)
| occurs x ty2 = mzero
| otherwise = return $ Left (x |-> ty2)
unifyOne' (ty1, x@(TyVar _)) =
unifyOne (x, ty1)
-- At this point we know ty2 isn't the same skolem nor a unification variable.
-- Skolems don't unify with anything.
unifyOne' (TySkolem _, _) = mzero
unifyOne' (_, TySkolem _) = mzero
-- Unify two container types: unify the container descriptors as
-- well as the type arguments
unifyOne' p@(TyCon (CContainer ctr1) tys1, TyCon (CContainer ctr2) tys2) = do
modify (S.insert p)
return $ Right ((TyAtom ctr1, TyAtom ctr2) : zip tys1 tys2)
-- If one of the types to be unified is a user-defined type,
-- unfold its definition before continuing the matching
unifyOne' p@(TyCon (CUser t) tys1, ty2) = do
modify (S.insert p)
case M.lookup t tyDefns of
Nothing -> mzero
Just (TyDefBody _ body) -> return $ Right [(body tys1, ty2)]
unifyOne' p@(ty1, TyCon (CUser t) tys2) = do
modify (S.insert p)
case M.lookup t tyDefns of
Nothing -> mzero
Just (TyDefBody _ body) -> return $ Right [(ty1, body tys2)]
-- Unify any other pair of type constructor applications: the type
-- constructors must match exactly
unifyOne' p@(TyCon c1 tys1, TyCon c2 tys2)
| c1 == c2 = do
modify (S.insert p)
return $ Right (zip tys1 tys2)
| otherwise = mzero
unifyOne' (TyAtom (ABase b1), TyAtom (ABase b2))
| baseEq b1 b2 = return $ Left idS
| otherwise = mzero
unifyOne' _ = mzero -- Atom = Cons
equate :: TyDefCtx -> [Type] -> Maybe S
equate tyDefns tys = unify tyDefns eqns
where
eqns = zip tys (tail tys)
occurs :: Name Type -> Type -> Bool
occurs x = anyOf fv (== x)
unifyAtoms :: TyDefCtx -> [Atom] -> Maybe (Substitution Atom)
unifyAtoms tyDefns = fmap (fmap fromTyAtom) . equate tyDefns . map TyAtom
where
fromTyAtom (TyAtom a) = a
fromTyAtom _ = error "fromTyAtom on non-TyAtom!"
unifyUAtoms :: TyDefCtx -> [UAtom] -> Maybe (Substitution UAtom)
unifyUAtoms tyDefns = fmap (fmap fromTyAtom) . equate tyDefns . map (TyAtom . uatomToAtom)
where
fromTyAtom (TyAtom (ABase b)) = UB b
fromTyAtom (TyAtom (AVar (U v))) = UV v
fromTyAtom _ = error "fromTyAtom on wrong thing!"