on master help sign in
New coverage checker
61d5865Edwin Brady
CHANGELOG.md+1
@@ -62,6 +62,7 @@
62
62
63## Miscellaneous updates
63## Miscellaneous updates
64
64
65+* New, faster, better, implementation of the coverage checker
65* The test suite now uses [tasty-golden](https://hackage.haskell.org/package/tasty-golden). New tests must be registered in `test/TestData.hs`, as explained in the relevant `README.md`.
66* The test suite now uses [tasty-golden](https://hackage.haskell.org/package/tasty-golden). New tests must be registered in `test/TestData.hs`, as explained in the relevant `README.md`.
66* Added OSX and Windows continous integration with Travis and Appveyor.
67* Added OSX and Windows continous integration with Travis and Appveyor.
67
68
libs/base/Data/List/Views.idr+1−1
@@ -187,7 +187,7 @@ public export
187data VList : List a -> Type where
187data VList : List a -> Type where
188 VNil : VList []
188 VNil : VList []
189 VOne : VList [x]
189 VOne : VList [x]
190− VCons : {x : a} -> {y : a} -> {xs : List a} ->
190+ VCons : {x : a} -> {y : a} -> .{xs : List a} ->
191 (rec : VList xs) -> VList (x :: xs ++ [y])
191 (rec : VList xs) -> VList (x :: xs ++ [y])
192
192
193total
193total
src/Idris/Core/CaseTree.hs+21−3
@@ -244,14 +244,18 @@ type CaseBuilder a = ReaderT ErasureInfo (State CS) a
244runCaseBuilder :: ErasureInfo -> CaseBuilder a -> (CS -> (a, CS))
244runCaseBuilder :: ErasureInfo -> CaseBuilder a -> (CS -> (a, CS))
245runCaseBuilder ei bld = runState $ runReaderT bld ei
245runCaseBuilder ei bld = runState $ runReaderT bld ei
246
246
247+data Phase = CoverageCheck [Int] -- list of positions explicitly given
247−data Phase = CompileTime | RunTime
248+ | CompileTime
249+ | RunTime
248 deriving (Show, Eq)
250 deriving (Show, Eq)
249
251
250-- Generate a simple case tree
252-- Generate a simple case tree
251-- Work Right to Left
253-- Work Right to Left
252
254
253simpleCase :: Bool -> SC -> Bool ->
255simpleCase :: Bool -> SC -> Bool ->
254− Phase -> FC -> [Int] ->
256+ Phase -> FC ->
257+ -- Following two can be empty lists when Phase = CoverageCheck
258+ [Int] -> -- Inaccessible argument positions
255 [(Type, Bool)] -> -- (Argument type, whether it's canonical)
259 [(Type, Bool)] -> -- (Argument type, whether it's canonical)
256 [([Name], Term, Term)] ->
260 [([Name], Term, Term)] ->
257 ErasureInfo ->
261 ErasureInfo ->
@@ -291,7 +295,7 @@ simpleCase tc defcase reflect phase fc inacc argtys cs erInfo
291 -- Check that all pattern variables are reachable by a case split
295 -- Check that all pattern variables are reachable by a case split
292 -- Otherwise, they won't make sense on the RHS.
296 -- Otherwise, they won't make sense on the RHS.
293 chkAccessible (avs, l, c)
297 chkAccessible (avs, l, c)
294− | phase == RunTime || reflect = return (l, c)
298+ | phase /= CompileTime || reflect = return (l, c)
295 | otherwise = do mapM_ (acc l) avs
299 | otherwise = do mapM_ (acc l) avs
296 return (l, c)
300 return (l, c)
297
301
@@ -438,6 +442,20 @@ order :: Phase -> [(Name, Bool)] -> [Clause] -> [Bool] -> ([Name], [Clause])
438-- order CompileTime ns cs _ = (map fst ns, cs)
442-- order CompileTime ns cs _ = (map fst ns, cs)
439order _ [] cs cans = ([], cs)
443order _ [] cs cans = ([], cs)
440order _ ns' [] cans = (map fst ns', [])
444order _ ns' [] cans = (map fst ns', [])
445+order (CoverageCheck pos) ns' cs cans
446+ = let ns_out = pick 0 [] (map fst ns')
447+ cs_out = map pickClause cs in
448+ (ns_out, cs_out)
449+ where
450+ pickClause (pats, def) = (pick 0 [] pats, def)
451+
452+ -- Order the list so that things in a position in 'pos' are in the first
453+ -- part, then all the other things later. Otherwise preserve order.
454+ pick i skipped [] = reverse skipped
455+ pick i skipped (x : xs)
456+ | i `elem` pos = x : pick (i + 1) skipped xs
457+ | otherwise = pick (i + 1) (x : skipped) xs
458+
441order phase ns' cs cans
459order phase ns' cs cans
442 = let patnames = transpose (map (zip ns') (map (zip cans) (map fst cs)))
460 = let patnames = transpose (map (zip ns') (map (zip cans) (map fst cs)))
443 -- only sort the arguments where there is no clash in
461 -- only sort the arguments where there is no clash in
src/Idris/Coverage.hs+258−249
@@ -6,7 +6,8 @@ License : BSD3
6Maintainer : The Idris Community.
6Maintainer : The Idris Community.
7-}
7-}
8{-# LANGUAGE PatternGuards #-}
8{-# LANGUAGE PatternGuards #-}
9−module Idris.Coverage where
9+module Idris.Coverage(genClauses, validCoverageCase, recoverableCoverage,
10+ mkPatTm) where
10
11
11import Idris.AbsSyntax
12import Idris.AbsSyntax
12import Idris.Core.CaseTree
13import Idris.Core.CaseTree
@@ -61,100 +62,267 @@ mkPatTm t = do i <- getIState
61--
62--
62-- This will only work after the given clauses have been typechecked and the
63-- This will only work after the given clauses have been typechecked and the
63-- names are fully explicit!
64-- names are fully explicit!
65+genClauses :: FC -> Name -> [([Name], Term)] -> -- (Argument names, LHS)
64−genClauses :: FC -> Name -> [Term] -> [PTerm] -> Idris [PTerm]
66+ [PTerm] -> Idris [PTerm]
67+-- No clauses (only valid via elab reflection). We should probably still do
68+-- a check here somehow, e.g. that one of the arguments is an obviously
69+-- empty type. In practice, this should only really be used for Void elimination.
70+genClauses fc n lhs_tms [] = return []
65−genClauses fc n xs given
71+genClauses fc n lhs_tms given
66 = do i <- getIState
72 = do i <- getIState
67− let lhs_tms = map (\x -> flattenArgs $ delab' i x True True) xs
73+
68− -- if a placeholder was given, don't bother generating cases for it
69− let lhs_tms' = zipWith mergePlaceholders lhs_tms
74+ let lhs_given = zipWith removePlaceholders lhs_tms
70− (map (stripUnmatchable i) (map flattenArgs given))
75+ (map (stripUnmatchable i) (map flattenArgs given))
71− let lhss = map pUnApply lhs_tms'
76+
72− nty <- case lookupTyExact n (tt_ctxt i) of
77+ let givenpos = mergePos (map getGivenPos given)
73− Just t -> return (normalise (tt_ctxt i) [] t)
78+
74− _ -> fail "Can't happen - genClauses, lookupTyExact"
79+ (cns, ctree_in) <-
75−
80+ case simpleCase False (UnmatchedCase "Undefined") False
76− let argss = transpose lhss
81+ (CoverageCheck givenpos) emptyFC [] []
77− let all_args = map (genAll i) (zip (genPH i nty) argss)
82+ lhs_given
78− logCoverage 5 $ "COVERAGE of " ++ show n
83+ (const []) of
79− logCoverage 5 $ "using type " ++ show nty
84+ OK (CaseDef cns ctree_in _) ->
80− logCoverage 5 $ "non-concrete args " ++ show (genPH i nty)
85+ return (cns, ctree_in)
81−
86+ Error e -> tclift $ tfail $ At fc e
82− logCoverage 5 $ show (lhs_tms, lhss)
87+
83− logCoverage 5 $ "Args are: " ++ show argss
88+ let ctree = trimOverlapping (addMissingCons i ctree_in)
84− logCoverage 5 $ show (map length argss) ++ "\nAll args:\n" ++ show (map length all_args)
89+ let (coveredas, missingas) = mkNewClauses (tt_ctxt i) n cns ctree
85− logCoverage 10 $ "All args are: " ++ show all_args
90+ let covered = map (\t -> delab' i t True True) coveredas
86− logCoverage 3 $ "Original: \n" ++
91+ let missing = filter (\x -> x `notElem` covered) $
87− showSep "\n" (map (\t -> showTmImpls (delab' i t True True)) xs)
92+ map (\t -> delab' i t True True) missingas
88− -- add an infinite supply of explicit arguments to update the possible
93+
89− -- cases for (the return type may be variadic, or function type, so
94+ logCoverage 5 $ "Coverage from case tree for " ++ show n ++ ": " ++ show ctree
90− -- there may be more case splitting that the idris_implicits record
91− -- suggests)
92− let parg = case lookupCtxt n (idris_implicits i) of
93− (p : _) ->
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−
100− let xsdelab = map (\x -> stripUnmatchable i (delab' i x True True)) xs
101− let new = filter (noMatch i xsdelab) tryclauses
102− logCoverage 2 $ show (length new) ++ " clauses to check for impossibility"
95+ logCoverage 2 $ show (length missing) ++ " missing clauses for " ++ show n
103− logCoverage 4 $ "New clauses: \n" ++ showSep "\n" (map (showTm i) new)
96+ logCoverage 3 $ "Missing clauses:\n" ++ showSep "\n"
104−-- ++ " from:\n" ++ showSep "\n" (map (showImp True) tryclauses)
97+ (map showTmImpls missing)
98+ logCoverage 10 $ "Covered clauses:\n" ++ showSep "\n"
99+ (map showTmImpls covered)
105− return new
100+ return missing
106−-- return (map (\t -> PClause n t [] PImpossible []) new)
107− where getLHS i term
101+ where
108− | (f, args) <- unApply term = map (\t -> delab' i t True True) args
109− | otherwise = []
110−
111− -- if an argument has a non-concrete type (i.e. it's a function
112− -- that calculates something from a previous argument) we'll also
113− -- need to generate a placeholder pattern for it since it could be
114− -- anything based on earlier values
115− genPH :: IState -> Type -> [Bool]
116− genPH ist (Bind n (Pi _ ty _) sc) = notConcrete ist ty : genPH ist sc
117− genPH ist ty = []
118−
119− notConcrete ist t | (P _ n _, args) <- unApply t,
120− not (isConName n (tt_ctxt ist)) = True
121− notConcrete _ _ = False
122−
123− pUnApply (PApp _ f args) = map getTm args
124− pUnApply _ = []
125−
126 flattenArgs (PApp fc (PApp _ f as) as')
102 flattenArgs (PApp fc (PApp _ f as) as')
127 = flattenArgs (PApp fc f (as ++ as'))
103 = flattenArgs (PApp fc f (as ++ as'))
128 flattenArgs t = t
104 flattenArgs t = t
129
105
130− -- Return whether the given clause matches none of the input clauses
106+getGivenPos :: PTerm -> [Int]
131− -- (xs)
107+getGivenPos (PApp _ _ pargs) = getGiven 0 (map getTm pargs)
132− noMatch i xs tm = all (\x -> case matchClause i x tm of
108+ where
133− Right ms -> False
109+ getGiven i (Placeholder : tms) = getGiven (i + 1) tms
134− Left miss -> True) xs
110+ getGiven i (_ : tms) = i : getGiven (i + 1) tms
135−
111+ getGiven i [] = []
136−
112+getGivenPos _ = []
137− mergePlaceholders :: PTerm -> PTerm -> PTerm
113+
138− mergePlaceholders x Placeholder = Placeholder
114+-- Return a list of Ints which are in every list
139− mergePlaceholders (PApp fc f args) (PApp fc' f' args')
115+mergePos :: [[Int]] -> [Int]
140− = PApp fc' f' (zipWith mergePArg args args')
116+mergePos [] = []
141− where mergePArg x y = let xtm = mergePlaceholders (getTm x) (getTm y) in
117+mergePos [x] = x
142− x { getTm = xtm}
118+mergePos (x : xs) = intersect x (mergePos xs)
143− mergePlaceholders x _ = x
119+
144−
120+removePlaceholders :: ([Name], Term) -> PTerm -> ([Name], Term, Term)
145− mkClauses :: [PArg] -> [[PTerm]] -> [PTerm]
121+removePlaceholders (ns, tm) ptm = (ns, tm, Erased) -- rp tm ptm, Erased)
146− mkClauses parg args
122+ where
147− | all (== [Placeholder]) args = []
123+ rp tm Placeholder = Erased
148− mkClauses parg args
124+ rp tm (PApp _ pf pargs)
149− = do args' <- mkArg args
125+ | (tf, targs) <- unApply tm
150− let tm = PApp fc (PRef fc [] n) (zipWith upd args' parg)
126+ = let tf' = rp tf pf
151− return tm
127+ targs' = zipWith rp targs (map getTm pargs) in
152− where
128+ mkApp tf' targs'
153− mkArg :: [[PTerm]] -> [[PTerm]]
129+ rp tm (PPair _ _ _ pl pr)
154− mkArg [] = return []
130+ | (tf, [tyl, tyr, tl, tr]) <- unApply tm
155− mkArg (a : as) = do a' <- a
131+ = let tl' = rp tl pl
156− as' <- mkArg as
132+ tr' = rp tr pr in
157− return (a':as')
133+ mkApp tf [Erased, Erased, tl', tr']
134+ rp tm (PDPair _ _ _ pl pt pr)
135+ | (tf, [tyl, tyr, tl, tr]) <- unApply tm
136+ = let tl' = rp tl pl
137+ tr' = rp tr pr in
138+ mkApp tf [Erased, Erased, tl', tr']
139+ rp tm _ = tm
140+
141+mkNewClauses :: Context -> Name -> [Name] -> SC -> ([Term], [Term])
142+mkNewClauses ctxt fn ns sc
143+ = (map (mkPlApp (P Ref fn Erased)) $
144+ mkFromSC True (map (\n -> P Ref n Erased) ns) sc,
145+ map (mkPlApp (P Ref fn Erased)) $
146+ mkFromSC False (map (\n -> P Ref n Erased) ns) sc)
147+ where
148+ mkPlApp f args = mkApp f (map erasePs args)
149+
150+ erasePs ap@(App t f a)
151+ | (f, args) <- unApply ap = mkApp f (map erasePs args)
152+ erasePs (P _ n _) | not (isConName n ctxt) = Erased
153+ erasePs tm = tm
154+
155+ mkFromSC :: Bool -> [Term] -> SC -> [[Term]]
156+ mkFromSC cov args (STerm _) = if cov then [args] else [] -- leaf of provided case
157+ mkFromSC cov args (UnmatchedCase _) = if cov then [] else [args] -- leaf of missing case
158+ mkFromSC cov args ImpossibleCase = []
159+ mkFromSC cov args (Case _ x alts)
160+ = concatMap (mkFromAlt cov args x) alts
161+ mkFromSC cov args _ = [] -- Should never happen
162+
163+ mkFromAlt :: Bool -> [Term] -> Name -> CaseAlt -> [[Term]]
164+ mkFromAlt cov args x (ConCase c t conargs sc)
165+ = let argrep = mkApp (P (DCon t (length args) False) c Erased)
166+ (map (\n -> P Ref n Erased) conargs)
167+ args' = map (subst x argrep) args in
168+ mkFromSC cov args' sc
169+ mkFromAlt cov args x (ConstCase c sc)
170+ = let argrep = Constant c
171+ args' = map (subst x argrep) args in
172+ mkFromSC cov args' sc
173+ mkFromAlt cov args x (DefaultCase sc)
174+ = mkFromSC cov args sc
175+ mkFromAlt cov _ _ _ = []
176+
177+-- Modify the generated case tree (the case tree builder doesn't have access
178+-- to the context, so can't do this itself).
179+-- Replaces any missing cases with explicit cases for the missing constructors
180+addMissingCons :: IState -> SC -> SC
181+addMissingCons ist (Case t n alts) = Case t n (addMissingAlts n alts)
182+ where
183+ addMissingAlt :: CaseAlt -> CaseAlt
184+ addMissingAlt (ConCase n i ns sc) = ConCase n i ns (addMissingCons ist sc)
185+ addMissingAlt (FnCase n ns sc) = FnCase n ns (addMissingCons ist sc)
186+ addMissingAlt (ConstCase c sc) = ConstCase c (addMissingCons ist sc)
187+ addMissingAlt (SucCase n sc) = SucCase n (addMissingCons ist sc)
188+ addMissingAlt (DefaultCase sc) = DefaultCase (addMissingCons ist sc)
189+
190+ -- TODO: deal with constants
191+ addMissingAlts argn as
192+-- | any hasDefault as = map addMissingAlt as
193+ | cons@(n:_) <- mapMaybe collectCons as,
194+ Just tyn <- getConType n,
195+ Just ti <- lookupCtxtExact tyn (idris_datatypes ist)
196+ -- If we've fallen through on this argument earlier, then the
197+ -- things which were matched in other cases earlier can't be missing
198+ -- cases now
199+ = let missing = con_names ti \\ cons in
200+ map addMissingAlt $ addCases missing as
201+ | consts@(n:_) <- mapMaybe collectConsts as
202+ = let missing = nub (map nextConst consts) \\ consts in
203+ map addMissingAlt $ addCons missing as
204+ addMissingAlts n as = map addMissingAlt as
205+
206+ -- Add the constructors already ruled out for an argument (because
207+ -- we've fallen through those cases)
208+ addArgCons argn cons [] = [(argn, cons)]
209+ addArgCons argn cons ((a, cs) : ms)
210+ | a == argn = ((a, nub (cons ++ cs)) : ms)
211+ addArgCons argn cons (m : ms) = m : addArgCons argn cons ms
212+
213+
214+ addCases missing [] = []
215+ addCases missing (DefaultCase rhs : rest)
216+ = mapMaybe (genMissingAlt rhs) missing ++ rest
217+ addCases missing (c : rest) = c : addCases missing rest
218+
219+ addCons missing [] = []
220+ addCons missing (DefaultCase rhs : rest)
221+ = map (genMissingConAlt rhs) missing ++ rest
222+ addCons missing (c : rest) = c : addCons missing rest
223+
224+ genMissingAlt rhs n
225+ | Just (TyDecl (DCon tag arity _) ty) <- lookupDefExact n (tt_ctxt ist)
226+ = Just $ ConCase n tag (map (\i -> sMN i "miss") [0..arity-1])
227+ rhs
228+ | otherwise = Nothing
229+
230+ genMissingConAlt rhs n = ConstCase n rhs
231+
232+ collectCons (ConCase n i args sc) = Just n
233+ collectCons _ = Nothing
234+
235+ collectConsts (ConstCase c sc) = Just c
236+ collectConsts _ = Nothing
237+
238+ hasDefault (DefaultCase (UnmatchedCase _)) = False
239+ hasDefault (DefaultCase _) = True
240+ hasDefault _ = False
241+
242+ getConType n = do ty <- lookupTyExact n (tt_ctxt ist)
243+ case unApply (getRetTy (normalise (tt_ctxt ist) [] ty)) of
244+ (P _ tyn _, _) -> Just tyn
245+ _ -> Nothing
246+
247+ -- for every constant in a term (at any level) take next one to make sure
248+ -- that constants which are not explicitly handled are covered
249+ nextConst (I c) = I (c + 1)
250+ nextConst (BI c) = BI (c + 1)
251+ nextConst (Fl c) = Fl (c + 1)
252+ nextConst (B8 c) = B8 (c + 1)
253+ nextConst (B16 c) = B16 (c + 1)
254+ nextConst (B32 c) = B32 (c + 1)
255+ nextConst (B64 c) = B64 (c + 1)
256+ nextConst (Ch c) = Ch (chr $ ord c + 1)
257+ nextConst (Str c) = Str (c ++ "'")
258+ nextConst o = o
259+
260+addMissingCons ist sc = sc
261+
262+trimOverlapping :: SC -> SC
263+trimOverlapping sc = trim [] [] sc
264+ where
265+ trim :: [(Name, (Name, [Name]))] -> -- Variable - constructor+args already matched
266+ [(Name, [Name])] -> -- Variable - constructors which it can't be
267+ SC -> SC
268+ trim mustbes nots (Case t vn alts)
269+ | Just (c, args) <- lookup vn mustbes
270+ = Case t vn (trimAlts mustbes nots vn (substMatch (c, args) alts))
271+-- = let alts' = filter (isConMatch c) alts in
272+-- Case t vn (trimAlts mustbes nots vn alts')
273+ | Just cantbe <- lookup vn nots
274+ = let alts' = filter (notConMatch cantbe) alts in
275+ Case t vn (trimAlts mustbes nots vn alts')
276+ | otherwise = Case t vn (trimAlts mustbes nots vn alts)
277+ trim cs nots sc = sc
278+
279+ trimAlts cs nots vn [] = []
280+ trimAlts cs nots vn (ConCase cn t args sc : rest)
281+ = ConCase cn t args (trim (addMatch vn (cn, args) cs) nots sc) :
282+ trimAlts cs (addCantBe vn cn nots) vn rest
283+ trimAlts cs nots vn (FnCase n ns sc : rest)
284+ = FnCase n ns (trim cs nots sc) : trimAlts cs nots vn rest
285+ trimAlts cs nots vn (ConstCase c sc : rest)
286+ = ConstCase c (trim cs nots sc) : trimAlts cs nots vn rest
287+ trimAlts cs nots vn (SucCase n sc : rest)
288+ = SucCase n (trim cs nots sc) : trimAlts cs nots vn rest
289+ trimAlts cs nots vn (DefaultCase sc : rest)
290+ = DefaultCase (trim cs nots sc) : trimAlts cs nots vn rest
291+
292+ isConMatch c (ConCase cn t args sc) = c == cn
293+ isConMatch _ _ = False
294+
295+ substMatch :: (Name, [Name]) -> [CaseAlt] -> [CaseAlt]
296+ substMatch ca [] = []
297+ substMatch (c,args) (ConCase cn t args' sc : _)
298+ | c == cn = [ConCase c t args (substNames (zip args' args) sc)]
299+ substMatch ca (_:cs) = substMatch ca cs
300+
301+ substNames [] sc = sc
302+ substNames ((n, n') : ns) sc
303+ | not (missName n || missName n') = substNames ns (substSC n n' sc)
304+ | otherwise = substNames ns sc
305+
306+ notConMatch cs (ConCase cn t args sc) = cn `notElem` cs
307+ notConMatch cs _ = True
308+
309+ addMatch vn (cn, _) cs | missName vn || missName cn = cs
310+ addMatch vn cn cs = (vn, cn) : cs
311+
312+ addCantBe :: Name -> Name -> [(Name, [Name])] -> [(Name, [Name])]
313+ addCantBe vn cn nots | missName vn || missName cn = nots
314+ addCantBe vn cn [] = [(vn, [cn])]
315+ addCantBe vn cn ((n, cbs) : nots)
316+ | vn == n = ((n, nub (cn : cbs)) : nots)
317+ | otherwise = ((n, cbs) : addCantBe vn cn nots)
318+
319+ -- "miss" names are inserted by addMissingCons, and are treated as
320+ -- placeholders throughout, so we need to make a note to treat them as
321+ -- placeholders here too (otherwise we might substitute them and end up
322+ -- matching on them, and that'd be a disaster because we don't take care
323+ -- to make unique names...)
324+ missName (MN i miss) = miss == txt "miss"
325+ missName _ = False
158
326
159-- | Does this error result rule out a case as valid when coverage checking?
327-- | Does this error result rule out a case as valid when coverage checking?
160validCoverageCase :: Context -> Err -> Bool
328validCoverageCase :: Context -> Err -> Bool
@@ -203,162 +371,3 @@ recoverableCoverage ctxt (Elaborating _ _ _ e) = recoverableCoverage ctxt e
203recoverableCoverage ctxt (ElaboratingArg _ _ _ e) = recoverableCoverage ctxt e
371recoverableCoverage ctxt (ElaboratingArg _ _ _ e) = recoverableCoverage ctxt e
204recoverableCoverage _ _ = False
372recoverableCoverage _ _ = False
205
373
206−genAll :: IState -> (Bool, [PTerm]) -> [PTerm]
207−genAll i (addPH, args)
208− = case filter (/=Placeholder) $ fnub $ mergePHs $ fnub (concatMap otherPats (fnub args)) of
209− [] -> [Placeholder]
210− xs -> ph xs
211− where
212− -- Add a placeholder - this is to account for the case where an argument
213− -- has a dependent type calculated from another argument, to make sure that
214− -- all dependencies are covered. If they are, any generated case will
215− -- match given cases.
216− -- (Also this helps when checking, because if we eliminate the placeholder
217− -- quickly then lots of other cases get ruled out fast)
218− ph ts = if addPH then Placeholder : ts else ts
219−
220− -- Merge patterns with placeholders in other patterns, to ensure we've
221− -- covered all possible generated cases
222− -- e.g. if we have (True, _) (False, _) (_, True) (_, False)
223− -- we should merge these to get (True, True), (False, False)
224− -- (False, True) and (True, False)
225− mergePHs :: [PTerm] -> [PTerm]
226− mergePHs xs = mergePHs' xs xs
227−
228− mergePHs' [] all = []
229− mergePHs' (x : xs) all = mergePH x all ++ mergePHs' xs all
230−
231− mergePH :: PTerm -> [PTerm] -> [PTerm]
232− mergePH tm [] = []
233− mergePH tm (x : xs)
234− | Just merged <- mergePTerm tm x = merged : mergePH tm xs
235− | otherwise = mergePH tm xs
236−
237− mergePTerm :: PTerm -> PTerm -> Maybe PTerm
238− mergePTerm x Placeholder = Just x
239− mergePTerm Placeholder x = Just x
240− mergePTerm tm@(PRef fc1 hl1 n1) (PRef fc2 hl2 n2)
241− | n1 == n2 = Just tm
242− mergePTerm (PPair fc1 hl1 p1 x1 y1) (PPair fc2 hl2 p2 x2 y2)
243− = do x <- mergePTerm x1 x2
244− y <- mergePTerm y1 y2
245− Just (PPair fc1 hl1 p1 x y)
246− mergePTerm (PDPair fc1 hl1 p1 x1 t1 y1) (PDPair fc2 hl2 p2 x2 t2 y2)
247− = do x <- mergePTerm x1 x2
248− y <- mergePTerm y1 y2
249− t <- mergePTerm t1 t2
250− Just (PDPair fc1 hl1 p1 x t y)
251− mergePTerm (PApp fc1 f1 args1) (PApp fc2 f2 args2)
252− = do fm <- mergePTerm f1 f2
253− margs <- mergeArgs args1 args2
254− Just (PApp fc1 fm margs)
255− where
256− mergeArgs [] [] = Just []
257− mergeArgs (a : as) (b : bs)
258− = do ma_in <- mergePTerm (getTm a) (getTm b)
259− let ma = a { getTm = ma_in }
260− mas <- mergeArgs as bs
261− Just (ma : mas)
262− mergeArgs _ _ = Nothing
263− mergePTerm x y | x == y = Just x
264− | otherwise = Nothing
265−
266− -- for every constant in a term (at any level) take next one to make sure
267− -- that constants which are not explicitly handled are covered
268− nextConst (PConstant fc c) = PConstant NoFC (nc' c)
269− nextConst o = o
270−
271− nc' (I c) = I (c + 1)
272− nc' (BI c) = BI (c + 1)
273− nc' (Fl c) = Fl (c + 1)
274− nc' (B8 c) = B8 (c + 1)
275− nc' (B16 c) = B16 (c + 1)
276− nc' (B32 c) = B32 (c + 1)
277− nc' (B64 c) = B64 (c + 1)
278− nc' (Ch c) = Ch (chr $ ord c + 1)
279− nc' (Str c) = Str (c ++ "'")
280− nc' o = o
281−
282− otherPats :: PTerm -> [PTerm]
283− otherPats (PRef fc hl n) = ph $ ops fc n []
284− otherPats (PApp _ (PRef fc hl n) xs) = ph $ ops fc n xs
285− otherPats (PPair fc hls _ l r)
286− = ph $ ops fc pairCon
287− ([pimp (sUN "A") Placeholder True,
288− pimp (sUN "B") Placeholder True] ++
289− [pexp l, pexp r])
290− otherPats (PDPair fc hls p t _ v)
291− = ph $ ops fc sigmaCon
292− ([pimp (sUN "a") Placeholder True,
293− pimp (sUN "P") Placeholder True] ++
294− [pexp t,pexp v])
295− otherPats o@(PConstant _ c) = ph [o, nextConst o]
296− otherPats arg = return Placeholder
297−
298− ops fc n xs
299− | (TyDecl c@(DCon _ arity _) ty : _) <- lookupDef n (tt_ctxt i)
300− = do xs' <- mapM otherPats (map getExpTm xs)
301− let p = resugar (PApp fc (PRef fc [] n) (zipWith upd xs' xs))
302− let tyn = getTy n (tt_ctxt i)
303− case lookupCtxt tyn (idris_datatypes i) of
304− (TI ns _ _ _ _ : _) -> p : map (mkPat fc) (ns \\ [n])
305− _ -> [p]
306− ops fc n arg = return Placeholder
307−
308− getExpTm (PImp _ True _ _ _) = Placeholder -- machine inferred, no point!
309− getExpTm t = getTm t
310−
311− -- put it back to its original form
312− resugar (PApp _ (PRef fc hl n) [_,_,t,v])
313− | n == sigmaCon
314− = PDPair fc [] TypeOrTerm (getTm t) Placeholder (getTm v)
315− resugar (PApp _ (PRef fc hl n) [_,_,l,r])
316− | n == pairCon
317− = PPair fc [] IsTerm (getTm l) (getTm r)
318− resugar t = t
319−
320− dropForce force (x : xs) i | i `elem` force
321− = upd Placeholder x : dropForce force xs (i + 1)
322− dropForce force (x : xs) i = x : dropForce force xs (i + 1)
323− dropForce _ [] _ = []
324−
325−
326− getTy n ctxt = case lookupTy n ctxt of
327− (t : _) -> case unApply (getRetTy t) of
328− (P _ tyn _, _) -> tyn
329− x -> error $ "Can't happen getTy 1 " ++ show (n, x)
330− _ -> error "Can't happen getTy 2"
331−
332− mkPat fc x = case lookupCtxt x (idris_implicits i) of
333− (pargs : _)
334− -> PApp fc (PRef fc [] x) (map (upd Placeholder) pargs)
335− _ -> error "Can't happen - genAll"
336−
337− fnub :: [PTerm] -> [PTerm]
338− fnub xs = fnub' [] xs
339−
340− fnub' :: [PTerm] -> [PTerm] -> [PTerm]
341− fnub' acc (x : xs) | x `qelem` acc = fnub' acc (filter (not.(quickEq x)) xs)
342− | otherwise = fnub' (x : acc) xs
343− fnub' acc [] = acc
344−
345− -- quick check for constructor equality
346− quickEq :: PTerm -> PTerm -> Bool
347− quickEq (PConstant _ n) (PConstant _ n') = n == n'
348− quickEq (PRef _ _ n) (PRef _ _ n') = n == n'
349− quickEq (PPair _ _ _ l r) (PPair _ _ _ l' r') = quickEq l l' && quickEq r r'
350− quickEq (PApp _ t as) (PApp _ t' as')
351− | length as == length as'
352− = quickEq t t' && and (zipWith quickEq (map getTm as) (map getTm as'))
353− quickEq Placeholder Placeholder = True
354− quickEq x y = False
355−
356− qelem :: PTerm -> [PTerm] -> Bool
357− qelem x [] = False
358− qelem x (y : ys) | x `quickEq` y = True
359− | otherwise = qelem x ys
360−
361−
362−upd :: t -> PArg' t -> PArg' t
363−upd p' p = p { getTm = p' }
364−
src/Idris/Elab/Clause.hs+12−3
@@ -186,12 +186,14 @@ elabClauses info' fc opts n_in cs =
186 pmissing <-
186 pmissing <-
187 if cov && not (hasDefault pats_raw)
187 if cov && not (hasDefault pats_raw)
188 then do -- Generate clauses from the given possible cases
188 then do -- Generate clauses from the given possible cases
189− missing <- genClauses fc n (map getLHS pdef_cov) cs_full
189+ missing <- genClauses fc n
190+ (map (\ (ns,tm,_) -> (ns, tm)) pdef)
191+ cs_full
190 -- missing <- genMissing n scargs sc
192 -- missing <- genMissing n scargs sc
191 missing' <- checkPossibles info fc True n missing
193 missing' <- checkPossibles info fc True n missing
192 -- Filter out the ones which match one of the
194 -- Filter out the ones which match one of the
193 -- given cases (including impossible ones)
195 -- given cases (including impossible ones)
194− let clhs = map getLHS pdef_cov
196+ let clhs = map getLHS pdef
195 logElab 2 $ "Must be unreachable (" ++ show (length missing') ++ "):\n" ++
197 logElab 2 $ "Must be unreachable (" ++ show (length missing') ++ "):\n" ++
196 showSep "\n" (map showTmImpls missing') ++
198 showSep "\n" (map showTmImpls missing') ++
197 "\nAgainst: " ++
199 "\nAgainst: " ++
@@ -201,7 +203,7 @@ elabClauses info' fc opts n_in cs =
201 -- unification may force a variable to take a
203 -- unification may force a variable to take a
202 -- particular form, rather than force a case
204 -- particular form, rather than force a case
203 -- to be impossible.
205 -- to be impossible.
204− return (filter (noMatch ist clhs) missing')
206+ return missing' -- (filter (noMatch ist clhs) missing')
205 else return []
207 else return []
206 let pcover = null pmissing
208 let pcover = null pmissing
207
209
@@ -562,6 +564,9 @@ elabPE info fc caller r =
562 qhash hash (x:xs) = qhash (hash * 33 + fromIntegral(fromEnum x)) xs
564 qhash hash (x:xs) = qhash (hash * 33 + fromIntegral(fromEnum x)) xs
563
565
564-- | Checks if the clause is a possible left hand side.
566-- | Checks if the clause is a possible left hand side.
567+-- NOTE: A lot of this is repeated for reflected definitions in Idris.Elab.Term
568+-- One day, these should be merged, but until then remember that if you edit
569+-- this you might need to edit the other version...
565checkPossible :: ElabInfo -> FC -> Bool -> Name -> PTerm -> Idris Bool
570checkPossible :: ElabInfo -> FC -> Bool -> Name -> PTerm -> Idris Bool
566checkPossible info fc tcgen fname lhs_in
571checkPossible info fc tcgen fname lhs_in
567 = do ctxt <- getContext
572 = do ctxt <- getContext
@@ -591,6 +596,10 @@ checkPossible info fc tcgen fname lhs_in
591 return False
596 return False
592 -- if it's a recoverable error, the case may become possible
597 -- if it's a recoverable error, the case may become possible
593 Error err -> do logLvl 10 $ "Impossible case " ++ show err
598 Error err -> do logLvl 10 $ "Impossible case " ++ show err
599+ -- tcgen means that it was generated by genClauses,
600+ -- so only looking for an error. Otherwise, it
601+ -- needs to be the right kind of error (a type mismatch
602+ -- in the same family).
594 if tcgen then return (recoverableCoverage ctxt err)
603 if tcgen then return (recoverableCoverage ctxt err)
595 else return (validCoverageCase ctxt err ||
604 else return (validCoverageCase ctxt err ||
596 recoverableCoverage ctxt err)
605 recoverableCoverage ctxt err)
src/Idris/Elab/Term.hs+6−7
@@ -2826,15 +2826,16 @@ processTacticDecls info steps =
2826 RClausesInstrs n cs ->
2826 RClausesInstrs n cs ->
2827 do logElab 3 $ "Pattern-matching definition from tactics: " ++ show n
2827 do logElab 3 $ "Pattern-matching definition from tactics: " ++ show n
2828 solveDeferred emptyFC n
2828 solveDeferred emptyFC n
2829− let lhss = map (\(_, lhs, _) -> lhs) cs
2829+ let lhss = map (\(ns, lhs, _) -> (map fst ns, lhs)) cs
2830 let fc = fileFC "elab_reflected"
2830 let fc = fileFC "elab_reflected"
2831 pmissing <-
2831 pmissing <-
2832 do ist <- getIState
2832 do ist <- getIState
2833 possible <- genClauses fc n lhss
2833 possible <- genClauses fc n lhss
2834− (map (\lhs ->
2834+ (map (\ (ns, lhs) ->
2835 delab' ist lhs True True) lhss)
2835 delab' ist lhs True True) lhss)
2836 missing <- filterM (checkPossible n) possible
2836 missing <- filterM (checkPossible n) possible
2837− return (filter (noMatch ist lhss) missing)
2837+ let undef = filter (noMatch ist (map snd lhss)) missing
2838+ return undef
2838 let tot = if null pmissing
2839 let tot = if null pmissing
2839 then Unchecked -- still need to check recursive calls
2840 then Unchecked -- still need to check recursive calls
2840 else Partial NotCovering -- missing cases implies not total
2841 else Partial NotCovering -- missing cases implies not total
@@ -2871,13 +2872,13 @@ processTacticDecls info steps =
2871 where
2872 where
2872 -- TODO: see if the code duplication with Idris.Elab.Clause can be
2873 -- TODO: see if the code duplication with Idris.Elab.Clause can be
2873 -- reduced or eliminated.
2874 -- reduced or eliminated.
2875+ -- These are always cases generated by genClauses
2874 checkPossible :: Name -> PTerm -> Idris Bool
2876 checkPossible :: Name -> PTerm -> Idris Bool
2875 checkPossible fname lhs_in =
2877 checkPossible fname lhs_in =
2876 do ctxt <- getContext
2878 do ctxt <- getContext
2877 ist <- getIState
2879 ist <- getIState
2878 let lhs = addImplPat ist lhs_in
2880 let lhs = addImplPat ist lhs_in
2879 let fc = fileFC "elab_reflected_totality"
2881 let fc = fileFC "elab_reflected_totality"
2880− let tcgen = False -- TODO: later we may support dictionary generation
2881 case elaborate (constraintNS info) ctxt (idris_datatypes ist) (idris_name ist) (sMN 0 "refPatLHS") infP initEState
2882 case elaborate (constraintNS info) ctxt (idris_datatypes ist) (idris_name ist) (sMN 0 "refPatLHS") infP initEState
2882 (erun fc (buildTC ist info ELHS [] fname (allNamesIn lhs_in)
2883 (erun fc (buildTC ist info ELHS [] fname (allNamesIn lhs_in)
2883 (infTerm lhs))) of
2884 (infTerm lhs))) of
@@ -2890,9 +2891,7 @@ processTacticDecls info steps =
2890 OK _ -> return True
2891 OK _ -> return True
2891 err -> return False
2892 err -> return False
2892 -- if it's a recoverable error, the case may become possible
2893 -- if it's a recoverable error, the case may become possible
2893− Error err -> if tcgen then return (recoverableCoverage ctxt err)
2894− else return (validCoverageCase ctxt err ||
2895− recoverableCoverage ctxt err)
2894+ Error err -> return (recoverableCoverage ctxt err)
2896
2895
2897
2896
2898 -- TODO: Attempt to reduce/eliminate code duplication with Idris.Elab.Clause
2897 -- TODO: Attempt to reduce/eliminate code duplication with Idris.Elab.Clause
src/Idris/Termination.hs+2−2
@@ -1,6 +1,6 @@
1{-|
1{-|
2−Module : Idris.Coverage
2+Module : Idris.Termination
3−Description : The coverage and totality checkers for Idris are in this module.
3+Description : The termination checker for Idris
4Copyright :
4Copyright :
5License : BSD3
5License : BSD3
6Maintainer : The Idris Community.
6Maintainer : The Idris Community.
test/TestData.hs+2−1
@@ -276,7 +276,8 @@ testFamiliesData = [
276 ( 15, ANY ),
276 ( 15, ANY ),
277 ( 16, ANY ),
277 ( 16, ANY ),
278 ( 17, ANY ),
278 ( 17, ANY ),
279− ( 18, ANY )]),
279+ ( 18, ANY ),
280+ ( 19, ANY )]),
280 ("tutorial", "Tutorial examples",
281 ("tutorial", "Tutorial examples",
281 [ ( 1, ANY ),
282 [ ( 1, ANY ),
282 ( 2, ANY ),
283 ( 2, ANY ),
test/totality019/expectednew file+2
@@ -0,0 +1,2 @@
1+totality019.idr:8:1:Main.testCov is not total as there are missing cases
2+totality019.idr:12:1:Main.testCov' is not total as there are missing cases
test/totality019/runnew file+4
@@ -0,0 +1,4 @@
1+#!/usr/bin/env bash
2+${IDRIS:-idris} $@ totality019.idr --check
3+${IDRIS:-idris} $@ totality019a.idr --check
4+rm -f *.ibc
test/totality019/totality019.idrnew file+14
@@ -0,0 +1,14 @@
1+%default total
2+
3+MkThing : Bool -> Type
4+MkThing True = Nat
5+MkThing False = Bool
6+
7+testCov : (x : Bool) -> MkThing x -> Nat
8+testCov False False = ?testCov_rhs_3
9+testCov False True = ?testCov_rhs_4
10+
11+testCov' : (x : Bool ** MkThing x) -> Nat
12+testCov' (False ** False) = ?testCov_rhs_3
13+testCov' (False ** True) = ?testCov_rhs_4
14+
test/totality019/totality019a.idrnew file+81
@@ -0,0 +1,81 @@
1+module Test
2+
3+import Data.Vect
4+%default total
5+
6+genT : Vect n Type -> Type
7+genT [] = Type
8+genT (x :: xs) = x -> genT xs
9+
10+data FunLang: Type where
11+ Atom : {dt : Vect n Type} -> genT dt -> FunLang
12+ And : FunLang -> FunLang -> FunLang
13+ Uor : FunLang -> FunLang -> FunLang
14+ Xor : FunLang -> FunLang -> FunLang -- d decides on that
15+
16+data IndexFL : FunLang -> FunLang -> Type where
17+ LHere : IndexFL fl fl
18+ LLeftAnd : IndexFL x rfl -> IndexFL (And x y) rfl
19+ LRightAnd : IndexFL y rfl -> IndexFL (And x y) rfl
20+ LLeftUor : IndexFL x rfl -> IndexFL (Uor x y) rfl
21+ LRightUor : IndexFL y rfl -> IndexFL (Uor x y) rfl
22+ LLeftXor : IndexFL x rfl -> IndexFL (Xor x y) rfl
23+ LRightXor : IndexFL y rfl -> IndexFL (Xor x y) rfl
24+
25+data SetFL : FunLang -> Type where
26+ SEmpty : SetFL fl
27+ SElem : SetFL fl
28+ SLeftAnd : SetFL x -> SetFL (And x y)
29+ SRightAnd : SetFL y -> SetFL (And x y)
30+ SBothAnd : SetFL x -> SetFL y -> SetFL (And x y)
31+ SLeftUor : SetFL x -> SetFL (Uor x y)
32+ SRightUor : SetFL y -> SetFL (Uor x y)
33+ SBothUor : SetFL x -> SetFL y -> SetFL (Uor x y)
34+ SLeftXor : SetFL x -> SetFL (Xor x y)
35+ SRightXor : SetFL y -> SetFL (Xor x y)
36+ SBothXor : SetFL x -> SetFL y -> SetFL (Xor x y)
37+
38+addIndexFLEmpty : IndexFL fl _ -> SetFL fl
39+addIndexFLEmpty LHere = SElem
40+addIndexFLEmpty (LLeftAnd z) = SLeftAnd $ addIndexFLEmpty z
41+addIndexFLEmpty (LRightAnd z) = SRightAnd $ addIndexFLEmpty z
42+addIndexFLEmpty (LLeftUor z) = SLeftUor $ addIndexFLEmpty z
43+addIndexFLEmpty (LRightUor z) = SRightUor $ addIndexFLEmpty z
44+addIndexFLEmpty (LLeftXor z) = SLeftXor $ addIndexFLEmpty z
45+addIndexFLEmpty (LRightXor z) = SRightXor $ addIndexFLEmpty z
46+
47+addIndexFL : SetFL fl -> IndexFL fl _ -> SetFL fl
48+addIndexFL SEmpty y = addIndexFLEmpty y
49+addIndexFL SElem y = SElem
50+addIndexFL x LHere = SElem
51+addIndexFL (SLeftAnd x) (LLeftAnd w) = SLeftAnd $ addIndexFL x w
52+addIndexFL (SLeftAnd SElem) (LRightAnd LHere) = SElem
53+addIndexFL (SLeftAnd x) (LRightAnd w) = SBothAnd x (addIndexFLEmpty w)
54+addIndexFL (SRightAnd SElem) (LLeftAnd LHere) = SElem
55+addIndexFL (SRightAnd x) (LLeftAnd w) = SBothAnd (addIndexFLEmpty w) x
56+addIndexFL (SRightAnd x) (LRightAnd w) = SRightAnd $ addIndexFL x w
57+addIndexFL (SBothAnd x SElem) (LLeftAnd LHere) = SElem
58+addIndexFL (SBothAnd SElem y) (LRightAnd LHere) = SElem
59+addIndexFL (SBothAnd x y) (LLeftAnd w) = SBothAnd (addIndexFL x w) y
60+addIndexFL (SBothAnd x y) (LRightAnd w) = SBothAnd x (addIndexFL y w)
61+addIndexFL (SLeftUor x) (LLeftUor w) = SLeftUor $ addIndexFL x w
62+addIndexFL (SLeftUor SElem) (LRightUor LHere) = SElem
63+addIndexFL (SLeftUor x) (LRightUor w) = SBothUor x (addIndexFLEmpty w)
64+addIndexFL (SRightUor SElem) (LLeftUor LHere) = SElem
65+addIndexFL (SRightUor x) (LLeftUor w) = SBothUor (addIndexFLEmpty w) x
66+addIndexFL (SRightUor x) (LRightUor w) = SRightUor $ addIndexFL x w
67+addIndexFL (SBothUor x SElem) (LLeftUor LHere) = SElem
68+addIndexFL (SBothUor SElem y) (LRightUor LHere) = SElem
69+addIndexFL (SBothUor x y) (LLeftUor w) = SBothUor (addIndexFL x w) y
70+addIndexFL (SBothUor x y) (LRightUor w) = SBothUor x (addIndexFL y w)
71+addIndexFL (SLeftXor x) (LLeftXor w) = SLeftXor $ addIndexFL x w
72+addIndexFL (SLeftXor SElem) (LRightXor LHere) = SElem
73+addIndexFL (SLeftXor x) (LRightXor w) = SBothXor x (addIndexFLEmpty w)
74+addIndexFL (SRightXor SElem) (LLeftXor LHere) = SElem
75+addIndexFL (SRightXor x) (LLeftXor w) = SBothXor (addIndexFLEmpty w) x
76+addIndexFL (SRightXor x) (LRightXor w) = SRightXor $ addIndexFL x w
77+addIndexFL (SBothXor x SElem) (LLeftXor LHere) = SElem
78+addIndexFL (SBothXor SElem y) (LRightXor LHere) = SElem
79+addIndexFL (SBothXor x y) (LLeftXor w) = SBothXor (addIndexFL x w) y
80+addIndexFL (SBothXor x y) (LRightXor w) = SBothXor x (addIndexFL y w)
81+