-
Notifications
You must be signed in to change notification settings - Fork 546
/
Copy pathBuiltinCommand.lean
489 lines (441 loc) · 21.2 KB
/
BuiltinCommand.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
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Util.CollectLevelParams
import Lean.Meta.Reduce
import Lean.Elab.DeclarationRange
import Lean.Elab.Eval
import Lean.Elab.Command
import Lean.Elab.Open
import Lean.Elab.SetOption
import Lean.PrettyPrinter
namespace Lean.Elab.Command
@[builtin_command_elab moduleDoc] def elabModuleDoc : CommandElab := fun stx => do
match stx[1] with
| Syntax.atom _ val =>
let doc := val.extract 0 (val.endPos - ⟨2⟩)
let range ← Elab.getDeclarationRange stx
modifyEnv fun env => addMainModuleDoc env ⟨doc, range⟩
| _ => throwErrorAt stx "unexpected module doc string{indentD stx[1]}"
private def addScope (isNewNamespace : Bool) (isNoncomputable : Bool) (header : String) (newNamespace : Name) : CommandElabM Unit := do
modify fun s => { s with
env := s.env.registerNamespace newNamespace,
scopes := { s.scopes.head! with header := header, currNamespace := newNamespace, isNoncomputable := s.scopes.head!.isNoncomputable || isNoncomputable } :: s.scopes
}
pushScope
if isNewNamespace then
activateScoped newNamespace
private def addScopes (isNewNamespace : Bool) (isNoncomputable : Bool) : Name → CommandElabM Unit
| .anonymous => pure ()
| .str p header => do
addScopes isNewNamespace isNoncomputable p
let currNamespace ← getCurrNamespace
addScope isNewNamespace isNoncomputable header (if isNewNamespace then Name.mkStr currNamespace header else currNamespace)
| _ => throwError "invalid scope"
private def addNamespace (header : Name) : CommandElabM Unit :=
addScopes (isNewNamespace := true) (isNoncomputable := false) header
def withNamespace {α} (ns : Name) (elabFn : CommandElabM α) : CommandElabM α := do
addNamespace ns
let a ← elabFn
modify fun s => { s with scopes := s.scopes.drop ns.getNumParts }
pure a
private def popScopes (numScopes : Nat) : CommandElabM Unit :=
for _ in [0:numScopes] do
popScope
private def checkAnonymousScope : List Scope → Option Name
| { header := "", .. } :: _ => none
| { header := h, .. } :: _ => some <| .mkSimple h
| _ => some .anonymous -- should not happen
private def checkEndHeader : Name → List Scope → Option Name
| .anonymous, _ => none
| .str p s, { header := h, .. } :: scopes =>
if h == s then
(.str · s) <$> checkEndHeader p scopes
else
some <| .mkSimple h
| _, _ => some .anonymous -- should not happen
@[builtin_command_elab «namespace»] def elabNamespace : CommandElab := fun stx =>
match stx with
| `(namespace $n) => addNamespace n.getId
| _ => throwUnsupportedSyntax
@[builtin_command_elab «section»] def elabSection : CommandElab := fun stx => do
match stx with
| `(section $header:ident) => addScopes (isNewNamespace := false) (isNoncomputable := false) header.getId
| `(section) => addScope (isNewNamespace := false) (isNoncomputable := false) "" (← getCurrNamespace)
| _ => throwUnsupportedSyntax
@[builtin_command_elab noncomputableSection] def elabNonComputableSection : CommandElab := fun stx => do
match stx with
| `(noncomputable section $header:ident) => addScopes (isNewNamespace := false) (isNoncomputable := true) header.getId
| `(noncomputable section) => addScope (isNewNamespace := false) (isNoncomputable := true) "" (← getCurrNamespace)
| _ => throwUnsupportedSyntax
@[builtin_command_elab «end»] def elabEnd : CommandElab := fun stx => do
let header? := (stx.getArg 1).getOptionalIdent?;
let endSize := match header? with
| none => 1
| some n => n.getNumParts
let scopes ← getScopes
if endSize < scopes.length then
modify fun s => { s with scopes := s.scopes.drop endSize }
popScopes endSize
else -- we keep "root" scope
let n := (← get).scopes.length - 1
modify fun s => { s with scopes := s.scopes.drop n }
popScopes n
throwError "invalid 'end', insufficient scopes"
match header? with
| none =>
if let some name := checkAnonymousScope scopes then
throwError "invalid 'end', name is missing (expected {name})"
| some header =>
if let some name := checkEndHeader header scopes then
addCompletionInfo <| CompletionInfo.endSection stx (scopes.map fun scope => scope.header)
throwError "invalid 'end', name mismatch (expected {if name == `«» then `nothing else name})"
private partial def elabChoiceAux (cmds : Array Syntax) (i : Nat) : CommandElabM Unit :=
if h : i < cmds.size then
let cmd := cmds.get ⟨i, h⟩;
catchInternalId unsupportedSyntaxExceptionId
(elabCommand cmd)
(fun _ => elabChoiceAux cmds (i+1))
else
throwUnsupportedSyntax
@[builtin_command_elab choice] def elabChoice : CommandElab := fun stx =>
elabChoiceAux stx.getArgs 0
@[builtin_command_elab «universe»] def elabUniverse : CommandElab := fun n => do
n[1].forArgsM addUnivLevel
@[builtin_command_elab «init_quot»] def elabInitQuot : CommandElab := fun _ => do
match (← getEnv).addDecl (← getOptions) Declaration.quotDecl with
| Except.ok env => setEnv env
| Except.error ex => throwError (ex.toMessageData (← getOptions))
@[builtin_command_elab «export»] def elabExport : CommandElab := fun stx => do
let `(export $ns ($ids*)) := stx | throwUnsupportedSyntax
let nss ← resolveNamespace ns
let currNamespace ← getCurrNamespace
if nss == [currNamespace] then throwError "invalid 'export', self export"
let mut aliases := #[]
for idStx in ids do
let id := idStx.getId
let declName ← resolveNameUsingNamespaces nss idStx
if (← getInfoState).enabled then
addConstInfo idStx declName
aliases := aliases.push (currNamespace ++ id, declName)
modify fun s => { s with env := aliases.foldl (init := s.env) fun env p => addAlias env p.1 p.2 }
@[builtin_command_elab «open»] def elabOpen : CommandElab
| `(open $decl:openDecl) => do
let openDecls ← elabOpenDecl decl
modifyScope fun scope => { scope with openDecls := openDecls }
| _ => throwUnsupportedSyntax
open Lean.Parser.Term
private def typelessBinder? : Syntax → Option (Array (TSyntax [`ident, `Lean.Parser.Term.hole]) × Bool)
| `(bracketedBinderF|($ids*)) => some (ids, true)
| `(bracketedBinderF|{$ids*}) => some (ids, false)
| _ => none
/-- If `id` is an identifier, return true if `ids` contains `id`. -/
private def containsId (ids : Array (TSyntax [`ident, ``Parser.Term.hole])) (id : TSyntax [`ident, ``Parser.Term.hole]) : Bool :=
id.raw.isIdent && ids.any fun id' => id'.raw.getId == id.raw.getId
/--
Auxiliary method for processing binder annotation update commands: `variable (α)` and `variable {α}`.
The argument `binder` is the binder of the `variable` command.
The method returns an array containing the "residue", that is, variables that do not correspond to updates.
Recall that a `bracketedBinder` can be of the form `(x y)`.
```
variable {α β : Type}
variable (α γ)
```
The second `variable` command updates the binder annotation for `α`, and returns "residue" `γ`.
-/
private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBinder) : CommandElabM (Array (TSyntax ``Parser.Term.bracketedBinder)) := do
let some (binderIds, explicit) := typelessBinder? binder | return #[binder]
let varDecls := (← getScope).varDecls
let mut varDeclsNew := #[]
let mut binderIds := binderIds
let mut binderIdsIniSize := binderIds.size
let mut modifiedVarDecls := false
for varDecl in varDecls do
let (ids, ty?, explicit') ← match varDecl with
| `(bracketedBinderF|($ids* $[: $ty?]? $(annot?)?)) =>
if annot?.isSome then
for binderId in binderIds do
if containsId ids binderId then
throwErrorAt binderId "cannot update binder annotation of variables with default values/tactics"
pure (ids, ty?, true)
| `(bracketedBinderF|{$ids* $[: $ty?]?}) =>
pure (ids, ty?, false)
| `(bracketedBinderF|[$id : $_]) =>
for binderId in binderIds do
if binderId.raw.isIdent && binderId.raw.getId == id.getId then
throwErrorAt binderId "cannot change the binder annotation of the previously declared local instance `{id.getId}`"
varDeclsNew := varDeclsNew.push varDecl; continue
| _ =>
varDeclsNew := varDeclsNew.push varDecl; continue
if explicit == explicit' then
-- no update, ensure we don't have redundant annotations.
for binderId in binderIds do
if containsId ids binderId then
throwErrorAt binderId "redundant binder annotation update"
varDeclsNew := varDeclsNew.push varDecl
else if binderIds.all fun binderId => !containsId ids binderId then
-- `binderIds` and `ids` are disjoint
varDeclsNew := varDeclsNew.push varDecl
else
let mkBinder (id : TSyntax [`ident, ``Parser.Term.hole]) (explicit : Bool) : CommandElabM (TSyntax ``Parser.Term.bracketedBinder) :=
if explicit then
`(bracketedBinderF| ($id $[: $ty?]?))
else
`(bracketedBinderF| {$id $[: $ty?]?})
for id in ids do
if let some idx := binderIds.findIdx? fun binderId => binderId.raw.isIdent && binderId.raw.getId == id.raw.getId then
binderIds := binderIds.eraseIdx idx
modifiedVarDecls := true
varDeclsNew := varDeclsNew.push (← mkBinder id explicit)
else
varDeclsNew := varDeclsNew.push (← mkBinder id explicit')
if modifiedVarDecls then
modifyScope fun scope => { scope with varDecls := varDeclsNew }
if binderIds.size != binderIdsIniSize then
binderIds.mapM fun binderId =>
if explicit then
`(bracketedBinderF| ($binderId))
else
`(bracketedBinderF| {$binderId})
else
return #[binder]
@[builtin_command_elab «variable»] def elabVariable : CommandElab
| `(variable $binders*) => do
-- Try to elaborate `binders` for sanity checking
runTermElabM fun _ => Term.withSynthesize <| Term.withAutoBoundImplicit <|
Term.elabBinders binders fun _ => pure ()
for binder in binders do
let binders ← replaceBinderAnnotation binder
-- Remark: if we want to produce error messages when variables shadow existing ones, here is the place to do it.
for binder in binders do
let varUIds ← getBracketedBinderIds binder |>.mapM (withFreshMacroScope ∘ MonadQuotation.addMacroScope)
modifyScope fun scope => { scope with varDecls := scope.varDecls.push binder, varUIds := scope.varUIds ++ varUIds }
| _ => throwUnsupportedSyntax
open Meta
def elabCheckCore (ignoreStuckTC : Bool) : CommandElab
| `(#check%$tk $term) => withoutModifyingEnv <| runTermElabM fun _ => Term.withDeclName `_check do
-- show signature for `#check id`/`#check @id`
if let `($id:ident) := term then
try
for c in (← realizeGlobalConstWithInfos term) do
addCompletionInfo <| .id term id.getId (danglingDot := false) {} none
logInfoAt tk <| .signature c
return
catch _ => pure () -- identifier might not be a constant but constant + projection
let e ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing (ignoreStuckTC := ignoreStuckTC)
let e ← Term.levelMVarToParam (← instantiateMVars e)
let type ← inferType e
if e.isSyntheticSorry then
return
logInfoAt tk m!"{e} : {type}"
| _ => throwUnsupportedSyntax
@[builtin_command_elab Lean.Parser.Command.check] def elabCheck : CommandElab := elabCheckCore (ignoreStuckTC := true)
@[builtin_command_elab Lean.Parser.Command.reduce] def elabReduce : CommandElab
| `(#reduce%$tk $term) => withoutModifyingEnv <| runTermElabM fun _ => Term.withDeclName `_reduce do
let e ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing
let e ← Term.levelMVarToParam (← instantiateMVars e)
-- TODO: add options or notation for setting the following parameters
withTheReader Core.Context (fun ctx => { ctx with options := ctx.options.setBool `smartUnfolding false }) do
let e ← withTransparency (mode := TransparencyMode.all) <| reduce e (skipProofs := false) (skipTypes := false)
logInfoAt tk e
| _ => throwUnsupportedSyntax
def hasNoErrorMessages : CommandElabM Bool := do
return !(← get).messages.hasErrors
def failIfSucceeds (x : CommandElabM Unit) : CommandElabM Unit := do
let resetMessages : CommandElabM MessageLog := do
let s ← get
let messages := s.messages;
modify fun s => { s with messages := {} };
pure messages
let restoreMessages (prevMessages : MessageLog) : CommandElabM Unit := do
modify fun s => { s with messages := prevMessages ++ s.messages.errorsToWarnings }
let prevMessages ← resetMessages
let succeeded ← try
x
hasNoErrorMessages
catch
| ex@(Exception.error _ _) => do logException ex; pure false
| Exception.internal id _ => do logError (← id.getName); pure false
finally
restoreMessages prevMessages
if succeeded then
throwError "unexpected success"
@[builtin_command_elab «check_failure»] def elabCheckFailure : CommandElab
| `(#check_failure $term) => do
failIfSucceeds <| elabCheckCore (ignoreStuckTC := false) (← `(#check $term))
| _ => throwUnsupportedSyntax
private def mkEvalInstCore (evalClassName : Name) (e : Expr) : MetaM Expr := do
let α ← inferType e
let u ← getDecLevel α
let inst := mkApp (Lean.mkConst evalClassName [u]) α
try
synthInstance inst
catch _ =>
-- Put `α` in WHNF and try again
try
let α ← whnf α
synthInstance (mkApp (Lean.mkConst evalClassName [u]) α)
catch _ =>
-- Fully reduce `α` and try again
try
let α ← reduce (skipTypes := false) α
synthInstance (mkApp (Lean.mkConst evalClassName [u]) α)
catch _ =>
throwError "expression{indentExpr e}\nhas type{indentExpr α}\nbut instance{indentExpr inst}\nfailed to be synthesized, this instance instructs Lean on how to display the resulting value, recall that any type implementing the `Repr` class also implements the `{evalClassName}` class"
private def mkRunMetaEval (e : Expr) : MetaM Expr :=
withLocalDeclD `env (mkConst ``Lean.Environment) fun env =>
withLocalDeclD `opts (mkConst ``Lean.Options) fun opts => do
let α ← inferType e
let u ← getDecLevel α
let instVal ← mkEvalInstCore ``Lean.MetaEval e
let e := mkAppN (mkConst ``Lean.runMetaEval [u]) #[α, instVal, env, opts, e]
instantiateMVars (← mkLambdaFVars #[env, opts] e)
private def mkRunEval (e : Expr) : MetaM Expr := do
let α ← inferType e
let u ← getDecLevel α
let instVal ← mkEvalInstCore ``Lean.Eval e
instantiateMVars (mkAppN (mkConst ``Lean.runEval [u]) #[α, instVal, mkSimpleThunk e])
unsafe def elabEvalUnsafe : CommandElab
| `(#eval%$tk $term) => do
let declName := `_eval
let addAndCompile (value : Expr) : TermElabM Unit := do
let value ← Term.levelMVarToParam (← instantiateMVars value)
let type ← inferType value
let us := collectLevelParams {} value |>.params
let value ← instantiateMVars value
let decl := Declaration.defnDecl {
name := declName
levelParams := us.toList
type := type
value := value
hints := ReducibilityHints.opaque
safety := DefinitionSafety.unsafe
}
Term.ensureNoUnassignedMVars decl
addAndCompile decl
-- Elaborate `term`
let elabEvalTerm : TermElabM Expr := do
let e ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing
if (← Term.logUnassignedUsingErrorInfos (← getMVars e)) then throwAbortTerm
if (← isProp e) then
mkDecide e
else
return e
-- Evaluate using term using `MetaEval` class.
let elabMetaEval : CommandElabM Unit := do
-- Generate an action without executing it. We use `withoutModifyingEnv` to ensure
-- we don't polute the environment with auxliary declarations.
-- We have special support for `CommandElabM` to ensure `#eval` can be used to execute commands
-- that modify `CommandElabM` state not just the `Environment`.
let act : Sum (CommandElabM Unit) (Environment → Options → IO (String × Except IO.Error Environment)) ←
runTermElabM fun _ => Term.withDeclName declName do withoutModifyingEnv do
let e ← elabEvalTerm
let eType ← instantiateMVars (← inferType e)
if eType.isAppOfArity ``CommandElabM 1 then
let mut stx ← Term.exprToSyntax e
unless (← isDefEq eType.appArg! (mkConst ``Unit)) do
stx ← `($stx >>= fun v => IO.println (repr v))
let act ← Lean.Elab.Term.evalTerm (CommandElabM Unit) (mkApp (mkConst ``CommandElabM) (mkConst ``Unit)) stx
pure <| Sum.inl act
else
let e ← mkRunMetaEval e
addAndCompile e
let act ← evalConst (Environment → Options → IO (String × Except IO.Error Environment)) declName
pure <| Sum.inr act
match act with
| .inl act => act
| .inr act =>
let (out, res) ← act (← getEnv) (← getOptions)
logInfoAt tk out
match res with
| Except.error e => throwError e.toString
| Except.ok env => setEnv env; pure ()
-- Evaluate using term using `Eval` class.
let elabEval : CommandElabM Unit := runTermElabM fun _ => Term.withDeclName declName do withoutModifyingEnv do
-- fall back to non-meta eval if MetaEval hasn't been defined yet
-- modify e to `runEval e`
let e ← mkRunEval (← elabEvalTerm)
addAndCompile e
let act ← evalConst (IO (String × Except IO.Error Unit)) declName
let (out, res) ← liftM (m := IO) act
logInfoAt tk out
match res with
| Except.error e => throwError e.toString
| Except.ok _ => pure ()
if (← getEnv).contains ``Lean.MetaEval then do
elabMetaEval
else
elabEval
| _ => throwUnsupportedSyntax
@[builtin_command_elab «eval», implemented_by elabEvalUnsafe]
opaque elabEval : CommandElab
private def checkImportsForRunCmds : CommandElabM Unit := do
unless (← getEnv).contains ``CommandElabM do
throwError "to use this command, include `import Lean.Elab.Command`"
@[builtin_command_elab runCmd]
def elabRunCmd : CommandElab
| `(run_cmd $elems:doSeq) => do
checkImportsForRunCmds
(← liftTermElabM <| Term.withDeclName `_run_cmd <|
unsafe Term.evalTerm (CommandElabM Unit)
(mkApp (mkConst ``CommandElabM) (mkConst ``Unit))
(← `(discard do $elems)))
| _ => throwUnsupportedSyntax
@[builtin_command_elab runElab]
def elabRunElab : CommandElab
| `(run_elab $elems:doSeq) => do
checkImportsForRunCmds
(← liftTermElabM <| Term.withDeclName `_run_elab <|
unsafe Term.evalTerm (CommandElabM Unit)
(mkApp (mkConst ``CommandElabM) (mkConst ``Unit))
(← `(Command.liftTermElabM <| discard do $elems)))
| _ => throwUnsupportedSyntax
@[builtin_command_elab runMeta]
def elabRunMeta : CommandElab := fun stx =>
match stx with
| `(run_meta $elems:doSeq) => do
checkImportsForRunCmds
let stxNew ← `(command| run_elab (show Lean.Meta.MetaM Unit from do $elems))
withMacroExpansion stx stxNew do elabCommand stxNew
| _ => throwUnsupportedSyntax
@[builtin_command_elab «synth»] def elabSynth : CommandElab := fun stx => do
let term := stx[1]
withoutModifyingEnv <| runTermElabM fun _ => Term.withDeclName `_synth_cmd do
let inst ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing
let inst ← instantiateMVars inst
let val ← synthInstance inst
logInfo val
pure ()
@[builtin_command_elab «set_option»] def elabSetOption : CommandElab := fun stx => do
let options ← Elab.elabSetOption stx[1] stx[3]
modify fun s => { s with maxRecDepth := maxRecDepth.get options }
modifyScope fun scope => { scope with opts := options }
@[builtin_macro Lean.Parser.Command.«in»] def expandInCmd : Macro
| `($cmd₁ in $cmd₂) => `(section $cmd₁:command $cmd₂ end)
| _ => Macro.throwUnsupported
@[builtin_command_elab Parser.Command.addDocString] def elabAddDeclDoc : CommandElab := fun stx => do
match stx with
| `($doc:docComment add_decl_doc $id) =>
let declName ← liftCoreM <| realizeGlobalConstNoOverloadWithInfo id
unless ((← getEnv).getModuleIdxFor? declName).isNone do
throwError "invalid 'add_decl_doc', declaration is in an imported module"
if let .none ← findDeclarationRangesCore? declName then
-- this is only relevant for declarations added without a declaration range
-- in particular `Quot.mk` et al which are added by `init_quot`
addAuxDeclarationRanges declName stx id
addDocString declName (← getDocStringText doc)
| _ => throwUnsupportedSyntax
@[builtin_command_elab Parser.Command.exit] def elabExit : CommandElab := fun _ =>
logWarning "using 'exit' to interrupt Lean"
@[builtin_command_elab Parser.Command.import] def elabImport : CommandElab := fun _ =>
throwError "invalid 'import' command, it must be used in the beginning of the file"
@[builtin_command_elab Parser.Command.eoi] def elabEoi : CommandElab := fun _ =>
return
end Lean.Elab.Command