tempus-0.1.0: Tempus/TypeCheck.hs
-- | Semantical analysis (i.e. type check and variances test) for Tempus expressions.
module Tempus.TypeCheck (
TypeEnv,
TypeSynEnv,
ContextError (..),
checkProgram,
getType,
substVar,
expandTypeSyn,
expandMuType,
expandNuType,
Variance (..),
invertVariance
) where
import Control.Applicative
import Control.Arrow
import Control.Monad.Error
import Control.Monad.State
import Data.Generics.Uniplate.Operations
import Data.Function
import Data.List
import Data.Maybe
import Tempus.Loc
import Tempus.Syntax
-- | Type check monad.
type T a = ErrorT ContextError (State TState) a
-- | A general context check error.
data ContextError = UndefinedVariable Var -- ^ Undefined global variable
| DuplicateVariable Var -- ^ Variable defined twice
| DuplicateType Var -- ^ Type defined twice
| OccursCheck Type Type -- ^ Occurs check failed when trying to unify the types
| SymbolClash Type Type -- ^ The types cannot be unified due to different
-- outer-most constructors
| IncorrectVariances Type -- ^ Incorrect variances
| UndefinedType Var -- ^ Undefined type synonym or free type variable used
| TypeArgsMismatch Var -- ^ Wrong number of arguments for the type synonym
| NoMuType Type -- ^ Type has to be a mu type but isn't
| NoNuType Type -- ^ Type has to be a nu type but isn't
| NoRecType Type -- ^ Type has to be a recursive type but isn't
deriving (Show)
instance Error ContextError where
noMsg = error "noMsg not implemented for Error ContextError"
strMsg msg = error "strMsg not implemented for Error ContextError"
instance Error e => Error (Loc e) where
noMsg = error "noMsg not implemented for Error (Loc e)"
strMsg msg = error "stdMsg not implemented for Error (Loc e)"
-- | Type check state.
data TState = TState {
-- | The ID to use for the next fresh type variable.
varID :: Integer,
-- | The ID to use for the next fresh type constructor variable.
conID :: Integer
} deriving (Eq, Show)
-- | Initial type check state.
initTState :: TState
initTState = TState { varID = 0, conID = 0 }
{- |
Performs a semantical analysis for a module using the given global variables with types and
type synonyms. Either a context error with source location or lists with the global variables
and their inferred types and type synonyms is returned.
-}
checkProgram :: TypeEnv -> TypeSynEnv -> Program
-> Either (Loc ContextError) (TypeEnv, TypeSynEnv)
checkProgram tEnv sEnv prog =
fst $ runState (runErrorT $ checkDecls tEnv [] sEnv [] prog) initTState
-- | Helper function for 'checkProgram'.
checkDecls :: TypeEnv -> TypeEnv -> TypeSynEnv -> TypeSynEnv -> [Decl]
-> ErrorT (Loc ContextError) (State TState) (TypeEnv, TypeSynEnv)
checkDecls _ tEnv' _ sEnv'[] = return (reverse tEnv', reverse sEnv')
checkDecls tEnv tEnv' sEnv sEnv' (DeclType loc v vs t : ds) = do
when (not . null $ filter ((== v) . fst) $ sEnv ++ sEnv') $
throwError $ Loc loc $ DuplicateType v
checkDecls tEnv tEnv' sEnv ((v, (vs, t)) : sEnv') ds
checkDecls tEnv tEnv' sEnv sEnv' (DeclVal loc v e : ds) = do
let env = tEnv ++ tEnv'
builtIns = map (\v -> (Var v, undefined)) ["add", "mult"]
when (not . null $ filter ((== v) . fst) (env ++ builtIns)) $
throwError $ Loc loc $ DuplicateVariable v
t <- addErrorLoc loc $ checkedTypeExpr env (sEnv ++ sEnv') e
checkDecls tEnv ((v, generalize t) : tEnv') sEnv sEnv' ds
-- | Infer the most general type for an expression using the types from a list of global variables
-- and a list of type synonyms.
getType :: TypeEnv -> TypeSynEnv -> Loc Expr -> Either (Loc ContextError) Type
getType tEnv sEnv (Loc loc e) =
fst $ runState (runErrorT $ addErrorLoc loc $ checkedTypeExpr tEnv sEnv e) initTState
-- TODO: Remove type schemes?
-- | Type schemes.
type TypeScheme = ([Integer], Type)
-- | A type environment as a list of variables with their types.
type TypeEnv = [(Var, TypeScheme)]
-- | A substitution as a list of pairs of type variable IDs and the types of the corresponding
-- type variables.
type Subst = [(Integer, Type)]
-- | A type synonym environment as a list of pairs of a type synonym name with its parameter
-- names and type.
type TypeSynEnv = [(Var, ([Var], Type))]
-- | Transforms an error monad by adding a location information to the error type.
addErrorLoc :: (Error e, Functor m) => SrcLoc -> ErrorT e m a -> ErrorT (Loc e) m a
addErrorLoc loc = mapErrorT (fmap . left $ Loc loc)
-- | Perform a type inference with variance test within the @T@ monad for an expression, using the
-- given global variables with types and type synonyms.
checkedTypeExpr :: TypeEnv -> TypeSynEnv -> Expr -> T Type
checkedTypeExpr tEnv sEnv e = do
t <- snd <$> typeExpr tEnv [] sEnv e
var <- correctVariances sEnv t
when (not $ var) $
throwError $ IncorrectVariances t
return t
-- | Performs a type inference for an expression. Returns the inferred type with a substitution
-- for the used type variables.
typeExpr :: TypeEnv -- ^ Global type environment
-> TypeEnv -- ^ Local type environment
-> TypeSynEnv -- ^ Type synonym environment
-> Expr -- ^ Expression
-> T (Subst, Type)
typeExpr gamma lambda sEnv e =
case e of
-- TODO: Put somewhere else
ExVar (Var v)
| v `elem` ["add", "mult"]
-> return ([], TyFun TyNat $ TyFun TyNat TyNat)
ExVar v -> case lookup v (lambda ++ gamma) of
Just s -> (\t -> ([], t)) <$> instantiate s
Nothing -> throwError $ UndefinedVariable v
ExLam x e -> do
b <- freshVar
(s, t) <- typeExpr (deleteBy ((==) `on` fst) (x, undefined) gamma)
(replVar (x, b) lambda) sEnv e
return (s, TyFun (substType s b) t)
ExApp e1 e2 -> do
b <- freshVar
(s1, t1) <- typeExpr gamma lambda sEnv e1
(s2, t2) <- typeExpr gamma (substEnv s1 lambda) sEnv e2
s3 <- mgu sEnv (substType s2 t1) (TyFun t2 b) []
return (compSubst s3 $ compSubst s2 s1, substType s3 b)
ExPair e1 e2 -> do
(s1, t1) <- typeExpr gamma lambda sEnv e1
(s2, t2) <- typeExpr gamma (substEnv s1 lambda) sEnv e2
return (compSubst s2 s1, TyPair (substType s2 t1) t2)
{-
ExLiftApp e1 e2 -> do
(s1, t1) <- typeExpr gamma lambda e1
[c, d] <- replicateM 2 freshVar
s2 <- mgu (TyBehav $ TyFun c d) t1 []
(s3, t2) <- typeExpr gamma (substEnv (compSubst s2 s1) lambda) e2
s4 <- mgu (substType (compSubst s2 s1) $ TyBehav c) t2 []
return (compSubst s4 $ compSubst s3 $ compSubst s2 s1, substType s4 $ TyBehav d)
-}
-- TODO: rewrite
ExLiftAppB e1 e2 -> do
(s2', t1) <- typeExpr gamma lambda sEnv e1
[a, b, c, d] <- replicateM 4 freshVar
s3' <- mgu sEnv (TyFun (TyBehav $ TyFun c d)
(TyFun (TyBehav c) (TyBehav d))) (TyFun t1 a) []
(s2, t2) <- typeExpr gamma (substEnv (compSubst s3' s2') lambda) sEnv e2
s3 <- mgu sEnv (substType (compSubst s2 s3') a) (TyFun t2 b) []
return (compSubst s3 $ compSubst s2 $ compSubst s3' s2', substType s3 b)
-- TODO: rewrite
ExLiftAppE e1 e2 -> do
(s2', t1) <- typeExpr gamma lambda sEnv e1
[a, b, c, d] <- replicateM 4 freshVar
s3' <- mgu sEnv (TyFun (TyBehav $ TyFun c d)
(TyFun (TyEvent c) (TyEvent d))) (TyFun t1 a) []
(s2, t2) <- typeExpr gamma (substEnv (compSubst s3' s2') lambda) sEnv e2
s3 <- mgu sEnv (substType (compSubst s2 s3') a) (TyFun t2 b) []
return (compSubst s3 $ compSubst s2 $ compSubst s3' s2', substType s3 b)
ExConst e -> do
(_, t) <- typeExpr gamma [] sEnv e
return ([], TyBehav t)
ExBehav f -> do
(_, t) <- typeExpr gamma [] sEnv f
a <- freshVar
s <- mgu sEnv (TyFun TyNat a) t []
return (s, substType s (TyBehav a))
ExEvent t e -> do
(s1, t1) <- typeExpr gamma lambda sEnv t
s2 <- mgu sEnv TyNat t1 []
(_, t2) <- typeExpr gamma [] sEnv e
return (compSubst s2 s1, TyEvent t2)
ExFold t f -> do
mu@(MuType a t1) <- either throwError return $ expandMuType sEnv t
(_, t2) <- typeExpr gamma [] sEnv f
b <- freshVar
s <- mgu sEnv (TyFun (substVar a b t1) b) t2 []
return (s, substType s $ TyFun (TyMu mu) b)
ExUnfold t f -> do
nu@(NuType a t1) <- either throwError return $ expandNuType sEnv t
(_, t2) <- typeExpr gamma [] sEnv f
b <- freshVar
s <- mgu sEnv (TyFun b (substVar a b t1)) t2 []
return (s, substType s $ TyFun b (TyNu nu))
-- TODO: throw returned error, not NoRecType
ExPack t -> case (expandMuType sEnv t, expandNuType sEnv t) of
(Right s@(MuType x t), Left _) -> return ([], TyFun (substVar x (TyMu s) t) (TyMu s))
(Left _, Right s@(NuType x t)) -> return ([], TyFun (substVar x (TyNu s) t) (TyNu s))
(Left _, Left _) -> throwError $ NoRecType t
ExUnpack t -> case (expandMuType sEnv t, expandNuType sEnv t) of
(Right s@(MuType x t), Left _) -> return ([], TyFun (TyMu s) (substVar x (TyMu s) t))
(Left _, Right s@(NuType x t)) -> return ([], TyFun (TyNu s) (substVar x (TyNu s) t))
(Left _, Left _) -> throwError $ NoRecType t
_ -> (\t -> ([], t)) <$> typeSimple e
-- | Looks up a variable name in the type synonym list and applies the type synonym to the list of
-- argument types. Either a context error or the expanded type is returned.
expandTypeSyn :: TypeSynEnv -> Var -> [Type] -> Either ContextError Type
expandTypeSyn env v ts = case lookup v env of
Nothing -> Left $ UndefinedType v
Just (vs, t)
| length vs == length ts -> Right $ foldr (uncurry substVar) t $ zip vs ts
| otherwise -> Left $ TypeArgsMismatch v
-- | Tries to expand a type to a mu type. If the resulting type isn't a mu type, an error is
-- returned, otherwise, the mu type is returned.
expandMuType :: TypeSynEnv -> Type -> Either ContextError MuType
expandMuType sEnv (TyMu mu) = Right mu
expandMuType sEnv (TyApp v ts) = expandTypeSyn sEnv v ts >>= expandMuType sEnv
expandMuType sEnv t = Left $ NoMuType t
-- | Tries to expand a type to a nu type. If the resulting type isn't a nu type, an error is
-- returned, otherwise, the nu type is returned.
expandNuType :: TypeSynEnv -> Type -> Either ContextError NuType
expandNuType sEnv (TyNu nu) = Right nu
expandNuType sEnv (TyApp v ts) = expandTypeSyn sEnv v ts >>= expandNuType sEnv
expandNuType sEnv t = Left $ NoNuType t
-- | Instantiates a type scheme to a type.
instantiate :: TypeScheme -> T Type
instantiate (vs, t) = do
bs <- replicateM (length vs) freshVar
return $ substType (zip vs bs) t
-- TODO: condense type vars
-- | Generalizes a type to a type scheme.
generalize :: Type -> TypeScheme
generalize t = (ftv t, t)
-- | Returns the free type variables of a type as a list of type variable IDs.
ftv :: Type -> [Integer]
ftv = nub . ftv'
where
ftv' (TyVar i) = [i]
ftv' t = concatMap ftv' $ children t
-- | Compose two substitutions to a new one.
compSubst :: Subst -> Subst -> Subst
compSubst sigma theta = let theta' = map (second $ substType sigma) theta
sigma' = let domTheta = map fst theta
in [ (x,t) | (x,t) <- sigma, x `notElem` domTheta ]
theta'' = [ (x,t) | (x,t) <- theta', t /= TyVar x ]
in nub $ theta'' ++ sigma'
{-
-- defined in Baader
compSubst :: Subst' -> Subst' -> Subst'
compSubst sigma theta = let sigma' = map (second $ substType theta) sigma
theta' = let domSigma = map fst sigma
in [ (x,t) | (x,t) <- theta, x `notElem` domSigma ]
sigma'' = [ (x,t) | (x,t) <- sigma', t /= TyVar x ]
in nub $ sigma'' ++ theta'
-}
-- | Apply a substitution to each type in the type environment.
substEnv :: Subst -> TypeEnv -> TypeEnv
substEnv s = map (second $ second $ substType s)
-- | Apply a substitution to a type.
substType :: Subst -> Type -> Type
substType s t = transform f t
where
f (TyVar i) = fromMaybe (TyVar i) $ lookup i s
f t = t
-- | Add, or replace if already present, a variable with a type in a list of variables with types.
replVar :: (Var, Type) -> TypeEnv -> TypeEnv
replVar (v,t) = ((v, ([], t)):) . filter ((/= v) . fst)
-- version Baader, Unification Theory; see also Heeren, Top Quality Type Error Messages
-- | Try to unify two types using a type synonym list and an initial substitution.
mgu :: TypeSynEnv -> Type -> Type -> Subst -> T Subst
mgu sEnv t1 t2 s =
case (condSubst t1, condSubst t2) of
(TyVar x, TyVar y)
| x == y -> return []
(TyVar x, t)
| x `occursIn` t -> throwError $ OccursCheck (TyVar x) t
| otherwise -> return $ compSubst [(x, t)] s
(t1, t2@(TyVar _)) -> mgu sEnv t2 t1 s
(TyMu (MuType x1 t1), TyMu (MuType x2 t2)) -> do
c <- freshCon
mgu sEnv (substVar x1 c t1) (substVar x2 c t2) []
(TyNu (NuType x1 t1), TyNu (NuType x2 t2)) -> do
c <- freshCon
mgu sEnv (substVar x1 c t1) (substVar x2 c t2) []
(TyApp v ts, t) -> either throwError (\t' -> mgu sEnv t' t s) $ expandTypeSyn sEnv v ts
(t, TyApp v ts) -> either throwError (\t' -> mgu sEnv t t' s) $ expandTypeSyn sEnv v ts
(t1, t2)
| equalSymbols t1 t2 -> unifySubterms (children t1) (children t2) s
| otherwise -> throwError $ SymbolClash t1 t2
where
condSubst t@(TyVar _) = substType s t
condSubst t = t
equalSymbols t1 t2 = case (t1, t2) of
(TyFun _ _, TyFun _ _) -> True
(TyPlus _ _, TyPlus _ _) -> True
(TyPair _ _, TyPair _ _) -> True
(TyBehav _, TyBehav _) -> True
(TyEvent _, TyEvent _) -> True
(TyNat, TyNat) -> True
(TyZero, TyZero) -> True
(TyUnit, TyUnit) -> True
(TyCon x, TyCon y) -> x == y
_ -> False
-- TODO: use foldM
unifySubterms [] [] sigma = return sigma
unifySubterms (s:ss) (t:ts) sigma = do
sigma' <- mgu sEnv s t sigma
unifySubterms ss ts $ compSubst sigma sigma'
unifySubterms _ _ _ = error "unifySubterms: same symbols with different arities"
-- | Check if a type variable with the given ID occurs in the type.
occursIn :: Integer -> Type -> Bool
occursIn x t = x `elem` [ y | TyVar y <- universe t]
-- | @substVar v s t@ replaces all occurrences of @v@ in @t@ by @s@. @v@ is not replaced if it is
-- bound locally by a mu type.
substVar :: Var -> Type -> Type -> Type
substVar x s t@(TyMu (MuType y t'))
| x == y = t
| otherwise = TyMu (MuType y $ substVar x s t')
substVar x s t@(TyNu (NuType y t'))
| x == y = t
| otherwise = TyNu (NuType y $ substVar x s t')
substVar x s t@(TyApp y [])
| x == y = s
| otherwise = t
substVar x s t = descend (substVar x s) t
-- | Returns the type of a Tempus primitive expression.
typeSimple :: Expr -> T Type
typeSimple e =
case e of
ExNatLit _ -> return $ TyNat
ExNull -> TyFun TyZero <$> freshVar
ExUnit -> return $ TyUnit
ExLeft -> fresh2 $ \t1 t2 -> TyFun t1 (TyPlus t1 t2)
ExRight -> fresh2 $ \t1 t2 -> TyFun t2 (TyPlus t1 t2)
ExCase -> fresh3 $ \t1 t2 s -> TyFun (TyFun t1 s) $
TyFun (TyFun t2 s) $ TyFun (TyPlus t1 t2) s
ExFst -> fresh2 $ \t1 t2 -> TyFun (TyPair t1 t2) t1
ExSnd -> fresh2 $ \t1 t2 -> TyFun (TyPair t1 t2) t2
ExExpand -> fresh1 $ \t -> TyFun (TyBehav t) (TyBehav $ TyPair t (TyBehav t))
ExNever -> return $ TyEvent TyZero
ExRace -> fresh2 $ \t1 t2 -> TyFun (TyEvent t1) $
TyFun (TyEvent t2) $
TyEvent $ TyPlus (TyPair t1 t2) $
TyPlus (TyPair t1 $ TyEvent t2)
(TyPair t2 $ TyEvent t1)
-- TODO: Replace n, s by new var
ExReflect -> return $ TyFun TyNat $ TyMu $
MuType (Var "n") (TyPlus TyUnit (TyApp (Var "n") []))
ExUJump -> fresh1 $ \t -> TyFun (TyNu $ NuType (Var "s") $ TyEvent $
TyPlus t (TyApp (Var "s") []))
(TyEvent t)
ExUSwitch -> fresh1 $ \t -> TyFun (TyNu $ NuType (Var "s") $
TyPair (TyBehav t) $ TyEvent $ TyPair t $
TyApp (Var "s") [])
(TyBehav t)
_ -> error "typeSimple: expression is not simple"
-- | Generate a new unused type variable as a type expression.
freshVar :: T Type
freshVar = do
i <- gets varID
modify (\st -> st { varID = varID st + 1 })
return $ TyVar i
-- | Generate a new unused type variable as a type expression and apply the given function to that
-- type.
fresh1 :: (Type -> Type) -> T Type
fresh1 f = liftM f freshVar
-- | Like 'fresh1' but for two variables.
fresh2 :: (Type -> Type -> Type) -> T Type
fresh2 f = liftM2 f freshVar freshVar
-- | Like 'fresh1' but for three variables.
fresh3 :: (Type -> Type -> Type -> Type) -> T Type
fresh3 f = liftM3 f freshVar freshVar freshVar
-- | Generate a new unused type constructor variable as a type expression.
freshCon :: T Type
freshCon = do
i <- gets conID
modify (\st -> st { conID = conID st + 1 })
return $ TyCon i
-- | A variance.
data Variance = CoVariant | ContraVariant deriving (Eq, Show)
-- | Inverts a variance.
invertVariance :: Variance -> Variance
invertVariance CoVariant = ContraVariant
invertVariance ContraVariant = CoVariant
-- TODO: Distinguish between bound variables with wrong variances and unbound variables
-- | Perform a variances check for the type using a type synonym environment, and return @true@
-- iff the test was successful.
correctVariances :: TypeSynEnv -> Type -> T Bool
correctVariances = var []
where
var delta sEnv (TyApp x ts)
| (x, CoVariant) `elem` delta = return True
| (x, ContraVariant) `elem` delta = return False
| otherwise = either throwError (var delta sEnv) $
expandTypeSyn sEnv x ts
var delta _ (TyVar x) = return True
var _ _ TyNat = return True
var _ _ TyZero = return True
var _ _ TyUnit = return True
var delta sEnv (TyBehav t) = var delta sEnv t
var delta sEnv (TyEvent t) = var delta sEnv t
var delta sEnv (TyPair t1 t2) = liftM2 (&&) (var delta sEnv t1) (var delta sEnv t2)
var delta sEnv (TyPlus t1 t2) = liftM2 (&&) (var delta sEnv t1) (var delta sEnv t2)
var delta sEnv (TyFun t1 t2) = liftM2 (&&)
(var (map (second invertVariance) delta) sEnv t1)
(var delta sEnv t2)
var delta sEnv (TyMu (MuType v t)) = var (replVar (v, CoVariant) delta) sEnv t
var delta sEnv (TyNu (NuType v t)) = var (replVar (v, CoVariant) delta) sEnv t
replVar :: (Var, Variance) -> [(Var, Variance)] -> [(Var, Variance)]
replVar (v,t) = ((v, t):) . filter ((/= v) . fst)