on master help sign in
Make totality checker to report partial functions correctly (fix #2884)

Totality checker incorrectly reported some partial functions with complex arguments (data constructors inside other data constructors) as total. For example, these functions were considered total: h : (Bool, Bool) -> Bool h (True, x) = True h (y, True) = True data PP : Type where MkPP : Int -> PP f : PP -> Bool f (MkPP 0) = True f (MkPP 1) = True f (MkPP 2) = True This commit fixes this and now such functions are reported as partial. To fix this we generate more clauses which cover more possible cases.

commit9cc36e86af49fb43e2517d4dff027a617f566743 authorVitaly Bragilevsky <bravit111@gmail.com> dateWed 10 Aug 2016, 16:17 (10 years ago) parent64ee875
9 files changed+177−38
CONTRIBUTORS+1 src/Idris/Coverage.hs+55−37 test/TestData.hs+3−1 test/totality016/expected+9 test/totality016/run+3 test/totality016/totality016.idr+48 test/totality017/expected+6 test/totality017/run+3 test/totality017/totality017.idr+49
CONTRIBUTORS+1
@@ -7,6 +7,7 @@ Michael R. Bernstein
77Jan Bessai
88Nicola Botta
99Edwin Brady
10+Vitaly Bragilevsky
1011Jakob Brünker
1112Alyssa Carter
1213Carter Charbonneau
src/Idris/Coverage.hs+55−37
@@ -17,6 +17,7 @@ import Idris.Delaborate
1717import Idris.Error
1818import Idris.Output (iWarn, iputStrLn)
1919
20+import Data.Char
2021import Data.List
2122import Data.Either
2223import Data.Maybe
@@ -203,46 +204,62 @@ recoverableCoverage _ _ = False
203204
204205genAll :: IState -> (Bool, [PTerm]) -> [PTerm]
205206genAll i (addPH, args)
206− = case filter (/=Placeholder) $ fnub (concatMap otherPats (fnub args)) of
207+ = case filter (/=Placeholder) $ fnub $ enrichPats $ fnub (concatMap otherPats (fnub args)) of
207208 [] -> [Placeholder]
208− xs -> ph $ inventConsts xs
209+ xs -> ph xs
209210 where
210211 ph ts = if addPH then Placeholder : ts else ts
211212
212− -- if they're constants, invent a new one to make sure that
213− -- constants which are not explicitly handled are covered
214− inventConsts cs@(PConstant fc c : _) = map (PConstant NoFC) (ic' (mapMaybe getConst cs))
215− where getConst (PConstant _ c) = Just c
216− getConst _ = Nothing
217− inventConsts xs = xs
218−
219− -- try constants until they're not in the list.
220− -- FIXME: It is, of course, possible that someone has enumerated all
221− -- the constants and matched on them (maybe in generated code) and this
222− -- will be really slow. This is sufficiently unlikely that we won't
223− -- worry for now...
224−
225− ic' xs@(I _ : _) = firstMissing xs (lotsOfNums I)
226− ic' xs@(BI _ : _) = firstMissing xs (lotsOfNums BI)
227− ic' xs@(Fl _ : _) = firstMissing xs (lotsOfNums Fl)
228− ic' xs@(B8 _ : _) = firstMissing xs (lotsOfNums B8)
229− ic' xs@(B16 _ : _) = firstMissing xs (lotsOfNums B16)
230− ic' xs@(B32 _ : _) = firstMissing xs (lotsOfNums B32)
231− ic' xs@(B64 _ : _) = firstMissing xs (lotsOfNums B64)
232− ic' xs@(Ch _ : _) = firstMissing xs lotsOfChars
233− ic' xs@(Str _ : _) = firstMissing xs lotsOfStrings
234− -- The rest are types with only one case
235− ic' xs = xs
236−
237− firstMissing cs (x : xs) | x `elem` cs = firstMissing cs xs
238− | otherwise = x : cs
239−
240− lotsOfNums t = map t [0..]
241− lotsOfChars = map Ch ['a'..]
242− lotsOfStrings = map Str (map (("some string " ++).show) [1..])
243−
244− nubMap f acc [] = acc
245− nubMap f acc (x : xs) = nubMap f (fnub' acc (f x)) xs
213+ -- for every constant in a term (at any level) take next one to make sure
214+ -- that constants which are not explicitly handled are covered
215+ nextConst (PConstant fc c) = PConstant NoFC (nc' c)
216+ nextConst o = o
217+
218+ nc' (I c) = I (c + 1)
219+ nc' (BI c) = BI (c + 1)
220+ nc' (Fl c) = Fl (c + 1)
221+ nc' (B8 c) = B8 (c + 1)
222+ nc' (B16 c) = B16 (c + 1)
223+ nc' (B32 c) = B32 (c + 1)
224+ nc' (B64 c) = B64 (c + 1)
225+ nc' (Ch c) = Ch (chr $ ord c + 1)
226+ nc' (Str c) = Str (c ++ "'")
227+ nc' o = o
228+
229+ -- enrich patterns with cartesian products so that
230+ -- (True, _) and (_, True) give (True, True)
231+ -- with all other combinations to be checked for coverage later
232+ enrichPats :: [PTerm] -> [PTerm]
233+ enrichPats xs = xs ++ enrichPats' xs
234+
235+ enrichPats' :: [PTerm] -> [PTerm]
236+ enrichPats' [] = []
237+ enrichPats' [x] = [x]
238+ enrichPats' (x : xs) = concatMap (combinePHPats x) xs ++ enrichPats' xs
239+
240+ combinePHPats :: PTerm -> PTerm -> [PTerm]
241+ combinePHPats Placeholder x = [x]
242+ combinePHPats x Placeholder = [x]
243+ combinePHPats (PPair fc1 hls1 pi1 l1 r1) (PPair fc2 hls2 pi2 l2 r2) =
244+ let ls = enrichPats $ combinePHPats l1 l2
245+ rs = enrichPats $ combinePHPats r1 r2
246+ in [ PPair fc1 hls1 pi1 l r | l <- ls, r <- rs]
247+ combinePHPats (PDPair fc1 hls1 p1 t1 t v1) (PDPair fc2 hls2 p2 t2 _ v2) =
248+ let ts = enrichPats $ combinePHPats t1 t2
249+ vs = enrichPats $ combinePHPats v1 v2
250+ in [ PDPair fc1 hls1 p1 t' t v' | t' <- ts, v' <- vs]
251+ combinePHPats p1@(PApp fc x xs@(_:_)) p2@(PApp _ x' xs')
252+ | quickEq x x' && length xs == length xs' =
253+ let ls = map getExpTm xs
254+ rs = map getExpTm xs'
255+ pss = cph $ zipWith combinePHPats ls rs
256+ in (map (\ps -> resugar (PApp fc x $ zipWith upd ps xs)) pss)
257+ combinePHPats x y = [x, y]
258+
259+ cph :: [[PTerm]] -> [[PTerm]]
260+ cph [] = []
261+ cph [x] = [x]
262+ cph (xs : xss) = concatMap (\x -> map (x:) $ cph xss) xs
246263
247264 otherPats :: PTerm -> [PTerm]
248265 otherPats o@(PRef fc hl n) = ph $ ops fc n [] o
@@ -257,7 +274,7 @@ genAll i (addPH, args)
257274 ([pimp (sUN "a") Placeholder True,
258275 pimp (sUN "P") Placeholder True] ++
259276 [pexp t,pexp v]) o
260− otherPats o@(PConstant _ c) = ph $ inventConsts [o] -- return o
277+ otherPats o@(PConstant _ c) = ph [o, nextConst o]
261278 otherPats arg = return Placeholder
262279
263280 ops fc n xs o
@@ -311,6 +328,7 @@ genAll i (addPH, args)
311328 quickEq :: PTerm -> PTerm -> Bool
312329 quickEq (PConstant _ n) (PConstant _ n') = n == n'
313330 quickEq (PRef _ _ n) (PRef _ _ n') = n == n'
331+ quickEq (PPair _ _ _ l r) (PPair _ _ _ l' r') = quickEq l l' && quickEq r r'
314332 quickEq (PApp _ t as) (PApp _ t' as')
315333 | length as == length as'
316334 = quickEq t t' && and (zipWith quickEq (map getTm as) (map getTm as'))
test/TestData.hs+3−1
@@ -268,7 +268,9 @@ testFamiliesData = [
268268 ( 12, ANY ),
269269 ( 13, ANY ),
270270 ( 14, ANY ),
271− ( 15, ANY )]),
271+ ( 15, ANY ),
272+ ( 16, ANY ),
273+ ( 17, ANY )]),
272274 ("tutorial", "Tutorial examples",
273275 [ ( 1, ANY ),
274276 ( 2, ANY ),
test/totality016/expectednew file+9
@@ -0,0 +1,9 @@
1+totality016.idr:15:1:Main.f1 is not total as there are missing cases
2+totality016.idr:19:1:Main.f2 is not total as there are missing cases
3+totality016.idr:23:1:Main.f3 is not total as there are missing cases
4+totality016.idr:27:1:Main.g1 is not total as there are missing cases
5+totality016.idr:31:1:Main.g2 is not total as there are missing cases
6+totality016.idr:35:1:Main.h1 is not total as there are missing cases
7+totality016.idr:39:1:Main.h2 is not total as there are missing cases
8+totality016.idr:43:1:Main.h3 is not total as there are missing cases
9+totality016.idr:47:1:Main.h4 is not total as there are missing cases
test/totality016/runnew file+3
@@ -0,0 +1,3 @@
1+#!/usr/bin/env bash
2+${IDRIS:-idris} $@ totality016.idr --check
3+rm -f *.ibc
test/totality016/totality016.idrnew file+48
@@ -0,0 +1,48 @@
1+%default total
2+
3+data TS : Type where
4+ T1 : TS
5+ T2 : TS
6+ T3 : TS
7+
8+data TP : Type where
9+ MkTP : TS -> TS -> TP
10+
11+data TP2 : Type where
12+ MkTP2 : (TS, TS) -> TP2
13+
14+f1 : TP -> Bool
15+f1 (MkTP T1 x) = True
16+f1 (MkTP x T1) = True
17+
18+f2 : (TP, TS) -> Bool
19+f2 (MkTP T1 x, _) = True
20+f2 (MkTP x T1, _) = True
21+
22+f3 : ((TP, TS), TS) -> Bool
23+f3 ((MkTP T1 x, _), _) = True
24+f3 ((MkTP x T1, _), _) = True
25+
26+g1 : TS -> TS -> Bool
27+g1 T1 x = True
28+g1 x T1 = True
29+
30+g2 : (TS, TS) -> Bool
31+g2 (T1, x) = True
32+g2 (y, T1) = True
33+
34+h1 : Bool -> Bool -> Bool
35+h1 True x = True
36+h1 x True = True
37+
38+h2 : (Bool, Bool) -> Bool
39+h2 (True, x) = True
40+h2 (y, True) = True
41+
42+h3 : TP2 -> Bool
43+h3 (MkTP2 (T1, x)) = True
44+h3 (MkTP2 (x, T1)) = True
45+
46+h4 : (TP2, TS) -> TS -> Bool
47+h4 (MkTP2 (T1, x), _) _ = True
48+h4 (MkTP2 (x, T1), _) _ = True
test/totality017/expectednew file+6
@@ -0,0 +1,6 @@
1+totality017.idr:19:1:Main.g1 is not total as there are missing cases
2+totality017.idr:25:1:Main.g2 is not total as there are missing cases
3+totality017.idr:31:1:Main.g3 is not total as there are missing cases
4+totality017.idr:37:1:Main.g4 is not total as there are missing cases
5+totality017.idr:42:1:Main.f' is not total as there are missing cases
6+totality017.idr:47:1:Main.f is not total as there are missing cases
test/totality017/runnew file+3
@@ -0,0 +1,3 @@
1+#!/usr/bin/env bash
2+${IDRIS:-idris} $@ totality017.idr --check
3+rm -f *.ibc
test/totality017/totality017.idrnew file+49
@@ -0,0 +1,49 @@
1+%default total
2+
3+data PP : Type where
4+ MkPP : Int -> PP
5+
6+data TT' : Nat -> Type where
7+ T1 : TT' 0
8+ T2 : (n : Nat) -> TT' (S n)
9+
10+data TT'' : Int -> Type where
11+ T1' : TT'' 0
12+ T2' : (n : Int) -> TT'' (n + 1)
13+
14+data TTPP : Type where
15+ MkTTPP : PP -> PP -> TTPP
16+
17+
18+g1 : TT' n -> TT' n -> Bool
19+g1 T1 T1 = True
20+g1 (T2 Z) (T2 Z) = True
21+g1 (T2 (S Z)) (T2 (S Z)) = True
22+g1 (T2 (S (S Z))) (T2 (S (S Z))) = True
23+
24+g2 : TT'' n -> TT'' n -> Bool
25+g2 T1' T1' = True
26+g2 (T2' 0) (T2' 0) = True
27+g2 (T2' 1) (T2' 1) = True
28+g2 (T2' 2) (T2' 2) = True
29+
30+g3 : TT'' n -> Bool
31+g3 T1' = True
32+g3 (T2' 0) = True
33+g3 (T2' 1) = True
34+g3 (T2' 2) = True
35+
36+g4 : TTPP -> Bool
37+g4 (MkTTPP (MkPP 0) (MkPP 0)) = True
38+g4 (MkTTPP (MkPP 1) (MkPP 1)) = True
39+g4 (MkTTPP (MkPP 2) (MkPP n)) = True
40+
41+f' : Int -> Bool
42+f' 0 = True
43+f' 1 = True
44+f' 2 = True
45+
46+f : PP -> Bool
47+f (MkPP 0) = True
48+f (MkPP 1) = True
49+f (MkPP 2) = True