packages feed

idris-0.9.3: src/Idris/Coverage.hs

{-# LANGUAGE PatternGuards #-}

module Idris.Coverage where

import Core.TT
import Core.Evaluate
import Core.CaseTree

import Idris.AbsSyntax
import Idris.Delaborate
import Idris.Error

import Data.List
import Data.Either
import Debug.Trace

-- Given a list of LHSs, generate a extra clauses which cover the remaining
-- cases. The ones which haven't been provided are marked 'absurd' so that the
-- checker will make sure they can't happen.

-- This will only work after the given clauses have been typechecked and the
-- names are fully explicit!

genClauses :: FC -> Name -> [Term] -> [PClause] -> Idris [PTerm]
genClauses fc n xs given
   = do i <- getIState
        let lhss = map (getLHS i) xs
        let argss = transpose lhss
        let all_args = map (genAll i) argss
        logLvl 7 $ "COVERAGE of " ++ show n
        logLvl 10 $ show argss ++ "\n" ++ show all_args
        logLvl 10 $ "Original: \n" ++ 
                        showSep "\n" (map (\t -> showImp True (delab' i t True)) xs)
        let parg = case lookupCtxt Nothing n (idris_implicits i) of
                        (p : _) -> p
                        _ -> repeat (pexp Placeholder)
        let tryclauses = mkClauses parg all_args
        let new = mnub i $ filter (noMatch i) tryclauses 
        logLvl 7 $ "New clauses: \n" ++ showSep "\n" (map (showImp True) new)
--                     ++ " from:\n" ++ showSep "\n" (map (showImp True) tryclauses) 
        return new
--         return (map (\t -> PClause n t [] PImpossible []) new)
  where getLHS i term 
            | (f, args) <- unApply term = map (\t -> delab' i t True) args
            | otherwise = []

        lhsApp (PClause _ _ l _ _ _) = l
        lhsApp (PWith _ _ l _ _ _) = l

        mnub i [] = []
        mnub i (x : xs) = 
            if (any (\t -> case matchClause i x t of
                                Right _ -> True
                                Left _ -> False) xs) then mnub i xs 
                                                     else x : mnub i xs

        noMatch i tm = all (\x -> case matchClause i (delab' i x True) tm of
                                          Right _ -> False
                                          Left miss -> True) xs 


        mkClauses :: [PArg] -> [[PTerm]] -> [PTerm]
        mkClauses parg args
            | all (== [Placeholder]) args = []
        mkClauses parg args
            = do args' <- mkArg args
                 let tm = PApp fc (PRef fc n) (zipWith upd args' parg)
                 return tm
          where
            mkArg :: [[PTerm]] -> [[PTerm]]
            mkArg [] = return []
            mkArg (a : as) = do a' <- a
                                as' <- mkArg as
                                return (a':as')

-- FIXME: Just look for which one is the deepest, then generate all possibilities
-- up to that depth.

genAll :: IState -> [PTerm] -> [PTerm]
genAll i args = case filter (/=Placeholder) $ concatMap otherPats (nub args) of
                    [] -> [Placeholder]
                    xs -> xs
  where 
    conForm (PApp _ (PRef fc n) _) = isConName Nothing n (tt_ctxt i)
    conForm (PRef fc n) = isConName Nothing n (tt_ctxt i)
    conForm _ = False

    otherPats :: PTerm -> [PTerm]
    otherPats o@(PRef fc n) = ops fc n [] o
    otherPats o@(PApp _ (PRef fc n) xs) = ops fc n xs o
    otherPats arg = return Placeholder 

    ops fc n xs o
        | (TyDecl c@(DCon _ arity) ty : _) <- lookupDef Nothing n (tt_ctxt i)
            = do xs' <- mapM otherPats (map getTm xs)
                 let p = PApp fc (PRef fc n) (zipWith upd xs' xs)
                 let tyn = getTy n (tt_ctxt i)
                 case lookupCtxt Nothing tyn (idris_datatypes i) of
                         (TI ns : _) -> p : map (mkPat fc) (ns \\ [n])
                         _ -> [p]
    ops fc n arg o = return Placeholder

    getTy n ctxt = case lookupTy Nothing n ctxt of
                          (t : _) -> case unApply (getRetTy t) of
                                        (P _ tyn _, _) -> tyn
                                        x -> error $ "Can't happen getTy 1 " ++ show (n, x)
                          _ -> error "Can't happen getTy 2"

    mkPat fc x = case lookupCtxt Nothing x (idris_implicits i) of
                      (pargs : _)
                         -> PApp fc (PRef fc x) (map (upd Placeholder) pargs)  
                      _ -> error "Can't happen - genAll"

upd p' p = p { getTm = p' }

-- Check if, in a given type n, the constructor cn : ty is strictly positive,
-- and update the context accordingly

checkPositive :: Name -> (Name, Type) -> Idris ()
checkPositive n (cn, ty) 
    = do let p = cp ty
         i <- getIState
         let tot = if p then Total (args ty) else Partial NotPositive
         let ctxt' = setTotal cn tot (tt_ctxt i)
         putIState (i { tt_ctxt = ctxt' })
         addIBC (IBCTotal cn tot)
  where
    args t = [0..length (getArgTys t)-1]

    cp (Bind n (Pi aty) sc) = posArg aty && cp sc
    cp t = True

    posArg (Bind _ (Pi nty) sc)
        | (P _ n' _, args) <- unApply nty
            = n /= n' && posArg sc
    posArg t = True

-- Totality checking - check for structural recursion (no mutual definitions yet)

data LexOrder = LexXX | LexEQ | LexLT
    deriving (Show, Eq, Ord)

calcTotality :: [Name] -> FC -> Name -> [([Name], Term, Term)] -> Idris Totality
calcTotality path fc n pats 
    = do orders <- mapM ctot pats 
         let order = sortBy cmpOrd $ concat orders
         let (errs, valid) = partitionEithers order
         let lex = stripNoLT (stripXX valid)
         case errs of
            [] -> do logLvl 3 $ show n ++ ":\n" ++ showSep "\n" (map show lex) 
                     logLvl 10 $ show pats
                     checkDecreasing lex
            (e : _) -> return e -- FIXME: should probably combine them
  where
    cmpOrd (Left _) (Left _) = EQ
    cmpOrd (Left _) (Right _) = LT
    cmpOrd (Right _) (Left _) = GT
    cmpOrd (Right x) (Right y) = compare x y

    checkDecreasing [] = return (Total [])
    checkDecreasing (c : cs) | dec c = checkDecreasing cs
                             | otherwise = return (Partial Itself)
    
    dec [] = False
    dec (LexLT : _) = True
    dec (LexEQ : xs) = dec xs
    dec (LexXX : xs) = False

    stripXX [] = []
    stripXX v@(c : cs) 
        = case span (==LexXX) c of
               (ns, rest) -> map (drop (length ns)) v

    -- argument positions which are never LT are no use to us
    stripNoLT [] = [] -- no recursive calls
    stripNoLT xs = case transpose (filter (any (==LexLT)) (transpose xs)) of
                        [] -> [[]] -- recursive calls are all useless...
                        xs -> xs

    ctot (_, lhs, rhs) 
        | (_, args) <- unApply lhs
            = do -- check lhs doesn't use any dodgy names
                    lhsOK <- mapM (chkOrd [] []) args
                    chkOrd (filter isLeft (concat lhsOK)) args rhs

    isLeft (Left _) = True
    isLeft _ = False

    chkOrd ords args (Bind n (Let t v) sc) 
        = do ov <- chkOrd ords args v
             chkOrd ov args sc
    chkOrd ords args (Bind n b sc) = chkOrd ords (args ++ [P Ref n Erased]) sc
    chkOrd ords args ap@(App f a)
        | (P _ fn _, args') <- unApply ap
            = if fn == n && length args == length args'
                 then do orf <- chkOrd (Right (zipWith lexOrd args args') : ords) args f
                         chkOrd orf args a
                 else do orf <- chkOrd ords args f
                         chkOrd orf args a
        | otherwise = do orf <- chkOrd ords args f
                         chkOrd orf args a
    chkOrd ords args (P _ fn _)
        | n /= fn
            = do tf <- checkTotality (n : path) fc fn
                 case tf of
                    Total _ -> return ords
                    p@(Partial (Mutual x)) -> return ((Left p) : ords)
                    _ -> return (Left (Partial (Other [fn])) : ords)
        | null args = return (Left (Partial Itself) : ords)
    chkOrd ords args _ = return ords

    lexOrd x y | x == y = LexEQ
    lexOrd f@(App _ _) x 
        | (f', args) <- unApply f
            = let ords = map (\x' -> lexOrd x' x) args in
                if any (\o -> o == LexEQ || o == LexLT) ords
                    then LexLT
                    else LexXX
    lexOrd _ _ = LexXX

checkTotality :: [Name] -> FC -> Name -> Idris Totality
checkTotality path fc n 
    | n `elem` path = return (Partial (Mutual (n : path)))
    | otherwise = do
        t <- getTotality n
        ctxt <- getContext
        i <- getIState
        let opts = case lookupCtxt Nothing n (idris_flags i) of
                            [fs] -> fs
                            _ -> []
        t' <- case t of 
                Unchecked -> 
                    case lookupDef Nothing n ctxt of
                        [CaseOp _ _ pats _ _ _ _] -> 
                            do t' <- if AssertTotal `elem` opts
                                        then return $ Total []
                                        else calcTotality path fc n pats
                               setTotality n t'
                               addIBC (IBCTotal n t')
                            -- if it's not total, it can't reduce, to keep
                            -- typechecking decidable
                               case t' of
-- FIXME: Put this back when we can handle mutually recursive things
--                                            p@(Partial _) -> 
--                                                 do setAccessibility n Frozen 
--                                                    addIBC (IBCAccess n Frozen)
--                                                    iputStrLn $ "HIDDEN: " ++ show n ++ show p
                                           _ -> return ()
                               return t'
                        _ -> return $ Total []
                x -> return x
        if TotalFn `elem` opts
            then case t' of
                    Total _ -> return t'
                    e -> totalityError t'
            else return t'
  where
    totalityError t = tclift $ tfail (At fc (Msg (show n ++ " is " ++ show t)))

checkDeclTotality :: (FC, Name) -> Idris Totality
checkDeclTotality (fc, n) 
    = do logLvl 2 $ "Checking " ++ show n ++ " for totality"
         checkTotality [] fc n