{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE EmptyDataDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE GADTs #-} module HSVIS.Typecheck ( StageTyped, typecheck, -- * Typed AST synonyms -- TProgram, TDataDef, TFunDef, TFunEq, TKind, TType, TPattern, TRHS, TExpr, ) where import Control.Monad import Data.Bifunctor (first, second) import Data.Foldable (toList) import Data.List (find, inits) import Data.Map.Strict (Map) import Data.Maybe (fromMaybe) import Data.Monoid (Ap(..)) import qualified Data.Map.Strict as Map import Data.Tuple (swap) import Data.Set (Set) import qualified Data.Set as Set import Debug.Trace import Data.Bag import Data.List.NonEmpty.Util import HSVIS.AST import HSVIS.Parser import HSVIS.Diagnostic import HSVIS.Pretty import HSVIS.Typecheck.Solve data StageTC type instance X DataDef StageTC = CKind type instance X FunDef StageTC = CType type instance X FunEq StageTC = () type instance X Kind StageTC = () type instance X Type StageTC = CKind type instance X Pattern StageTC = CType type instance X RHS StageTC = CType type instance X Expr StageTC = CType data instance E Type StageTC = TUniVar Int deriving (Show) data instance E Kind StageTC = KUniVar Int deriving (Show, Eq, Ord) data instance E TypeSig StageTC deriving (Show) type CProgram = Program StageTC type CDataDef = DataDef StageTC type CFunDef = FunDef StageTC type CFunEq = FunEq StageTC type CKind = Kind StageTC type CType = Type StageTC type CPattern = Pattern StageTC type CRHS = RHS StageTC type CExpr = Expr StageTC data StageTyped type instance X DataDef StageTyped = TType type instance X FunDef StageTyped = TType type instance X FunEq StageTyped = () type instance X Kind StageTyped = () type instance X Type StageTyped = TKind type instance X Pattern StageTyped = TType type instance X RHS StageTyped = TType type instance X Expr StageTyped = TType data instance E Type StageTyped deriving (Show) data instance E Kind StageTyped deriving (Show) data instance E TypeSig StageTyped deriving (Show) type TProgram = Program StageTyped type TDataDef = DataDef StageTyped type TFunDef = FunDef StageTyped type TFunEq = FunEq StageTyped type TKind = Kind StageTyped type TType = Type StageTyped type TPattern = Pattern StageTyped type TRHS = RHS StageTyped type TExpr = Expr StageTyped instance Pretty (E Type StageTC) where prettysPrec _ (TUniVar n) = showString ("?t" ++ show n) instance Pretty (E Kind StageTC) where prettysPrec _ (KUniVar n) = showString ("?k" ++ show n) typecheck :: FilePath -> String -> PProgram -> ([Diagnostic], TProgram) typecheck fp source prog = let (ds1, cs, _, _, progtc) = runTCM (tcProgram prog) (fp, source) 1 (Env mempty mempty mempty) (ds2, subK, subT) = solveConstrs cs in (toList (ds1 <> ds2), doneProg subK subT progtc) data Constr -- Equality constraints: "left" must be equal to "right" because of the thing -- at the given range. "left" is the expected thing; "right" is the observed -- thing. = CEq CType CType Range | CEqK CKind CKind Range deriving (Show) data Env = Env (Map Name CKind) -- ^ types in scope (including variables) (Map Name CType) -- ^ values in scope (constructors and variables) (Map Name InvCon) -- ^ patterns in scope (inverse constructors) deriving (Show) data InvCon = InvCon (Map Name CKind) -- ^ universally quantified type variables CType -- ^ input type of the inverse constructor (result of the constructor) [CType] -- ^ field types deriving (Show) newtype TCM a = TCM { runTCM :: (FilePath, String) -- ^ reader context: file and file contents -> Int -- ^ state: next id to generate -> Env -- ^ state: type and value environment -> (Bag Diagnostic -- ^ writer: diagnostics ,Bag Constr -- ^ writer: constraints ,Int, Env, a) } instance Functor TCM where fmap f (TCM g) = TCM $ \ctx i env -> let (ds, cs, i', env', x) = g ctx i env in (ds, cs, i', env', f x) instance Applicative TCM where pure x = TCM $ \_ i env -> (mempty, mempty, i, env, x) (<*>) = ap instance Monad TCM where TCM f >>= g = TCM $ \ctx i1 env1 -> let (ds2, cs2, i2, env2, x) = f ctx i1 env1 (ds3, cs3, i3, env3, y) = runTCM (g x) ctx i2 env2 in (ds2 <> ds3, cs2 <> cs3, i3, env3, y) raise :: Severity -> Range -> String -> TCM () raise sev rng@(Range (Pos y _) _) msg = TCM $ \(fp, source) i env -> (pure (Diagnostic sev fp rng [] (lines source !! y) msg), mempty, i, env, ()) emit :: Constr -> TCM () emit c = TCM $ \_ i env -> (mempty, pure c, i, env, ()) collectConstraints :: (Constr -> Maybe b) -> TCM a -> TCM (Bag b, a) collectConstraints predicate (TCM f) = TCM $ \ctx i env -> let (ds, cs, i', env', x) = f ctx i env (yes, no) = bagPartition predicate cs in (ds, no, i', env', (yes, x)) getFullEnv :: TCM Env getFullEnv = TCM $ \_ i env -> (mempty, mempty, i, env, env) putFullEnv :: Env -> TCM () putFullEnv env = TCM $ \_ i _ -> (mempty, mempty, i, env, ()) genId :: TCM Int genId = TCM $ \_ i env -> (mempty, mempty, i + 1, env, i) getKind :: Name -> TCM (Maybe CKind) getKind name = do Env tenv _ _ <- getFullEnv return (Map.lookup name tenv) getType :: Name -> TCM (Maybe CType) getType name = do Env _ venv _ <- getFullEnv return (Map.lookup name venv) getInvCon :: Name -> TCM (Maybe InvCon) getInvCon name = do Env _ _ icenv <- getFullEnv return (Map.lookup name icenv) modifyTEnv :: (Map Name CKind -> Map Name CKind) -> TCM () modifyTEnv f = do Env tenv venv icenv <- getFullEnv putFullEnv (Env (f tenv) venv icenv) modifyVEnv :: (Map Name CType -> Map Name CType) -> TCM () modifyVEnv f = do Env tenv venv icenv <- getFullEnv putFullEnv (Env tenv (f venv) icenv) modifyICEnv :: (Map Name InvCon -> Map Name InvCon) -> TCM () modifyICEnv f = do Env tenv venv icenv <- getFullEnv putFullEnv (Env tenv venv (f icenv)) scopeTEnv :: TCM a -> TCM a scopeTEnv m = do Env origtenv _ _ <- getFullEnv res <- m modifyTEnv (\_ -> origtenv) return res scopeVEnv :: TCM a -> TCM a scopeVEnv m = do Env _ origvenv _ <- getFullEnv res <- m modifyVEnv (\_ -> origvenv) return res genKUniVar :: TCM CKind genKUniVar = KExt () . KUniVar <$> genId genUniVar :: CKind -> TCM CType genUniVar k = TExt k . TUniVar <$> genId getKind' :: Range -> Name -> TCM CKind getKind' rng name = getKind name >>= \case Nothing -> do raise SError rng $ "Type not in scope: " ++ pretty name genKUniVar Just k -> return k getType' :: Range -> Name -> TCM CType getType' rng name = getType name >>= \case Nothing -> do raise SError rng $ "Variable not in scope: " ++ pretty name genUniVar (KType ()) Just k -> return k tcProgram :: PProgram -> TCM CProgram tcProgram (Program ddefs1 fdefs1) = do (kconstrs, ddefs2) <- collectConstraints isCEqK $ do ks <- mapM prepareDataDef ddefs1 zipWithM kcDataDef ks ddefs1 kinduvars <- solveKindVars kconstrs let ddefs3 = map (substDdef kinduvars mempty) ddefs2 modifyTEnv (Map.map (substKind kinduvars)) forM_ ddefs3 $ \ddef -> modifyICEnv (Map.fromList (generateInvCons ddef) <>) traceM (unlines (map pretty ddefs3)) fdefs2 <- mapM tcFunDef fdefs1 return (Program ddefs3 fdefs2) -- Bring data type name in scope with a kind of the specified arity prepareDataDef :: PDataDef -> TCM (CKind, [CKind]) prepareDataDef (DataDef _ name params _) = do parkinds <- mapM (\_ -> genKUniVar) params let k = foldr (KFun ()) (KType ()) parkinds modifyTEnv (Map.insert name k) return (k, parkinds) -- Assumes that the kind of the name itself has already been registered with -- the correct arity (this is done by prepareDataDef). kcDataDef :: (CKind, [CKind]) -> PDataDef -> TCM CDataDef kcDataDef (kd, parkinds) (DataDef _ name params cons) = do -- ensure unicity of type param names params' <- let prenames = Set.fromList (map snd params) namegen = filter (`Set.notMember` prenames) [Name ('t' : show i) | i <- [1::Int ..]] in forM (zip3 params (inits (map snd params)) namegen) $ \((rng, pname), previous, replname) -> if pname `elem` previous then do raise SError rng $ "Duplicate type parameter: " ++ pretty pname return replname else return pname -- kind-check the constructors cons' <- scopeTEnv $ do modifyTEnv (Map.fromList (zip params' parkinds) <>) forM cons $ \(cname, fieldtys) -> do fieldtys' <- mapM (kcType (Just (KType ()))) fieldtys return (cname, fieldtys') return (DataDef kd name (zip parkinds params') cons') generateInvCons :: CDataDef -> [(Name, InvCon)] generateInvCons (DataDef k tname params cons) = let tyvars = Map.fromList (map swap params) resty = TApp (KType ()) (TCon k tname) (map (uncurry TVar) params) in [(cname, InvCon tyvars resty fields) | (cname, fields) <- cons] promoteDownK :: Maybe CKind -> TCM CKind promoteDownK Nothing = genKUniVar promoteDownK (Just k) = return k downEqK :: Range -> Maybe CKind -> CKind -> TCM () downEqK _ Nothing _ = return () downEqK rng (Just k1) k2 = emit $ CEqK k1 k2 rng -- | Given (maybe) the expected kind of this type, and a type, check it for -- kind-correctness. kcType :: Maybe CKind -> PType -> TCM CType kcType mdown = \case TApp rng t ts -> do t' <- kcType Nothing t ts' <- mapM (kcType Nothing) ts retk <- promoteDownK mdown let expected = foldr (KFun ()) retk (map extOf ts') emit $ CEqK (extOf t') expected rng return (TApp retk t' ts') TTup rng ts -> do ts' <- mapM (kcType (Just (KType ()))) ts forM_ (zip (map extOf ts) ts') $ \(trng, ct) -> emit $ CEqK (extOf ct) (KType ()) trng downEqK rng mdown (KType ()) return (TTup (KType ()) ts') TList rng t -> do t' <- kcType (Just (KType ())) t emit $ CEqK (extOf t') (KType ()) (extOf t) downEqK rng mdown (KType ()) return (TList (KType ()) t') TFun rng t1 t2 -> do t1' <- kcType (Just (KType ())) t1 t2' <- kcType (Just (KType ())) t2 emit $ CEqK (extOf t1') (KType ()) (extOf t1) emit $ CEqK (extOf t2') (KType ()) (extOf t2) downEqK rng mdown (KType ()) return (TFun (KType ()) t1' t2') TCon rng n -> do k <- getKind' rng n downEqK rng mdown k return (TCon k n) TVar rng n -> do k <- getKind' rng n downEqK rng mdown k return (TVar k n) TForall rng n t -> do -- implicit forall k1 <- genKUniVar k2 <- genKUniVar downEqK rng mdown k2 t' <- scopeTEnv $ do modifyTEnv (Map.insert n k1) kcType (Just k2) t return (TForall k2 n t') -- not 'k1 -> k2' because the forall is implicit tcFunDef :: PFunDef -> TCM CFunDef tcFunDef (FunDef rng name msig eqs) = do when (not $ allEq (fmap (length . funeqPats) eqs)) $ raise SError rng "Function equations have differing numbers of arguments" typ <- case msig of TypeSig sig -> kcType (Just (KType ())) sig TypeSigExt NoTypeSig -> genUniVar (KType ()) eqs' <- mapM (tcFunEq typ) eqs return (FunDef typ name (TypeSig typ) eqs') tcFunEq :: CType -> PFunEq -> TCM CFunEq tcFunEq down (FunEq rng name pats rhs) = do -- getFullEnv >>= \env -> traceM $ "[tcFunEq] Env = " ++ show env (argtys, rhsty) <- unfoldFunTy rng (length pats) down scopeVEnv $ do pats' <- zipWithM tcPattern argtys pats rhs' <- tcRHS rhsty rhs return (FunEq () name pats' rhs') tcPattern :: CType -> PPattern -> TCM CPattern tcPattern down = \case PWildcard _ -> return $ PWildcard down PVar _ n -> modifyVEnv (Map.insert n down) >> return (PVar down n) PAs _ n p -> modifyVEnv (Map.insert n down) >> tcPattern down p PCon rng n ps -> getInvCon n >>= \case Just (InvCon tyvars match fields) -> do unisub <- mapM genUniVar tyvars -- substitution for the universally quantified variables let match' = substType mempty mempty unisub match fields' = map (substType mempty mempty unisub) fields emit $ CEq down match' rng PCon match' n <$> zipWithM tcPattern fields' ps Nothing -> do raise SError rng $ "Constructor not in scope: " ++ pretty n return (PWildcard down) POp rng p1 op p2 -> case op of OCons -> do eltty <- genUniVar (KType ()) let listty = TList (KType ()) eltty emit $ CEq down listty rng p1' <- tcPattern eltty p1 p2' <- tcPattern listty p2 return (POp listty p1' OCons p2') _ -> do raise SError rng $ "Operator is not a constructor: " ++ pretty op return (PWildcard down) PList rng ps -> do eltty <- genUniVar (KType ()) let listty = TList (KType ()) eltty emit $ CEq down listty rng PList listty <$> mapM (tcPattern eltty) ps PTup rng ps -> do ts <- mapM (\_ -> genUniVar (KType ())) ps emit $ CEq down (TTup (KType ()) ts) rng PTup (TTup (KType ()) ts) <$> zipWithM tcPattern ts ps tcRHS :: CType -> PRHS -> TCM CRHS tcRHS = error "tcRHS" unfoldFunTy :: Range -> Int -> CType -> TCM ([CType], CType) unfoldFunTy _ n t | n <= 0 = return ([], t) unfoldFunTy rng n (TFun _ t1 t2) = do (params, core) <- unfoldFunTy rng (n - 1) t2 return (t1 : params, core) unfoldFunTy rng n t = do vars <- replicateM n (genUniVar (KType ())) core <- genUniVar (KType ()) let expected = foldr (TFun (KType ())) core vars emit $ CEq expected t rng return (vars, core) solveKindVars :: Bag (CKind, CKind, Range) -> TCM (Map Int CKind) solveKindVars cs = do let (asg, errs) = solveConstraints reduce (foldMap pure . kindUniVars) substKind (\case KExt () (KUniVar v) -> Just v _ -> Nothing) kindSize (toList cs) forM_ errs $ \case UEUnequal k1 k2 rng -> raise SError rng $ "Kind mismatch:\n\ \- " ++ pretty k1 ++ "\n\ \- " ++ pretty k2 UERecursive uvar k rng -> raise SError rng $ "Kind cannot be recursive: " ++ pretty (KExt () (KUniVar uvar)) ++ " = " ++ pretty k -- default unconstrained kind variables to Type let unconstrKUVars = foldMap kindUniVars (Map.elems asg) Set.\\ Map.keysSet asg defaults = Map.fromList (map (,KType ()) (toList unconstrKUVars)) asg' = Map.map (substKind defaults) asg <> defaults return asg' where reduce :: CKind -> CKind -> Range -> (Bag (Int, CKind, Range), Bag (CKind, CKind, Range)) reduce lhs rhs rng = case (lhs, rhs) of -- unification variables produce constraints on a unification variable (KExt () (KUniVar i), KExt () (KUniVar j)) | i == j -> mempty (KExt () (KUniVar i), k ) -> (pure (i, k, rng), mempty) (k , KExt () (KUniVar i)) -> (pure (i, k, rng), mempty) -- if lhs and rhs have equal prefixes, recurse (KType () , KType () ) -> mempty (KFun () a b, KFun () c d) -> reduce a c rng <> reduce b d rng -- otherwise, this is a kind mismatch (k1, k2) -> (mempty, pure (k1, k2, rng)) kindSize :: CKind -> Int kindSize KType{} = 1 kindSize (KFun () a b) = 1 + kindSize a + kindSize b kindSize (KExt () KUniVar{}) = 2 solveConstrs :: Bag Constr -> (Bag Diagnostic, Map Int TKind, Map Int TType) solveConstrs = error "solveConstrs" -- substitute unification variables substProg :: Map Int CKind -- ^ Kind unification variable instantiations -> Map Int CType -- ^ Type unification variable instantiations -> CProgram -> CProgram substProg = error "substProg" -- substitute unification variables substDdef :: Map Int CKind -> Map Int CType -> CDataDef -> CDataDef substDdef mk mt (DataDef k name pars cons) = DataDef (substKind mk k) name (map (first (substKind mk)) pars) (map (second (map (substType mk mt mempty))) cons) substType :: Map Int CKind -- ^ kind uvars -> Map Int CType -- ^ type uvars -> Map Name CType -- ^ type variables -> CType -> CType substType mk mt mtv = go where go (TApp k t ts) = TApp (substKind mk k) (go t) (map go ts) go (TTup k ts) = TTup (substKind mk k) (map go ts) go (TList k t) = TList (substKind mk k) (go t) go (TFun k t1 t2) = TFun (substKind mk k) (go t1) (go t2) go (TCon k n) = TCon (substKind mk k) n go t@(TVar _ n) = fromMaybe t (Map.lookup n mtv) go (TForall k n t) = TForall (substKind mk k) n (go t) go t@(TExt _ (TUniVar v)) = fromMaybe t (Map.lookup v mt) -- substitute unification variables substKind :: Map Int CKind -> CKind -> CKind substKind m = \case KType () -> KType () KFun () k1 k2 -> KFun () (substKind m k1) (substKind m k2) k@(KExt () (KUniVar v)) -> fromMaybe k (Map.lookup v m) doneProg :: Map Int TKind -- ^ Kind unification variable instantiations -> Map Int TType -- ^ Type unification variable instantiations -> CProgram -> TProgram doneProg = error "doneProg" kindUniVars :: CKind -> Set Int kindUniVars = \case KType{} -> mempty KFun () a b -> kindUniVars a <> kindUniVars b KExt () (KUniVar v) -> Set.singleton v allEq :: (Eq a, Foldable t) => t a -> Bool allEq l = case toList l of [] -> True x : xs -> all (== x) xs funeqPats :: FunEq t -> [Pattern t] funeqPats (FunEq _ _ pats _) = pats isCEqK :: Constr -> Maybe (CKind, CKind, Range) isCEqK (CEqK k1 k2 rng) = Just (k1, k2, rng) isCEqK _ = Nothing foldMapM :: (Applicative f, Monoid m, Foldable t) => (a -> f m) -> t a -> f m foldMapM f = getAp . foldMap (Ap . f)