{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PatternGuards #-} #if __GLASGOW_HASKELL__ >= 710 {-# LANGUAGE FlexibleContexts #-} #endif module Agda.TypeChecking.ProjectionLike where import Control.Monad import qualified Data.Map as Map import Agda.Syntax.Abstract.Name import Agda.Syntax.Common import Agda.Syntax.Internal import Agda.Syntax.Internal.Pattern import Agda.TypeChecking.Monad import Agda.TypeChecking.Free (isBinderUsed) import Agda.TypeChecking.Substitute import Agda.TypeChecking.Positivity import Agda.TypeChecking.Pretty import Agda.TypeChecking.Reduce (reduce) import Agda.TypeChecking.DropArgs import Agda.Utils.Maybe import Agda.Utils.Monad import Agda.Utils.Size import Agda.Utils.Permutation #include "undefined.h" import Agda.Utils.Impossible -- | View for a @Def f (Apply a : es)@ where @isProjection f@. -- Used for projection-like @f@s. data ProjectionView = ProjectionView { projViewProj :: QName , projViewSelf :: Arg Term , projViewSpine :: Elims } -- ^ A projection or projection-like function, applied to its -- principal argument | LoneProjectionLike QName ArgInfo -- ^ Just a lone projection-like function, missing its principal -- argument (from which we could infer the parameters). | NoProjection Term -- ^ Not a projection or projection-like thing. -- | Semantics of 'ProjectionView'. unProjView :: ProjectionView -> Term unProjView pv = case pv of ProjectionView f a es -> Def f (Apply a : es) LoneProjectionLike f ai -> Def f [] NoProjection v -> v -- | Top-level 'ProjectionView' (no reduction). {-# SPECIALIZE projView :: Term -> TCM ProjectionView #-} projView :: HasConstInfo m => Term -> m ProjectionView projView v = do let fallback = return $ NoProjection v case ignoreSharing v of Def f es -> caseMaybeM (isProjection f) fallback $ \ isP -> do if projIndex isP <= 0 then fallback else do case es of [] -> return $ LoneProjectionLike f $ projArgInfo isP Apply a : es -> return $ ProjectionView f a es -- Since a projection is a function, it cannot be projected itself. Proj{} : _ -> __IMPOSSIBLE__ _ -> fallback -- | Reduce away top-level projection like functions. -- (Also reduces projections, but they should not be there, -- since Internal is in lambda- and projection-beta-normal form.) -- reduceProjectionLike :: Term -> TCM Term reduceProjectionLike v = do -- Andreas, 2013-11-01 make sure we do not reduce a constructor -- because that could be folded back into a literal by reduce. pv <- projView v case pv of ProjectionView{} -> onlyReduceProjections $ reduce v -- ordinary reduce, only different for Def's _ -> return v -- | Turn prefix projection-like function application into postfix ones. -- This does just one layer, such that the top spine contains -- the projection-like functions as projections. -- Used in 'compareElims' in @TypeChecking.Conversion@ -- and in "Agda.TypeChecking.CheckInternal". -- -- If the 'Bool' is 'True', a lone projection like function will be -- turned into a lambda-abstraction, expecting the principal argument. -- If the 'Bool' is 'False', it will be returned unaltered. -- -- No precondition. -- Preserves constructorForm, since it really does only something -- on (applications of) projection-like functions. elimView :: Bool -> Term -> TCM Term elimView loneProjToLambda v = do reportSDoc "tc.conv.elim" 30 $ text "elimView of " <+> prettyTCM v reportSLn "tc.conv.elim" 50 $ "v = " ++ show v v <- reduceProjectionLike v reportSDoc "tc.conv.elim" 40 $ text "elimView (projections reduced) of " <+> prettyTCM v pv <- projView v case pv of NoProjection{} -> return v LoneProjectionLike f ai | loneProjToLambda -> return $ Lam ai $ Abs "r" $ Var 0 [Proj f] | otherwise -> return v ProjectionView f a es -> (`applyE` (Proj f : es)) <$> elimView loneProjToLambda (unArg a) -- | Which @Def@types are eligible for the principle argument -- of a projection-like function? eligibleForProjectionLike :: QName -> TCM Bool eligibleForProjectionLike d = do defn <- theDef <$> getConstInfo d return $ case defn of Datatype{} -> True Record{} -> True Axiom{} -> True _ -> False -- | Turn a definition into a projection if it looks like a projection. makeProjection :: QName -> TCM () makeProjection x = inTopContext $ do -- reportSLn "tc.proj.like" 30 $ "Considering " ++ show x ++ " for projection likeness" defn <- getConstInfo x let t = defType defn reportSDoc "tc.proj.like" 20 $ sep [ text "Checking for projection likeness " , prettyTCM x <+> text " : " <+> prettyTCM t ] case theDef defn of Function{funClauses = cls} | any (isNothing . getBodyUnraised) cls -> reportSLn "tc.proj.like" 30 $ " projection-like functions cannot have absurd clauses" -- Constructor-headed functions can't be projection-like (at the moment). The reason -- for this is that invoking constructor-headedness will circumvent the inference of -- the dropped arguments. -- Nor can abstract definitions be projection-like since they won't reduce -- outside the abstract block. def@Function{funProjection = Nothing, funClauses = cls, funCompiled = cc0, funInv = NotInjective, funMutual = [], -- Andreas, 2012-09-28: only consider non-mutual funs (or those whose recursion status has not yet been determined) funAbstr = ConcreteDef} -> do ps0 <- filterM validProj $ candidateArgs [] t reportSLn "tc.proj.like" 30 $ if null ps0 then " no candidates found" else " candidates: " ++ show ps0 unless (null ps0) $ do -- Andreas 2012-09-26: only consider non-recursive functions for proj.like. -- Issue 700: problems with recursive funs. in term.checker and reduction ifM recursive (reportSLn "tc.proj.like" 30 $ " recursive functions are not considered for projection-likeness") $ do ps <- return $ filter (checkOccurs cls . snd) ps0 when (null ps) $ reportSLn "tc.proj.like" 50 $ " occurs check failed\n clauses = " ++ show cls case reverse ps of [] -> return () (d, n) : _ -> do reportSDoc "tc.proj.like" 10 $ sep [ prettyTCM x <+> text " : " <+> prettyTCM t , text $ " is projection like in argument " ++ show n ++ " for type " ++ show d ] {- reportSLn "tc.proj.like" 10 $ show (defName defn) ++ " is projection like in argument " ++ show n ++ " for type " ++ show d -} let cls' = map (dropArgs n) cls cc = dropArgs n cc0 -- cc <- compileClauses (Just (x, __IMPOSSIBLE__)) cls' reportSLn "tc.proj.like" 60 $ " rewrote clauses to\n " ++ show cc -- Andreas, 2013-10-20 build parameter dropping function let (ptel, Dom ai _ : _) = splitAt n $ telToList $ theTel $ telView' t -- leading lambdas are to ignore parameter applications proj = teleNoAbs ptel $ Def x [] -- proj = foldr (\ (Dom ai (y, _)) -> Lam ai . NoAbs y) (Def x []) ptel let projection = Projection { projProper = Nothing , projFromType = d , projIndex = n + 1 , projDropPars = proj , projArgInfo = ai } let newDef = def { funProjection = Just projection , funClauses = cls' , funCompiled = cc , funInv = dropArgs n $ funInv def } addConstant x $ defn { defPolarity = drop n $ defPolarity defn , defArgOccurrences = drop n $ defArgOccurrences defn , defDisplay = [] , theDef = newDef } Function{funInv = Inverse{}} -> reportSLn "tc.proj.like" 30 $ " injective functions can't be projections" Function{funAbstr = AbstractDef} -> reportSLn "tc.proj.like" 30 $ " abstract functions can't be projections" Function{funProjection = Just{}} -> reportSLn "tc.proj.like" 30 $ " already projection like" _ -> reportSLn "tc.proj.like" 30 $ " not a function" where -- @validProj (d,n)@ checks whether the head @d@ of the type of the -- @n@th argument is injective in all args (i.d. being name of data/record/axiom). validProj :: (QName, Int) -> TCM Bool validProj (_, 0) = return False validProj (d, _) = eligibleForProjectionLike d -- NOTE: If the following definition turns out to be slow, then -- one could perhaps reuse information computed by the termination -- and/or positivity checkers. recursive = do occs <- computeOccurrences x let xocc = Map.lookup (ADef x) occs case xocc of Just (_ : _) -> return True -- recursive occurrence _ -> return False checkOccurs cls n = all (nonOccur n) cls nonOccur n cl = and [ take n p == [0..n - 1] , onlyMatch n ps -- projection-like functions are only allowed to match on the eliminatee -- otherwise we may end up projecting from constructor applications, in -- which case we can't reconstruct the dropped parameters , checkBody n b ] where Perm _ p = clausePerm cl ps = namedClausePats cl b = clauseBody cl onlyMatch n ps = all (shallowMatch . namedArg) (take 1 ps1) && noMatches (ps0 ++ drop 1 ps1) where (ps0, ps1) = splitAt n ps shallowMatch (ConP _ _ ps) = noMatches ps shallowMatch _ = True noMatches = all (noMatch . namedArg) noMatch ConP{} = False noMatch LitP{} = False noMatch ProjP{}= False noMatch VarP{} = True noMatch DotP{} = True checkBody 0 _ = True checkBody _ NoBody = __IMPOSSIBLE__ -- we check this earlier checkBody n (Bind b) = not (isBinderUsed b) && checkBody (n - 1) (unAbs b) checkBody _ Body{} = __IMPOSSIBLE__ -- @candidateArgs [var 0,...,var(n-1)] t@ adds @(n,d)@ to the output, -- if @t@ is a function-type with domain @t 0 .. (n-1)@ -- (the domain of @t@ is the type of the arg @n@). -- -- This means that from the type of arg @n@ all previous arguments -- can be computed by a simple matching. -- (Provided the @d@ is data/record/postulate, checked in @validProj@). -- -- E.g. f : {x : _}(y : _){z : _} -> D x y z -> ... -- will return (D,3) as a candidate (amongst maybe others). -- candidateArgs :: [Term] -> Type -> [(QName,Int)] candidateArgs vs t = case ignoreSharing $ unEl t of Pi a b | Def d es <- ignoreSharing $ unEl $ unDom a, Just us <- allApplyElims es, vs == map unArg us -> (d, length vs) : candidateRec b | otherwise -> candidateRec b _ -> [] where candidateRec NoAbs{} = [] candidateRec (Abs x t) = candidateArgs (var (size vs) : vs) t