3Description : The coverage and totality checkers for Idris are in this module.
6Maintainer : The Idris Community.
8{-# LANGUAGE PatternGuards #-}
9module Idris.Coverage where
12import Idris.Core.CaseTree
13import Idris.Core.Evaluate
15import Idris.Delaborate
17import Idris.Output (iWarn, iputStrLn)
19import Control.Monad.State.Strict
26-- | Generate a pattern from an 'impossible' LHS.
28-- We need this to eliminate the pattern clauses which have been
29-- provided explicitly from new clause generation.
30mkPatTm :: PTerm -> Idris Term
31mkPatTm t = do i <- getIState
32 let timp = addImpl' True [] [] [] i t
33 evalStateT (toTT (mapPT deNS timp)) 0
35 toTT (PRef _ _ n) = do i <- lift getIState
36 case lookupNameDef n (tt_ctxt i) of
37 [(n', TyDecl nt _)] -> return $ P nt n' Erased
38 _ -> return $ P Ref n Erased
39 toTT (PApp _ t args) = do t' <- toTT t
40 args' <- mapM (toTT . getTm) args
41 return $ mkApp t' args'
42 toTT (PDPair _ _ _ l _ r) = do l' <- toTT l
44 return $ mkApp (P Ref sigmaCon Erased) [Erased, Erased, l', r']
45 toTT (PPair _ _ _ l r) = do l' <- toTT l
47 return $ mkApp (P Ref pairCon Erased) [Erased, Erased, l', r']
48 -- For alternatives, pick the first and drop the namespaces. It doesn't
49 -- really matter which is taken since matching will ignore the namespace.
50 toTT (PAlternative _ _ (a : as)) = toTT a
53 return (P Bound (sMN v "imp") Erased)
55 deNS (PRef f hl (NS n _)) = PRef f hl n
58-- | Given a list of LHSs, generate a extra clauses which cover the remaining
59-- cases. The ones which haven't been provided are marked 'absurd' so
60-- that the checker will make sure they can't happen.
62-- This will only work after the given clauses have been typechecked and the
63-- names are fully explicit!
64genClauses :: FC -> Name -> [Term] -> [PTerm] -> Idris [PTerm]
65genClauses fc n xs given
67 let lhs_tms = map (\x -> flattenArgs $ delab' i x True True) xs
68 -- if a placeholder was given, don't bother generating cases for it
69 let lhs_tms' = zipWith mergePlaceholders lhs_tms
70 (map (stripUnmatchable i) (map flattenArgs given))
71 let lhss = map pUnApply lhs_tms'
72 nty <- case lookupTyExact n (tt_ctxt i) of
73 Just t -> return (normalise (tt_ctxt i) [] t)
74 _ -> fail "Can't happen - genClauses, lookupTyExact"
76 let argss = transpose lhss
77 let all_args = map (genAll i) (zip (genPH i nty) argss)
78 logCoverage 5 $ "COVERAGE of " ++ show n
79 logCoverage 5 $ "using type " ++ show nty
80 logCoverage 5 $ "non-concrete args " ++ show (genPH i nty)
82 logCoverage 5 $ show (lhs_tms, lhss)
83 logCoverage 5 $ "Args are: " ++ show argss
84 logCoverage 5 $ show (map length argss) ++ "\nAll args:\n" ++ show (map length all_args)
85 logCoverage 10 $ "All args are: " ++ show all_args
86 logCoverage 3 $ "Original: \n" ++
87 showSep "\n" (map (\t -> showTmImpls (delab' i t True True)) xs)
88 -- add an infinite supply of explicit arguments to update the possible
89 -- cases for (the return type may be variadic, or function type, so
90 -- there may be more case splitting that the idris_implicits record
92 let parg = case lookupCtxt n (idris_implicits i) of
94 p ++ repeat (PExp 0 [] (sMN 0 "gcarg") Placeholder)
95 _ -> repeat (pexp Placeholder)
96 let tryclauses = mkClauses parg all_args
97 logCoverage 3 $ show (length tryclauses) ++ " initially to check"
98 logCoverage 2 $ showSep "\n" (map (showTm i) tryclauses)
99 let new = filter (noMatch i) (nub tryclauses)
100 logCoverage 2 $ show (length new) ++ " clauses to check for impossibility"
101 logCoverage 4 $ "New clauses: \n" ++ showSep "\n" (map (showTm i) new)
102-- ++ " from:\n" ++ showSep "\n" (map (showImp True) tryclauses)
104-- return (map (\t -> PClause n t [] PImpossible []) new)
106 | (f, args) <- unApply term = map (\t -> delab' i t True True) args
109 -- if an argument has a non-concrete type (i.e. it's a function
110 -- that calculates something from a previous argument) we'll also
111 -- need to generate a placeholder pattern for it since it could be
112 -- anything based on earlier values
113 genPH :: IState -> Type -> [Bool]
114 genPH ist (Bind n (Pi _ ty _) sc) = notConcrete ist ty : genPH ist sc
117 notConcrete ist t | (P _ n _, args) <- unApply t,
118 not (isConName n (tt_ctxt ist)) = True
119 notConcrete _ _ = False
121 pUnApply (PApp _ f args) = map getTm args
124 flattenArgs (PApp fc (PApp _ f as) as')
125 = flattenArgs (PApp fc f (as ++ as'))
128 -- Return whether the given clause matches none of the input clauses
130 noMatch i tm = all (\x -> case matchClause i (stripUnmatchable i (delab' i x True True)) tm of
132 Left miss -> True) xs
135 mergePlaceholders :: PTerm -> PTerm -> PTerm
136 mergePlaceholders x Placeholder = Placeholder
137 mergePlaceholders (PApp fc f args) (PApp fc' f' args')
138 = PApp fc' f' (zipWith mergePArg args args')
139 where mergePArg x y = let xtm = mergePlaceholders (getTm x) (getTm y) in
141 mergePlaceholders x _ = x
143 mkClauses :: [PArg] -> [[PTerm]] -> [PTerm]
145 | all (== [Placeholder]) args = []
147 = do args' <- mkArg args
148 let tm = PApp fc (PRef fc [] n) (zipWith upd args' parg)
151 mkArg :: [[PTerm]] -> [[PTerm]]
153 mkArg (a : as) = do a' <- a
157-- | Does this error result rule out a case as valid when coverage checking?
158validCoverageCase :: Context -> Err -> Bool
159validCoverageCase ctxt (CantUnify _ (topx, _) (topy, _) e _ _)
160 = let topx' = normalise ctxt [] topx
161 topy' = normalise ctxt [] topy in
162 not (sameFam topx' topy' || not (validCoverageCase ctxt e))
163 where sameFam topx topy
164 = case (unApply topx, unApply topy) of
165 ((P _ x _, _), (P _ y _, _)) -> x == y
167validCoverageCase ctxt (InfiniteUnify _ _ _) = False
168validCoverageCase ctxt (CantConvert _ _ _) = False
169validCoverageCase ctxt (At _ e) = validCoverageCase ctxt e
170validCoverageCase ctxt (Elaborating _ _ _ e) = validCoverageCase ctxt e
171validCoverageCase ctxt (ElaboratingArg _ _ _ e) = validCoverageCase ctxt e
172validCoverageCase ctxt _ = True
174-- | Check whether an error is recoverable in the sense needed for
176recoverableCoverage :: Context -> Err -> Bool
177recoverableCoverage ctxt (CantUnify r (topx, _) (topy, _) e _ _)
178 = let topx' = normalise ctxt [] topx
179 topy' = normalise ctxt [] topy in
180 r || checkRec topx' topy'
181 where -- different notion of recoverable than in unification, since we
182 -- have no metavars -- just looking to see if a constructor is failing
183 -- to unify with a function that may be reduced later
184 checkRec (App _ f a) p@(P _ _ _) = checkRec f p
185 checkRec p@(P _ _ _) (App _ f a) = checkRec p f
186 checkRec fa@(App _ _ _) fa'@(App _ _ _)
187 | (f, as) <- unApply fa,
188 (f', as') <- unApply fa'
189 = if (length as /= length as')
191 else checkRec f f' && and (zipWith checkRec as as')
192 checkRec (P xt x _) (P yt y _) = x == y || ntRec xt yt
195 ntRec x y | Ref <- x = True
197 | (Bound, Bound) <- (x, y) = True
198 | otherwise = False -- name is different, unrecoverable
199recoverableCoverage ctxt (At _ e) = recoverableCoverage ctxt e
200recoverableCoverage ctxt (Elaborating _ _ _ e) = recoverableCoverage ctxt e
201recoverableCoverage ctxt (ElaboratingArg _ _ _ e) = recoverableCoverage ctxt e
202recoverableCoverage _ _ = False
204genAll :: IState -> (Bool, [PTerm]) -> [PTerm]
205genAll i (addPH, args)
206 = case filter (/=Placeholder) $ fnub $ mergePHs $ fnub (concatMap otherPats (fnub args)) of
210 ph ts = if addPH then Placeholder : ts else ts
212 -- Merge patterns with placeholders in other patterns, to ensure we've
213 -- covered all possible generated cases
214 -- e.g. if we have (True, _) (False, _) (_, True) (_, False)
215 -- we should merge these to get (True, True), (False, False)
216 -- (False, True) and (True, False)
217 mergePHs :: [PTerm] -> [PTerm]
218 mergePHs xs = mergePHs' xs xs
220 mergePHs' [] all = []
221 mergePHs' (x : xs) all = mergePH x all ++ mergePHs' xs all
223 mergePH :: PTerm -> [PTerm] -> [PTerm]
226 | Just merged <- mergePTerm tm x = merged : mergePH tm xs
227 | otherwise = mergePH tm xs
229 mergePTerm :: PTerm -> PTerm -> Maybe PTerm
230 mergePTerm x Placeholder = Just x
231 mergePTerm Placeholder x = Just x
232 mergePTerm tm@(PRef fc1 hl1 n1) (PRef fc2 hl2 n2)
234 mergePTerm (PPair fc1 hl1 p1 x1 y1) (PPair fc2 hl2 p2 x2 y2)
235 = do x <- mergePTerm x1 x2
236 y <- mergePTerm y1 y2
237 Just (PPair fc1 hl1 p1 x y)
238 mergePTerm (PDPair fc1 hl1 p1 x1 t1 y1) (PDPair fc2 hl2 p2 x2 t2 y2)
239 = do x <- mergePTerm x1 x2
240 y <- mergePTerm y1 y2
241 t <- mergePTerm t1 t2
242 Just (PDPair fc1 hl1 p1 x t y)
243 mergePTerm (PApp fc1 f1 args1) (PApp fc2 f2 args2)
244 = do fm <- mergePTerm f1 f2
245 margs <- mergeArgs args1 args2
246 Just (PApp fc1 fm margs)
248 mergeArgs [] [] = Just []
249 mergeArgs (a : as) (b : bs)
250 = do ma_in <- mergePTerm (getTm a) (getTm b)
251 let ma = a { getTm = ma_in }
252 mas <- mergeArgs as bs
254 mergeArgs _ _ = Nothing
255 mergePTerm x y | x == y = Just x
256 | otherwise = Nothing
258 -- for every constant in a term (at any level) take next one to make sure
259 -- that constants which are not explicitly handled are covered
260 nextConst (PConstant fc c) = PConstant NoFC (nc' c)
263 nc' (I c) = I (c + 1)
264 nc' (BI c) = BI (c + 1)
265 nc' (Fl c) = Fl (c + 1)
266 nc' (B8 c) = B8 (c + 1)
267 nc' (B16 c) = B16 (c + 1)
268 nc' (B32 c) = B32 (c + 1)
269 nc' (B64 c) = B64 (c + 1)
270 nc' (Ch c) = Ch (chr $ ord c + 1)
271 nc' (Str c) = Str (c ++ "'")
274 otherPats :: PTerm -> [PTerm]
275 otherPats o@(PRef fc hl n) = ph $ ops fc n [] o
276 otherPats o@(PApp _ (PRef fc hl n) xs) = ph $ ops fc n xs o
277 otherPats o@(PPair fc hls _ l r)
278 = ph $ ops fc pairCon
279 ([pimp (sUN "A") Placeholder True,
280 pimp (sUN "B") Placeholder True] ++
282 otherPats o@(PDPair fc hls p t _ v)
283 = ph $ ops fc sigmaCon
284 ([pimp (sUN "a") Placeholder True,
285 pimp (sUN "P") Placeholder True] ++
287 otherPats o@(PConstant _ c) = ph [o, nextConst o]
288 otherPats arg = return Placeholder
291 | (TyDecl c@(DCon _ arity _) ty : _) <- lookupDef n (tt_ctxt i)
292 = do xs' <- mapM otherPats (map getExpTm xs)
293 let p = resugar (PApp fc (PRef fc [] n) (zipWith upd xs' xs))
294 let tyn = getTy n (tt_ctxt i)
295 case lookupCtxt tyn (idris_datatypes i) of
296 (TI ns _ _ _ _ : _) -> p : map (mkPat fc) (ns \\ [n])
298 ops fc n arg o = return Placeholder
300 getExpTm (PImp _ True _ _ _) = Placeholder -- machine inferred, no point!
303 -- put it back to its original form
304 resugar (PApp _ (PRef fc hl n) [_,_,t,v])
306 = PDPair fc [] TypeOrTerm (getTm t) Placeholder (getTm v)
307 resugar (PApp _ (PRef fc hl n) [_,_,l,r])
309 = PPair fc [] IsTerm (getTm l) (getTm r)
312 dropForce force (x : xs) i | i `elem` force
313 = upd Placeholder x : dropForce force xs (i + 1)
314 dropForce force (x : xs) i = x : dropForce force xs (i + 1)
315 dropForce _ [] _ = []
318 getTy n ctxt = case lookupTy n ctxt of
319 (t : _) -> case unApply (getRetTy t) of
320 (P _ tyn _, _) -> tyn
321 x -> error $ "Can't happen getTy 1 " ++ show (n, x)
322 _ -> error "Can't happen getTy 2"
324 mkPat fc x = case lookupCtxt x (idris_implicits i) of
326 -> PApp fc (PRef fc [] x) (map (upd Placeholder) pargs)
327 _ -> error "Can't happen - genAll"
329 fnub :: [PTerm] -> [PTerm]
330 fnub xs = fnub' [] xs
332 fnub' :: [PTerm] -> [PTerm] -> [PTerm]
333 fnub' acc (x : xs) | x `qelem` acc = fnub' acc (filter (not.(quickEq x)) xs)
334 | otherwise = fnub' (x : acc) xs
337 -- quick check for constructor equality
338 quickEq :: PTerm -> PTerm -> Bool
339 quickEq (PConstant _ n) (PConstant _ n') = n == n'
340 quickEq (PRef _ _ n) (PRef _ _ n') = n == n'
341 quickEq (PPair _ _ _ l r) (PPair _ _ _ l' r') = quickEq l l' && quickEq r r'
342 quickEq (PApp _ t as) (PApp _ t' as')
343 | length as == length as'
344 = quickEq t t' && and (zipWith quickEq (map getTm as) (map getTm as'))
345 quickEq Placeholder Placeholder = True
348 qelem :: PTerm -> [PTerm] -> Bool
350 qelem x (y : ys) | x `quickEq` y = True
351 | otherwise = qelem x ys
354upd :: t -> PArg' t -> PArg' t
355upd p' p = p { getTm = p' }
357-- | Check whether function and all descendants cover all cases
358-- (partial is okay, as long as it's due to recursion)
359checkAllCovering :: FC -> [Name] -> Name -> Name -> Idris ()
360checkAllCovering fc done top n | not (n `elem` done)
362 case lookupTotal n (tt_ctxt i) of
363 [tot@(Partial NotCovering)] ->
364 do let msg = show top ++ " is " ++ show tot ++ " due to " ++ show n
365 putIState i { idris_totcheckfail = (fc, msg) : idris_totcheckfail i }
366 addIBC (IBCTotCheckErr fc msg)
368 case lookupCtxt n (idris_callgraph i) of
369 [cg] -> mapM_ (checkAllCovering fc (n : done) top)
372 x -> return () -- stop if total
373checkAllCovering _ _ _ _ = return ()
375-- | Check whether all 'Inf' arguments to the name end up guaranteed to be
376-- guarded by constructors (conservatively... maybe this can do better later).
377-- Essentially, all it does is check that every branch is a constructor application
378-- with no other function applications.
380-- If so, set the 'AllGuarded' flag which can be used by the productivity check
381checkIfGuarded :: Name -> Idris ()
385 case lookupDefExact n ctxt of
386 Just (CaseOp _ ty _ _ _ cases) ->
387 let gnames = fst (cases_compiletime cases) in
388 if allGuarded gnames i (snd (cases_compiletime cases))
389 then -- trace (show (n, gnames, ty, cases_compiletime cases)) $
390 addFnOpt n AllGuarded
394 guard n ist = isConName n (tt_ctxt ist) || guardFlag n ist
395 guardFlag n ist = case lookupCtxtExact n (idris_flags ist) of
397 Just fs -> AllGuarded `elem` fs
399 -- Top level thing always needs to be a constructor application if
400 -- the delayed things are going to be definitely guarded by this definition
401 allGuarded names i (STerm t)
402 | (P _ fn _, args) <- unApply t,
404 = and (map (guardedTerm names i) args)
406 allGuarded names i (ProjCase _ alts) = and (map (guardedAlt names i) alts)
407 allGuarded names i (Case _ _ alts) = and (map (guardedAlt names i) alts)
408 allGuarded names i _ = True
410 guardedTerm names i (P _ v _) = v `elem` names || guard v i
411 guardedTerm names i (Bind n (Let t v) sc)
412 = guardedTerm names i v && guardedTerm names i sc
413 guardedTerm names i (Bind n b sc) = False
414 guardedTerm names i ap@(App _ _ _)
415 | (P _ fn _, args) <- unApply ap,
416 guard fn i || fn `elem` names
417 = and (map (guardedTerm names i) args)
418 guardedTerm names i (App _ f a) = False
419 guardedTerm names i tm = True
421 guardedAlt names i (ConCase _ _ _ t) = allGuarded names i t
422 guardedAlt names i (FnCase _ _ t) = allGuarded names i t
423 guardedAlt names i (ConstCase _ t) = allGuarded names i t
424 guardedAlt names i (SucCase _ t) = allGuarded names i t
425 guardedAlt names i (DefaultCase t) = allGuarded names i t
427-- | Check if, in a given group of type declarations mut_ns, the
428-- constructor cn : ty is strictly positive, and update the context
430checkPositive :: [Name] -- ^ the group of type declarations
431 -> (Name, Type) -- ^ the constructor
433checkPositive mut_ns (cn, ty')
435 let ty = delazy' True (normalise (tt_ctxt i) [] ty')
437 let tot = if p then Total (args ty) else Partial NotPositive
438 let ctxt' = setTotal cn tot (tt_ctxt i)
439 putIState (i { tt_ctxt = ctxt' })
440 logCoverage 5 $ "Constructor " ++ show cn ++ " is " ++ show tot ++ " with " ++ show mut_ns
441 addIBC (IBCTotal cn tot)
444 args t = [0..length (getArgTys t)-1]
446 cp i (Bind n (Pi _ aty _) sc)
447 = posArg i aty && cp i sc
448 cp i t | (P _ n' _ , args) <- unApply t,
449 n' `elem` mut_ns = all noRec args
452 posArg ist (Bind _ (Pi _ nty _) sc) = noRec nty && posArg ist sc
453 posArg ist t = posParams ist t
455 noRec arg = all (\x -> x `notElem` mut_ns) (allTTNames arg)
457 -- If the type appears recursively in a parameter argument, that's
458 -- fine, otherwise if it appears in an argument it's not fine.
459 posParams ist t | (P _ n _, args) <- unApply t
460 = case lookupCtxtExact n (idris_datatypes ist) of
461 Just ti -> checkParamsOK (param_pos ti) 0 args
462 Nothing -> and (map (posParams ist) args)
463 posParams ist t = noRec t
465 checkParamsOK ppos i [] = True
466 checkParamsOK ppos i (p : ps)
467 | i `elem` ppos = checkParamsOK ppos (i + 1) ps
468 | otherwise = noRec p && checkParamsOK ppos (i + 1) ps
470-- | Calculate the totality of a function from its patterns. Either
471-- follow the size change graph (if inductive) or check for
472-- productivity (if coinductive)
473calcTotality :: FC -> Name -> [([Name], Term, Term)] -> Idris Totality
474calcTotality fc n pats
476 let opts = case lookupCtxt n (idris_flags i) of
479 case mapMaybe (checkLHS i) (map (\ (_, l, r) -> l) pats) of
480 (failure : _) -> return failure
481 _ -> checkSizeChange n
483 checkLHS i (P _ fn _)
484 = case lookupTotalExact fn (tt_ctxt i) of
485 Just (Partial _) -> return (Partial (Other [fn]))
487 checkLHS i (App _ f a) = mplus (checkLHS i f) (checkLHS i a)
488 checkLHS _ _ = Nothing
490checkTotality :: [Name] -> FC -> Name -> Idris Totality
491checkTotality path fc n
492 | n `elem` path = return (Partial (Mutual (n : path)))
496 ctxt' <- do ctxt <- getContext
497 tclift $ simplifyCasedef n (getErasureInfo i) ctxt
501 let opts = case lookupCtxt n (idris_flags i) of
506 case lookupDef n ctxt of
507 [CaseOp _ _ _ _ pats _] ->
508 do t' <- if AssertTotal `elem` opts
509 then return $ Total []
510 else calcTotality fc n pats
511 logCoverage 2 $ "Set to " ++ show t'
513 addIBC (IBCTotal n t')
515 [TyDecl (DCon _ _ _) ty] ->
516 case unApply (getRetTy ty) of
518 let ms = case lookupCtxt tyn (idris_datatypes i) of
519 [TI _ _ _ _ xs@(_:_)] -> xs
521 checkPositive ms (n, ty)
522 _-> return $ Total []
523 _ -> return $ Total []
527 Productive -> return t'
528 e -> do w <- cmdOptType WarnPartial
529 if TotalFn `elem` opts
530 then do totalityError t'; return t'
531 else do when (w && not (PartialFn `elem` opts)) $
535 totalityError t = do i <- getIState
536 let msg = show n ++ " is " ++ show t
537 putIState i { idris_totcheckfail = (fc, msg) : idris_totcheckfail i}
538 addIBC (IBCTotCheckErr fc msg)
542 case lookupDef n (tt_ctxt i) of
544 iWarn fc . pprintErr i . Msg $ "Warning - " ++ show n ++ " is " ++ show t
546-- let cg = lookupCtxtName Nothing n (idris_callgraph i)
547-- iputStrLn (show cg)
550checkDeclTotality :: (FC, Name) -> Idris Totality
551checkDeclTotality (fc, n)
552 = do logCoverage 2 $ "Checking " ++ show n ++ " for totality"
554 let opts = case lookupCtxtExact n (idris_flags i) of
557 when (CoveringFn `elem` opts) $ checkAllCovering fc [] n n
558 t <- checkTotality [] fc n
561-- If the name calls something which is partial, set it as partial
562verifyTotality :: (FC, Name) -> Idris ()
563verifyTotality (fc, n)
564 = do logCoverage 2 $ "Checking " ++ show n ++ "'s descendents are total"
566 case lookupTotalExact n (tt_ctxt ist) of
568 let ns = getNames (tt_ctxt ist)
570 case getPartial ist [] ns of
572 Just bad -> do let t' = Partial (Other bad)
573 logCoverage 2 $ "Set to " ++ show t'
575 addIBC (IBCTotal n t')
578 getNames ctxt = case lookupDefExact n ctxt of
579 Just (CaseOp _ _ _ _ _ defs)
580 -> let (top, def) = cases_compiletime defs in
581 map fst (findCalls' True def top)
584 getPartial ist [] [] = Nothing
585 getPartial ist bad [] = Just bad
586 getPartial ist bad (n : ns)
587 = case lookupTotalExact n (tt_ctxt ist) of
588 Just (Partial _) -> getPartial ist (n : bad) ns
589 _ -> getPartial ist bad ns
591-- | Calculate the size change graph for this definition
593-- SCG for a function f consists of a list of:
594-- (g, [(a1, sizechange1), (a2, sizechange2), ..., (an, sizechangen)])
596-- where g is a function called
597-- a1 ... an are the arguments of f in positions 1..n of g
598-- sizechange1 ... sizechange2 is how their size has changed wrt the input
600-- Nothing, if the argument is unrelated to the input
601buildSCG :: (FC, Name) -> Idris ()
604 case lookupCtxt n (idris_callgraph ist) of
605 [cg] -> case lookupDefExact n (tt_ctxt ist) of
606 Just (CaseOp _ _ _ pats _ cd) ->
607 let (args, sc) = cases_totcheck cd in
608 do logCoverage 2 $ "Building SCG for " ++ show n ++ " from\n"
609 ++ show pats ++ "\n" ++ show sc
610 let newscg = buildSCG' ist n (rights pats) args
611 logCoverage 5 $ "SCG is: " ++ show newscg
612 addToCG n ( cg { scg = newscg } )
613 _ -> return () -- CG comes from a type declaration only
614 [] -> logCoverage 5 $ "Could not build SCG for " ++ show n ++ "\n"
615 x -> error $ "buildSCG: " ++ show (n, x)
617delazy = delazy' False -- not lazy codata
618delazy' all t@(App _ f a)
619 | (P _ (UN l) _, [_, _, arg]) <- unApply t,
620 l == txt "Force" = delazy' all arg
621 | (P _ (UN l) _, [P _ (UN lty) _, _, arg]) <- unApply t,
622 l == txt "Delay" && (all || lty /= txt "Infinite") = delazy arg
623 | (P _ (UN l) _, [P _ (UN lty) _, arg]) <- unApply t,
624 l == txt "Delayed" && (all || lty /= txt "Infinite") = delazy' all arg
625delazy' all (App s f a) = App s (delazy' all f) (delazy' all a)
626delazy' all (Bind n b sc) = Bind n (fmap (delazy' all) b) (delazy' all sc)
629data Guardedness = Toplevel | Unguarded | Guarded | Delayed
632buildSCG' :: IState -> Name -> [(Term, Term)] -> [Name] -> [SCGEntry]
633buildSCG' ist topfn pats args = nub $ concatMap scgPat pats where
634 scgPat (lhs, rhs) = let lhs' = delazy lhs
636 (f, pargs) = unApply (dePat lhs') in
637 findCalls [] Toplevel (dePat rhs') (patvars lhs')
640 -- Under a delay, calls are excluded from the graph - if it's a call to a
641 -- non-total function we'll find that in the final totality check
642 findCalls cases Delayed ap@(P _ n _) pvs args = []
643 findCalls cases guarded ap@(App _ f a) pvs pargs
644 -- under a call to "assert_total", don't do any checking, just believe
646 | (P _ (UN at) _, [_, _]) <- unApply ap,
647 at == txt "assert_total" = []
648 -- don't go under calls to functions which are asserted total
649 | (P _ n _, _) <- unApply ap,
650 Just opts <- lookupCtxtExact n (idris_flags ist),
651 AssertTotal `elem` opts = []
652 -- under a guarded call to "Delay Infinite", we are 'Delayed', so don't
653 -- check under guarded constructors.
654 | (P _ (UN del) _, [_,_,arg]) <- unApply ap,
657 = findCalls cases Delayed arg pvs pargs
658 | (P _ n _, args) <- unApply ap,
660 isConName n (tt_ctxt ist) || allGuarded n ist
661 = -- Still under a 'Delay' and constructor guarded, so check
662 -- just the arguments to the constructor, remaining Delayed
663 concatMap (\x -> findCalls cases guarded x pvs pargs) args
664 | (P _ ifthenelse _, [_, _, t, e]) <- unApply ap,
665 ifthenelse == sNS (sUN "ifThenElse") ["Bool", "Prelude"]
666 -- Continue look inside if...then...else blocks
667 -- TODO: Consider whether we should do this for user defined ifThenElse
668 -- rather than just the one in the Prelude as a special case
669 = findCalls cases guarded t pvs pargs ++
670 findCalls cases guarded e pvs pargs
671 | (P _ n _, args) <- unApply ap,
672 caseName n && n /= topfn,
673 notPartial (lookupTotalExact n (tt_ctxt ist))
674 -- case block - look inside the block, as long as it was covering
675 -- (i.e. not currently set at Partial) to get a more accurate size
676 -- change result from the top level patterns (also to help pass
677 -- information through from guarded corecursion accurately)
678 = concatMap (\x -> findCalls cases Unguarded x pvs pargs) args ++
680 then findCallsCase (n : cases) guarded n args pvs pargs
682 | (P _ n _, args) <- unApply ap,
684 -- Under a delayed recursive call just check the arguments
685 = concatMap (\x -> findCalls cases Unguarded x pvs pargs) args
686 | (P _ n _, args) <- unApply ap
687 -- Ordinary call, not under a delay.
688 -- If n is a constructor, set 'args' as Guarded
689 = let nguarded = case guarded of
690 Unguarded -> Unguarded
691 x -> if isConName n (tt_ctxt ist)
695 mkChange n args pargs ++
696 concatMap (\x -> findCalls cases nguarded x pvs pargs) args
697 where notPartial (Just (Partial NotCovering)) = False
699 findCalls cases guarded (App _ f a) pvs pargs
700 = findCalls cases Unguarded f pvs pargs ++ findCalls cases Unguarded a pvs pargs
701 findCalls cases guarded (Bind n (Let t v) e) pvs pargs
702 = findCalls cases Unguarded t pvs pargs ++
703 findCalls cases Unguarded v pvs pargs ++
704 -- Substitute in the scope since this might reveal some useful
706 findCalls cases guarded (substV v e) pvs pargs
707 findCalls cases guarded (Bind n t e) pvs pargs
708 = findCalls cases Unguarded (binderTy t) pvs pargs ++
709 findCalls cases guarded e (n : pvs) pargs
710 findCalls cases guarded (P _ f _ ) pvs pargs
711 | not (f `elem` pvs) = [(f, [])]
712 findCalls _ _ _ _ _ = []
714 -- Assumption is that names are preserved in the case block (shadowing
715 -- dealt with by the elaborator) so we can just assume the top level names
716 -- are okay for building the size change
717 findCallsCase cases guarded n args pvs pargs
718 = case lookupDefExact n (tt_ctxt ist) of
719 Just (CaseOp _ _ _ pats _ cd) ->
720 concatMap (fccPat cases pvs pargs args guarded) (rights pats)
723 fccPat cases pvs pargs args g (lhs, rhs)
724 = let lhs' = delazy lhs
726 (f, pargs_case) = unApply (dePat lhs')
727 -- pargs is a pair of a term, and the argument position that
728 -- term appears in. If any of the arguments to the case block
729 -- are also on the lhs, we also want those patterns to appear
730 -- in the parg list so that we'll spot patterns which are
732 newpargs = newPArg args pargs
733 -- Now need to update the rhs of the case with the names in the
734 -- outer definition: In rhs', wherever we see what's in pargs_case,
735 -- replace it with the corresponding thing in pargs
736 csubs = zip pargs_case args
737 newrhs = doCaseSubs csubs (dePat rhs')
738 pargs' = pargs ++ addPArg newpargs pargs_case in
739 findCalls cases g newrhs pvs pargs'
741 doCaseSubs [] tm = tm
742 doCaseSubs ((x, x') : cs) tm
743 = doCaseSubs (subIn x x' cs) (substTerm x x' tm)
746 subIn x x' ((l, r) : cs)
747 = (substTerm x x' l, substTerm x x' r) : subIn x x' cs
749 addPArg (Just (t, i) : ts) (t' : ts') = (t', i) : addPArg ts ts'
750 addPArg (Nothing : ts) (t' : ts') = addPArg ts ts'
753 newPArg :: [Term] -> [(Term, Int)] -> [Maybe (Term, Int)]
754 newPArg (t : ts) pargs = case lookup t pargs of
755 Just i -> Just (t, i) : newPArg ts pargs
756 Nothing -> Nothing : newPArg ts pargs
757 newPArg [] pargs = []
760 = case lookupTy n (tt_ctxt ist) of
761 [ty] -> expand 0 (normalise (tt_ctxt ist) [] ty) args
763 where expand i (Bind n (Pi _ _ _) sc) (x : xs) = x : expand (i + 1) sc xs
764 expand i (Bind n (Pi _ _ _) sc) [] = Just (i, Same) : expand (i + 1) sc []
767 mkChange n args pargs = [(n, expandToArity n (sizes args))]
770 sizes (a : as) = checkSize a pargs : sizes as
772 -- find which argument in pargs <a> is smaller than, if any
773 checkSize a ((p, i) : ps)
774 | a == p = Just (i, Same)
775 | (P _ (UN as) _, [_,_,arg,_]) <- unApply a,
776 as == txt "assert_smaller" && arg == p
778 | smaller Nothing a (p, Nothing) = Just (i, Smaller)
779 | otherwise = checkSize a ps
780 checkSize a [] = Nothing
782 -- the smaller thing we find must be defined in the same group of mutally
783 -- defined types as <a>, and not be coinductive - so carry the type of
784 -- the constructor we've gone under.
786 smaller (Just tyn) a (t, Just tyt)
787 | a == t = isInductive (fst (unApply (getRetTy tyn)))
788 (fst (unApply (getRetTy tyt)))
789 smaller ty a (ap@(App _ f s), _)
790 | (P (DCon _ _ _) n _, args) <- unApply ap
791 = let tyn = getType n in
792 any (smaller (ty `mplus` Just tyn) a)
793 (zip args (map toJust (getArgTys tyn)))
794 -- check higher order recursive arguments
795 smaller ty (App _ f s) a = smaller ty f a
796 smaller _ _ _ = False
798 toJust (n, t) = Just t
800 getType n = case lookupTyExact n (tt_ctxt ist) of
801 Just ty -> delazy (normalise (tt_ctxt ist) [] ty) -- must exist
803 isInductive (P _ nty _) (P _ nty' _) =
804 let (co, muts) = case lookupCtxt nty (idris_datatypes ist) of
805 [TI _ x _ _ muts] -> (x, muts)
807 (nty == nty' || any (== nty') muts) && not co
808 isInductive _ _ = False
810 dePat (Bind x (PVar ty) sc) = dePat (instantiate (P Bound x ty) sc)
813 patvars (Bind x (PVar _) sc) = x : patvars sc
816 allGuarded n ist = case lookupCtxtExact n (idris_flags ist) of
818 Just fs -> AllGuarded `elem` fs
820checkSizeChange :: Name -> Idris Totality
821checkSizeChange n = do
823 case lookupCtxt n (idris_callgraph ist) of
824 [cg] -> do let ms = mkMultiPaths ist [] (scg cg)
825 logCoverage 5 ("Multipath for " ++ show n ++ ":\n" ++
826 "from " ++ show (scg cg) ++ "\n" ++
827 show (length ms) ++ "\n" ++
828 showSep "\n" (map show ms))
829 logCoverage 6 (show cg)
830 -- every multipath must have an infinitely descending
831 -- thread, then the function terminates
832 -- also need to checks functions called are all total
833 -- (Unchecked is okay as we'll spot problems here)
834 let tot = map (checkMP ist n (getArity ist n)) ms
835 logCoverage 4 $ "Generated " ++ show (length tot) ++ " paths"
836 logCoverage 5 $ "Paths for " ++ show n ++ " yield " ++
837 (showSep "\n" (map show (zip ms tot)))
838 return (noPartial tot)
839 [] -> do logCoverage 5 $ "No paths for " ++ show n
842 = case lookupTy n (tt_ctxt ist) of
843 [ty] -> arity (normalise (tt_ctxt ist) [] ty)
844 _ -> error "Can't happen: checkSizeChange.getArity"
846type MultiPath = [SCGEntry]
848mkMultiPaths :: IState -> MultiPath -> [SCGEntry] -> [MultiPath]
849mkMultiPaths ist path [] = [reverse path]
850mkMultiPaths ist path cg = concatMap extend cg
851 where extend (nextf, args)
852 | (nextf, args) `inPath` path = [ reverse ((nextf, args) : path) ]
853 | [Unchecked] <- lookupTotal nextf (tt_ctxt ist)
854 = case lookupCtxt nextf (idris_callgraph ist) of
855 [ncg] -> mkMultiPaths ist ((nextf, args) : path) (scg ncg)
856 _ -> [ reverse ((nextf, args) : path) ]
857 | otherwise = [ reverse ((nextf, args) : path) ]
859 inPath :: SCGEntry -> [SCGEntry] -> Bool
861 inPath f (g : gs) = smallerEq f g || f == g || inPath f gs
863 smallerEq :: SCGEntry -> SCGEntry -> Bool
864 smallerEq (f, args) (g, args')
865 = f == g && not (null (filter smallers args))
866 && filter smallers args == filter smallers args'
867 smallers (Just (_, Smaller)) = True
870-- If any route along the multipath leads to infinite descent, we're fine.
871-- Try a route beginning with every argument.
872-- If we reach a point we've been to before, but with a smaller value,
873-- that means there is an infinitely descending path from that argument.
875checkMP :: IState -> Name -> Int -> MultiPath -> Totality
876checkMP ist topfn i mp = if i > 0
877 then let paths = (map (tryPath 0 [] mp) [0..i-1]) in
878-- trace ("Paths " ++ show paths) $
880 else tryPath 0 [] mp 0
882 tryPath' d path mp arg
883 = let res = tryPath d path mp arg in
884 trace (show mp ++ "\n" ++ show arg ++ " " ++ show res) res
886 mkBig (e, d) = (e, 10000)
888 tryPath :: Int -> [((SCGEntry, Int), Int)] -> MultiPath -> Int -> Totality
889 tryPath desc path [] _ = Total []
890-- tryPath desc path ((UN "believe_me", _) : _) arg
891-- = Partial BelieveMe
892 -- if we get to a constructor, it's fine as long as it's strictly positive
893 tryPath desc path ((f, _) : es) arg
894 | [TyDecl (DCon _ _ _) _] <- lookupDef f (tt_ctxt ist)
895 = case lookupTotalExact f (tt_ctxt ist) of
896 Just (Total _) -> Unchecked -- okay so far
897 Just (Partial _) -> Partial (Other [f])
898 x -> Unchecked -- An error elsewhere, set as Unchecked to make
900 | [TyDecl (TCon _ _) _] <- lookupDef f (tt_ctxt ist)
902 tryPath desc path (e@(f, args) : es) arg
903 | [Total a] <- lookupTotal f (tt_ctxt ist) = Total a
904 | e `elem` es && allNothing args = Partial (Mutual [f])
905 tryPath desc path (e@(f, nextargs) : es) arg
906 | [Total a] <- lookupTotal f (tt_ctxt ist) = Total a
907 | [Partial _] <- lookupTotal f (tt_ctxt ist) = Partial (Other [f])
908 | Just d <- lookup (e, arg) path
909 = if desc - d > 0 -- Now lower than when we were last here
910 then -- trace ("Descent " ++ show (desc - d) ++ " "
911 -- ++ show (path, e)) $
913 else Partial (Mutual (map (fst . fst . fst) path ++ [f]))
914 | e `elem` map (fst . fst) path
915 && not (f `elem` map fst es)
916 = Partial (Mutual (map (fst . fst . fst) path ++ [f]))
917 | [Unchecked] <- lookupTotal f (tt_ctxt ist) =
918 let argspos = zip nextargs [0..]
920 do (a, pos) <- argspos
922 Nothing -> -- gone up, but if the
923 -- rest definitely terminates without any
924 -- cycles (including the path so far, which
925 -- we take as inaccessible) the path is fine
926 return $ tryPath 0 (map mkBig (((e, arg), desc) : path)) es pos
927 Just (nextarg, sc) ->
928 if nextarg == arg then
930 Same -> return $ tryPath desc (((e, arg), desc) : path)
932 Smaller -> return $ tryPath (desc+1)
933 (((e, arg), desc) : path)
936 _ -> trace ("Shouldn't happen " ++ show e) $
937 return (Partial Itself)
938 else return Unchecked in
939-- trace (show (desc, argspos, path, es, pathres)) $
942 | otherwise = Unchecked
944allNothing :: [Maybe a] -> Bool
945allNothing xs = null (collapseNothing (zip xs [0..]))
947collapseNothing :: [(Maybe a, b)] -> [(Maybe a, b)]
948collapseNothing ((Nothing, t) : xs)
949 = (Nothing, t) : filter (\ (x, _) -> case x of
952collapseNothing (x : xs) = x : collapseNothing xs
953collapseNothing [] = []
955noPartial :: [Totality] -> Totality
956noPartial (Partial p : xs) = Partial p
957noPartial (_ : xs) = noPartial xs
958noPartial [] = Total []
960collapse :: [Totality] -> Totality
961collapse xs = collapse' Unchecked xs
962collapse' def (Total r : xs) = Total r
963collapse' def (Unchecked : xs) = collapse' def xs
964collapse' def (d : xs) = collapse' d xs
965-- collapse' Unchecked [] = Total []
966collapse' def [] = def
968totalityCheckBlock :: Idris ()
969totalityCheckBlock = do
971 -- Do totality checking after entire mutual block
972 mapM_ (\n -> do logElab 5 $ "Simplifying " ++ show n
973 ctxt' <- do ctxt <- getContext
974 tclift $ simplifyCasedef n (getErasureInfo ist) ctxt
976 (map snd (idris_totcheck ist))
977 mapM_ buildSCG (idris_totcheck ist)
978 mapM_ checkDeclTotality (idris_totcheck ist)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list