{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NondecreasingIndentation #-}
module Agda.TypeChecking.Rewriting.Confluence ( checkConfluenceOfRules , checkConfluenceOfClause ) where
import Control.Applicative
import Control.Monad
import Control.Monad.Reader
import Data.Function ( on )
import Data.Functor ( ($>) )
import Data.List ( elemIndex , tails )
import qualified Data.Set as Set
import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.Syntax.Internal.MetaVars
import Agda.TypeChecking.Constraints
import Agda.TypeChecking.Conversion
import Agda.TypeChecking.Datatypes
import Agda.TypeChecking.Free
import Agda.TypeChecking.Irrelevance ( workOnTypes )
import Agda.TypeChecking.Level
import Agda.TypeChecking.MetaVars
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Monad.Builtin
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Pretty.Warning
import Agda.TypeChecking.Records
import Agda.TypeChecking.Reduce
import Agda.TypeChecking.Rewriting.Clause
import Agda.TypeChecking.Rewriting.NonLinPattern
import Agda.TypeChecking.Sort
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Telescope
import Agda.TypeChecking.Warnings
import Agda.Utils.Except
import Agda.Utils.Impossible
import Agda.Utils.List
import Agda.Utils.ListT
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.Singleton
import Agda.Utils.Size
checkConfluenceOfRules :: [RewriteRule] -> TCM ()
checkConfluenceOfRules = checkConfluenceOfRules' False
checkConfluenceOfClause :: QName -> Int -> Clause -> TCM ()
checkConfluenceOfClause f i cl = do
fi <- clauseQName f i
whenJust (clauseToRewriteRule f fi cl) $ \rew -> do
checkConfluenceOfRules' True [rew]
let matchables = getMatchables rew
reportSDoc "rewriting.confluence" 30 $
"Function" <+> prettyTCM f <+> "has matchable symbols" <+> prettyList_ (map prettyTCM matchables)
modifySignature $ setMatchableSymbols f matchables
checkConfluenceOfRules' :: Bool -> [RewriteRule] -> TCM ()
checkConfluenceOfRules' isClause rews = inTopContext $ inAbstractMode $ do
forM_ (tails rews) $ listCase (return ()) $ \rew rewsRest -> do
reportSDoc "rewriting.confluence" 10 $
"Checking confluence of rule" <+> prettyTCM (rewName rew)
let f = rewHead rew
qs = rewPats rew
tel = rewContext rew
def <- getConstInfo f
(fa , hdf) <- addContext tel $ makeHead def (rewType rew)
forMM_ (getRulesFor f isClause) $ \ rew' ->
unless (any (sameName rew') $ rew:rewsRest) $
checkConfluenceTop hdf rew rew'
es <- nlPatToTerm qs
forMM_ (addContext tel $ allHolesList (fa, hdf) es) $ \ hole ->
caseMaybe (headView $ ohContents hole) __IMPOSSIBLE__ $ \ (g , hdg , _) -> do
forMM_ (getRulesFor g isClause) $ \rew' ->
unless (any (sameName rew') rewsRest) $
checkConfluenceSub hdf hdg rew rew' hole
forM_ (defMatchable def) $ \ g -> do
forMM_ (getClausesAndRewriteRulesFor g) $ \ rew' -> do
unless (any (sameName rew') rewsRest) $ do
es' <- nlPatToTerm (rewPats rew')
let tel' = rewContext rew'
def' <- getConstInfo g
(ga , hdg) <- addContext tel' $ makeHead def' (rewType rew')
forMM_ (addContext tel' $ allHolesList (ga , hdg) es') $ \ hole ->
caseMaybe (headView $ ohContents hole) __IMPOSSIBLE__ $ \ (f' , _ , _) ->
when (f == f') $ checkConfluenceSub hdg hdf rew' rew hole
where
sameName = (==) `on` rewName
checkConfluenceTop :: (Elims -> Term) -> RewriteRule -> RewriteRule -> TCM ()
checkConfluenceTop hd rew1 rew2 =
traceCall (CheckConfluence (rewName rew1) (rewName rew2)) $
localTCStateSavingWarnings $ do
sub1 <- makeMetaSubst $ rewContext rew1
sub2 <- makeMetaSubst $ rewContext rew2
let f = rewHead rew1
a1 = applySubst sub1 $ rewType rew1
a2 = applySubst sub2 $ rewType rew2
es1 <- applySubst sub1 <$> nlPatToTerm (rewPats rew1)
es2 <- applySubst sub2 <$> nlPatToTerm (rewPats rew2)
let n = min (size es1) (size es2)
(es1' , es1r) = splitAt n es1
(es2' , es2r) = splitAt n es2
lhs1 = hd $ es1' ++ es2r
lhs2 = hd $ es2' ++ es1r
a | null es1r = a2
| otherwise = a1
reportSDoc "rewriting.confluence" 20 $ sep
[ "Considering potential critical pair at top-level: "
, nest 2 $ prettyTCM $ lhs1, " =?= "
, nest 2 $ prettyTCM $ lhs2 , " : " , nest 2 $ prettyTCM a
]
maybeCriticalPair <- tryUnification lhs1 lhs2 $ do
fa <- defType <$> getConstInfo f
fpol <- getPolarity' CmpEq f
onlyReduceTypes $
compareElims fpol [] fa (hd []) es1' es2'
let rhs1 = applySubst sub1 (rewRHS rew1) `applyE` es2r
rhs2 = applySubst sub2 (rewRHS rew2) `applyE` es1r
return (rhs1 , rhs2)
whenJust maybeCriticalPair $ \ (rhs1 , rhs2) ->
checkCriticalPair a hd (es1' ++ es2r) rhs1 rhs2
checkConfluenceSub :: (Elims -> Term) -> (Elims -> Term) -> RewriteRule -> RewriteRule -> OneHole Elims -> TCM ()
checkConfluenceSub hdf hdg rew1 rew2 hole0 =
traceCall (CheckConfluence (rewName rew1) (rewName rew2)) $
localTCStateSavingWarnings $ do
sub1 <- makeMetaSubst $ rewContext rew1
let f = rewHead rew1
bvTel0 = ohBoundVars hole0
k = size bvTel0
b0 = applySubst (liftS k sub1) $ ohType hole0
(g,_,es0) = fromMaybe __IMPOSSIBLE__ $ headView $
applySubst (liftS k sub1) $ ohContents hole0
qs2 = rewPats rew2
hole1 <- addContext bvTel0 $
forceEtaExpansion b0 (hdg es0) $ drop (size es0) qs2
verboseS "rewriting.confluence.eta" 30 $
unless (size es0 == size qs2) $
addContext bvTel0 $
reportSDoc "rewriting.confluence.eta" 30 $ vcat
[ "forceEtaExpansion result:"
, nest 2 $ "bound vars: " <+> prettyTCM (ohBoundVars hole1)
, nest 2 $ "hole contents: " <+> addContext (ohBoundVars hole1) (prettyTCM $ ohContents hole1)
]
let hole = hole1 `composeHole` hole0
(g,_,es') = fromMaybe __IMPOSSIBLE__ $ headView $ ohContents hole
bvTel = ohBoundVars hole
plug = ohPlugHole hole
sub2 <- addContext bvTel $ makeMetaSubst $ rewContext rew2
let es1 = applySubst (liftS (size bvTel) sub1) es'
es2 <- applySubst sub2 <$> nlPatToTerm (rewPats rew2)
when (size es1 < size es2) __IMPOSSIBLE__
let n = size es2
(es1' , es1r) = splitAt n es1
let lhs1 = applySubst sub1 $ hdf $ plug $ hdg es1
lhs2 = applySubst sub1 $ hdf $ plug $ hdg $ es2 ++ es1r
a = applySubst sub1 $ rewType rew1
reportSDoc "rewriting.confluence" 20 $ sep
[ "Considering potential critical pair at subpattern: "
, nest 2 $ prettyTCM $ lhs1 , " =?= "
, nest 2 $ prettyTCM $ lhs2 , " : " , nest 2 $ prettyTCM a
]
maybeCriticalPair <- tryUnification lhs1 lhs2 $ do
ga <- defType <$> getConstInfo g
gpol <- getPolarity' CmpEq g
onlyReduceTypes $ addContext bvTel $
compareElims gpol [] ga (hdg []) es1' es2
let rhs1 = applySubst sub1 $ rewRHS rew1
let w = applySubst sub2 (rewRHS rew2) `applyE` es1r
reportSDoc "rewriting.confluence" 30 $ sep
[ "Plugging hole with w = "
, nest 2 $ addContext bvTel $ prettyTCM w
]
let rhs2 = applySubst sub1 $ hdf $ plug w
return (rhs1 , rhs2)
whenJust maybeCriticalPair $ \ (rhs1 , rhs2) ->
checkCriticalPair a hdf (applySubst sub1 $ plug $ hdg es1) rhs1 rhs2
headView :: Term -> Maybe (QName, Elims -> Term, Elims)
headView (Def f es) = Just (f , Def f , es)
headView (Con c ci es) = Just (conName c , Con c ci , es)
headView _ = Nothing
makeHead :: Definition -> Type -> TCM (Type , Elims -> Term)
makeHead def a = case theDef def of
Constructor{ conSrcCon = ch } -> do
ca <- snd . fromMaybe __IMPOSSIBLE__ <$> getFullyAppliedConType ch a
return (ca , Con ch ConOSystem)
Function { funProjection = Just proj } -> do
let f = projOrig proj
r = unArg $ projFromType proj
rtype <- defType <$> getConstInfo r
TelV ptel _ <- telView rtype
n <- getContextSize
let pars :: Args
pars = raise (n - size ptel) $ teleArgs ptel
ftype <- defType def `piApplyM` pars
return (ftype , Def f)
_ -> return (defType def , Def $ defName def)
getClausesAndRewriteRulesFor :: QName -> TCM [RewriteRule]
getClausesAndRewriteRulesFor f =
(++) <$> getClausesAsRewriteRules f <*> getRewriteRulesFor f
getRulesFor :: QName -> Bool -> TCM [RewriteRule]
getRulesFor f isClause
| isClause = getRewriteRulesFor f
| otherwise = getClausesAndRewriteRulesFor f
makeMetaSubst :: (MonadMetaSolver m) => Telescope -> m Substitution
makeMetaSubst gamma = parallelS . reverse . map unArg <$> newTelMeta gamma
tryUnification :: Term -> Term -> TCM a -> TCM (Maybe a)
tryUnification lhs1 lhs2 f = (Just <$> f)
`catchError` \case
err@TypeError{} -> do
reportSDoc "rewriting.confluence" 20 $ vcat
[ "Unification failed with error: "
, nest 2 $ prettyTCM err
]
return Nothing
err -> throwError err
`ifNoConstraints` return $ \pid _ -> do
cs <- getConstraintsForProblem pid
prettyCs <- prettyInterestingConstraints cs
warning $ RewriteMaybeNonConfluent lhs1 lhs2 prettyCs
return Nothing
checkCriticalPair
:: Type
-> (Elims -> Term)
-> Elims
-> Term
-> Term
-> TCM ()
checkCriticalPair a hd es rhs1 rhs2 = do
(a,es,rhs1,rhs2) <- instantiateFull (a,es,rhs1,rhs2)
let ms = Set.toList $ allMetas singleton $ (a,es,rhs1,rhs2)
reportSDoc "rewriting.confluence" 30 $ sep
[ "Abstracting over metas: "
, prettyList_ (map (text . show) ms)
]
(gamma , (a,es,rhs1,rhs2)) <- fromMaybe __IMPOSSIBLE__ <$>
abstractOverMetas ms (a,es,rhs1,rhs2)
addContext gamma $ reportSDoc "rewriting.confluence" 10 $ sep
[ "Found critical pair: " , nest 2 $ prettyTCM rhs1
, " =?= " , nest 2 $ prettyTCM rhs2
, " : " , nest 2 $ prettyTCM a ]
addContext gamma $ do
dontAssignMetas $ noConstraints $ equalTerm a rhs1 rhs2
`catchError` \case
TypeError s err -> do
prettyErr <- withTCState (const s) $ prettyTCM err
warning $ RewriteNonConfluent (hd es) rhs1 rhs2 prettyErr
err -> throwError err
abstractOverMetas :: (MetasToVars a) => [MetaId] -> a -> TCM (Maybe (Telescope, a))
abstractOverMetas ms x = do
forMM (dependencySortMetas ms) $ \ms' -> do
as <- forM ms' getMetaType
ns <- forM ms' getMetaNameSuggestion
let gamma = unflattenTel ns $ map defaultDom as
let n = size ms'
metaIndex x = (n-1-) <$> elemIndex x ms'
runReaderT (metasToVars (gamma, x)) metaIndex
data OneHole a = OneHole
{ ohBoundVars :: Telescope
, ohType :: Type
, ohPlugHole :: Term -> a
, ohContents :: Term
} deriving (Functor)
idHole :: Type -> Term -> OneHole Term
idHole a v = OneHole EmptyTel a id v
composeHole :: OneHole Term -> OneHole a -> OneHole a
composeHole inner outer = OneHole
{ ohBoundVars = ohBoundVars outer `abstract` ohBoundVars inner
, ohType = ohType inner
, ohPlugHole = ohPlugHole outer . ohPlugHole inner
, ohContents = ohContents inner
}
ohAddBV :: ArgName -> Dom Type -> OneHole a -> OneHole a
ohAddBV x a oh = oh { ohBoundVars = ExtendTel a $ Abs x $ ohBoundVars oh }
class (Subst Term p , Free p) => AllHoles p where
type PType p
allHoles :: (Alternative m , MonadReduce m, MonadAddContext m, HasBuiltins m, HasConstInfo m)
=> PType p -> p -> m (OneHole p)
allHoles_
:: ( Alternative m , MonadReduce m, MonadAddContext m, HasBuiltins m, HasConstInfo m, MonadDebug m
, AllHoles p , PType p ~ () )
=> p -> m (OneHole p)
allHoles_ = allHoles ()
allHolesList
:: ( MonadReduce m, MonadAddContext m, HasBuiltins m, HasConstInfo m , AllHoles p)
=> PType p -> p -> m [OneHole p]
allHolesList a = sequenceListT . allHoles a
forceEtaExpansion :: Type -> Term -> [Elim' a] -> TCM (OneHole Term)
forceEtaExpansion a v [] = return $ idHole a v
forceEtaExpansion a v (e:es) = case e of
Apply (Arg i w) -> do
reportSDoc "rewriting.confluence.eta" 40 $ fsep
[ "Forcing" , prettyTCM v , ":" , prettyTCM a , "to take one more argument" ]
dom <- defaultArgDom i <$> newTypeMeta_
cod <- addContext dom $ newTypeMeta_
equalType a $ mkPi (("x",) <$> dom) cod
let body = raise 1 v `apply` [Arg i $ var 0]
addContext dom $ ohAddBV "x" dom . fmap (Lam i . mkAbs "x") <$>
forceEtaExpansion cod body es
Proj o f -> do
reportSDoc "rewriting.confluence.eta" 40 $ fsep
[ "Forcing" , prettyTCM v , ":" , prettyTCM a , "to be projectible by" , prettyTCM f ]
r <- fromMaybe __IMPOSSIBLE__ <$> getRecordOfField f
rdef <- getConstInfo r
let ra = defType rdef
pars <- newArgsMeta ra
s <- ra `piApplyM` pars >>= \s -> ifIsSort s return __IMPOSSIBLE__
equalType a $ El s (Def r $ map Apply pars)
(_ , c , ci , fields) <- etaExpandRecord_ r pars (theDef rdef) v
let fs = map argFromDom $ recFields $ theDef rdef
i = fromMaybe __IMPOSSIBLE__ $ elemIndex f $ map unArg fs
fContent = unArg $ fromMaybe __IMPOSSIBLE__ $ fields !!! i
fUpdate w = Con c ci $ map Apply $ updateAt i (w <$) fields
~(Just (El _ (Pi b c))) <- getDefType f =<< reduce a
let fa = c `absApp` v
fmap fUpdate <$> forceEtaExpansion fa fContent es
IApply{} -> __IMPOSSIBLE__
instance AllHoles p => AllHoles (Arg p) where
type PType (Arg p) = Dom (PType p)
allHoles a x = fmap (x $>) <$> allHoles (unDom a) (unArg x)
instance AllHoles p => AllHoles (Dom p) where
type PType (Dom p) = PType p
allHoles a x = fmap (x $>) <$> allHoles a (unDom x)
instance AllHoles (Abs Term) where
type PType (Abs Term) = (Dom Type , Abs Type)
allHoles (dom , a) x = addContext (absName x , dom) $
ohAddBV (absName a) dom . fmap (mkAbs $ absName x) <$>
allHoles (absBody a) (absBody x)
instance AllHoles (Abs Type) where
type PType (Abs Type) = Dom Type
allHoles dom a = addContext (absName a , dom) $
ohAddBV (absName a) dom . fmap (mkAbs $ absName a) <$>
allHoles_ (absBody a)
instance AllHoles Elims where
type PType Elims = (Type , Elims -> Term)
allHoles (a,hd) [] = empty
allHoles (a,hd) (e:es) = do
reportSDoc "rewriting.confluence.hole" 65 $ fsep
[ "Head symbol" , prettyTCM (hd []) , ":" , prettyTCM a
, "is eliminated by" , prettyTCM e
]
case e of
Apply x -> do
~(Pi b c) <- unEl <$> reduce a
let a' = c `absApp` unArg x
hd' = hd . (e:)
(fmap ((:es) . Apply) <$> allHoles b x)
<|> (fmap (e:) <$> allHoles (a' , hd') es)
Proj o f -> do
~(Just (El _ (Pi b c))) <- getDefType f =<< reduce a
let a' = c `absApp` hd []
hd' <- applyE <$> applyDef o f (argFromDom b $> hd [])
fmap (e:) <$> allHoles (a' , hd') es
IApply x y u -> __IMPOSSIBLE__
instance AllHoles Type where
type PType Type = ()
allHoles _ (El s a) = workOnTypes $
fmap (El s) <$> allHoles (sort s) a
instance AllHoles Term where
type PType Term = Type
allHoles a u = do
reportSDoc "rewriting.confluence.hole" 60 $ fsep
[ "Getting holes of term" , prettyTCM u , ":" , prettyTCM a ]
case u of
Var i es -> do
ai <- typeOfBV i
fmap (Var i) <$> allHoles (ai , Var i) es
Lam i u -> do
~(Pi b c) <- unEl <$> reduce a
fmap (Lam i) <$> allHoles (b,c) u
Lit l -> empty
v@(Def f es) -> do
fa <- defType <$> getConstInfo f
pure (idHole a v)
<|> (fmap (Def f) <$> allHoles (fa , Def f) es)
v@(Con c ci es) -> do
ca <- snd . fromMaybe __IMPOSSIBLE__ <$> do
getFullyAppliedConType c =<< reduce a
pure (idHole a v)
<|> (fmap (Con c ci) <$> allHoles (ca , Con c ci) es)
Pi a b ->
(fmap (\a -> Pi a b) <$> allHoles_ a) <|>
(fmap (\b -> Pi a b) <$> allHoles a b)
Sort s -> fmap Sort <$> allHoles_ s
Level l -> fmap Level <$> allHoles_ l
MetaV{} -> __IMPOSSIBLE__
DontCare{} -> empty
Dummy{} -> empty
instance AllHoles Sort where
type PType Sort = ()
allHoles _ = \case
Type l -> fmap Type <$> allHoles_ l
Prop l -> fmap Prop <$> allHoles_ l
Inf -> empty
SizeUniv -> empty
PiSort{} -> __IMPOSSIBLE__
FunSort{} -> __IMPOSSIBLE__
UnivSort{} -> __IMPOSSIBLE__
MetaS{} -> __IMPOSSIBLE__
DefS f es -> do
fa <- defType <$> getConstInfo f
fmap (DefS f) <$> allHoles (fa , Def f) es
DummyS{} -> empty
instance AllHoles Level where
type PType Level = ()
allHoles _ (Max n ls) = fmap (Max n) <$> allHoles_ ls
instance AllHoles [PlusLevel] where
type PType [PlusLevel] = ()
allHoles _ [] = empty
allHoles _ (l:ls) =
(fmap (:ls) <$> allHoles_ l)
<|> (fmap (l:) <$> allHoles_ ls)
instance AllHoles PlusLevel where
type PType PlusLevel = ()
allHoles _ (Plus n l) = fmap (Plus n) <$> allHoles_ l
instance AllHoles LevelAtom where
type PType LevelAtom = ()
allHoles _ l = do
la <- levelType
case l of
MetaLevel{} -> __IMPOSSIBLE__
BlockedLevel{} -> __IMPOSSIBLE__
NeutralLevel b u -> fmap (NeutralLevel b) <$> allHoles la u
UnreducedLevel u -> fmap UnreducedLevel <$> allHoles la u
class MetasToVars a where
metasToVars
:: (MonadReader (MetaId -> Maybe Nat) m , HasBuiltins m)
=> a -> m a
default metasToVars
:: ( MetasToVars a', Traversable f, a ~ f a'
, MonadReader (MetaId -> Maybe Nat) m , HasBuiltins m)
=> a -> m a
metasToVars = traverse metasToVars
instance MetasToVars a => MetasToVars [a] where
instance MetasToVars a => MetasToVars (Arg a) where
instance MetasToVars a => MetasToVars (Dom a) where
instance MetasToVars a => MetasToVars (Elim' a) where
instance MetasToVars a => MetasToVars (Abs a) where
metasToVars (Abs i x) = Abs i <$> local (fmap succ .) (metasToVars x)
metasToVars (NoAbs i x) = NoAbs i <$> metasToVars x
instance MetasToVars Term where
metasToVars = \case
Var i es -> Var i <$> metasToVars es
Lam i u -> Lam i <$> metasToVars u
Lit l -> Lit <$> pure l
Def f es -> Def f <$> metasToVars es
Con c i es -> Con c i <$> metasToVars es
Pi a b -> Pi <$> metasToVars a <*> metasToVars b
Sort s -> Sort <$> metasToVars s
Level l -> Level <$> metasToVars l
MetaV x es -> ($ x) <$> ask >>= \case
Just i -> Var i <$> metasToVars es
Nothing -> MetaV x <$> metasToVars es
DontCare u -> DontCare <$> metasToVars u
Dummy s es -> Dummy s <$> metasToVars es
instance MetasToVars Type where
metasToVars (El s t) = El <$> metasToVars s <*> metasToVars t
instance MetasToVars Sort where
metasToVars = \case
Type l -> Type <$> metasToVars l
Prop l -> Prop <$> metasToVars l
Inf -> pure Inf
SizeUniv -> pure SizeUniv
PiSort s t -> PiSort <$> metasToVars s <*> metasToVars t
FunSort s t -> FunSort <$> metasToVars s <*> metasToVars t
UnivSort s -> UnivSort <$> metasToVars s
MetaS x es -> MetaS x <$> metasToVars es
DefS f es -> DefS f <$> metasToVars es
DummyS s -> pure $ DummyS s
instance MetasToVars Level where
metasToVars (Max n ls) = Max n <$> metasToVars ls
instance MetasToVars PlusLevel where
metasToVars (Plus n x) = Plus n <$> metasToVars x
instance MetasToVars LevelAtom where
metasToVars = \case
MetaLevel m es -> NeutralLevel mempty <$> metasToVars (MetaV m es)
BlockedLevel _ u -> UnreducedLevel <$> metasToVars u
NeutralLevel nb u -> NeutralLevel nb <$> metasToVars u
UnreducedLevel u -> UnreducedLevel <$> metasToVars u
instance MetasToVars a => MetasToVars (Tele a) where
metasToVars EmptyTel = pure EmptyTel
metasToVars (ExtendTel a tel) = ExtendTel <$> metasToVars a <*> metasToVars tel
instance (MetasToVars a, MetasToVars b) => MetasToVars (a,b) where
metasToVars (x,y) = (,) <$> metasToVars x <*> metasToVars y
instance (MetasToVars a, MetasToVars b, MetasToVars c) => MetasToVars (a,b,c) where
metasToVars (x,y,z) = (,,) <$> metasToVars x <*> metasToVars y <*> metasToVars z
instance (MetasToVars a, MetasToVars b, MetasToVars c, MetasToVars d) => MetasToVars (a,b,c,d) where
metasToVars (x,y,z,w) = (,,,) <$> metasToVars x <*> metasToVars y <*> metasToVars z <*> metasToVars w