{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Ivory.Opts.SanityCheck
( sanityCheck
, showSanityChkModule
, existErrors
, dupDefs
, showDupDefs
) where
import Prelude ()
import Prelude.Compat
import System.IO (hPutStrLn, stderr)
import Control.Monad (unless)
import qualified Data.List as L
import qualified Data.Map as M
import MonadLib (Id, StateM (..),
StateT, WriterM (..),
WriterT, runId,
runStateT, runWriterT,
sets_)
import Data.Monoid.Compat ((<>))
import Text.PrettyPrint hiding ((<>))
import qualified Ivory.Language.Array as I
import qualified Ivory.Language.Syntax.AST as I
import Ivory.Language.Syntax.Concrete.Location
import Ivory.Language.Syntax.Concrete.Pretty
import qualified Ivory.Language.Syntax.Names as I
import qualified Ivory.Language.Syntax.Type as I
import Ivory.Opts.Utils
data Error = UnboundValue String
| TypeError String I.Type I.Type
deriving (Show, Eq)
type Result = Located Error
type Results = [Located Error]
existErrors :: ModResult Result -> Bool
existErrors (ModResult _ ls) = or (map go ls)
where
go (SymResult _ res) = not (null res)
showError :: Error -> Doc
showError err = case err of
UnboundValue x
-> text "Unbound value:" <+> quotes (text x)
TypeError x actual expected
-> quotes (text x) <+> text "has type:"
$$ nest 4 (quotes (pretty actual))
$$ text "but is used with type:"
$$ nest 4 (quotes (pretty expected))
showSanityChkModule :: ModResult Result -> IO ()
showSanityChkModule res = showModErrs go res
where
go :: Result -> Doc
go = showWithLoc showError
data MaybeType = Imported | Defined I.Type
deriving (Show, Eq)
data St = St { loc :: SrcLoc, env :: M.Map String MaybeType }
newtype SCResults a = SCResults { unTC :: WriterT Results (StateT St Id) a }
deriving (Functor, Applicative, Monad)
instance WriterM SCResults Results where
put e = SCResults (put e)
instance StateM SCResults St where
get = SCResults get
set = SCResults . set
getStLoc :: SCResults SrcLoc
getStLoc = fmap loc get
setStLoc :: SrcLoc -> SCResults ()
setStLoc l = sets_ (\s -> s { loc = l })
localEnv :: SCResults a -> SCResults a
localEnv doThis = do
env <- fmap env get
res <- doThis
sets_ (\s -> s { env = env })
return res
checkScope :: String -> SCResults ()
checkScope name =
lookupType name >>= \case
Nothing -> putError (UnboundValue name)
Just _ -> return ()
lookupType :: String -> SCResults (Maybe MaybeType)
lookupType name = do
env <- fmap env get
return $ M.lookup name env
hasType :: String -> I.Type -> SCResults ()
hasType name ty = sets_ (\s -> s { env = M.insert name (Defined ty) (env s) })
putError :: Error -> SCResults ()
putError err = do
loc <- getStLoc
put [err `at` loc]
runSCResults :: SCResults a -> (a, Results)
runSCResults tc = fst $ runId $ runStateT (St NoLoc M.empty) $ runWriterT (unTC tc)
varString :: I.Var -> String
varString v = case v of
I.VarName s -> s
I.VarInternal s -> s
I.VarLitName s -> s
nameString :: I.Name -> String
nameString n = case n of
I.NameSym s -> s
I.NameVar v -> varString v
getType :: I.Typed a -> I.Type
getType (I.Typed t _) = t
sanityCheck :: [I.Module] -> [ModResult Result]
sanityCheck ms = map goMod ms
where
goMod m =
ModResult (I.modName m)
(concatMap goProc (getVisible I.modProcs m))
goProc p =
let scp = sanityCheckProc topLevel p in
if null scp then [] else [SymResult (I.procSym p) scp]
topLevel :: M.Map String MaybeType
topLevel = M.fromList $
concat [ procs m
++ imports m
++ externs m
++ areas m
++ importAreas m
| m <- ms
]
showDupDefs :: [String] -> IO ()
showDupDefs dups =
if null dups then return ()
else hPutStrLn stderr $ render (vcat (map docDups dups) $$ empty)
where
docDups x = (text "*** WARNING" <> colon)
<+> quotes (text x)
<+> text "has multiple definitions."
dupDefs :: [I.Module] -> [String]
dupDefs ms = map fst dups
where
dups :: [(String, MaybeType)]
dups = defs L.\\ (L.nubBy (\a b -> (fst a == fst b) && (snd a /= snd b))) defs
where
defs = concat [ procs m ++ areas m ++ structs m | m <- ms ]
getVisible :: (t -> I.Visible a) -> t -> [a]
getVisible acc m =
let ps = acc m in
I.public ps ++ I.private ps
procs, areas, imports, externs, importAreas, structs :: I.Module -> [(I.Sym, MaybeType)]
procs m = [ (procSym, Defined $ I.TyProc procRetTy (map getType procArgs) )
| I.Proc {..} <- getVisible I.modProcs m ]
areas m = [ (areaSym, Defined areaType)
| I.Area {..} <- getVisible I.modAreas m ]
imports m = [ (importSym, Imported)
| I.Import {..} <- I.modImports m ]
externs m = [ (externSym, Defined externType)
| I.Extern {..} <- I.modExterns m ]
importAreas m = [ (aiSym, Imported)
| I.AreaImport {..} <- I.modAreaImports m ]
structs m = [ (nm, Imported)
| (I.Struct nm _) <- getVisible I.modStructs m ]
sanityCheckProc :: M.Map String MaybeType -> I.Proc -> Results
sanityCheckProc env (I.Proc {..}) = snd $ runSCResults $ do
sets_ (\s -> s { env = env })
mapM_ (\ (I.Typed t v) -> varString v `hasType` t) procArgs
check procBody
check :: [I.Stmt] -> SCResults ()
check = mapM_ go
where
go stmt = case stmt of
I.Deref t v e
-> checkExpr e >> varString v `hasType` t
I.Store _ e1 e2
-> checkExpr e1 >> checkExpr e2
I.Assign t v e
-> checkExpr e >> varString v `hasType` t
I.Call t mv (nameString -> f) args
-> do mapM_ (\ (I.Typed _ e) -> checkExpr e) args
mt <- lookupType f
case mt of
Nothing
-> putError (UnboundValue f)
Just mty
-> case mty of
Imported -> return ()
Defined ty -> checkCall f ty args t
case mv of
Nothing -> return ()
Just v -> varString v `hasType` t
I.Local t v _init
-> varString v `hasType` t
I.RefCopy _ e1 e2
-> checkExpr e1 >> checkExpr e2
I.RefZero _ e1
-> checkExpr e1
I.AllocRef t v _
-> varString v `hasType` t
I.Loop _ v _ _ stmts
-> localEnv $ do varString v `hasType` I.ixRep
check stmts
I.Forever stmts
-> localEnv $ check stmts
I.IfTE b t f
-> do checkExpr b
localEnv $ check t
localEnv $ check f
I.Comment (I.SourcePos loc)
-> setStLoc loc
_ -> return ()
checkExpr :: I.Expr -> SCResults ()
checkExpr expr = case expr of
I.ExpSym v -> checkScope v
I.ExpExtern v -> checkScope (I.externSym v)
I.ExpAddrOfGlobal v -> checkScope v
I.ExpVar v -> checkScope (varString v)
I.ExpLabel _ e _ -> checkExpr e
I.ExpIndex _ e1 _ e2 -> checkExpr e1 >> checkExpr e2
I.ExpToIx e _ -> checkExpr e
I.ExpSafeCast _ e -> checkExpr e
I.ExpOp _ exprs -> mapM_ checkExpr exprs
_ -> return ()
checkCall :: String -> I.Type -> [I.Typed I.Expr] -> I.Type -> SCResults ()
checkCall f ty args retTy = case ty of
I.TyProc r as
-> unless (all eq $ zip (r:as) (retTy : argTys))
(putError $ TypeError f (I.TyProc r as) (I.TyProc retTy argTys))
_ -> putError $ TypeError f ty (I.TyProc retTy argTys)
where
argTys = [t | I.Typed t _ <- args]
eq (x,y) = x == y
instance Pretty I.Type where
pretty ty = case ty of
I.TyVoid
-> text "()"
I.TyInt I.Int8
-> text "Sint8"
I.TyInt I.Int16
-> text "Sint16"
I.TyInt I.Int32
-> text "Sint32"
I.TyInt I.Int64
-> text "Sint64"
I.TyWord I.Word8
-> text "Uint8"
I.TyWord I.Word16
-> text "Uint16"
I.TyWord I.Word32
-> text "Uint32"
I.TyWord I.Word64
-> text "Uint64"
I.TyIndex i
-> text "Ix" <+> text (show i)
I.TyBool
-> text "Bool"
I.TyChar
-> text "Char"
I.TyFloat
-> text "Float"
I.TyDouble
-> text "Double"
I.TyProc ret args
-> pretty args <+> text ":->" <+> pretty ret
I.TyRef ref
-> text "Ref" <+> pretty ref
I.TyConstRef ref
-> text "ConstRef" <+> pretty ref
I.TyPtr ptr
-> text "Ptr" <+> pretty ptr
I.TyConstPtr ptr
-> text "ConstPtr" <+> pretty ptr
I.TyArr n t
-> text "Array" <+> pretty n <+> pretty t
I.TyStruct s
-> text "Struct" <+> pretty s
I.TyCArray t
-> text "CArray" <+> pretty t
I.TyOpaque
-> text "Opaque"