on master help sign in
Merge pull request #2340 from david-christiansen/wip/tactical

Initial docs for Elab + better error display for debugging tactics

commit0d6c32f36bec4ba11fc27424664bbe661528455c authorDavid Christiansen <david@davidchristiansen.dk> dateMon 1 Jun 2015, 21:47 (11 years ago) parent37119eb5db2080
13 files changed+242−34
docs/reference/elaborator-reflection.rst+143 docs/reference/index.rst+3 libs/prelude/Language/Reflection.idr+10−8 libs/prelude/Language/Reflection/Elab.idr+4−4 src/Idris/Core/Binary.hs+2 src/Idris/Core/DeepSeq.hs+8−1 src/Idris/Core/Elaborate.hs+1−1 src/Idris/Core/TT.hs+50−1 src/Idris/DeepSeq.hs−6 src/Idris/Delaborate.hs+9−7 src/Idris/Elab/Term.hs+3−5 src/Idris/IBC.hs+1−1 src/Idris/Reflection.hs+8
docs/reference/elaborator-reflection.rstnew file+143
@@ -0,0 +1,143 @@
1+*********************
2+Elaborator Reflection
3+*********************
4+
5+The Idris elaborator is responsible for converting high-level Idris code into the core language.
6+It is implemented as a kind of embedded tactic language in Haskell, where tactic scripts are written in an *elaboration monad* that provides error handling and a proof state.
7+For details, see Edwin Brady's 2013 paper in the Journal of Functional Programming.
8+
9+Elaborator reflection makes the elaboration type as well as a selection of its tactics available to Idris code.
10+This means that metaprograms written in Idris can have complete control over the elaboration process, generating arbitrary code, and they have access to all of the facilities available in the elaborator, such as higher-order unification, type checking, and emitting auxiliary definitions.
11+
12+WARNING
13+=======
14+
15+Elaborator reflection is a new, **experimental** feature. It **will change**. You've been warned.
16+
17+The Elaborator State
18+====================
19+
20+The elaborator state contains information about the ongoing elaboration process.
21+In particular, it contains a *goal type*, which is to be filled by an under-construction *proof term*.
22+The proof term can contain *holes*, each of which has a scope in which it is valid and a type.
23+Some holes may additionally contain *guesses*, which can be substituted in the scope of the hole.
24+The holes are tracked in a *hole queue*, and one of them is *focused*.
25+In addition to the goal type, proof term, and holes, the elaborator state contains a collection of unsolved unification problems that can affect elaboration.
26+
27+The elaborator state is not directly available to Idris programs.
28+Instead, it is modified through the use of *tactics*, which are operations that affect the elaborator state.
29+A tactic that returns a value of type ``a``, potentially modifying the elaborator state, has type ``Elab a``.
30+The default tactics are all in the namespace ``Language.Reflection.Elab.Tactics``.
31+
32+
33+Running Elaborator Scripts
34+==========================
35+
36+On their own, tactics have no effect.
37+The meta-operation ``%runElab script`` runs ``script`` in the current elaboration context.
38+For example, the following script constructs the identity function at type ``Nat``:
39+
40+.. code-block:: idris
41+ idNat : Nat -> Nat
42+ idNat = %runElab (do intro (Just (UN "x"))
43+ fill (Var (UN "x"))
44+ solve)
45+
46+On the right-hand side, the Idris elaborator has the goal ``Nat -> Nat``.
47+When it encounters the ``%runElab`` directive, it fulfills this goal by running the provided script.
48+The first tactic, ``intro``, constructs a lambda that binds the name ``x``.
49+The name argument is optional because a default name can be taken from the function type.
50+Now, the proof term is of the form ``\x : Nat => {hole}``.
51+The second tactic, ``fill``, fills this hole with a guess, giving the term ``\x : Nat => {hole≈x}``.
52+Finally, the ``solve`` tactic instantiates the guess, giving the result ``\x : Nat => x``.
53+
54+Because elaborator scripts are ordinary Idris expressions, it is also possible to use them in multiple contexts.
55+Note that there is nothing ``Nat``-specific about the above script.
56+We can generate identity functions at any concrete type using the same script:
57+
58+.. code-block:: idris
59+ mkId : Elab ()
60+ mkId = do intro (Just (UN "x"))
61+ fill (Var (UN "x"))
62+ solve
63+
64+ idNat : Nat -> Nat
65+ idNat = %runElab mkId
66+
67+ idUnit : () -> ()
68+ idUnit = %runElab mkId
69+
70+ idString : String -> String
71+ idString = %runElab mkId
72+
73+
74+Failure
75+=======
76+
77+Some tactics may *fail*.
78+For example, ``intro`` will fail if the focused hole does not have a function type, ``solve`` will fail if the current hole does not contain a guess, and ``fill`` will fail if the term to be filled in has the wrong type.
79+Scripts can also fail explicitly using the ``fail`` tactic.
80+
81+To account for failure, there is an ``Alternative`` instance for ``Elab``.
82+The ``<|>`` operator first tries the script to its left.
83+If that script fails, any changes that it made to the state are undone and the right argument is executed.
84+If the first argument succeeds, then the second argument is not executed.
85+
86+Querying the Elaboration State
87+==============================
88+
89+``Elab`` includes operations to query the elaboration state, allowing scripts to use information about their environment to steer the elaboration process.
90+The ordinary Idris bind syntax can be used to propagate this information.
91+For example, a tactic that solves the current goal when it is the unit type might look like this:
92+
93+.. code-block:: idris
94+ triv : Elab ()
95+ triv = do compute
96+ g <- getGoal
97+ case (snd g) of
98+ `(() : Type) => do fill `(() : ())
99+ solve
100+ otherGoal => fail [ TermPart otherGoal
101+ , TextPart "is not trivial"
102+ ]
103+
104+The tactic ``compute`` normalises the type of its goal with respect to the current context.
105+While not strictly necessary, this allows ``triv`` to be used in contexts where the triviality of the goal is not immediately apparent.
106+Then, ``getGoal`` is used, and its result is bound to ``g``.
107+Because it returns a pair consisting of the current goal's name and type, we case-split on its second projection.
108+If the goal type turns out to have been the unit type, we fill using the unit constructor and solve the goal.
109+Otherwise, we fail with an error message informing the user that the current goal is not trivial.
110+
111+Additionally, the elaboration state can be dumped into an error message with the ``debug`` tactic.
112+A variant, ``debugMessage``, allows arbitrary messages to be included with the state, allowing for a kind of "``printf`` debuggging" of elaboration scripts.
113+The message format used by ``debugMessage`` is the same for errors produced by the error reflection mechanism, allowing the re-use of the Idris pretty-printer when rendering messages.
114+
115+Changing the Global Context
116+===========================
117+
118+``Elab`` scripts can modify the global context during execution.
119+Just as the Idris elaborator produces auxiliary definitions to implement features such as ``where``-blocks and ``case`` expressions, user elaboration scripts may need to define functions.
120+Furthermore, this allows ``Elab`` reflection to be used to implement features such as type class deriving.
121+The operations ``declareType``, ``defineFunction``, and ``addInstance`` allow ``Elab`` scripts to modify the global context.
122+
123+Using Idris's Features
124+======================
125+
126+The Idris compiler has a number of ways to automate the construction of terms.
127+On its own, the ``Elab`` state and its interactions with the unifier allow implicits to be solved using unification.
128+Additional operations use further features of Idris.
129+In particular, ``resolveTC`` solves the current goal using type class resolution, ``search`` invokes the proof search mechanism, and ``sourceLocation`` finds the context in the original file at which the elaboration script is invoked.
130+
131+
132+Recursive Elaboration
133+=====================
134+
135+The elaboration mechanism can be invoked recursively using the ``runElab`` tactic.
136+This tactic takes a goal type and an elaboration script as arguments and runs the script in a fresh lexical evironment to create an inhabitant of the provided goal type.
137+This is primarily useful for code generation, particularly for generating pattern-matching clauses, where variable scope needs to be one that isn't the present local context.
138+
139+Learn More
140+==========
141+While this documentation is still incomplete, elaboration reflection works in Idris today.
142+As you wait for the completion of the documentation, the list of built-in tactics can be obtained using the ``:browse`` command in an Idris REPL or the corresponding feature in one of the graphical IDE clients to explore the ``Language.Reflection.Elab.Tactics`` namespace.
143+All of the built-in tactics contain documentation strings.
docs/reference/index.rst+3
@@ -31,4 +31,7 @@ This will tell you how Idris works, for using it you should read the Idris Tutor
3131 compilation
3232 language-features
3333 language-extensions
34+ elaborator-reflection
3435 misc
36+
37+
libs/prelude/Language/Reflection.idr+10−8
@@ -215,6 +215,8 @@ data ErrorReportPart =
215215 NamePart TTName |
216216 ||| An Idris term, to be pretty printed
217217 TermPart TT |
218+ ||| A Raw term to be displayed by the compiler
219+ RawPart Raw |
218220 ||| An indented sub-report, to provide more details
219221 SubReport (List ErrorReportPart)
220222%name ErrorReportPart part, p
@@ -576,14 +578,6 @@ mutual
576578 quote (PVar x) = `(PVar {a=TT} ~(assert_total (quote x)))
577579 quote (PVTy x) = `(PVTy {a=TT} ~(assert_total (quote x)))
578580
579−
580−instance Quotable ErrorReportPart TT where
581− quotedTy = `(ErrorReportPart)
582− quote (TextPart x) = `(TextPart ~(quote x))
583− quote (NamePart n) = `(NamePart ~(quote n))
584− quote (TermPart tm) = `(TermPart ~(quote tm))
585− quote (SubReport xs) = `(SubReport ~(assert_total $ quote xs))
586−
587581mutual
588582 quoteRaw : Raw -> TT
589583 quoteRaw (Var n) = `(Var ~(quote n))
@@ -613,6 +607,14 @@ instance Quotable (Binder Raw) TT where
613607 quotedTy = `(Binder Raw)
614608 quote = quoteRawBinder
615609
610+instance Quotable ErrorReportPart TT where
611+ quotedTy = `(ErrorReportPart)
612+ quote (TextPart x) = `(TextPart ~(quote x))
613+ quote (NamePart n) = `(NamePart ~(quote n))
614+ quote (TermPart tm) = `(TermPart ~(quote tm))
615+ quote (RawPart tm) = `(RawPart ~(quote tm))
616+ quote (SubReport xs) = `(SubReport ~(assert_total $ quote xs))
617+
616618instance Quotable Tactic TT where
617619 quotedTy = `(Tactic)
618620 quote (Try tac tac') = `(Try ~(quote tac) ~(quote tac'))
libs/prelude/Language/Reflection/Elab.idr+4−4
@@ -118,7 +118,7 @@ data Elab : Type -> Type where
118118 prim__Search : Int -> List TTName -> Elab ()
119119 prim__RecursiveElab : Raw -> Elab () -> Elab (TT, TT)
120120
121− prim__Debug : {a : Type} -> Maybe String -> Elab a
121+ prim__Debug : {a : Type} -> List ErrorReportPart -> Elab a
122122
123123
124124-------------
@@ -329,7 +329,7 @@ namespace Tactics
329329 ||| This is intended for elaboration script developers, not for
330330 ||| end-users. Use `fail` for final scripts.
331331 debug : Elab a
332− debug = prim__Debug Nothing
332+ debug = prim__Debug []
333333
334334 ||| Halt elaboration, dumping the internal state and displaying a
335335 ||| message.
@@ -338,8 +338,8 @@ namespace Tactics
338338 ||| end-users. Use `fail` for final scripts.
339339 |||
340340 ||| @ msg the message to display
341− debugMessage : (msg : String) -> Elab a
342− debugMessage msg = prim__Debug (Just msg)
341+ debugMessage : (msg : List ErrorReportPart) -> Elab a
342+ debugMessage msg = prim__Debug msg
343343
344344 ||| Recursively invoke the reflected elaborator with some goal.
345345 |||
src/Idris/Core/Binary.hs+2
@@ -18,6 +18,7 @@ instance Binary ErrorReportPart where
1818 put (NamePart n) = do putWord8 1 ; put n
1919 put (TermPart t) = do putWord8 2 ; put t
2020 put (SubReport ps) = do putWord8 3 ; put ps
21+ put (RawPart r) = do putWord8 4 ; put r
2122
2223 get = do i <- getWord8
2324 case i of
@@ -25,6 +26,7 @@ instance Binary ErrorReportPart where
2526 1 -> fmap NamePart get
2627 2 -> fmap TermPart get
2728 3 -> fmap SubReport get
29+ 4 -> fmap RawPart get
2830 _ -> error "Corrupted binary data for ErrorReportPart"
2931
3032instance Binary Provenance where
src/Idris/Core/DeepSeq.hs+8−1
@@ -76,7 +76,7 @@ instance NFData Err where
7676 = rnf x1 `seq` rnf x2 `seq` rnf x3 `seq` rnf x4 `seq` ()
7777 rnf (CantInferType x1) = rnf x1 `seq` ()
7878 rnf (CantMatch x1) = rnf x1 `seq` ()
79− rnf (ReflectionError x1 x2) = x1 `seq` rnf x2 `seq` ()
79+ rnf (ReflectionError x1 x2) = rnf x1 `seq` rnf x2 `seq` ()
8080 rnf (ReflectionFailed x1 x2) = rnf x1 `seq` rnf x2 `seq` ()
8181 rnf (CantSolveGoal x1 x2) = rnf x1 `seq` rnf x2 `seq` ()
8282 rnf (UniqueError x1 x2) = rnf x1 `seq` rnf x2 `seq` ()
@@ -111,6 +111,13 @@ instance NFData Err where
111111 rnf (ElabScriptDebug x1 x2 x3) = rnf x1 `seq` rnf x2 `seq` rnf x3 `seq` ()
112112 rnf (ElabScriptStuck x1) = rnf x1 `seq` ()
113113
114+instance NFData ErrorReportPart where
115+ rnf (TextPart x1) = rnf x1 `seq` ()
116+ rnf (TermPart x1) = rnf x1 `seq` ()
117+ rnf (RawPart x1) = rnf x1 `seq` ()
118+ rnf (NamePart x1) = rnf x1 `seq` ()
119+ rnf (SubReport x1) = rnf x1 `seq` ()
120+
114121instance NFData ImplicitInfo where
115122 rnf (Impl x1) = rnf x1 `seq` ()
116123
src/Idris/Core/Elaborate.hs+1−1
@@ -877,7 +877,7 @@ prunStateT pmax zok ps x s
877877 else OK ((v, newpmax, problems p), s')
878878 Error e -> Error e
879879
880−debugElaborator :: Maybe String -> Elab' aux a
880+debugElaborator :: [ErrorReportPart] -> Elab' aux a
881881debugElaborator msg = do ps <- fmap proof get
882882 saveState -- so we don't need to remember the hole order
883883 hs <- get_holes
src/Idris/Core/TT.hs+50−1
@@ -176,6 +176,7 @@ data OutputAnnotation = AnnName Name (Maybe NameOutput) (Maybe String) (Maybe St
176176data ErrorReportPart = TextPart String
177177 | NamePart Name
178178 | TermPart Term
179+ | RawPart Raw
179180 | SubReport [ErrorReportPart]
180181 deriving (Show, Eq, Data, Typeable)
181182
@@ -244,7 +245,7 @@ data Err' t
244245 | LoadingFailed String (Err' t)
245246 | ReflectionError [[ErrorReportPart]] (Err' t)
246247 | ReflectionFailed String (Err' t)
247− | ElabScriptDebug (Maybe String) t [(Name, t, [(Name, Binder t)])]
248+ | ElabScriptDebug [ErrorReportPart] t [(Name, t, [(Name, Binder t)])]
248249 -- ^ User-specified message, proof term, goals with context (first goal is focused)
249250 | ElabScriptStuck t
250251 deriving (Eq, Functor, Data, Typeable)
@@ -288,6 +289,7 @@ deriving instance NFData Err
288289instance Sized ErrorReportPart where
289290 size (TextPart msg) = 1 + length msg
290291 size (TermPart t) = 1 + size t
292+ size (RawPart r) = 1 + size r
291293 size (NamePart n) = 1 + size n
292294 size (SubReport rs) = 1 + size rs
293295
@@ -1715,6 +1717,53 @@ pprintTT bound tm = pp startPrec bound tm
17151717 | outer > inner = lparen <> doc <> rparen
17161718 | otherwise = doc
17171719
1720+-- | Pretty-print a raw term.
1721+pprintRaw :: [Name] -- ^ Bound names, for highlighting
1722+ -> Raw -- ^ The term to pretty-print
1723+ -> Doc OutputAnnotation
1724+pprintRaw bound (Var n) =
1725+ enclose lparen rparen . group . align . hang 2 $
1726+ (text "Var") <$> annotate (if n `elem` bound
1727+ then AnnBoundName n False
1728+ else AnnName n Nothing Nothing Nothing)
1729+ (text $ show n)
1730+pprintRaw bound (RBind n b body) =
1731+ enclose lparen rparen . group . align . hang 2 $
1732+ vsep [ text "RBind"
1733+ , annotate (AnnBoundName n False) (text $ show n)
1734+ , ppb b
1735+ , pprintRaw (n:bound) body]
1736+ where
1737+ ppb (Lam ty) = enclose lparen rparen . group . align . hang 2 $
1738+ text "Lam" <$> pprintRaw bound ty
1739+ ppb (Pi _ ty k) = enclose lparen rparen . group . align . hang 2 $
1740+ vsep [text "Pi", pprintRaw bound ty, pprintRaw bound k]
1741+ ppb (Let ty v) = enclose lparen rparen . group . align . hang 2 $
1742+ vsep [text "Let", pprintRaw bound ty, pprintRaw bound v]
1743+ ppb (NLet ty v) = enclose lparen rparen . group . align . hang 2 $
1744+ vsep [text "NLet", pprintRaw bound ty, pprintRaw bound v]
1745+ ppb (Hole ty) = enclose lparen rparen . group . align . hang 2 $
1746+ text "Hole" <$> pprintRaw bound ty
1747+ ppb (GHole _ ty) = enclose lparen rparen . group . align . hang 2 $
1748+ text "GHole" <$> pprintRaw bound ty
1749+ ppb (Guess ty v) = enclose lparen rparen . group . align . hang 2 $
1750+ vsep [text "Guess", pprintRaw bound ty, pprintRaw bound v]
1751+ ppb (PVar ty) = enclose lparen rparen . group . align . hang 2 $
1752+ text "PVar" <$> pprintRaw bound ty
1753+ ppb (PVTy ty) = enclose lparen rparen . group . align . hang 2 $
1754+ text "PVTy" <$> pprintRaw bound ty
1755+pprintRaw bound (RApp f x) =
1756+ enclose lparen rparen . group . align . hang 2 . vsep $
1757+ [text "RApp", pprintRaw bound f, pprintRaw bound x]
1758+pprintRaw bound RType = text "RType"
1759+pprintRaw bound (RUType u) = enclose lparen rparen . group . align . hang 2 $
1760+ text "RUType" <$> text (show u)
1761+pprintRaw bound (RForce r) =
1762+ enclose lparen rparen . group . align . hang 2 $
1763+ vsep [text "RForce", pprintRaw bound r]
1764+pprintRaw bound (RConstant c) =
1765+ enclose lparen rparen . group . align . hang 2 $
1766+ vsep [text "RConstant", annotate (AnnConst c) (text (show c))]
17181767
17191768-- | Pretty-printer helper for the binding site of a name
17201769bindingOf :: Name -- ^^ the bound name
src/Idris/DeepSeq.hs−6
@@ -310,12 +310,6 @@ instance (NFData t) => NFData (PTactic' t) where
310310 rnf SourceFC = ()
311311 rnf Unfocus = ()
312312
313−instance NFData ErrorReportPart where
314− rnf (TermPart x1) = rnf x1 `seq` ()
315− rnf (TextPart x1) = rnf x1 `seq` ()
316− rnf (NamePart x1) = rnf x1 `seq` ()
317− rnf (SubReport x1) = rnf x1 `seq` ()
318−
319313instance (NFData t) => NFData (PDo' t) where
320314 rnf (DoExp x1 x2) = rnf x1 `seq` rnf x2 `seq` ()
321315 rnf (DoBind x1 x2 x3 x4) = rnf x1 `seq` rnf x2 `seq` rnf x3 `seq` rnf x4 `seq` ()
src/Idris/Delaborate.hs+9−7
@@ -376,23 +376,18 @@ pprintErr' i (ElaboratingArg f x _ e)
376376pprintErr' i (ProviderError msg) = text ("Type provider error: " ++ msg)
377377pprintErr' i (LoadingFailed fn e) = text "Loading" <+> text fn <+> text "failed:" <+> pprintErr' i e
378378pprintErr' i (ReflectionError parts orig) =
379− let parts' = map (fillSep . map showPart) parts in
379+ let parts' = map (fillSep . map (showPart i)) parts in
380380 align (fillSep parts') <>
381381 if (opt_origerr (idris_options i))
382382 then line <> line <> text "Original error:" <$> indented (pprintErr' i orig)
383383 else empty
384− where showPart :: ErrorReportPart -> Doc OutputAnnotation
385− showPart (TextPart str) = fillSep . map text . words $ str
386− showPart (NamePart n) = annName n
387− showPart (TermPart tm) = pprintTerm i (delab i tm)
388− showPart (SubReport rs) = indented . hsep . map showPart $ rs
389384pprintErr' i (ReflectionFailed msg err) =
390385 text "When attempting to perform error reflection, the following internal error occurred:" <>
391386 indented (pprintErr' i err) <>
392387 text ("This is probably a bug. Please consider reporting it at " ++ bugaddr)
393388pprintErr' i (ElabScriptDebug msg tm holes) =
394389 text "Elaboration halted." <>
395− maybe empty (indented . text) msg <>
390+ indented (align . hsep $ map (showPart i) msg) <>
396391 line <> line <>
397392 text "Holes:" <>
398393 indented (vsep (map ppHole holes)) <> line <> line <>
@@ -416,6 +411,13 @@ pprintErr' i (ElabScriptStuck tm) =
416411 text "Can't run" <+> pprintTT [] tm <+> text "as an elaborator script." <$>
417412 text "Is it a stuck term?"
418413
414+showPart :: IState -> ErrorReportPart -> Doc OutputAnnotation
415+showPart ist (TextPart str) = fillSep . map text . words $ str
416+showPart ist (NamePart n) = annName n
417+showPart ist (TermPart tm) = pprintTerm ist (delab ist tm)
418+showPart ist (RawPart tm) = pprintRaw [] tm
419+showPart ist (SubReport rs) = indented . hsep . map (showPart ist) $ rs
420+
419421-- | Make sure the machine invented names are shown helpfully to the user, so
420422-- that any names which differ internally also differ visibly
421423renameMNs :: Term -> Term -> (Term, Term, [Name])
src/Idris/Elab/Term.hs+3−5
@@ -1970,11 +1970,9 @@ runTactical ist fc env tm = do tm' <- eval tm
19701970 rawPair (Var $ reflm "TT", Var $ reflm "TT")
19711971 (tm', ty')
19721972 | n == tacN "prim__Debug", [ty, msg] <- args
1973− = do let msg' = fromTTMaybe msg
1974− case msg' of
1975− Nothing -> debugElaborator Nothing
1976− Just (Constant (Str m)) -> debugElaborator (Just m)
1977− Just x -> lift . tfail . InternalMsg $ "Can't reify message for debugging: " ++ show x
1973+ = do msg' <- eval msg
1974+ parts <- reifyReportParts msg
1975+ debugElaborator parts
19781976 runTacTm x = lift . tfail $ ElabScriptStuck x
19791977
19801978-- Running tactics directly
src/Idris/IBC.hs+1−1
@@ -41,7 +41,7 @@ import Codec.Archive.Zip
4141import Util.Zlib (decompressEither)
4242
4343ibcVersion :: Word16
44−ibcVersion = 110
44+ibcVersion = 111
4545
4646data IBCFile = IBCFile { ver :: Word16,
4747 sourcefile :: FilePath,
src/Idris/Reflection.hs+8
@@ -913,6 +913,14 @@ reifyReportPart (App _ (P (DCon _ _ _) n _) tm)
913913 show tm ++
914914 " when reflecting an error:" ++ show e
915915 OK (tm', _) -> Right $ TermPart tm'
916+reifyReportPart (App _ (P (DCon _ _ _) n _) tm)
917+ | n == reflm "RawPart" =
918+ case runElab initEState (reifyRaw tm) (initElaborator NErased initContext emptyContext Erased) of
919+ Error e -> Left . InternalMsg $
920+ "could not reify reflected raw term " ++
921+ show tm ++
922+ " when reflecting an error: " ++ show e
923+ OK (tm', _) -> Right $ RawPart tm'
916924reifyReportPart (App _ (P (DCon _ _ _) n _) tm)
917925 | n == reflm "SubReport" =
918926 case unList tm of