-
Notifications
You must be signed in to change notification settings - Fork 418
/
Message.lean
427 lines (351 loc) · 18.6 KB
/
Message.lean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/-
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Sebastian Ullrich, Leonardo de Moura
Message type used by the Lean frontend
-/
prelude
import Lean.Data.Position
import Lean.Data.OpenDecl
import Lean.MetavarContext
import Lean.Environment
import Lean.Util.PPExt
import Lean.Util.Sorry
namespace Lean
def mkErrorStringWithPos (fileName : String) (pos : Position) (msg : String) (endPos : Option Position := none) : String :=
let endPos := match endPos with
| some endPos => s!"-{endPos.line}:{endPos.column}"
| none => ""
s!"{fileName}:{pos.line}:{pos.column}{endPos}: {msg}"
inductive MessageSeverity where
| information | warning | error
deriving Inhabited, BEq, ToJson, FromJson
structure MessageDataContext where
env : Environment
mctx : MetavarContext
lctx : LocalContext
opts : Options
/-- A naming context is the information needed to shorten names in pretty printing.
It gives the current namespace and the list of open declarations.
-/
structure NamingContext where
currNamespace : Name
openDecls : List OpenDecl
/-- Lazily formatted text to be used in `MessageData`. -/
structure PPFormat where
/-- Pretty-prints text using surrounding context, if any. -/
pp : Option PPContext → IO FormatWithInfos
/-- Searches for synthetic sorries in original input. Used to filter out certain messages. -/
hasSyntheticSorry : MetavarContext → Bool := fun _ => false
structure TraceData where
/-- Trace class, e.g. `Elab.step`. -/
cls : Name
/-- Start time in seconds; 0 if unknown to avoid `Option` allocation. -/
startTime : Float := 0
/-- Stop time in seconds; 0 if unknown to avoid `Option` allocation. -/
stopTime : Float := startTime
/-- Whether trace node defaults to collapsed in the infoview. -/
collapsed : Bool := true
/-- Optional tag shown in `trace.profiler.output` output after the trace class name. -/
tag : String := ""
/-- Structured message data. We use it for reporting errors, trace messages, etc. -/
inductive MessageData where
/-- Eagerly formatted text. We inspect this in various hacks, so it is not immediately subsumed by `ofPPFormat`. -/
| ofFormat : Format → MessageData
/-- Lazily formatted text. -/
| ofPPFormat : PPFormat → MessageData
| ofGoal : MVarId → MessageData
/-- `withContext ctx d` specifies the pretty printing context `(env, mctx, lctx, opts)` for the nested expressions in `d`. -/
| withContext : MessageDataContext → MessageData → MessageData
| withNamingContext : NamingContext → MessageData → MessageData
/-- Lifted `Format.nest` -/
| nest : Nat → MessageData → MessageData
/-- Lifted `Format.group` -/
| group : MessageData → MessageData
/-- Lifted `Format.compose` -/
| compose : MessageData → MessageData → MessageData
/-- Tagged sections. `Name` should be viewed as a "kind", and is used by `MessageData` inspector functions.
Example: an inspector that tries to find "definitional equality failures" may look for the tag "DefEqFailure". -/
| tagged : Name → MessageData → MessageData
| trace (data : TraceData) (msg : MessageData) (children : Array MessageData)
deriving Inhabited
namespace MessageData
variable (p : Name → Bool) in
/-- Returns true when the message contains a `MessageData.tagged tag ..` constructor where `p tag` is true. -/
partial def hasTag : MessageData → Bool
| withContext _ msg => hasTag msg
| withNamingContext _ msg => hasTag msg
| nest _ msg => hasTag msg
| group msg => hasTag msg
| compose msg₁ msg₂ => hasTag msg₁ || hasTag msg₂
| tagged n msg => p n || hasTag msg
| trace data msg msgs => p data.cls || hasTag msg || msgs.any hasTag
| _ => false
/-- An empty message. -/
def nil : MessageData :=
ofFormat Format.nil
def mkPPContext (nCtx : NamingContext) (ctx : MessageDataContext) : PPContext := {
env := ctx.env, mctx := ctx.mctx, lctx := ctx.lctx, opts := ctx.opts,
currNamespace := nCtx.currNamespace, openDecls := nCtx.openDecls
}
def ofSyntax (stx : Syntax) : MessageData :=
-- discard leading/trailing whitespace
let stx := stx.copyHeadTailInfoFrom .missing
.ofPPFormat {
pp := fun
| some ctx => ppTerm ctx ⟨stx⟩ -- HACK: might not be a term
| none => return stx.formatStx
}
def ofExpr (e : Expr) : MessageData :=
.ofPPFormat {
pp := fun
| some ctx => ppExprWithInfos ctx e
| none => return format (toString e)
hasSyntheticSorry := (instantiateMVarsCore · e |>.1.hasSyntheticSorry)
}
def ofLevel (l : Level) : MessageData :=
.ofPPFormat {
pp := fun
| some ctx => ppLevel ctx l
| none => return format l
}
def ofName (n : Name) : MessageData := ofFormat (format n)
partial def hasSyntheticSorry (msg : MessageData) : Bool :=
visit none msg
where
visit (mctx? : Option MetavarContext) : MessageData → Bool
| ofPPFormat f => f.hasSyntheticSorry (mctx?.getD {})
| withContext ctx msg => visit ctx.mctx msg
| withNamingContext _ msg => visit mctx? msg
| nest _ msg => visit mctx? msg
| group msg => visit mctx? msg
| compose msg₁ msg₂ => visit mctx? msg₁ || visit mctx? msg₂
| tagged _ msg => visit mctx? msg
| trace _ msg msgs => visit mctx? msg || msgs.any (visit mctx?)
| _ => false
partial def formatAux : NamingContext → Option MessageDataContext → MessageData → IO Format
| _, _, ofFormat fmt => return fmt
| nCtx, ctx?, ofPPFormat f => (·.fmt) <$> f.pp (ctx?.map (mkPPContext nCtx))
| _, none, ofGoal mvarId => return "goal " ++ format (mkMVar mvarId)
| nCtx, some ctx, ofGoal mvarId => ppGoal (mkPPContext nCtx ctx) mvarId
| nCtx, _, withContext ctx d => formatAux nCtx ctx d
| _, ctx, withNamingContext nCtx d => formatAux nCtx ctx d
| nCtx, ctx, tagged _ d => formatAux nCtx ctx d
| nCtx, ctx, nest n d => Format.nest n <$> formatAux nCtx ctx d
| nCtx, ctx, compose d₁ d₂ => return (← formatAux nCtx ctx d₁) ++ (← formatAux nCtx ctx d₂)
| nCtx, ctx, group d => Format.group <$> formatAux nCtx ctx d
| nCtx, ctx, trace data header children => do
let mut msg := f!"[{data.cls}]"
if data.startTime != 0 then
msg := f!"{msg} [{data.stopTime - data.startTime}]"
msg := f!"{msg} {(← formatAux nCtx ctx header).nest 2}"
let children ← children.mapM (formatAux nCtx ctx)
return .nest 2 (.joinSep (msg::children.toList) "\n")
protected def format (msgData : MessageData) : IO Format :=
formatAux { currNamespace := Name.anonymous, openDecls := [] } none msgData
protected def toString (msgData : MessageData) : IO String := do
return toString (← msgData.format)
instance : Append MessageData := ⟨compose⟩
instance : Coe String MessageData := ⟨ofFormat ∘ format⟩
instance : Coe Format MessageData := ⟨ofFormat⟩
instance : Coe Level MessageData := ⟨ofLevel⟩
instance : Coe Expr MessageData := ⟨ofExpr⟩
instance : Coe Name MessageData := ⟨ofName⟩
instance : Coe Syntax MessageData := ⟨ofSyntax⟩
instance : Coe MVarId MessageData := ⟨ofGoal⟩
instance : Coe (Option Expr) MessageData := ⟨fun o => match o with | none => "none" | some e => ofExpr e⟩
partial def arrayExpr.toMessageData (es : Array Expr) (i : Nat) (acc : MessageData) : MessageData :=
if h : i < es.size then
let e := es.get ⟨i, h⟩;
let acc := if i == 0 then acc ++ ofExpr e else acc ++ ", " ++ ofExpr e;
toMessageData es (i+1) acc
else
acc ++ "]"
instance : Coe (Array Expr) MessageData := ⟨fun es => arrayExpr.toMessageData es 0 "#["⟩
/-- Wrap the given message in `l` and `r`. See also `Format.bracket`. -/
def bracket (l : String) (f : MessageData) (r : String) : MessageData := group (nest l.length <| l ++ f ++ r)
/-- Wrap the given message in parentheses `()`. -/
def paren (f : MessageData) : MessageData := bracket "(" f ")"
/-- Wrap the given message in square brackets `[]`. -/
def sbracket (f : MessageData) : MessageData := bracket "[" f "]"
/-- Append the given list of messages with the given separator. -/
def joinSep : List MessageData → MessageData → MessageData
| [], _ => Format.nil
| [a], _ => a
| a::as, sep => a ++ sep ++ joinSep as sep
/-- Write the given list of messages as a list, separating each item with `,\n` and surrounding with square brackets. -/
def ofList : List MessageData → MessageData
| [] => "[]"
| xs => sbracket <| joinSep xs (ofFormat "," ++ Format.line)
/-- See `MessageData.ofList`. -/
def ofArray (msgs : Array MessageData) : MessageData :=
ofList msgs.toList
instance : Coe (List MessageData) MessageData := ⟨ofList⟩
instance : Coe (List Expr) MessageData := ⟨fun es => ofList <| es.map ofExpr⟩
end MessageData
/--
A `BaseMessage` is a richly formatted piece of information emitted by Lean.
They are rendered by client editors in the infoview and in diagnostic windows.
There are two varieties in the Lean core:
* `Message`: Uses structured, effectful `MessageData` for formatting content.
* `SerialMessage`: Stores pure `String` data. Obtained by running the effectful
`Message.serialize`.
-/
structure BaseMessage (α : Type u) where
fileName : String
pos : Position
endPos : Option Position := none
/-- If `true`, report range as given; see `msgToInteractiveDiagnostic`. -/
keepFullRange : Bool := false
severity : MessageSeverity := MessageSeverity.error
caption : String := ""
/-- The content of the message. -/
data : α
deriving Inhabited, ToJson, FromJson
/-- A `Message` is a richly formatted piece of information emitted by Lean.
They are rendered by client editors in the infoview and in diagnostic windows. -/
abbrev Message := BaseMessage MessageData
/-- A `SerialMessage` is a `Message` whose `MessageData` has been eagerly
serialized and is thus appropriate for use in pure contexts where the effectful
`MessageData.toString` cannot be used. -/
abbrev SerialMessage := BaseMessage String
namespace SerialMessage
@[inline] def toMessage (msg : SerialMessage) : Message :=
{msg with data := msg.data}
protected def toString (msg : SerialMessage) (includeEndPos := false) : String := Id.run do
let mut str := msg.data
let endPos := if includeEndPos then msg.endPos else none
unless msg.caption == "" do
str := msg.caption ++ ":\n" ++ str
match msg.severity with
| .information => pure ()
| .warning => str := mkErrorStringWithPos msg.fileName msg.pos (endPos := endPos) "warning: " ++ str
| .error => str := mkErrorStringWithPos msg.fileName msg.pos (endPos := endPos) "error: " ++ str
if str.isEmpty || str.back != '\n' then
str := str ++ "\n"
return str
instance : ToString SerialMessage := ⟨SerialMessage.toString⟩
end SerialMessage
namespace Message
@[inline] def serialize (msg : Message) : IO SerialMessage := do
return {msg with data := ← msg.data.toString}
protected def toString (msg : Message) (includeEndPos := false) : IO String := do
-- Remark: The inline here avoids a new message allocation when `msg` is shared
return inline <| (← msg.serialize).toString includeEndPos
protected def toJson (msg : Message) : IO Json := do
-- Remark: The inline here avoids a new message allocation when `msg` is shared
return inline <| toJson (← msg.serialize)
end Message
/-- A persistent array of messages. -/
structure MessageLog where
msgs : PersistentArray Message := {}
deriving Inhabited
namespace MessageLog
def empty : MessageLog := ⟨{}⟩
def isEmpty (log : MessageLog) : Bool :=
log.msgs.isEmpty
def add (msg : Message) (log : MessageLog) : MessageLog :=
⟨log.msgs.push msg⟩
protected def append (l₁ l₂ : MessageLog) : MessageLog :=
⟨l₁.msgs ++ l₂.msgs⟩
instance : Append MessageLog :=
⟨MessageLog.append⟩
def hasErrors (log : MessageLog) : Bool :=
log.msgs.any fun m => match m.severity with
| MessageSeverity.error => true
| _ => false
def errorsToWarnings (log : MessageLog) : MessageLog :=
{ msgs := log.msgs.map (fun m => match m.severity with | MessageSeverity.error => { m with severity := MessageSeverity.warning } | _ => m) }
def getInfoMessages (log : MessageLog) : MessageLog :=
{ msgs := log.msgs.filter fun m => match m.severity with | MessageSeverity.information => true | _ => false }
def forM {m : Type → Type} [Monad m] (log : MessageLog) (f : Message → m Unit) : m Unit :=
log.msgs.forM f
/-- Converts the log to a list, oldest message first. -/
def toList (log : MessageLog) : List Message :=
log.msgs.toList
/-- Converts the log to an array, oldest message first. -/
def toArray (log : MessageLog) : Array Message :=
log.msgs.toArray
end MessageLog
def MessageData.nestD (msg : MessageData) : MessageData :=
MessageData.nest 2 msg
def indentD (msg : MessageData) : MessageData :=
MessageData.nestD (Format.line ++ msg)
def indentExpr (e : Expr) : MessageData :=
indentD e
class AddMessageContext (m : Type → Type) where
addMessageContext : MessageData → m MessageData
export AddMessageContext (addMessageContext)
instance (m n) [MonadLift m n] [AddMessageContext m] : AddMessageContext n where
addMessageContext := fun msg => liftM (addMessageContext msg : m _)
def addMessageContextPartial {m} [Monad m] [MonadEnv m] [MonadOptions m] (msgData : MessageData) : m MessageData := do
let env ← getEnv
let opts ← getOptions
return MessageData.withContext { env := env, mctx := {}, lctx := {}, opts := opts } msgData
def addMessageContextFull {m} [Monad m] [MonadEnv m] [MonadMCtx m] [MonadLCtx m] [MonadOptions m] (msgData : MessageData) : m MessageData := do
let env ← getEnv
let mctx ← getMCtx
let lctx ← getLCtx
let opts ← getOptions
return MessageData.withContext { env := env, mctx := mctx, lctx := lctx, opts := opts } msgData
class ToMessageData (α : Type) where
toMessageData : α → MessageData
export ToMessageData (toMessageData)
def stringToMessageData (str : String) : MessageData :=
let lines := str.split (· == '\n')
let lines := lines.map (MessageData.ofFormat ∘ format)
MessageData.joinSep lines (MessageData.ofFormat Format.line)
instance [ToFormat α] : ToMessageData α := ⟨MessageData.ofFormat ∘ format⟩
instance : ToMessageData Expr := ⟨MessageData.ofExpr⟩
instance : ToMessageData Level := ⟨MessageData.ofLevel⟩
instance : ToMessageData Name := ⟨MessageData.ofName⟩
instance : ToMessageData String := ⟨stringToMessageData⟩
instance : ToMessageData Syntax := ⟨MessageData.ofSyntax⟩
instance : ToMessageData (TSyntax k) := ⟨(MessageData.ofSyntax ·)⟩
instance : ToMessageData Format := ⟨MessageData.ofFormat⟩
instance : ToMessageData MVarId := ⟨MessageData.ofGoal⟩
instance : ToMessageData MessageData := ⟨id⟩
instance [ToMessageData α] : ToMessageData (List α) := ⟨fun as => MessageData.ofList <| as.map toMessageData⟩
instance [ToMessageData α] : ToMessageData (Array α) := ⟨fun as => toMessageData as.toList⟩
instance [ToMessageData α] : ToMessageData (Subarray α) := ⟨fun as => toMessageData as.toArray.toList⟩
instance [ToMessageData α] : ToMessageData (Option α) := ⟨fun | none => "none" | some e => "some (" ++ toMessageData e ++ ")"⟩
instance [ToMessageData α] [ToMessageData β] : ToMessageData (α × β) :=
⟨fun (a, b) => .paren <| toMessageData a ++ "," ++ Format.line ++ toMessageData b⟩
instance : ToMessageData (Option Expr) := ⟨fun | none => "<not-available>" | some e => toMessageData e⟩
syntax:max "m!" interpolatedStr(term) : term
macro_rules
| `(m! $interpStr) => do interpStr.expandInterpolatedStr (← `(MessageData)) (← `(toMessageData))
def toMessageList (msgs : Array MessageData) : MessageData :=
indentD (MessageData.joinSep msgs.toList m!"\n\n")
namespace KernelException
private def mkCtx (env : Environment) (lctx : LocalContext) (opts : Options) (msg : MessageData) : MessageData :=
MessageData.withContext { env := env, mctx := {}, lctx := lctx, opts := opts } msg
def toMessageData (e : KernelException) (opts : Options) : MessageData :=
match e with
| unknownConstant env constName => mkCtx env {} opts m!"(kernel) unknown constant '{constName}'"
| alreadyDeclared env constName => mkCtx env {} opts m!"(kernel) constant has already been declared '{constName}'"
| declTypeMismatch env decl givenType =>
mkCtx env {} opts <|
let process (n : Name) (expectedType : Expr) : MessageData :=
m!"(kernel) declaration type mismatch, '{n}' has type{indentExpr givenType}\nbut it is expected to have type{indentExpr expectedType}";
match decl with
| Declaration.defnDecl { name := n, type := type, .. } => process n type
| Declaration.thmDecl { name := n, type := type, .. } => process n type
| _ => "(kernel) declaration type mismatch" -- TODO fix type checker, type mismatch for mutual decls does not have enough information
| declHasMVars env constName _ => mkCtx env {} opts m!"(kernel) declaration has metavariables '{constName}'"
| declHasFVars env constName _ => mkCtx env {} opts m!"(kernel) declaration has free variables '{constName}'"
| funExpected env lctx e => mkCtx env lctx opts m!"(kernel) function expected{indentExpr e}"
| typeExpected env lctx e => mkCtx env lctx opts m!"(kernel) type expected{indentExpr e}"
| letTypeMismatch env lctx n _ _ => mkCtx env lctx opts m!"(kernel) let-declaration type mismatch '{n}'"
| exprTypeMismatch env lctx e _ => mkCtx env lctx opts m!"(kernel) type mismatch at{indentExpr e}"
| appTypeMismatch env lctx e fnType argType =>
mkCtx env lctx opts m!"application type mismatch{indentExpr e}\nargument has type{indentExpr argType}\nbut function has type{indentExpr fnType}"
| invalidProj env lctx e => mkCtx env lctx opts m!"(kernel) invalid projection{indentExpr e}"
| thmTypeIsNotProp env constName type => mkCtx env {} opts m!"(kernel) type of theorem '{constName}' is not a proposition{indentExpr type}"
| other msg => m!"(kernel) {msg}"
| deterministicTimeout => "(kernel) deterministic timeout"
| excessiveMemory => "(kernel) excessive memory consumption detected"
| deepRecursion => "(kernel) deep recursion detected"
| interrupted => "(kernel) interrupted"
end KernelException
end Lean