AsyncRattus-0.2: src/AsyncRattus/Plugin/ScopeCheck.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE CPP #-}
-- | This module implements the source plugin that checks the variable
-- scope of of Async Rattus programs.
module AsyncRattus.Plugin.ScopeCheck (checkAll) where
import AsyncRattus.Plugin.Utils
import AsyncRattus.Plugin.Dependency
import AsyncRattus.Plugin.Annotation
import Control.Monad.Trans.State.Strict
import Data.IORef
import Prelude hiding ((<>))
import GHC.Parser.Annotation
import GHC.Plugins
import GHC.Tc.Types
import GHC.Data.Bag
import GHC.Tc.Types.Evidence
import GHC.Hs.Extension
import GHC.Hs.Expr
import GHC.Hs.Pat
import GHC.Hs.Binds
import Data.Graph
import qualified Data.Set as Set
import qualified Data.Map as Map
import Data.Set (Set)
import Data.Map (Map)
import Data.List
import Data.List.NonEmpty (NonEmpty(..),(<|),nonEmpty)
import System.Exit
import Data.Either
import Data.Maybe
import Data.Data hiding (tyConName)
import Control.Monad
type ErrorMsg = (Severity,SrcSpan,SDoc)
type ErrorMsgsRef = IORef [ErrorMsg]
-- | The current context for scope checking
data Ctxt = Ctxt
{
errorMsgs :: ErrorMsgsRef,
-- | Variables that are in scope now (i.e. occurring in the typing
-- context but not to the left of a tick)
current :: LCtxt,
-- | Variables that are in the typing context, but to the left of a
-- tick
earlier :: Either NoTickReason (NonEmpty LCtxt),
-- | Variables that have fallen out of scope. The map contains the
-- reason why they have fallen out of scope.
hidden :: Hidden,
-- -- | Same as 'hidden' but for recursive variables.
-- hiddenRec :: Hidden,
-- | The current location information.
srcLoc :: SrcSpan,
-- | If we are in the body of a recursively defined function, this
-- field contains the variables that are defined recursively
-- (could be more than one due to mutual recursion or because of a
-- recursive pattern definition) and the location of the recursive
-- definition.
recDef :: Maybe RecDef,
-- | Type variables with a 'Stable' constraint attached to them.
stableTypes :: Set Var,
-- | A mapping from variables to the primitives that they are
-- defined equal to. For example, a program could contain @let
-- mydel = delay in mydel 1@, in which case @mydel@ is mapped to
-- 'Delay'.
primAlias :: Map Var Prim,
-- | Allow general recursion.
allowRecursion :: Bool}
-- | The starting context for checking a top-level definition. For
-- non-recursive definitions, the argument is @Nothing@. Otherwise, it
-- contains the recursively defined variables along with the location
-- of the recursive definition.
emptyCtxt :: ErrorMsgsRef -> Maybe (Set Var,SrcSpan) -> Bool -> Ctxt
emptyCtxt em mvar allowRec =
Ctxt { errorMsgs = em,
current = Set.empty,
earlier = Left NoDelay,
hidden = Map.empty,
srcLoc = noLocationInfo,
recDef = mvar,
primAlias = Map.empty,
stableTypes = Set.empty,
allowRecursion = allowRec}
-- | A local context, consisting of a set of variables.
type LCtxt = Set Var
-- | The recursively defined variables + the position where the
-- recursive definition starts
type RecDef = (Set Var, SrcSpan)
data StableReason = StableRec SrcSpan | StableBox deriving Show
-- | Indicates, why a variable has fallen out of scope.
data HiddenReason = Stabilize StableReason | FunDef | DelayApp | AdvApp | SelectApp deriving Show
-- | Indicates, why there is no tick
data NoTickReason = NoDelay | TickHidden HiddenReason deriving Show
-- | Hidden context, containing variables that have fallen out of
-- context along with the reason why they have.
type Hidden = Map Var HiddenReason
-- | The 5 primitive Asynchronous Rattus operations.
data Prim = Delay | Adv | Select | Box | Unbox deriving Show
-- | This constraint is used to pass along the context implicitly via
-- an implicit parameter.
type GetCtxt = ?ctxt :: Ctxt
type CheckM = StateT ([Maybe (Prim, SrcSpan)]) TcM
-- | This type class is implemented for each AST type @a@ for which we
-- can check whether it adheres to the scoping rules of Asynchronous Rattus.
class Scope a where
-- | Check whether the argument is a scope correct piece of syntax
-- in the given context.
check :: GetCtxt => a -> CheckM Bool
-- | This is a variant of 'Scope' for syntax that can also bind
-- variables.
class ScopeBind a where
-- | 'checkBind' checks whether its argument is scope-correct and in
-- addition returns the the set of variables bound by it.
checkBind :: GetCtxt => a -> CheckM (Bool,Set Var)
-- | set the current context.
setCtxt :: Ctxt -> (GetCtxt => a) -> a
setCtxt c a = let ?ctxt = c in a
-- | modify the current context.
modifyCtxt :: (Ctxt -> Ctxt) -> (GetCtxt => a) -> (GetCtxt => a)
modifyCtxt f a =
let newc = f ?ctxt in
let ?ctxt = newc in a
getLocAnn' :: SrcSpanAnn' b -> SrcSpan
getLocAnn' = locA
updateLoc :: SrcSpanAnn' b -> (GetCtxt => a) -> (GetCtxt => a)
updateLoc src = modifyCtxt (\c -> c {srcLoc = getLocAnn' src})
-- | Check all definitions in the given module. If Scope errors are
-- found, the current execution is halted with 'exitFailure'.
checkAll :: TcGblEnv -> TcM ()
checkAll env = do
let bindDep = dependency (tcg_binds env)
result <- mapM (checkSCC' (tcg_mod env) (tcg_ann_env env)) bindDep
let (res,msgs) = foldl' (\(b,l) (b',l') -> (b && b', l ++ l')) (True,[]) result
printAccErrMsgs msgs
if res then return () else liftIO exitFailure
printAccErrMsgs :: [ErrorMsg] -> TcM ()
printAccErrMsgs msgs = mapM_ printMsg (sortOn (\(_,l,_)->l) msgs)
where printMsg (sev,loc,doc) = printMessage sev loc doc
instance Scope a => Scope (GenLocated SrcSpan a) where
check (L l x) = (\c -> c {srcLoc = l}) `modifyCtxt` check x
instance Scope a => Scope (GenLocated (SrcSpanAnn' b) a) where
check (L l x) = updateLoc l $ check x
instance Scope a => Scope (Bag a) where
check bs = fmap and (mapM check (bagToList bs))
instance Scope a => Scope [a] where
check ls = fmap and (mapM check ls)
instance Scope (Match GhcTc (GenLocated SrcAnno (HsExpr GhcTc))) where
check Match{m_pats=ps,m_grhss=rhs} = addVars (getBV ps) `modifyCtxt` check rhs
instance Scope (Match GhcTc (GenLocated SrcAnno (HsCmd GhcTc))) where
check Match{m_pats=ps,m_grhss=rhs} = addVars (getBV ps) `modifyCtxt` check rhs
instance Scope (MatchGroup GhcTc (GenLocated SrcAnno (HsExpr GhcTc))) where
check MG {mg_alts = alts} = check alts
instance Scope (MatchGroup GhcTc (GenLocated SrcAnno (HsCmd GhcTc))) where
check MG {mg_alts = alts} = check alts
instance Scope a => ScopeBind (StmtLR GhcTc GhcTc a) where
checkBind (LastStmt _ b _ _) = ( , Set.empty) <$> check b
checkBind (BindStmt _ p b) = do
let vs = getBV p
let c' = addVars vs ?ctxt
r <- setCtxt c' (check b)
return (r,vs)
checkBind (BodyStmt _ b _ _) = ( , Set.empty) <$> check b
checkBind (LetStmt _ bs) = checkBind bs
checkBind ParStmt{} = notSupported "monad comprehensions"
checkBind TransStmt{} = notSupported "monad comprehensions"
checkBind ApplicativeStmt{} = notSupported "applicative do notation"
checkBind RecStmt{} = notSupported "recursive do notation"
instance ScopeBind a => ScopeBind [a] where
checkBind [] = return (True,Set.empty)
checkBind (x:xs) = do
(r,vs) <- checkBind x
(r',vs') <- addVars vs `modifyCtxt` (checkBind xs)
return (r && r',vs `Set.union` vs')
instance ScopeBind a => ScopeBind (GenLocated SrcSpan a) where
checkBind (L l x) = (\c -> c {srcLoc = l}) `modifyCtxt` checkBind x
instance ScopeBind a => ScopeBind (GenLocated (SrcSpanAnn' b) a) where
checkBind (L l x) = updateLoc l $ checkBind x
instance Scope a => Scope (GRHS GhcTc a) where
check (GRHS _ gs b) = do
(r, vs) <- checkBind gs
r' <- addVars vs `modifyCtxt` (check b)
return (r && r')
checkRec :: GetCtxt => LHsBindLR GhcTc GhcTc -> CheckM Bool
checkRec b = liftM2 (&&) (checkPatBind b) (check b)
checkPatBind :: GetCtxt => LHsBindLR GhcTc GhcTc -> CheckM Bool
checkPatBind (L l b) = updateLoc l $ checkPatBind' b
checkPatBind' :: GetCtxt => HsBindLR GhcTc GhcTc -> CheckM Bool
checkPatBind' PatBind{} = do
printMessage' SevError ("(Mutual) recursive pattern binding definitions are not supported in Asynchronous Rattus")
return False
#if __GLASGOW_HASKELL__ < 904
checkPatBind' AbsBinds {abs_binds = binds} =
#else
checkPatBind' (XHsBindsLR AbsBinds {abs_binds = binds}) =
#endif
liftM and (mapM checkPatBind (bagToList binds))
checkPatBind' _ = return True
-- | Check the scope of a list of (mutual) recursive bindings. The
-- second argument is the set of variables defined by the (mutual)
-- recursive bindings
checkRecursiveBinds :: GetCtxt => [LHsBindLR GhcTc GhcTc] -> Set Var -> CheckM (Bool, Set Var)
checkRecursiveBinds bs vs = do
res <- fmap and (mapM check' bs)
return (res, vs)
where check' b@(L l _) = fc (getLocAnn' l) `modifyCtxt` checkRec b
fc l c = let
ctxHid = either (const $ current c) (Set.union (current c) . Set.unions) (earlier c)
in c {current = Set.empty,
earlier = Left (TickHidden $ Stabilize $ StableRec l),
hidden = hidden c `Map.union`
(Map.fromSet (const (Stabilize (StableRec l))) ctxHid),
recDef = maybe (Just (vs,l)) (\(vs',_) -> Just (Set.union vs' vs,l)) (recDef c)
-- TODO fix location info of recDef (needs one location for each var)
}
instance ScopeBind (SCC (GenLocated SrcSpanAnnA (HsBindLR GhcTc GhcTc), Set Var)) where
checkBind (AcyclicSCC (b,vs)) = (, vs) <$> check b
checkBind (CyclicSCC bs) = checkRecursiveBinds (map fst bs) (foldMap snd bs)
instance ScopeBind (HsValBindsLR GhcTc GhcTc) where
checkBind (ValBinds _ bs _) = checkBind (dependency bs)
checkBind (XValBindsLR (NValBinds binds _)) = checkBind binds
instance ScopeBind (HsBindLR GhcTc GhcTc) where
checkBind b = (, getBV b) <$> check b
-- | Compute the set of variables defined by the given Haskell binder.
getAllBV :: GenLocated l (HsBindLR GhcTc GhcTc) -> Set Var
getAllBV (L _ b) = getAllBV' b where
getAllBV' (FunBind{fun_id = L _ v}) = Set.singleton v
#if __GLASGOW_HASKELL__ < 904
getAllBV' (AbsBinds {abs_exports = es, abs_binds = bs}) = Set.fromList (map abe_poly es) `Set.union` foldMap getBV bs
getAllBV' XHsBindsLR{} = Set.empty
#else
getAllBV' (XHsBindsLR (AbsBinds {abs_exports = es, abs_binds = bs})) = Set.fromList (map abe_poly es) `Set.union` foldMap getBV bs
#endif
getAllBV' (PatBind {pat_lhs = pat}) = getBV pat
getAllBV' (VarBind {var_id = v}) = Set.singleton v
getAllBV' PatSynBind{} = Set.empty
-- Check nested bindings
instance ScopeBind (RecFlag, Bag (GenLocated SrcSpanAnnA (HsBindLR GhcTc GhcTc))) where
checkBind (NonRecursive, bs) = checkBind $ bagToList bs
checkBind (Recursive, bs) = checkRecursiveBinds bs' (foldMap getAllBV bs')
where bs' = bagToList bs
instance ScopeBind (HsLocalBindsLR GhcTc GhcTc) where
checkBind (HsValBinds _ bs) = checkBind bs
checkBind HsIPBinds {} = notSupported "implicit parameters"
checkBind EmptyLocalBinds{} = return (True,Set.empty)
type SrcAnno = SrcSpanAnnA
instance Scope (GRHSs GhcTc (GenLocated SrcAnno (HsExpr GhcTc))) where
check GRHSs{grhssGRHSs = rhs, grhssLocalBinds = lbinds} = do
(l,vs) <- checkBind lbinds
r <- addVars vs `modifyCtxt` (check rhs)
return (r && l)
instance Scope (GRHSs GhcTc (GenLocated SrcAnno (HsCmd GhcTc))) where
check GRHSs{grhssGRHSs = rhs, grhssLocalBinds = lbinds} = do
(l,vs) <- checkBind lbinds
r <- addVars vs `modifyCtxt` (check rhs)
return (r && l)
instance Show Var where
show v = getOccString v
tickHidden :: HiddenReason -> SDoc
tickHidden FunDef = "a function definition"
tickHidden DelayApp = "a nested application of delay"
tickHidden AdvApp = "an application of adv"
tickHidden SelectApp = "an application of select"
tickHidden (Stabilize StableBox) = "an application of box"
tickHidden (Stabilize (StableRec src)) = "a nested recursive definition (at " <> ppr src <> ")"
isSelect :: GetCtxt => LHsExpr GhcTc -> Bool
isSelect e =
case isPrimExpr e of
Just (Select, _) -> True
_ -> False
instance Scope (HsExpr GhcTc) where
check (HsVar _ (L _ v))
| Just p <- isPrim v =
case p of
Unbox -> return True
_ -> printMessageCheck SevError ("Defining an alias for " <> ppr v <> " is not allowed")
| otherwise = case getScope v of
Hidden reason -> printMessageCheck SevError reason
Visible -> return True
ImplUnboxed -> return True
-- printMessageCheck SevWarning
-- (ppr v <> text " is an external temporal function used under delay, which may cause time leaks.")
check (HsApp _ (L _ (HsApp _ f arg)) arg2) | isSelect f =
case earlier ?ctxt of
Right (er :| ers) -> do
res <- get
case res of
Just _ : _ -> printMessageCheck SevError ("only one adv or select may be used in the scope of a delay.")
Nothing : pre -> do put pre
b1 <- mod `modifyCtxt` check arg
b2 <- mod `modifyCtxt` check arg2
modify (Just (Select, srcLoc ?ctxt) :)
return $ b1 && b2
_ -> error "Asynchronous Rattus: internal error"
where mod c = c{earlier = case nonEmpty ers of
Nothing -> Left $ TickHidden SelectApp
Just ers' -> Right ers',
current = er,
hidden = hidden ?ctxt `Map.union`
Map.fromSet (const SelectApp) (current ?ctxt)}
Left NoDelay -> printMessageCheck SevError "select may only be used in the scope of a delay."
Left (TickHidden hr) -> printMessageCheck SevError ("select may only be used in the scope of a delay. "
<> " There is a delay, but its scope is interrupted by " <> tickHidden hr <> ".")
check (HsApp _ e1 e2) =
case isPrimExpr e1 of
Just (p,_) -> case p of
Box -> do
ch <- stabilize StableBox `modifyCtxt` check e2
return ch
Unbox -> check e2
Delay -> do modify (Nothing :)
b <- (\c -> c{current = Set.empty,
earlier = case earlier c of
Left _ -> Right (current c :| [])
Right cs -> Right (current c <| cs)})
`modifyCtxt` check e2
res <- get
case res of
Nothing : _ -> printMessageCheck SevError "No adv or select found in the scope of this occurrence of delay"
_ : pre -> put pre >> return b
_ -> error "Asynchronous Rattus: internal error"
Adv -> case earlier ?ctxt of
Right (er :| ers) -> do
res <- get
case res of
Just _ : _ -> printMessageCheck SevError ("only one adv or select may be used in the scope of a delay.")
Nothing : pre -> do put pre
b <- mod `modifyCtxt` check e2
modify (Just (Adv,srcLoc ?ctxt) :)
return b
_ -> error "Asynchronous Rattus: internal error"
where mod c = c{earlier = case nonEmpty ers of
Nothing -> Left $ TickHidden AdvApp
Just ers' -> Right ers',
current = er,
hidden = hidden ?ctxt `Map.union`
Map.fromSet (const AdvApp) (current ?ctxt)}
Left NoDelay -> printMessageCheck SevError ("adv may only be used in the scope of a delay.")
Left (TickHidden hr) -> printMessageCheck SevError ("adv may only be used in the scope of a delay. "
<> " There is a delay, but its scope is interrupted by " <> tickHidden hr <> ".")
Select -> printMessageCheck SevError ("select must be fully applied")
_ -> liftM2 (&&) (check e1) (check e2)
check HsUnboundVar{} = return True
#if __GLASGOW_HASKELL__ >= 904
check (HsPar _ _ e _) = check e
check (HsLamCase _ _ mg) = check mg
check HsRecSel{} = return True
check HsTypedBracket{} = notSupported "MetaHaskell"
check HsUntypedBracket{} = notSupported "MetaHaskell"
#else
check HsConLikeOut{} = return True
check HsRecFld{} = return True
check (HsPar _ e) = check e
check (HsLamCase _ mg) = check mg
check HsBracket{} = notSupported "MetaHaskell"
check (HsTick _ _ e) = check e
check (HsBinTick _ _ _ e) = check e
check HsRnBracketOut{} = notSupported "MetaHaskell"
check HsTcBracketOut{} = notSupported "MetaHaskell"
#endif
#if __GLASGOW_HASKELL__ >= 904
check (HsLet _ _ bs _ e) = do
#else
check (HsLet _ bs e) = do
#endif
(l,vs) <- checkBind bs
r <- addVars vs `modifyCtxt` (check e)
return (r && l)
check HsOverLabel{} = return True
check HsIPVar{} = notSupported "implicit parameters"
check HsOverLit{} = return True
check HsLit{} = return True
check (OpApp _ e1 e2 e3) = and <$> mapM check [e1,e2,e3]
check (HsLam _ mg) = check mg
check (HsCase _ e1 e2) = (&&) <$> check e1 <*> check e2
check (SectionL _ e1 e2) = (&&) <$> check e1 <*> check e2
check (SectionR _ e1 e2) = (&&) <$> check e1 <*> check e2
check (ExplicitTuple _ e _) = check e
check (NegApp _ e _) = check e
check (ExplicitSum _ _ _ e) = check e
check (HsMultiIf _ e) = check e
check (ExplicitList _ e) = check e
check HsProjection {} = return True
check HsGetField {gf_expr = e} = check e
check RecordUpd { rupd_expr = e, rupd_flds = fs} = (&&) <$> check e <*> check fs
check RecordCon { rcon_flds = f} = check f
check (ArithSeq _ _ e) = check e
#if __GLASGOW_HASKELL__ >= 906
check HsTypedSplice{} = notSupported "Template Haskell"
check HsUntypedSplice{} = notSupported "Template Haskell"
#else
check HsSpliceE{} = notSupported "Template Haskell"
#endif
check (HsProc _ _ e) = check e
check (HsStatic _ e) = check e
check (HsDo _ _ e) = fst <$> checkBind e
check (XExpr e) = check e
#if __GLASGOW_HASKELL__ >= 906
check (HsAppType _ e _ _) = check e
check (ExprWithTySig _ e _) = check e
#else
check (HsAppType _ e _) = check e
check (ExprWithTySig _ e _) = check e
#endif
check (HsPragE _ _ e) = check e
check (HsIf _ e1 e2 e3) = and <$> mapM check [e1,e2,e3]
instance (Scope a, Scope b) => Scope (Either a b) where
check (Left x) = check x
check (Right x) = check x
#if __GLASGOW_HASKELL__ >= 908
instance Scope (LHsRecUpdFields GhcTc) where
check RegularRecUpdFields {recUpdFields = x} = check x
check OverloadedRecUpdFields {olRecUpdFields = x} = check x
#endif
instance Scope XXExprGhcTc where
check (WrapExpr (HsWrap _ e)) = check e
check (ExpansionExpr (HsExpanded _ e)) = check e
#if __GLASGOW_HASKELL__ >= 904
check ConLikeTc{} = return True
check (HsTick _ e) = check e
check (HsBinTick _ _ e) = check e
#endif
instance Scope (HsCmdTop GhcTc) where
check (HsCmdTop _ e) = check e
instance Scope (HsCmd GhcTc) where
check (HsCmdArrApp _ e1 e2 _ _) = (&&) <$> check e1 <*> check e2
check (HsCmdDo _ e) = fst <$> checkBind e
check (HsCmdArrForm _ e1 _ _ e2) = (&&) <$> check e1 <*> check e2
check (HsCmdApp _ e1 e2) = (&&) <$> check e1 <*> check e2
check (HsCmdLam _ e) = check e
#if __GLASGOW_HASKELL__ >= 904
check (HsCmdPar _ _ e _) = check e
check (HsCmdLamCase _ _ e) = check e
check (HsCmdLet _ _ bs _ e) = do
#else
check (HsCmdPar _ e) = check e
check (HsCmdLamCase _ e) = check e
check (HsCmdLet _ bs e) = do
#endif
(l,vs) <- checkBind bs
r <- addVars vs `modifyCtxt` (check e)
return (r && l)
check (HsCmdCase _ e1 e2) = (&&) <$> check e1 <*> check e2
check (HsCmdIf _ _ e1 e2 e3) = (&&) <$> ((&&) <$> check e1 <*> check e2) <*> check e3
check (XCmd (HsWrap _ e)) = check e
instance Scope (ArithSeqInfo GhcTc) where
check (From e) = check e
check (FromThen e1 e2) = (&&) <$> check e1 <*> check e2
check (FromTo e1 e2) = (&&) <$> check e1 <*> check e2
check (FromThenTo e1 e2 e3) = (&&) <$> ((&&) <$> check e1 <*> check e2) <*> check e3
instance Scope a => Scope (HsRecFields GhcTc a) where
check HsRecFields {rec_flds = fs} = check fs
#if __GLASGOW_HASKELL__ >= 904
instance Scope b => Scope (HsFieldBind a b) where
check HsFieldBind{hfbRHS = a} = check a
#else
instance Scope b => Scope (HsRecField' a b) where
check HsRecField{hsRecFieldArg = a} = check a
#endif
instance Scope (HsTupArg GhcTc) where
check (Present _ e) = check e
check Missing{} = return True
instance Scope (HsBindLR GhcTc GhcTc) where
#if __GLASGOW_HASKELL__ >= 904
check (XHsBindsLR AbsBinds {abs_binds = binds, abs_ev_vars = ev})
#else
check AbsBinds {abs_binds = binds, abs_ev_vars = ev}
#endif
= mod `modifyCtxt` check binds
where mod c = c { stableTypes= stableTypes c `Set.union`
Set.fromList (mapMaybe (isStableConstr . varType) ev)}
check FunBind{fun_matches= matches, fun_id = L _ v,
fun_ext = wrapper} =
mod `modifyCtxt` check matches
where mod c = c { stableTypes= stableTypes c `Set.union`
Set.fromList (stableConstrFromWrapper' wrapper) `Set.union`
Set.fromList (extractStableConstr (varType v))}
check PatBind{pat_lhs = lhs, pat_rhs=rhs} = addVars (getBV lhs) `modifyCtxt` check rhs
check VarBind{var_rhs = rhs} = check rhs
check PatSynBind {} = return True -- pattern synonyms are not supported
-- | Checks whether the given type is a type constraint of the form
-- @Stable a@ for some type variable @a@. In that case it returns the
-- type variable @a@.
isStableConstr :: Type -> Maybe TyVar
isStableConstr t =
case splitTyConApp_maybe t of
Just (con,[args]) ->
case getNameModule con of
Just (name, mod) ->
if isRattModule mod && name == "Stable"
then (getTyVar_maybe args)
else Nothing
_ -> Nothing
_ -> Nothing
#if __GLASGOW_HASKELL__ >= 906
stableConstrFromWrapper' :: (HsWrapper , a) -> [TyVar]
stableConstrFromWrapper' (x , _) = stableConstrFromWrapper x
#else
stableConstrFromWrapper' :: HsWrapper -> [TyVar]
stableConstrFromWrapper' = stableConstrFromWrapper
#endif
stableConstrFromWrapper :: HsWrapper -> [TyVar]
stableConstrFromWrapper (WpCompose v w) = stableConstrFromWrapper v ++ stableConstrFromWrapper w
stableConstrFromWrapper (WpEvLam v) = maybeToList $ isStableConstr (varType v)
stableConstrFromWrapper _ = []
-- | Given a type @(C1, ... Cn) => t@, this function returns the list
-- of type variables @[a1,...,am]@ for which there is a constraint
-- @Stable ai@ among @C1, ... Cn@.
extractStableConstr :: Type -> [TyVar]
extractStableConstr = mapMaybe isStableConstr . map irrelevantMult . fst . splitFunTys . snd . splitForAllTys'
getSCCLoc :: SCC (LHsBindLR GhcTc GhcTc, Set Var) -> SrcSpan
getSCCLoc (AcyclicSCC (L l _ ,_)) = getLocAnn' l
getSCCLoc (CyclicSCC ((L l _,_ ) : _)) = getLocAnn' l
getSCCLoc _ = noLocationInfo
checkSCC' :: Module -> AnnEnv -> SCC (LHsBindLR GhcTc GhcTc, Set Var) -> TcM (Bool, [ErrorMsg])
checkSCC' mod anEnv scc = do
err <- liftIO (newIORef [])
let allowRec = AllowRecursion `Set.member` getAnn mod anEnv scc
res <- checkSCC allowRec err scc
msgs <- liftIO (readIORef err)
let anns = getAnn mod anEnv scc
if ExpectWarning `Set.member` anns
then if ExpectError `Set.member` anns
then return (False,[(SevError, getSCCLoc scc, "Annotation to expect both warning and error is not allowed.")])
else if any (\(s,_,_) -> case s of SevWarning -> True; _ -> False) msgs
then return (res, filter (\(s,_,_) -> case s of SevWarning -> False; _ -> True) msgs)
else return (False,[(SevError, getSCCLoc scc, "Warning was expected, but typechecking produced no warning.")])
else if ExpectError `Set.member` anns
then if res
then return (False,[(SevError, getSCCLoc scc, "Error was expected, but typechecking produced no error.")])
else return (True,[])
else return (res, msgs)
getAnn :: forall a . (Data a, Ord a) => Module -> AnnEnv -> SCC (LHsBindLR GhcTc GhcTc, Set Var) -> Set a
getAnn mod anEnv scc =
case scc of
(AcyclicSCC (_,vs)) -> Set.unions $ Set.map checkVar vs
(CyclicSCC bs) -> Set.unions $ map (Set.unions . Set.map checkVar . snd) bs
where checkVar :: Var -> Set a
checkVar v =
let anns = findAnns deserializeWithData anEnv (NamedTarget name) :: [a]
annsMod = findAnns deserializeWithData anEnv (ModuleTarget mod) :: [a]
name :: Name
name = varName v
in Set.fromList anns `Set.union` Set.fromList annsMod
-- | Checks a top-level definition group, which is either a single
-- non-recursive definition or a group of (mutual) recursive
-- definitions.
checkSCC :: Bool -> ErrorMsgsRef -> SCC (LHsBindLR GhcTc GhcTc, Set Var) -> TcM Bool
checkSCC allowRec errm (AcyclicSCC (b,_)) = setCtxt (emptyCtxt errm Nothing allowRec) (evalStateT (check b) [])
checkSCC allowRec errm (CyclicSCC bs) = (fmap and (mapM check' bs'))
where bs' = map fst bs
vs = foldMap snd bs
check' b@(L l _) = setCtxt (emptyCtxt errm (Just (vs,getLocAnn' l)) allowRec) (evalStateT (checkRec b) [])
-- | Stabilizes the given context, i.e. remove all non-stable types
-- and any tick. This is performed on checking 'box', and
-- guarded recursive definitions. To provide better error messages a
-- reason has to be given as well.
stabilize :: StableReason -> Ctxt -> Ctxt
stabilize sr c = c
{current = Set.empty,
earlier = Left $ TickHidden hr,
hidden = hidden c `Map.union` Map.fromSet (const hr) ctxHid}
where ctxHid = either (const $ current c) (foldl' Set.union (current c)) (earlier c)
hr = Stabilize sr
data VarScope = Hidden SDoc | Visible | ImplUnboxed
-- | This function checks whether the given variable is in scope.
getScope :: GetCtxt => Var -> VarScope
getScope v =
case ?ctxt of
Ctxt{recDef = Just (vs,_), earlier = e, allowRecursion = allowRec} | v `Set.member` vs ->
if allowRec then Visible else
case e of
Right _ -> Visible
Left NoDelay -> Hidden ("The (mutually) recursive call to " <> ppr v <> " must occur in the scope of a delay")
Left (TickHidden hr) -> Hidden ("The (mutually) recursive call to " <> ppr v <> " must occur in the scope of a delay. "
<> "There is a delay, but its scope is interrupted by " <> tickHidden hr <> ".")
_ -> case Map.lookup v (hidden ?ctxt) of
Just (Stabilize (StableRec rv)) ->
if (isStable (stableTypes ?ctxt) (varType v)) || allowRecursion ?ctxt then Visible
else Hidden ("Variable " <> ppr v <> " is no longer in scope:" $$
"It appears in a local recursive definition (at " <> ppr rv <> ")"
$$ "and is of type " <> ppr (varType v) <> ", which is not stable.")
Just (Stabilize StableBox) ->
if (isStable (stableTypes ?ctxt) (varType v)) then Visible
else Hidden ("Variable " <> ppr v <> " is no longer in scope:" $$
"It occurs under " <> keyword "box" $$ "and is of type " <> ppr (varType v) <> ", which is not stable.")
Just AdvApp -> Hidden ("Variable " <> ppr v <> " is no longer in scope: It occurs under adv.")
Just SelectApp -> Hidden ("Variable " <> ppr v <> " is no longer in scope: It occurs under select.")
Just DelayApp -> Hidden ("Variable " <> ppr v <> " is no longer in scope due to repeated application of delay")
Just FunDef -> if (isStable (stableTypes ?ctxt) (varType v)) then Visible
else Hidden ("Variable " <> ppr v <> " is no longer in scope: It occurs in a function that is defined under a delay, is a of a non-stable type " <> ppr (varType v) <> ", and is bound outside delay")
Nothing
| either (const False) (any (Set.member v)) (earlier ?ctxt) ->
if isStable (stableTypes ?ctxt) (varType v) then Visible
else Hidden ("Variable " <> ppr v <> " is no longer in scope:" $$
"It occurs under delay" $$ "and is of type " <> ppr (varType v) <> ", which is not stable.")
| Set.member v (current ?ctxt) -> Visible
| isTemporal (varType v) && isRight (earlier ?ctxt) && userFunction v
-> ImplUnboxed
| otherwise -> Visible
-- | A map from the syntax of a primitive of Asynchronous Rattus to 'Prim'.
primMap :: Map FastString Prim
primMap = Map.fromList
[("Delay", Delay),
("delay", Delay),
("adv", Adv),
("select", Select),
("box", Box),
("unbox", Unbox)]
-- | Checks whether a given variable is in fact an Asynchronous Rattus primitive.
isPrim :: GetCtxt => Var -> Maybe Prim
isPrim v
| Just p <- Map.lookup v (primAlias ?ctxt) = Just p
| otherwise = do
(name,mod) <- getNameModule v
if isRattModule mod then Map.lookup name primMap
else Nothing
-- | Checks whether a given expression is in fact a Asynchronous Rattus primitive.
isPrimExpr :: GetCtxt => LHsExpr GhcTc -> Maybe (Prim,Var)
isPrimExpr (L _ e) = isPrimExpr' e where
isPrimExpr' :: GetCtxt => HsExpr GhcTc -> Maybe (Prim,Var)
isPrimExpr' (HsVar _ (L _ v)) = fmap (,v) (isPrim v)
#if __GLASGOW_HASKELL__ >= 906
isPrimExpr' (HsAppType _ e _ _) = isPrimExpr e
#else
isPrimExpr' (HsAppType _ e _) = isPrimExpr e
#endif
isPrimExpr' (XExpr (WrapExpr (HsWrap _ e))) = isPrimExpr' e
isPrimExpr' (XExpr (ExpansionExpr (HsExpanded _ e))) = isPrimExpr' e
isPrimExpr' (HsPragE _ _ e) = isPrimExpr e
#if __GLASGOW_HASKELL__ < 904
isPrimExpr' (HsTick _ _ e) = isPrimExpr e
isPrimExpr' (HsBinTick _ _ _ e) = isPrimExpr e
isPrimExpr' (HsPar _ e) = isPrimExpr e
#else
isPrimExpr' (XExpr (HsTick _ e)) = isPrimExpr e
isPrimExpr' (XExpr (HsBinTick _ _ e)) = isPrimExpr e
isPrimExpr' (HsPar _ _ e _) = isPrimExpr e
#endif
isPrimExpr' _ = Nothing
-- | This type class provides default implementations for 'check' and
-- 'checkBind' for Haskell syntax that is not supported. These default
-- implementations simply print an error message.
class NotSupported a where
notSupported :: GetCtxt => SDoc -> CheckM a
instance NotSupported Bool where
notSupported doc = printMessageCheck SevError ("Asynchronous Rattus does not support " <> doc)
instance NotSupported (Bool,Set Var) where
notSupported doc = (,Set.empty) <$> notSupported doc
-- | Add variables to the current context.
addVars :: Set Var -> Ctxt -> Ctxt
addVars vs c = c{current = vs `Set.union` current c }
-- | Print a message with the current location.
printMessage' :: GetCtxt => Severity -> SDoc -> CheckM ()
printMessage' sev doc =
liftIO (modifyIORef (errorMsgs ?ctxt) ((sev ,srcLoc ?ctxt, doc) :))
-- | Print a message with the current location. Returns 'False', if
-- the severity is 'SevError' and otherwise 'True.
printMessageCheck :: GetCtxt => Severity -> SDoc -> CheckM Bool
printMessageCheck sev doc = printMessage' sev doc >>
case sev of
SevError -> return False
_ -> return True