Agda-2.6.2.1: src/full/Agda/TypeChecking/Lock.hs
{-# LANGUAGE NondecreasingIndentation #-}
module Agda.TypeChecking.Lock
( isTimeless
, checkLockedVars
, checkEarlierThan
, requireGuarded
)
where
import Control.Monad.Reader
import qualified Data.IntMap as IMap
import qualified Data.IntSet as ISet
import qualified Data.Set as Set
import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.Interaction.Options ( optGuarded )
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Constraints
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Substitute.Class
import Agda.TypeChecking.Free
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.Size
import Agda.Utils.VarSet as VSet
import Agda.Utils.Impossible
requireGuarded :: String -> TCM ()
requireGuarded s = do
guarded <- optGuarded <$> pragmaOptions
unless guarded $
typeError $ GenericError $ "Missing option --guarded " ++ s
checkLockedVars
:: Term
-- ^ term to check
-> Type
-- ^ its type
-> Arg Term
-- ^ the lock
-> Type
-- ^ type of the lock
-> TCM ()
checkLockedVars t ty lk lk_ty = catchConstraint (CheckLockedVars t ty lk lk_ty) $ do
reportSDoc "tc.term.lock" 40 $ "Checking locked vars.."
reportSDoc "tc.term.lock" 50 $ nest 2 $ vcat
[ text "t = " <+> pretty t
, text "ty = " <+> pretty ty
, text "lk = " <+> pretty lk
, text "lk_ty = " <+> pretty lk_ty
]
-- Strategy: compute allowed variables, check that @t@ doesn't use more.
mi <- getLockVar (unArg lk)
caseMaybe mi (return ()) $ \ i -> do
cxt <- getContext
let toCheck = zip [0..] $ zipWith raise [1..] (take i cxt)
let fv = freeVarsIgnore IgnoreInAnnotations (t,ty)
let
rigid = rigidVars fv
-- flexible = IMap.keysSet $ flexibleVars fv
termVars = allVars fv -- ISet.union rigid flexible
earlierVars = ISet.fromList [i+1 .. size cxt - 1]
if termVars `ISet.isSubsetOf` earlierVars then return () else do
checked <- fmap catMaybes . forM toCheck $ \ (j,dom) -> do
ifM (isTimeless (snd . unDom $ dom))
(return $ Just j)
(return $ Nothing)
let allowedVars = ISet.union earlierVars (ISet.fromList checked)
if termVars `ISet.isSubsetOf` allowedVars then return () else do
let
illegalVars = rigid ISet.\\ allowedVars
-- flexVars = flexibleVars fv
-- blockingMetas = map (`lookupVarMap` flexVars) (ISet.toList $ termVars ISet.\\ allowedVars)
if ISet.null illegalVars then -- only flexible vars are infringing
-- TODO: be more precise about which metas
-- flexVars = flexibleVars fv
-- blockingMetas = map (`lookupVarMap` flexVars) (ISet.toList $ termVars ISet.\\ allowedVars)
patternViolation alwaysUnblock
else do
notAllowedVarsError (unArg lk) (ISet.toList illegalVars)
getLockVar :: Term -> TCMT IO (Maybe Int)
getLockVar lk = do
let
fv = freeVarsIgnore IgnoreInAnnotations lk
flex = flexibleVars fv
unless (IMap.null flex) $ do
let metas = Set.unions $ map (foldrMetaSet Set.insert Set.empty) $ IMap.elems flex
patternViolation $ unblockOnAnyMeta $ metas
is <- filterM isLock $ ISet.toList $ rigidVars fv
-- Out of the lock variables that appear in @lk@ the one in the
-- left-most position in the context is what will determine the
-- available context for the head.
let mi | Prelude.null is = Nothing
| otherwise = Just $ maximum is
return $ mi
where
isLock i = do
islock <- getLock . domInfo <$> lookupBV i
return $ islock == IsLock
isTimeless :: Type -> TCM Bool
isTimeless t = do
t <- abortIfBlocked t
timeless <- mapM getName' [builtinInterval, builtinIsOne]
case unEl t of
Def q _ | Just q `elem` timeless -> return True
_ -> return False
notAllowedVarsError :: Term -> [Int] -> TCM b
notAllowedVarsError lk is = do
typeError . GenericDocError =<<
("The following vars are not allowed in a later value applied to"
<+> prettyTCM lk <+> ":" <+> prettyTCM (map var $ is))
checkEarlierThan :: Term -> VarSet -> TCM ()
checkEarlierThan lk fvs = do
mv <- getLockVar lk
caseMaybe mv (return ()) $ \ i -> do
let problems = filter (<= i) $ VSet.toList fvs
forM_ problems $ \ j -> do
ty <- typeOfBV j
unlessM (isTimeless ty) $
notAllowedVarsError lk [j]