module Agda.Syntax.Internal.Blockers where import Control.DeepSeq import Data.Data (Data) import Data.Set (Set) import qualified Data.Set as Set import Data.Semigroup import GHC.Generics (Generic) import Agda.Syntax.Common import Agda.Syntax.Internal.Elim import Agda.Utils.Pretty hiding ((<>)) import Agda.Utils.Functor --------------------------------------------------------------------------- -- * Blocked Terms --------------------------------------------------------------------------- -- | Even if we are not stuck on a meta during reduction -- we can fail to reduce a definition by pattern matching -- for another reason. data NotBlocked' t = StuckOn (Elim' t) -- ^ The 'Elim' is neutral and blocks a pattern match. | Underapplied -- ^ Not enough arguments were supplied to complete the matching. | AbsurdMatch -- ^ We matched an absurd clause, results in a neutral 'Def'. | MissingClauses -- ^ We ran out of clauses, all considered clauses -- produced an actual mismatch. -- This can happen when try to reduce a function application -- but we are still missing some function clauses. -- See "Agda.TypeChecking.Patterns.Match". | ReallyNotBlocked -- ^ Reduction was not blocked, we reached a whnf -- which can be anything but a stuck @'Def'@. deriving (Show, Data, Generic) -- | 'ReallyNotBlocked' is the unit. -- 'MissingClauses' is dominant. -- @'StuckOn'{}@ should be propagated, if tied, we take the left. instance Semigroup (NotBlocked' t) where ReallyNotBlocked <> b = b -- MissingClauses is dominant (absorptive) b@MissingClauses <> _ = b _ <> b@MissingClauses = b -- StuckOn is second strongest b@StuckOn{} <> _ = b _ <> b@StuckOn{} = b b <> _ = b instance Monoid (NotBlocked' t) where -- ReallyNotBlocked is neutral mempty = ReallyNotBlocked mappend = (<>) instance NFData t => NFData (NotBlocked' t) -- | What is causing the blocking? Or in other words which metas or problems need to be solved to -- unblock the blocked computation/constraint. data Blocker = UnblockOnAll (Set Blocker) | UnblockOnAny (Set Blocker) | UnblockOnMeta MetaId -- ^ Unblock if meta is instantiated | UnblockOnProblem ProblemId deriving (Data, Show, Eq, Ord, Generic) instance NFData Blocker alwaysUnblock :: Blocker alwaysUnblock = UnblockOnAll Set.empty neverUnblock :: Blocker neverUnblock = UnblockOnAny Set.empty unblockOnAll :: Set Blocker -> Blocker unblockOnAll us = case allViewS us of us | [u] <- Set.toList us -> u us -> UnblockOnAll us where allViewS = Set.unions . map allView . Set.toList allView (UnblockOnAll us) = allViewS us allView u = Set.singleton u unblockOnAny :: Set Blocker -> Blocker unblockOnAny us = case anyViewS us of us | [u] <- Set.toList us -> u us | Set.member alwaysUnblock us -> alwaysUnblock | otherwise -> UnblockOnAny us where anyViewS = Set.unions . map anyView . Set.toList anyView (UnblockOnAny us) = anyViewS us anyView u = Set.singleton u unblockOnEither :: Blocker -> Blocker -> Blocker unblockOnEither a b = unblockOnAny $ Set.fromList [a, b] unblockOnMeta :: MetaId -> Blocker unblockOnMeta = UnblockOnMeta unblockOnProblem :: ProblemId -> Blocker unblockOnProblem = UnblockOnProblem unblockOnAllMetas :: Set MetaId -> Blocker unblockOnAllMetas = unblockOnAll . Set.mapMonotonic unblockOnMeta unblockOnAnyMeta :: Set MetaId -> Blocker unblockOnAnyMeta = unblockOnAny . Set.mapMonotonic unblockOnMeta onBlockingMetasM :: Monad m => (MetaId -> m Blocker) -> Blocker -> m Blocker onBlockingMetasM f (UnblockOnAll bs) = unblockOnAll . Set.fromList <$> mapM (onBlockingMetasM f) (Set.toList bs) onBlockingMetasM f (UnblockOnAny bs) = unblockOnAny . Set.fromList <$> mapM (onBlockingMetasM f) (Set.toList bs) onBlockingMetasM f (UnblockOnMeta x) = f x onBlockingMetasM f b@UnblockOnProblem{} = pure b allBlockingMetas :: Blocker -> Set MetaId allBlockingMetas (UnblockOnAll us) = Set.unions $ map allBlockingMetas $ Set.toList us allBlockingMetas (UnblockOnAny us) = Set.unions $ map allBlockingMetas $ Set.toList us allBlockingMetas (UnblockOnMeta x) = Set.singleton x allBlockingMetas UnblockOnProblem{} = Set.empty allBlockingProblems :: Blocker -> Set ProblemId allBlockingProblems (UnblockOnAll us) = Set.unions $ map allBlockingProblems $ Set.toList us allBlockingProblems (UnblockOnAny us) = Set.unions $ map allBlockingProblems $ Set.toList us allBlockingProblems UnblockOnMeta{} = Set.empty allBlockingProblems (UnblockOnProblem p) = Set.singleton p -- Note: We pick the All rather than the Any as the semigroup instance. instance Semigroup Blocker where x <> y = unblockOnAll $ Set.fromList [x, y] instance Monoid Blocker where mempty = alwaysUnblock mappend = (<>) instance Pretty Blocker where pretty (UnblockOnAll us) = "all" <> parens (fsep $ punctuate "," $ map pretty $ Set.toList us) pretty (UnblockOnAny us) = "any" <> parens (fsep $ punctuate "," $ map pretty $ Set.toList us) pretty (UnblockOnMeta m) = pretty m pretty (UnblockOnProblem pid) = "problem" <+> pretty pid -- | Something where a meta variable may block reduction. Notably a top-level meta is considered -- blocking. This did not use to be the case (pre Aug 2020). data Blocked' t a = Blocked { theBlocker :: Blocker, ignoreBlocking :: a } | NotBlocked { blockingStatus :: NotBlocked' t, ignoreBlocking :: a } deriving (Data, Show, Functor, Foldable, Traversable, Generic) instance Decoration (Blocked' t) where traverseF f (Blocked b x) = Blocked b <$> f x traverseF f (NotBlocked nb x) = NotBlocked nb <$> f x -- | Blocking on _all_ blockers. instance Applicative (Blocked' t) where pure = notBlocked f <*> e = ((f $> ()) `mappend` (e $> ())) $> ignoreBlocking f (ignoreBlocking e) instance Semigroup a => Semigroup (Blocked' t a) where Blocked x a <> Blocked y b = Blocked (x <> y) (a <> b) b@Blocked{} <> NotBlocked{} = b NotBlocked{} <> b@Blocked{} = b NotBlocked x a <> NotBlocked y b = NotBlocked (x <> y) (a <> b) instance (Semigroup a, Monoid a) => Monoid (Blocked' t a) where mempty = notBlocked mempty mappend = (<>) instance (NFData t, NFData a) => NFData (Blocked' t a) -- | When trying to reduce @f es@, on match failed on one -- elimination @e ∈ es@ that came with info @r :: NotBlocked@. -- @stuckOn e r@ produces the new @NotBlocked@ info. -- -- 'MissingClauses' must be propagated, as this is blockage -- that can be lifted in the future (as more clauses are added). -- -- @'StuckOn' e0@ is also propagated, since it provides more -- precise information as @StuckOn e@ (as @e0@ is the original -- reason why reduction got stuck and usually a subterm of @e@). -- An information like @StuckOn (Apply (Arg info (Var i [])))@ -- (stuck on a variable) could be used by the lhs/coverage checker -- to trigger a split on that (pattern) variable. -- -- In the remaining cases for @r@, we are terminally stuck -- due to @StuckOn e@. Propagating @'AbsurdMatch'@ does not -- seem useful. -- -- 'Underapplied' must not be propagated, as this would mean -- that @f es@ is underapplied, which is not the case (it is stuck). -- Note that 'Underapplied' can only arise when projection patterns were -- missing to complete the original match (in @e@). -- (Missing ordinary pattern would mean the @e@ is of function type, -- but we cannot match against something of function type.) stuckOn :: Elim' t -> NotBlocked' t -> NotBlocked' t stuckOn e r = case r of MissingClauses -> r StuckOn{} -> r Underapplied -> r' AbsurdMatch -> r' ReallyNotBlocked -> r' where r' = StuckOn e --------------------------------------------------------------------------- -- * Handling blocked terms. --------------------------------------------------------------------------- blockedOn :: Blocker -> a -> Blocked' t a blockedOn b | alwaysUnblock == b = notBlocked | otherwise = Blocked b blocked :: MetaId -> a -> Blocked' t a blocked = Blocked . unblockOnMeta notBlocked :: a -> Blocked' t a notBlocked = NotBlocked ReallyNotBlocked blocked_ :: MetaId -> Blocked' t () blocked_ x = blocked x () notBlocked_ :: Blocked' t () notBlocked_ = notBlocked () ----------------------------------------------------------------------------- -- * Waking up logic ----------------------------------------------------------------------------- -- | Should a constraint wake up or not? If not, we might refine the unblocker. data WakeUp = WakeUp | DontWakeUp (Maybe Blocker) deriving (Show, Eq) wakeUpWhen :: (constr -> Bool) -> (constr -> WakeUp) -> constr -> WakeUp wakeUpWhen guard wake c | guard c = wake c | otherwise = DontWakeUp Nothing wakeUpWhen_ :: (constr -> Bool) -> constr -> WakeUp wakeUpWhen_ p = wakeUpWhen p (const WakeUp) wakeIfBlockedOnProblem :: ProblemId -> Blocker -> WakeUp wakeIfBlockedOnProblem pid u | u' == alwaysUnblock = WakeUp | otherwise = DontWakeUp (Just u') where u' = unblockProblem pid u wakeIfBlockedOnMeta :: MetaId -> Blocker -> WakeUp wakeIfBlockedOnMeta x u | u' == alwaysUnblock = WakeUp | otherwise = DontWakeUp (Just u') where u' = unblockMeta x u unblockMeta :: MetaId -> Blocker -> Blocker unblockMeta x u@(UnblockOnMeta y) | x == y = alwaysUnblock | otherwise = u unblockMeta _ u@UnblockOnProblem{} = u unblockMeta x (UnblockOnAll us) = unblockOnAll $ Set.map (unblockMeta x) us unblockMeta x (UnblockOnAny us) = unblockOnAny $ Set.map (unblockMeta x) us unblockProblem :: ProblemId -> Blocker -> Blocker unblockProblem p u@(UnblockOnProblem q) | p == q = alwaysUnblock | otherwise = u unblockProblem _ u@UnblockOnMeta{} = u unblockProblem p (UnblockOnAll us) = unblockOnAll $ Set.map (unblockProblem p) us unblockProblem p (UnblockOnAny us) = unblockOnAny $ Set.map (unblockProblem p) us