Agda-2.6.4: src/full/Agda/TypeChecking/Opacity.hs
{-# LANGUAGE NondecreasingIndentation #-}
module Agda.TypeChecking.Opacity
( saturateOpaqueBlocks
, isAccessibleDef
, hasAccessibleDef
)
where
import Control.Monad.State
import Control.Exception
import Control.DeepSeq
import Control.Monad
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Map.Strict as Map
import qualified Data.HashSet as HashSet
import qualified Data.List as List
import Data.HashMap.Strict (HashMap)
import Data.HashSet (HashSet)
import Data.Map.Strict (Map)
import Data.Foldable
import Data.Maybe
import qualified Agda.Syntax.Abstract as A
import Agda.Syntax.Internal
import Agda.Syntax.Position
import Agda.Syntax.Common
import Agda.TypeChecking.Warnings
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Monad
import Agda.Utils.Impossible
import Agda.Utils.Monad
import Agda.Utils.Lens
-- | Ensure that opaque blocks defined in the current module have
-- saturated unfolding sets.
saturateOpaqueBlocks
:: forall m. (MonadTCState m, ReadTCState m, MonadFresh OpaqueId m, MonadDebug m, MonadTrace m, MonadWarning m, MonadIO m)
=> [A.Declaration]
-> m ()
saturateOpaqueBlocks moddecs = entry where
entry = do
known <- useTC stOpaqueBlocks
inverse <- useTC stOpaqueIds
OpaqueId _ ourmod <- fresh
reportSDoc "tc.opaque.copy" 45 $ "Canonical names of copied definitions:" $+$ pretty (HashMap.toList canonical)
reportSDoc "tc.opaque.copy" 45 $ "Backcopies:" $+$ pretty (HashMap.toList (toList <$> backcopies))
let
isOurs (OpaqueId _ mod, _) = mod == ourmod
ours = snd <$> filter isOurs (Map.toAscList known)
unless (null ours) $ do
() <- liftIO $ evaluate (rnf (canonical, backcopies))
reportSDoc "tc.opaque" 30 $ vcat $
text "Opaque blocks defined in this module:":map pretty ours
-- Only compute transitive closure for opaque blocks declared in
-- the current top-level module. Deserialised blocks are always
-- closed, so this work would be redundant.
(blocks, names) <- computeClosure known inverse ours
-- Associate copies with the opaque blocks of their originals. Since
-- modules importing this one won't know how to canonicalise names
-- we have defined, we make the work easier for them by associating
-- copies with their original's opaque blocks.
let names' = foldr addBackcopy names (HashMap.toList backcopies)
reportSDoc "tc.opaque.sat" 30 $ vcat $
text "Saturated local opaque blocks":[ pretty block | b@(_,block) <- Map.toList blocks, isOurs b ]
reportSDoc "tc.opaque.sat.full" 50 $ text "Saturated opaque blocks:" $+$ pretty blocks
modifyTC' $ \st -> st { stPostScopeState = (stPostScopeState st)
{ stPostOpaqueBlocks = blocks
, stPostOpaqueIds = names'
} }
-- Actually compute the closure.
computeClosure
:: Map OpaqueId OpaqueBlock
-- Accumulates the satured opaque blocks; also contains the
-- opaque blocks of imported modules.
-> Map QName OpaqueId
-- Accumulates a mapping from names to opaque blocks; also
-- contains imported opaque names.
-> [OpaqueBlock]
-- List of our opaque blocks, in dependency order.
-> m ( Map OpaqueId OpaqueBlock
, Map QName OpaqueId
)
computeClosure !blocks !names [] = pure (blocks, names)
computeClosure blocks names (block:xs) = setCurrentRange (opaqueRange block) $ do
let
yell nm accum = setCurrentRange (getRange nm) $ do
warning (UnfoldTransparentName nm)
pure accum
-- Add the unfolding-set of the given name to the accumulator
-- value.
transitive prenom accum = fromMaybe (yell prenom accum) $ do
-- NB: If the name is a local copy, we won't yet have added the
-- copy name to an opaque block, but we will have added the
-- reduced name (provided it is opaque)
let nm = canonise prenom
id <- Map.lookup nm names
block <- Map.lookup id blocks
pure . pure $ HashSet.union (opaqueUnfolding block) accum
reportSDoc "tc.opaque.copy" 45 $
vcat [ "Stated unfolding clause: " <+> pretty (HashSet.toList (opaqueUnfolding block))
, "with (sub)canonical names:" <+> pretty (canonise <$> HashSet.toList (opaqueUnfolding block))
]
-- Compute the transitive closure: bring in names
--
-- ... that are defined as immediate children of the opaque block
-- ... that are unfolded by the parent opaque block
-- ... that are implied by each name in the unfolding clause.
closed <- foldrM transitive
( opaqueDecls block
<> foldMap opaqueUnfolding (opaqueParent block >>= flip Map.lookup blocks)
)
(opaqueUnfolding block)
let
block' = block { opaqueUnfolding = closed }
-- Update the mapping from names to blocks, so that future
-- references to names defined in our opaque block will know the
-- right unfolding set.
names' = HashSet.foldr (\name -> Map.insert name (opaqueId block)) names
(opaqueDecls block)
computeClosure (Map.insert (opaqueId block) block' blocks) names' xs
(canonical, backcopies) = invertDefCopies moddecs
canonise name = fromMaybe name (HashMap.lookup name canonical)
addBackcopy :: (QName, HashSet QName) -> Map QName OpaqueId -> Map QName OpaqueId
addBackcopy (from, prop) map
| Just id <- Map.lookup from map = foldr (flip Map.insert id) map prop
| otherwise = map
-- | Decide whether or not a definition is reducible. Returns 'True' if
-- the definition /can/ step.
isAccessibleDef :: TCEnv -> TCState -> Definition -> Bool
-- IgnoreAbstractMode ignores both abstract and opaque. It is used for
-- getting the original definition (for inConcreteOrAbstractMode), and
-- for "normalise ignoring abstract" interactively.
isAccessibleDef env state defn
| envAbstractMode env == IgnoreAbstractMode = True
-- Otherwise, actually apply the reducibility rules..
isAccessibleDef env state defn =
let
-- Reducibility rules for abstract definitions:
concretise def = case envAbstractMode env of
-- Being outside an abstract block has no effect on concreteness
ConcreteMode -> def
-- This clause is redundant with the preceding guard but GHC can't
-- figure it out:
IgnoreAbstractMode -> ConcreteDef
AbstractMode
-- Symbols from enclosing modules will be made concrete:
| current `isLeChildModuleOf` m -> ConcreteDef
-- Symbols from child modules, or unrelated modules, will keep
-- the same concreteness:
| otherwise -> def
where
current = dropAnon $ envCurrentModule env
m = dropAnon $ qnameModule (defName defn)
dropAnon (MName ms) = MName $ List.dropWhileEnd isNoName ms
-- Reducibility rule for opaque definitions: If we are operating
-- under an unfolding block,
clarify def = case envCurrentOpaqueId env of
Just oid ->
let
block = fromMaybe __IMPOSSIBLE__ $ Map.lookup oid (view stOpaqueBlocks state)
-- Then any name which is a member of the unfolding-set
-- associated to that block will be unfolded.
okay = defName defn `HashSet.member` opaqueUnfolding block
in if okay then TransparentDef else def
Nothing -> def
-- Short-circuit the map lookup for vanilla definitions
plainDef = defAbstract defn == ConcreteDef
&& defOpaque defn == TransparentDef
in plainDef
|| ( concretise (defAbstract defn) == ConcreteDef
&& clarify (defOpaque defn) == TransparentDef)
-- | Will the given 'QName' have a proper definition, or will it be
-- wrapped in an 'AbstractDefn'?
hasAccessibleDef
:: (ReadTCState m, MonadTCEnv m, HasConstInfo m) => QName -> m Bool
hasAccessibleDef qn = do
env <- askTC
st <- getTCState
ignoreAbstractMode $ do
def <- getConstInfo qn
pure $ isAccessibleDef env st def
type Invert = State (HashMap QName QName, HashMap QName (HashSet QName))
-- | Compute maps inverting the module applications defined in the given
-- declarations. The first returned map associates copied names to their
-- (hereditary) originals, the second map associates original names to
-- their (transitive) copies.
invertDefCopies
:: [A.Declaration]
-> ( HashMap QName QName
, HashMap QName (HashSet QName)
)
invertDefCopies = flip execState mempty . traverse_ go where
canon :: QName -> Invert QName
canon n = gets (HashMap.lookup n . fst) >>= \case
Just n' -> do
c <- canon n'
modify' $ \(canon, backrefs) -> (HashMap.insert n c canon, backrefs)
pure c
Nothing -> pure n
copy :: QName -> QName -> Invert ()
copy from to = do
from <- canon from
modify' $ \(canon, backrefs) ->
( HashMap.insert to from canon
, HashMap.alter (pure . HashSet.insert to . fold) from backrefs
)
go :: A.Declaration -> Invert ()
-- Interesting case:
go (A.Apply _mi _e _mn _app info _imp) =
forM_ (Map.toList (A.renNames info)) $ \(from, tos) -> traverse_ (copy from) tos
-- Traversal:
go (A.Mutual _ ds) = traverse_ go ds
go (A.Section _r _e _mn _gt ds) = traverse_ go ds
go (A.ScopedDecl _si ds) = traverse_ go ds
go (A.RecDef _di _qn _uc _rd _ddp _t ds) = traverse_ go ds
-- Boring:
go A.Axiom{} = pure ()
go A.Generalize{} = pure ()
go A.Field{} = pure ()
go A.Primitive{} = pure ()
go A.Import{} = pure ()
go A.Pragma{} = pure ()
go A.Open{} = pure ()
go A.FunDef{} = pure ()
go A.DataSig{} = pure ()
go A.DataDef{} = pure ()
go A.RecSig{} = pure ()
go A.PatternSynDef{} = pure ()
go A.UnquoteDecl{} = pure ()
go A.UnquoteDef{} = pure ()
go A.UnquoteData{} = pure ()
go A.UnfoldingDecl{} = pure ()