on master help sign in
Totality checking for functions defined in Elab scripts
commite32e899e6af55f6a5ac56f42abd7de5b4372be3b authorDavid Raymond Christiansen <david@davidchristiansen.dk> dateWed 15 Apr 2015, 17:55 (12 years ago) parentac98430
10 files changed+184−94
src/Idris/Core/CaseTree.hs+3−4 src/Idris/Core/Evaluate.hs+7−5 src/Idris/Coverage.hs+52−7 src/Idris/Elab/Clause.hs+9−52 src/Idris/Elab/Instance.hs+1−1 src/Idris/Elab/Transform.hs+2−2 src/Idris/Elab/Type.hs+1−1 src/Idris/Elab/Value.hs+1−1 src/Idris/ElabTerm.hs+107−20 src/Idris/Reflection.hs+1−1
src/Idris/Core/CaseTree.hs+3−4
@@ -136,10 +136,9 @@ namesUsed sc = nub $ nu' [] sc where
136136 nut ps (Bind n b sc) = nut (n:ps) sc
137137 nut ps _ = []
138138
139−-- Return all called functions, and which arguments are used in each argument position
140−-- for the call, in order to help reduce compilation time, and trace all unused
141−-- arguments
142−
139+-- | Return all called functions, and which arguments are used
140+-- in each argument position for the call, in order to help reduce
141+-- compilation time, and trace all unused arguments
143142findCalls :: SC -> [Name] -> [(Name, [[Name]])]
144143findCalls sc topargs = nub $ nu' topargs sc where
145144 nu' ps (Case _ n alts) = nub (concatMap (nua (n : ps)) alts)
src/Idris/Core/Evaluate.hs+7−5
@@ -887,11 +887,13 @@ addCasedef n ei ci@(CaseInfo inline alwaysInline tcdict)
887887 cdef = CaseDefs (args_tot, sc_tot)
888888 (args_ct, sc_ct)
889889 (args_inl, sc_inl)
890− (args_rt, sc_rt) in
891− addDef n (CaseOp (ci { case_inlinable = inlc })
892− ty argtys ps_in ps_tot cdef,
893− access, Unchecked, EmptyMI) ctxt in
894− uctxt { definitions = ctxt' }
890+ (args_rt, sc_rt)
891+ op = (CaseOp (ci { case_inlinable = inlc })
892+ ty argtys ps_in ps_tot cdef,
893+ access, Unchecked, EmptyMI)
894+ in addDef n op ctxt
895+ other -> error ("Error adding case def: " ++ show other)
896+ in uctxt { definitions = ctxt' }
895897
896898-- simplify a definition for totality checking
897899simplifyCasedef :: Name -> ErasureInfo -> Context -> Context
src/Idris/Coverage.hs+52−7
@@ -1,5 +1,6 @@
11{-# LANGUAGE PatternGuards #-}
2−
2+{-| The coverage and totality checkers for Idris are in this module.
3+-}
34module Idris.Coverage where
45
56import Idris.Core.TT
@@ -120,6 +121,52 @@ genClauses fc n xs given
120121 as' <- mkArg as
121122 return (a':as')
122123
124+-- | Does this error result rule out a case as valid when coverage checking?
125+validCoverageCase :: Context -> Err -> Bool
126+validCoverageCase ctxt (CantUnify _ (topx, _) (topy, _) e _ _)
127+ = let topx' = normalise ctxt [] topx
128+ topy' = normalise ctxt [] topy in
129+ not (sameFam topx' topy' || not (validCoverageCase ctxt e))
130+ where sameFam topx topy
131+ = case (unApply topx, unApply topy) of
132+ ((P _ x _, _), (P _ y _, _)) -> x == y
133+ _ -> False
134+validCoverageCase ctxt (CantConvert _ _ _) = False
135+validCoverageCase ctxt (At _ e) = validCoverageCase ctxt e
136+validCoverageCase ctxt (Elaborating _ _ e) = validCoverageCase ctxt e
137+validCoverageCase ctxt (ElaboratingArg _ _ _ e) = validCoverageCase ctxt e
138+validCoverageCase ctxt _ = True
139+
140+-- | Check whether an error is recoverable in the sense needed for
141+-- coverage checking.
142+recoverableCoverage :: Context -> Err -> Bool
143+recoverableCoverage ctxt (CantUnify r (topx, _) (topy, _) e _ _)
144+ = let topx' = normalise ctxt [] topx
145+ topy' = normalise ctxt [] topy in
146+ checkRec topx' topy'
147+ where -- different notion of recoverable than in unification, since we
148+ -- have no metavars -- just looking to see if a constructor is failing
149+ -- to unify with a function that may be reduced later
150+ checkRec (App f a) p@(P _ _ _) = checkRec f p
151+ checkRec p@(P _ _ _) (App f a) = checkRec p f
152+ checkRec fa@(App _ _) fa'@(App _ _)
153+ | (f, as) <- unApply fa,
154+ (f', as') <- unApply fa'
155+ = if (length as /= length as')
156+ then checkRec f f'
157+ else checkRec f f' && and (zipWith checkRec as as')
158+ checkRec (P xt x _) (P yt y _) = x == y || ntRec xt yt
159+ checkRec _ _ = False
160+
161+ ntRec x y | Ref <- x = True
162+ | Ref <- y = True
163+ | (Bound, Bound) <- (x, y) = True
164+ | otherwise = False -- name is different, unrecoverable
165+recoverableCoverage ctxt (At _ e) = recoverableCoverage ctxt e
166+recoverableCoverage ctxt (Elaborating _ _ e) = recoverableCoverage ctxt e
167+recoverableCoverage ctxt (ElaboratingArg _ _ _ e) = recoverableCoverage ctxt e
168+recoverableCoverage _ _ = False
169+
123170-- FIXME: Just look for which one is the deepest, then generate all
124171-- possibilities up to that depth.
125172-- This and below issues for this function are tracked as Issue #1741 on the issue tracker.
@@ -350,10 +397,9 @@ calcProd i fc topn pats
350397 _ -> False
351398 cotype nt n ty = False
352399
353−-- Calculate the totality of a function from its patterns.
400+-- | Calculate the totality of a function from its patterns.
354401-- Either follow the size change graph (if inductive) or check for
355402-- productivity (if coinductive)
356−
357403calcTotality :: FC -> Name -> [([Name], Term, Term)] -> Idris Totality
358404calcTotality fc n pats
359405 = do i <- getIState
@@ -450,17 +496,16 @@ checkDeclTotality (fc, n)
450496 return t
451497
452498
453−-- Calculate the size change graph for this definition
454−
499+-- | Calculate the size change graph for this definition
500+--
455501-- SCG for a function f consists of a list of:
456502-- (g, [(a1, sizechange1), (a2, sizechange2), ..., (an, sizechangen)])
457−
503+--
458504-- where g is a function called
459505-- a1 ... an are the arguments of f in positions 1..n of g
460506-- sizechange1 ... sizechange2 is how their size has changed wrt the input
461507-- to f
462508-- Nothing, if the argument is unrelated to the input
463−
464509buildSCG :: (FC, Name) -> Idris ()
465510buildSCG (_, n) = do
466511 ist <- getIState
src/Idris/Elab/Clause.hs+9−52
@@ -455,58 +455,15 @@ checkPossible info fc tcgen fname lhs_in
455455 (erun fc (buildTC i info ELHS [] fname (infTerm lhs))) of
456456 OK (ElabResult lhs' _ _ ctxt' newDecls, _) ->
457457 do setContext ctxt'
458− processTacticDecls newDecls
458+ processTacticDecls info newDecls
459459 let lhs_tm = orderPats (getInferTerm lhs')
460460 case recheck ctxt [] (forget lhs_tm) lhs_tm of
461461 OK _ -> return True
462462 err -> return False
463463 -- if it's a recoverable error, the case may become possible
464− Error err -> if tcgen then return (recoverable ctxt err)
465− else return (validCase ctxt err ||
466− recoverable ctxt err)
467− where validCase ctxt (CantUnify _ (topx, _) (topy, _) e _ _)
468− = let topx' = normalise ctxt [] topx
469− topy' = normalise ctxt [] topy in
470− not (sameFam topx' topy' || not (validCase ctxt e))
471− validCase ctxt (CantConvert _ _ _) = False
472− validCase ctxt (At _ e) = validCase ctxt e
473− validCase ctxt (Elaborating _ _ e) = validCase ctxt e
474− validCase ctxt (ElaboratingArg _ _ _ e) = validCase ctxt e
475− validCase ctxt _ = True
476−
477− recoverable ctxt (CantUnify r (topx, _) (topy, _) e _ _)
478− = let topx' = normalise ctxt [] topx
479− topy' = normalise ctxt [] topy in
480− checkRec topx' topy'
481− recoverable ctxt (At _ e) = recoverable ctxt e
482− recoverable ctxt (Elaborating _ _ e) = recoverable ctxt e
483− recoverable ctxt (ElaboratingArg _ _ _ e) = recoverable ctxt e
484− recoverable _ _ = False
485−
486− sameFam topx topy
487− = case (unApply topx, unApply topy) of
488− ((P _ x _, _), (P _ y _, _)) -> x == y
489− _ -> False
490−
491− -- different notion of recoverable than in unification, since we
492− -- have no metavars -- just looking to see if a constructor is failing
493− -- to unify with a function that may be reduced later
494−
495− checkRec (App f a) p@(P _ _ _) = checkRec f p
496− checkRec p@(P _ _ _) (App f a) = checkRec p f
497− checkRec fa@(App _ _) fa'@(App _ _)
498− | (f, as) <- unApply fa,
499− (f', as') <- unApply fa'
500− = if (length as /= length as')
501− then checkRec f f'
502− else checkRec f f' && and (zipWith checkRec as as')
503− checkRec (P xt x _) (P yt y _) = x == y || ntRec xt yt
504− checkRec _ _ = False
505−
506− ntRec x y | Ref <- x = True
507− | Ref <- y = True
508− | (Bound, Bound) <- (x, y) = True
509− | otherwise = False -- name is different, unrecoverable
464+ Error err -> if tcgen then return (recoverableCoverage ctxt err)
465+ else return (validCoverageCase ctxt err ||
466+ recoverableCoverage ctxt err)
510467
511468propagateParams :: IState -> [Name] -> Type -> PTerm -> PTerm
512469propagateParams i ps t tm@(PApp _ (PRef fc n) args)
@@ -587,7 +544,7 @@ elabClause info opts (cnum, PClause fc fname lhs_in_as withs rhs_in_as wherebloc
587544 inj <- get_inj
588545 return (res, probs, inj))
589546 setContext ctxt'
590− processTacticDecls newDecls
547+ processTacticDecls info newDecls
591548 when inf $ addTyInfConstraints fc (map (\(x,y,_,_,_,_,_) -> (x,y)) probs)
592549
593550 let lhs_tm = orderPats (getInferTerm lhs')
@@ -672,7 +629,7 @@ elabClause info opts (cnum, PClause fc fname lhs_in_as withs rhs_in_as wherebloc
672629 probs <- get_probs
673630 return (tm, ds, is, probs, ctxt', newDecls))
674631 setContext ctxt'
675− processTacticDecls newDecls
632+ processTacticDecls info newDecls
676633 when inf $ addTyInfConstraints fc (map (\(x,y,_,_,_,_,_) -> (x,y)) probs)
677634
678635 logLvl 5 "DONE CHECK"
@@ -820,7 +777,7 @@ elabClause info opts (_, PWith fc fname lhs_in withs wval_in pn_in withblock)
820777 (errAt "left hand side of with in " fname
821778 (erun fc (buildTC i info ELHS opts fname (infTerm lhs))) )
822779 setContext ctxt'
823− processTacticDecls newDecls
780+ processTacticDecls info newDecls
824781 let lhs_tm = orderPats (getInferTerm lhs')
825782 let lhs_ty = getInferType lhs'
826783 let ret_ty = getRetTy (explicitNames (normalise ctxt [] lhs_ty))
@@ -844,7 +801,7 @@ elabClause info opts (_, PWith fc fname lhs_in withs wval_in pn_in withblock)
844801 tt <- get_term
845802 return (tt, d, is, ctxt', newDecls))
846803 setContext ctxt'
847− processTacticDecls newDecls
804+ processTacticDecls info newDecls
848805 def' <- checkDef fc iderr defer
849806 let def'' = map (\(n, (i, top, t)) -> (n, (i, top, t, False))) def'
850807 addDeferred def''
@@ -939,7 +896,7 @@ elabClause info opts (_, PWith fc fname lhs_in withs wval_in pn_in withblock)
939896 tt <- get_term
940897 return (tt, d, is, ctxt', newDecls))
941898 setContext ctxt'
942− processTacticDecls newDecls
899+ processTacticDecls info newDecls
943900
944901 def' <- checkDef fc iderr defer
945902 let def'' = map (\(n, (i, top, t)) -> (n, (i, top, t, False))) def'
src/Idris/Elab/Instance.hs+1−1
@@ -180,7 +180,7 @@ elabInstance info syn doc argDocs what fc cs n ps t expn ds = do
180180 tclift $ elaborate ctxt iname (TType (UVal 0)) initEState
181181 (errAt "type of " iname (erun fc (build i info ERHS [] iname ty)))
182182 setContext ctxt'
183− processTacticDecls newDecls
183+ processTacticDecls info newDecls
184184 ctxt <- getContext
185185 (cty, _) <- recheckC fc id [] tyT
186186 let nty = normalise ctxt [] cty
src/Idris/Elab/Transform.hs+2−2
@@ -58,7 +58,7 @@ elabTransform info fc safe lhs_in@(PApp _ (PRef _ tf) _) rhs_in
5858 (erun fc (buildTC i info ELHS [] (sUN "transform")
5959 (infTerm lhs)))
6060 setContext ctxt'
61− processTacticDecls newDecls
61+ processTacticDecls info newDecls
6262 let lhs_tm = orderPats (getInferTerm lhs')
6363 let lhs_ty = getInferType lhs'
6464 let newargs = pvars i lhs_tm
@@ -78,7 +78,7 @@ elabTransform info fc safe lhs_in@(PApp _ (PRef _ tf) _) rhs_in
7878 let (rhs', defer) = runState (collectDeferred Nothing tt) []
7979 return (rhs', defer, ctxt', newDecls))
8080 setContext ctxt'
81− processTacticDecls newDecls
81+ processTacticDecls info newDecls
8282
8383 (crhs_tm_in, crhs_ty) <- recheckC fc id [] rhs'
8484 let crhs_tm = renamepats pnames crhs_tm_in
src/Idris/Elab/Type.hs+1−1
@@ -70,7 +70,7 @@ buildType info syn fc opts n ty' = do
7070 tclift $ elaborate ctxt n (TType (UVal 0)) initEState
7171 (errAt "type of " n (erun fc (build i info ETyDecl [] n ty)))
7272 setContext ctxt'
73− processTacticDecls newDecls
73+ processTacticDecls info newDecls
7474
7575 let tyT = patToImp tyT'
7676
src/Idris/Elab/Value.hs+1−1
@@ -69,7 +69,7 @@ elabValBind info aspat norm tm_in
6969
7070 -- Extend the context with new definitions created
7171 setContext ctxt'
72− processTacticDecls newDecls
72+ processTacticDecls info newDecls
7373
7474 let vtm = orderPats (getInferTerm tm')
7575
src/Idris/ElabTerm.hs+107−20
@@ -10,12 +10,13 @@ import Idris.Error
1010import Idris.ProofSearch
1111import Idris.Output (pshow)
1212
13−import Idris.Core.CaseTree (SC, SC'(STerm))
13+import Idris.Core.CaseTree (SC, SC'(STerm), findCalls, findUsedArgs)
1414import Idris.Core.Elaborate hiding (Tactic(..))
1515import Idris.Core.TT
1616import Idris.Core.Evaluate
1717import Idris.Core.Unify
1818import Idris.Core.Typecheck (check, recheck)
19+import Idris.Coverage (buildSCG, checkDeclTotality, genClauses, recoverableCoverage, validCoverageCase)
1920import Idris.ErrReverse (errReverse)
2021import Idris.ElabQuasiquote (extractUnquotes)
2122import Idris.Elab.Utils
@@ -49,24 +50,6 @@ data ElabResult =
4950 -- ^ Meta-info about the new type declarations
5051 }
5152
52−processTacticDecls :: [RDeclInstructions] -> Idris ()
53−processTacticDecls info =
54− forM_ info $ \case
55− RTyDeclInstrs n fc impls ty ->
56− do logLvl 3 $ "Declaration from tactics: " ++ show n ++ " : " ++ show ty
57− logLvl 3 $ " It has impls " ++ show impls
58− updateIState $ \i -> i { idris_implicits =
59− addDef n impls (idris_implicits i) }
60− addIBC (IBCImp n)
61− ds <- checkDef fc (\_ e -> e) [(n, (-1, Nothing, ty))]
62− addIBC (IBCDef n)
63− let ds' = map (\(n, (i, top, t)) -> (n, (i, top, t, True))) ds
64− addDeferred ds'
65− RClausesInstrs n cs ->
66− do logLvl 3 $ "Pattern-matching definition from tactics: " ++ show n
67− solveDeferred n
68− updateIState $ \i -> i { idris_patdefs = addDef n (cs, []) $ idris_patdefs i } -- TODO: missing clauses
69− addIBC (IBCDef n)
7053
7154-- Using the elaborator, convert a term in raw syntax to a fully
7255-- elaborated, typechecked term.
@@ -1628,7 +1611,7 @@ runTactical fc env tm = do tm' <- eval tm
16281611 addCasedef n (const [])
16291612 info False (STerm Erased)
16301613 True False -- TODO what are these?
1631− [] [] -- TODO argument types, inaccessible types
1614+ (map snd $ getArgTys ty) [] -- TODO inaccessible types
16321615 clauses'
16331616 clauses''
16341617 clauses''
@@ -2088,3 +2071,107 @@ withErrorReflection x = idrisCatch x (\ e -> handle e >>= ierror)
20882071 parts -> ReflectionError errorparts e
20892072
20902073solveAll = try (do solve; solveAll) (return ())
2074+
2075+-- | Do the left-over work after creating declarations in reflected
2076+-- elaborator scripts
2077+processTacticDecls :: ElabInfo -> [RDeclInstructions] -> Idris ()
2078+processTacticDecls info steps =
2079+ -- The order of steps is important: type declarations might
2080+ -- establish metavars that later function bodies resolve.
2081+ forM_ (reverse steps) $ \case
2082+ RTyDeclInstrs n fc impls ty ->
2083+ do logLvl 3 $ "Declaration from tactics: " ++ show n ++ " : " ++ show ty
2084+ logLvl 3 $ " It has impls " ++ show impls
2085+ updateIState $ \i -> i { idris_implicits =
2086+ addDef n impls (idris_implicits i) }
2087+ addIBC (IBCImp n)
2088+ ds <- checkDef fc (\_ e -> e) [(n, (-1, Nothing, ty))]
2089+ addIBC (IBCDef n)
2090+ ctxt <- getContext
2091+ case lookupDef n ctxt of
2092+ (TyDecl _ _ : _) ->
2093+ -- If the function isn't defined at the end of the elab script,
2094+ -- then it must be added as a metavariable. This needs guarding
2095+ -- to prevent overwriting case defs with a metavar, if the case
2096+ -- defs come after the type decl in the same script!
2097+ let ds' = map (\(n, (i, top, t)) -> (n, (i, top, t, True))) ds
2098+ in addDeferred ds'
2099+ _ -> return ()
2100+ RClausesInstrs n cs ->
2101+ do logLvl 3 $ "Pattern-matching definition from tactics: " ++ show n
2102+ solveDeferred n
2103+ let lhss = map (\(_, lhs, _) -> lhs) cs
2104+ let fc = fileFC "elab_reflected"
2105+ pmissing <-
2106+ do ist <- getIState
2107+ possible <- genClauses fc n lhss
2108+ (map (\lhs ->
2109+ delab' ist lhs True True) lhss)
2110+ missing <- filterM (checkPossible n) possible
2111+ return (filter (noMatch ist lhss) missing)
2112+ let tot = if null pmissing
2113+ then Unchecked -- still need to check recursive calls
2114+ else Partial NotCovering -- missing cases implies not total
2115+ setTotality n tot
2116+ updateIState $ \i -> i { idris_patdefs =
2117+ addDef n (cs, pmissing) $ idris_patdefs i }
2118+ addIBC (IBCDef n)
2119+
2120+ ctxt <- getContext
2121+ case lookupDefExact n ctxt of
2122+ Just (CaseOp _ _ _ _ _ cd) ->
2123+ -- Here, we populate the call graph with a list of things
2124+ -- we refer to, so that if they aren't total, the whole
2125+ -- thing won't be.
2126+ let (scargs, sc) = cases_compiletime cd
2127+ (scargs', sc') = cases_runtime cd
2128+ calls = findCalls sc' scargs
2129+ used = findUsedArgs sc' scargs'
2130+ cg = CGInfo scargs' calls [] used []
2131+ in do logLvl 2 $ "Called names in reflected elab: " ++ show cg
2132+ addToCG n cg
2133+ addToCalledG n (nub (map fst calls))
2134+ addIBC $ IBCCG n
2135+ Just _ -> return () -- TODO throw internal error
2136+ Nothing -> return ()
2137+
2138+ -- checkDeclTotality requires that the call graph be present
2139+ -- before calling it.
2140+ -- TODO: reduce code duplication with Idris.Elab.Clause
2141+ buildSCG (fc, n)
2142+
2143+ -- Actually run the totality checker. In the main clause
2144+ -- elaborator, this is deferred until after. Here, we run it
2145+ -- now to get totality information as early as possible.
2146+ tot' <- checkDeclTotality (fc, n)
2147+ setTotality n tot'
2148+ when (tot' /= Unchecked) $ addIBC (IBCTotal n tot')
2149+ where
2150+ -- TODO: see if the code duplication with Idris.Elab.Clause can be
2151+ -- reduced or eliminated.
2152+ checkPossible :: Name -> PTerm -> Idris Bool
2153+ checkPossible fname lhs_in =
2154+ do ctxt <- getContext
2155+ ist <- getIState
2156+ let lhs = addImplPat ist lhs_in
2157+ let fc = fileFC "elab_reflected_totality"
2158+ let tcgen = False -- TODO: later we may support dictionary generation
2159+ case elaborate ctxt (sMN 0 "refPatLHS") infP initEState
2160+ (erun fc (buildTC ist info ELHS [] fname (infTerm lhs))) of
2161+ OK (ElabResult lhs' _ _ _ _, _) ->
2162+ do -- not recursively calling here, because we don't
2163+ -- want to run infinitely many times
2164+ let lhs_tm = orderPats (getInferTerm lhs')
2165+ case recheck ctxt [] (forget lhs_tm) lhs_tm of
2166+ OK _ -> return True
2167+ err -> return False
2168+ -- if it's a recoverable error, the case may become possible
2169+ Error err -> if tcgen then return (recoverableCoverage ctxt err)
2170+ else return (validCoverageCase ctxt err ||
2171+ recoverableCoverage ctxt err)
2172+
2173+
2174+ -- TODO: Attempt to reduce/eliminate code duplication with Idris.Elab.Clause
2175+ noMatch i cs tm = all (\x -> case matchClause i (delab' i x True True) tm of
2176+ Right _ -> False
2177+ Left _ -> True) cs
src/Idris/Reflection.hs+1−1
@@ -2,7 +2,7 @@
22quoters and unquoters along with some supporting datatypes.
33-}
44{-# LANGUAGE PatternGuards #-}
5−{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
5+{-# OPTIONS_GHC -fwarn-incomplete-patterns -fwarn-unused-imports #-}
66module Idris.Reflection where
77
88import Control.Monad (liftM, liftM2)