zwirn-0.1.0.0: src/Zwirn/Language/TypeCheck/Constraint.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Zwirn.Language.TypeCheck.Constraint
( Substitutable (..),
Subst (..),
TypeError (..),
Constraint,
runSolve,
)
where
{-
Constraint.hs - unification constraint solver adapted from
https://github.com/sdiehl/write-you-a-haskell/tree/master/chapter7/poly_constraints
Copyright (C) 2023, Martin Gius
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
-}
import Control.Monad.Except
import Control.Monad.Identity
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Text (Text)
import Zwirn.Language.Environment
import Zwirn.Language.TypeCheck.Types
data TypeError
= UnificationFail Type Type
| InfiniteType TypeVar Type
| UnboundVariable Text
| Ambigious [Constraint]
| UnificationMismatch [Type] [Type]
| NoInstance Predicate
deriving (Eq)
type Constraint = (Type, Type)
newtype Subst = Subst (Map.Map TypeVar Type)
deriving (Eq, Ord, Show, Semigroup, Monoid)
type Unifier = (Subst, [Constraint])
-- | Constraint solver monad
type Solve a = ExceptT TypeError Identity a
class Substitutable a where
apply :: Subst -> a -> a
ftv :: a -> Set.Set TypeVar
instance Substitutable Type where
apply _ (TypeCon a) = TypeCon a
apply (Subst s) t@(TypeVar a) = Map.findWithDefault t a s
apply s (t1 `TypeArr` t2) = apply s t1 `TypeArr` apply s t2
ftv TypeCon {} = Set.empty
ftv (TypeVar a) = Set.singleton a
ftv (t1 `TypeArr` t2) = ftv t1 `Set.union` ftv t2
instance Substitutable Scheme where
apply (Subst s) (Forall as t) = Forall as $ apply s' t
where
s' = Subst $ foldr Map.delete s as
ftv (Forall as t) = ftv t `Set.difference` Set.fromList as
instance Substitutable Constraint where
apply s (t1, t2) = (apply s t1, apply s t2)
ftv (t1, t2) = ftv t1 `Set.union` ftv t2
instance Substitutable AnnotatedExpression where
apply s (Annotated x sc d) = Annotated x (apply s sc) d
ftv (Annotated _ s _) = ftv s
instance (Substitutable a) => Substitutable [a] where
apply = map . apply
ftv = foldr (Set.union . ftv) Set.empty
instance Substitutable InterpreterEnv where
apply s (IEnv ty cl) = IEnv (Map.map (apply s) ty) (apply s cl)
ftv (IEnv ty cl) = ftv (Map.elems ty) `Set.union` ftv cl
instance Substitutable Predicate where
apply s (IsIn x t) = IsIn x (apply s t)
ftv (IsIn _ t) = ftv t
instance (Substitutable t) => Substitutable (Qualified t) where
apply s (Qual ps t) = Qual (apply s ps) (apply s t)
ftv (Qual ps t) = ftv ps `Set.union` ftv t
-------------------------------------------------------------------------------
-- Constraint Solver
-------------------------------------------------------------------------------
-- | The empty substitution
emptySubst :: Subst
emptySubst = mempty
-- | Compose substitutions
compose :: Subst -> Subst -> Subst
(Subst s1) `compose` (Subst s2) = Subst $ Map.map (apply (Subst s1)) s2 `Map.union` s1
-- | Run the constraint solver
runSolve :: [Constraint] -> Either TypeError Subst
runSolve cs = runIdentity $ runExceptT $ solver st
where
st = (emptySubst, cs)
unifyMany :: [Type] -> [Type] -> Solve Subst
unifyMany [] [] = return emptySubst
unifyMany (t1 : ts1) (t2 : ts2) =
do
su1 <- unifies t1 t2
su2 <- unifyMany (apply su1 ts1) (apply su1 ts2)
return (su2 `compose` su1)
unifyMany t1 t2 = throwError $ UnificationMismatch t1 t2
unifies :: Type -> Type -> Solve Subst
unifies t1 t2 | t1 == t2 = return emptySubst
unifies (TypeVar v) t = v `bind` t
unifies t (TypeVar v) = v `bind` t
unifies (TypeArr t1 t2) (TypeArr t3 t4) = unifyMany [t1, t2] [t3, t4]
unifies t1 t2 = throwError $ UnificationFail t1 t2
-- Unification solver
solver :: Unifier -> Solve Subst
solver (su, cs) =
case cs of
[] -> return su
((t1, t2) : cs0) -> do
su1 <- unifies t1 t2
solver (su1 `compose` su, apply su1 cs0)
bind :: TypeVar -> Type -> Solve Subst
bind a t
| t == TypeVar a = return emptySubst
| occursCheck a t = throwError $ InfiniteType a t
| otherwise = return (Subst $ Map.singleton a t)
occursCheck :: (Substitutable a) => TypeVar -> a -> Bool
occursCheck a t = a `Set.member` ftv t