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 Data.Maybe
import Debug.Trace
import Control.Monad.State
mkPatTm :: PTerm -> Idris Term
mkPatTm t = do i <- getIState
let timp = addImpl' True [] [] i t
evalStateT (toTT timp) 0
where
toTT (PRef _ n) = do i <- lift getIState
case lookupDef n (tt_ctxt i) of
[TyDecl nt _] -> return $ P nt n Erased
_ -> return $ P Ref n Erased
toTT (PApp _ t args) = do t' <- toTT t
args' <- mapM (toTT . getTm) args
return $ mkApp t' args'
toTT _ = do v <- get
put (v + 1)
return (P Bound (MN v "imp") Erased)
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 5 $ "COVERAGE of " ++ show n
logLvl 5 $ show (map length argss) ++ "\n" ++ show (map length all_args)
logLvl 10 $ show argss ++ "\n" ++ show all_args
logLvl 10 $ "Original: \n" ++
showSep "\n" (map (\t -> showImp Nothing True False (delab' i t True)) xs)
let parg = case lookupCtxt n (idris_implicits i) of
(p : _) -> p ++ repeat (PExp 0 False Placeholder "")
_ -> repeat (pexp Placeholder)
let tryclauses = mkClauses parg all_args
logLvl 2 $ show (length tryclauses) ++ " initially to check"
logLvl 5 $ showSep "\n" (map (showImp Nothing True False) tryclauses)
let new = filter (noMatch i) (nub tryclauses)
logLvl 1 $ show (length new) ++ " clauses to check for impossibility"
logLvl 5 $ "New clauses: \n" ++ showSep "\n" (map (showImp Nothing True False) new)
return 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
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')
fnub xs = fnub' [] xs
fnub' acc (x : xs) | x `qelem` acc = fnub' acc (filter (not.(quickEq x)) xs)
| otherwise = fnub' (x : acc) xs
fnub' acc [] = acc
quickEq :: PTerm -> PTerm -> Bool
quickEq (PRef _ n) (PRef _ n') = n == n'
quickEq (PApp _ t as) (PApp _ t' as')
| length as == length as'
= quickEq t t' && and (zipWith quickEq (map getTm as) (map getTm as'))
quickEq Placeholder Placeholder = True
quickEq x y = False
qelem x [] = False
qelem x (y : ys) | x `quickEq` y = True
| otherwise = qelem x ys
genAll :: IState -> [PTerm] -> [PTerm]
genAll i args
= case filter (/=Placeholder) $ fnub (concatMap otherPats (fnub args)) of
[] -> [Placeholder]
xs -> inventConsts xs
where
inventConsts cs@(PConstant c : _) = map PConstant (ic' (mapMaybe getConst cs))
where getConst (PConstant c) = Just c
getConst _ = Nothing
inventConsts xs = xs
ic' xs@(I _ : _) = firstMissing xs (lotsOfNums I)
ic' xs@(BI _ : _) = firstMissing xs (lotsOfNums BI)
ic' xs@(Fl _ : _) = firstMissing xs (lotsOfNums Fl)
ic' xs@(B8 _ : _) = firstMissing xs (lotsOfNums B8)
ic' xs@(B16 _ : _) = firstMissing xs (lotsOfNums B16)
ic' xs@(B32 _ : _) = firstMissing xs (lotsOfNums B32)
ic' xs@(B64 _ : _) = firstMissing xs (lotsOfNums B64)
ic' xs@(Ch _ : _) = firstMissing xs lotsOfChars
ic' xs@(Str _ : _) = firstMissing xs lotsOfStrings
ic' xs = xs
firstMissing cs (x : xs) | x `elem` cs = firstMissing cs xs
| otherwise = x : cs
lotsOfNums t = map t [0..]
lotsOfChars = map Ch ['a'..]
lotsOfStrings = map Str (map (("some string " ++).show) [1..])
conForm (PApp _ (PRef fc n) _) = isConName n (tt_ctxt i)
conForm (PRef fc n) = isConName n (tt_ctxt i)
conForm _ = False
nubMap f acc [] = acc
nubMap f acc (x : xs) = nubMap f (fnub' acc (f x)) xs
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 o@(PPair fc l r)
= ops fc pairCon
([pimp (UN "A") Placeholder True,
pimp (UN "B") Placeholder True] ++
[pexp l, pexp r]) o
otherPats o@(PDPair fc t _ v)
= ops fc (UN "Ex_intro")
([pimp (UN "a") Placeholder True,
pimp (UN "P") Placeholder True] ++
[pexp t,pexp v]) o
otherPats o@(PConstant c) = return o
otherPats arg = return Placeholder
ops fc n xs o
| (TyDecl c@(DCon _ arity) ty : _) <- lookupDef n (tt_ctxt i)
= do xs' <- mapM otherPats (map getExpTm xs)
let p = resugar (PApp fc (PRef fc n) (zipWith upd xs' xs))
let tyn = getTy n (tt_ctxt i)
case lookupCtxt tyn (idris_datatypes i) of
(TI ns _ _ : _) -> p : map (mkPat fc) (ns \\ [n])
_ -> [p]
ops fc n arg o = return Placeholder
getExpTm (PImp _ True _ _ _ _) = Placeholder
getExpTm t = getTm t
resugar (PApp _ (PRef fc (UN "Ex_intro")) [_,_,t,v])
= PDPair fc (getTm t) Placeholder (getTm v)
resugar (PApp _ (PRef fc n) [_,_,l,r])
| n == pairCon
= PPair fc (getTm l) (getTm r)
resugar t = t
getForceable i n = case lookupCtxt n (idris_optimisation i) of
[o] -> forceable o
_ -> []
dropForce force (x : xs) i | i `elem` force
= upd Placeholder x : dropForce force xs (i + 1)
dropForce force (x : xs) i = x : dropForce force xs (i + 1)
dropForce _ [] _ = []
getTy n ctxt = case lookupTy 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 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' }
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' })
logLvl 5 $ "Constructor " ++ show cn ++ " is " ++ show tot
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
calcProd :: IState -> FC -> Name -> [([Name], Term, Term)] -> Idris Totality
calcProd i fc topn pats
= cp topn pats []
where
cp n pats done = do patsprod <- mapM (prodRec n done) pats
if (and patsprod)
then return Productive
else return (Partial NotProductive)
prodRec :: Name -> [Name] -> ([Name], Term, Term) -> Idris Bool
prodRec n done _ | n `elem` done = return True
prodRec n done (_, _, tm) = prod n done False tm
prod :: Name -> [Name] -> Bool -> Term -> Idris Bool
prod n done ok ap@(App _ _)
| (P _ (UN "lazy") _, [_, arg]) <- unApply ap = prod n done ok arg
| (P nt f _, args) <- unApply ap
= do recOK <- checkProdRec (n:done) f
let ctxt = tt_ctxt i
let [ty] = lookupTy f ctxt
let co = cotype nt f ty in
if (not recOK) then return False else
if f == topn
then do argsprod <- mapM (prod n done co) args
return (and (ok : argsprod) )
else do argsprod <- mapM (prod n done co) args
return (and argsprod)
prod n done ok (App f a) = liftM2 (&&) (prod n done False f)
(prod n done False a)
prod n done ok (Bind _ (Let t v) sc)
= liftM2 (&&) (prod n done False v) (prod n done False v)
prod n done ok (Bind _ b sc) = prod n done ok sc
prod n done ok t = return True
checkProdRec :: [Name] -> Name -> Idris Bool
checkProdRec done f
= case lookupCtxt f (idris_patdefs i) of
[(def, _)] -> do ok <- mapM (prodRec f done) def
return (and ok)
_ -> return True
cotype (DCon _ _) n ty
| (P _ t _, _) <- unApply (getRetTy ty)
= case lookupCtxt t (idris_datatypes i) of
[TI _ True _] -> True
_ -> False
cotype nt n ty = False
calcTotality :: [Name] -> FC -> Name -> [([Name], Term, Term)]
-> Idris Totality
calcTotality path fc n pats
= do i <- getIState
let opts = case lookupCtxt n (idris_flags i) of
[fs] -> fs
_ -> []
case mapMaybe (checkLHS i) (map (\ (_, l, r) -> l) pats) of
(failure : _) -> return failure
_ -> if (Coinductive `elem` opts)
then calcProd i fc n pats
else checkSizeChange n
where
checkLHS i (P _ fn _)
= case lookupTotal fn (tt_ctxt i) of
[Partial _] -> return (Partial (Other [fn]))
_ -> Nothing
checkLHS i (App f a) = mplus (checkLHS i f) (checkLHS i a)
checkLHS _ _ = Nothing
checkTotality :: [Name] -> FC -> Name -> Idris Totality
checkTotality path fc n
| n `elem` path = return (Partial (Mutual (n : path)))
| otherwise = do
t <- getTotality n
updateContext (simplifyCasedef n)
ctxt <- getContext
i <- getIState
let opts = case lookupCtxt n (idris_flags i) of
[fs] -> fs
_ -> []
t' <- case t of
Unchecked ->
case lookupDef 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')
case t' of
p@(Partial _) ->
do setAccessibility n Frozen
addIBC (IBCAccess n Frozen)
logLvl 5 $ "HIDDEN: "
++ show n ++ show p
_ -> return ()
return t'
_ -> return $ Total []
x -> return x
case t' of
Total _ -> return t'
Productive -> return t'
e -> do w <- cmdOptType WarnPartial
if TotalFn `elem` opts
then totalityError t'
else do when (w && not (PartialFn `elem` opts)) $
warnPartial n t'
return t'
where
totalityError t = tclift $ tfail (At fc (Msg (show n ++ " is " ++ show t)))
warnPartial n t
= do i <- getIState
case lookupDef n (tt_ctxt i) of
[x] -> do
iputStrLn $ show fc ++ ":Warning - " ++ show n ++ " is " ++ show t
checkDeclTotality :: (FC, Name) -> Idris Totality
checkDeclTotality (fc, n)
= do logLvl 2 $ "Checking " ++ show n ++ " for totality"
checkTotality [] fc n
buildSCG :: (FC, Name) -> Idris ()
buildSCG (_, n) = do
ist <- getIState
case lookupCtxt n (idris_callgraph ist) of
[cg] -> case lookupDef n (tt_ctxt ist) of
[CaseOp _ _ pats _ cd] ->
let (args, sc) = cases_totcheck cd in
do logLvl 2 $ "Building SCG for " ++ show n ++ " from\n"
++ show pats ++ "\n" ++ show sc
let newscg = buildSCG' ist (rights pats) args
logLvl 5 $ show newscg
addToCG n ( cg { scg = newscg } )
[] -> logLvl 5 $ "Could not build SCG for " ++ show n ++ "\n"
x -> error $ "buildSCG: " ++ show (n, x)
buildSCG' :: IState -> [(Term, Term)] -> [Name] -> [SCGEntry]
buildSCG' ist pats args = nub $ concatMap scgPat pats where
scgPat (lhs, rhs) = let (f, pargs) = unApply (dePat lhs) in
findCalls (dePat rhs) (patvars lhs) pargs
findCalls ap@(App f a) pvs pargs
| (P _ (UN "lazy") _, [_, arg]) <- unApply ap
= findCalls arg pvs pargs
| (P _ n _, args) <- unApply ap
= mkChange n args pargs ++
concatMap (\x -> findCalls x pvs pargs) args
findCalls (App f a) pvs pargs
= findCalls f pvs pargs ++ findCalls a pvs pargs
findCalls (Bind n (Let t v) e) pvs pargs
= findCalls v pvs pargs ++ findCalls e (n : pvs) pargs
findCalls (Bind n _ e) pvs pargs
= findCalls e (n : pvs) pargs
findCalls (P _ f _ ) pvs pargs
| not (f `elem` pvs) = [(f, [])]
findCalls _ _ _ = []
expandToArity n args
= case lookupTy n (tt_ctxt ist) of
[ty] -> expand 0 (normalise (tt_ctxt ist) [] ty) args
_ -> args
where expand i (Bind n (Pi _) sc) (x : xs) = x : expand (i + 1) sc xs
expand i (Bind n (Pi _) sc) [] = Just (i, Same) : expand (i + 1) sc []
expand i _ xs = xs
mkChange n args pargs = [(n, expandToArity n (sizes args))]
where
sizes [] = []
sizes (a : as) = checkSize a pargs 0 : sizes as
checkSize a (p : ps) i
| a == p = Just (i, Same)
| smaller Nothing a (p, Nothing) = Just (i, Smaller)
| otherwise = checkSize a ps (i + 1)
checkSize a [] i = Nothing
smaller (Just tyn) a (t, Just tyt)
| a == t = isInductive (fst (unApply (getRetTy tyn)))
(fst (unApply (getRetTy tyt)))
smaller ty a (ap@(App f s), _)
| (P (DCon _ _) n _, args) <- unApply ap
= let tyn = getType n in
any (smaller (ty `mplus` Just tyn) a)
(zip args (map toJust (getArgTys tyn)))
smaller ty (App f s) a = smaller ty f a
smaller _ _ _ = False
toJust (n, t) = Just t
getType n = case lookupTy n (tt_ctxt ist) of
[ty] -> ty
isInductive (P _ nty _) (P _ nty' _) =
let co = case lookupCtxt nty (idris_datatypes ist) of
[TI _ x _] -> x
_ -> False in
nty == nty' && not co
isInductive _ _ = False
dePat (Bind x (PVar ty) sc) = dePat (instantiate (P Bound x ty) sc)
dePat t = t
patvars (Bind x (PVar _) sc) = x : patvars sc
patvars _ = []
checkSizeChange :: Name -> Idris Totality
checkSizeChange n = do
ist <- getIState
case lookupCtxt n (idris_callgraph ist) of
[cg] -> do let ms = mkMultiPaths ist [] (scg cg)
logLvl 5 ("Multipath for " ++ show n ++ ":\n" ++
"from " ++ show (scg cg) ++ "\n" ++
show (length ms) ++ "\n" ++
showSep "\n" (map show ms))
logLvl 6 (show cg)
let tot = map (checkMP ist (getArity ist n)) ms
logLvl 4 $ "Generated " ++ show (length tot) ++ " paths"
logLvl 6 $ "Paths for " ++ show n ++ " yield " ++ (show tot)
return (noPartial tot)
[] -> do logLvl 5 $ "No paths for " ++ show n
return Unchecked
where getArity ist n
= case lookupTy n (tt_ctxt ist) of
[ty] -> arity (normalise (tt_ctxt ist) [] ty)
_ -> error "Can't happen: checkSizeChange.getArity"
type MultiPath = [SCGEntry]
mkMultiPaths :: IState -> MultiPath -> [SCGEntry] -> [MultiPath]
mkMultiPaths ist path [] = [reverse path]
mkMultiPaths ist path cg
= concat (map extend cg)
where extend (nextf, args)
| (nextf, args) `elem` path = [ reverse ((nextf, args) : path) ]
| [Unchecked] <- lookupTotal nextf (tt_ctxt ist)
= case lookupCtxt nextf (idris_callgraph ist) of
[ncg] -> mkMultiPaths ist ((nextf, args) : path) (scg ncg)
_ -> [ reverse ((nextf, args) : path) ]
| otherwise = [ reverse ((nextf, args) : path) ]
checkMP :: IState -> Int -> MultiPath -> Totality
checkMP ist i mp = if i > 0
then let paths = (map (tryPath 0 [] mp) [0..i1]) in
collapse paths
else tryPath 0 [] mp 0
where
tryPath' d path mp arg
= let res = tryPath d path mp arg in
trace (show mp ++ "\n" ++ show arg ++ " " ++ show res) res
tryPath :: Int -> [((SCGEntry, Int), Int)] -> MultiPath -> Int -> Totality
tryPath desc path [] _ = Total []
tryPath desc path ((f, _) : es) arg
| [TyDecl (DCon _ _) _] <- lookupDef f (tt_ctxt ist)
= case lookupTotal f (tt_ctxt ist) of
[Total _] -> Unchecked
[Partial _] -> Partial (Other [f])
x -> error (show x)
| [TyDecl (TCon _ _) _] <- lookupDef f (tt_ctxt ist)
= Total []
tryPath desc path (e@(f, args) : es) arg
| e `elem` es && allNothing args = Partial (Mutual [f])
tryPath desc path (e@(f, nextargs) : es) arg
| Just d <- lookup (e, arg) path
= if desc > 0
then
Total []
else Partial (Mutual (map (fst . fst . fst) path ++ [f]))
| e `elem` map (fst . fst) path
&& not (f `elem` map fst es)
= Partial (Mutual (map (fst . fst . fst) path ++ [f]))
| [Unchecked] <- lookupTotal f (tt_ctxt ist) =
let argspos = case collapseNothing (zip nextargs [0..]) of
[] -> [(Nothing, 0)]
x -> x
pathres =
do (a, pos) <- argspos
case a of
Nothing ->
case collapse (map (tryPath 0 (((e, arg), 0):path) es)
[0..length nextargs 1]) of
Total _ -> return Unchecked
x -> return x
Just (nextarg, sc) ->
if nextarg == arg then
case sc of
Same -> return $ tryPath desc (((e, arg), desc) : path)
es pos
Smaller -> return $ tryPath (desc+1)
(((e, arg), desc) : path)
es
pos
_ -> trace ("Shouldn't happen " ++ show e) $
return (Partial Itself)
else return Unchecked in
collapse' Unchecked pathres
| [Total a] <- lookupTotal f (tt_ctxt ist) = Total a
| [Partial _] <- lookupTotal f (tt_ctxt ist) = Partial (Other [f])
| otherwise = Unchecked
allNothing xs = null (collapseNothing (zip xs [0..]))
collapseNothing ((Nothing, _) : xs)
= filter (\ (x, _) -> case x of
Nothing -> False
_ -> True) xs
collapseNothing (x : xs) = x : collapseNothing xs
collapseNothing [] = []
noPartial (Partial p : xs) = Partial p
noPartial (_ : xs) = noPartial xs
noPartial [] = Total []
collapse xs = collapse' Unchecked xs
collapse' def (Total r : xs) = Total r
collapse' def (Unchecked : xs) = collapse' def xs
collapse' def (d : xs) = collapse' d xs
collapse' def [] = def