lean4-htt/src/Lean/Elab/BuiltinCommand.lean
Mario Carneiro 7956a9bb15
chore: typos
2021-12-23 10:14:39 +01:00

371 lines
15 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.DocString
import Lean.Util.CollectLevelParams
import Lean.Elab.Command
import Lean.Elab.Open
namespace Lean.Elab.Command
@[builtinCommandElab moduleDoc] def elabModuleDoc : CommandElab := fun stx =>
match stx[1] with
| Syntax.atom _ val =>
let doc := val.extract 0 (val.bsize - 2)
modifyEnv fun env => addMainModuleDoc env doc
| _ => 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
| Name.anonymous => pure ()
| Name.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 i in [0:numScopes] do
popScope
private def checkAnonymousScope : List Scope → Bool
| { header := "", .. } :: _ => true
| _ => false
private def checkEndHeader : Name → List Scope → Bool
| Name.anonymous, _ => true
| Name.str p s _, { header := h, .. } :: scopes => h == s && checkEndHeader p scopes
| _, _ => false
@[builtinCommandElab «namespace»] def elabNamespace : CommandElab := fun stx =>
match stx with
| `(namespace $n) => addNamespace n.getId
| _ => throwUnsupportedSyntax
@[builtinCommandElab «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
@[builtinCommandElab 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
@[builtinCommandElab «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 =>
unless checkAnonymousScope scopes do
throwError "invalid 'end', name is missing"
| some header =>
unless checkEndHeader header scopes do
addCompletionInfo <| CompletionInfo.endSection stx (scopes.map fun scope => scope.header)
throwError "invalid 'end', name mismatch"
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 ex => elabChoiceAux cmds (i+1))
else
throwUnsupportedSyntax
@[builtinCommandElab choice] def elabChoice : CommandElab := fun stx =>
elabChoiceAux stx.getArgs 0
@[builtinCommandElab «universe»] def elabUniverse : CommandElab := fun n => do
n[1].forArgsM addUnivLevel
@[builtinCommandElab «init_quot»] def elabInitQuot : CommandElab := fun stx => do
match (← getEnv).addDecl Declaration.quotDecl with
| Except.ok env => setEnv env
| Except.error ex => throwError (ex.toMessageData (← getOptions))
@[builtinCommandElab «export»] def elabExport : CommandElab := fun stx => do
-- `stx` is of the form (Command.export "export" <namespace> "(" (null <ids>*) ")")
let id := stx[1].getId
let ns ← resolveNamespace id
let currNamespace ← getCurrNamespace
if ns == currNamespace then throwError "invalid 'export', self export"
let env ← getEnv
let ids := stx[3].getArgs
let aliases ← ids.foldlM (init := []) fun (aliases : List (Name × Name)) (idStx : Syntax) => do
let id := idStx.getId
let declName ← resolveOpenDeclId ns idStx
pure <| (currNamespace ++ id, declName) :: aliases
modify fun s => { s with env := aliases.foldl (init := s.env) fun env p => addAlias env p.1 p.2 }
@[builtinCommandElab «open»] def elabOpen : CommandElab := fun n => do
let openDecls ← elabOpenDecl n[1]
modifyScope fun scope => { scope with openDecls := openDecls }
private def typelessBinder? : Syntax → Option (Array Name × Bool)
| `(bracketedBinder|($ids*)) => some <| (ids.map Syntax.getId, true)
| `(bracketedBinder|{$ids*}) => some <| (ids.map Syntax.getId, false)
| _ => none
-- This function is used to implement the `variable` command that updates binder annotations.
private def matchBinderNames (ids : Array Syntax) (binderNames : Array Name) : CommandElabM Bool :=
let ids := ids.map Syntax.getId
/-
TODO: allow users to update the annotation of some of the ids.
The current application supports the common case
```
variable (α : Type)
...
variable {α : Type}
```
-/
if ids == binderNames then
return true
else if binderNames.any ids.contains then
/- We currently do not split binder blocks. -/
throwError "failed to update variable binder annotation" -- TODO: improve error message
else
return false
/--
Auxiliary method for processing binder annotation update commands: `variable (α)` and `variable {α}`.
The argument `binder` is the binder of the `variable` command.
The method retuns `true` if the binder annotation was updated.
Remark: we currently do not suppor updates of the form
```
variable (α β : Type)
...
variable {α} -- trying to update part of the binder block defined above.
```
-/
private def replaceBinderAnnotation (binder : Syntax) : CommandElabM Bool := do
if let some (binderNames, explicit) := typelessBinder? binder then
let varDecls := (← getScope).varDecls
let mut varDeclsNew := #[]
let mut found := false
for varDecl in varDecls do
if let some (ids, ty?, annot?) :=
match varDecl with
| `(bracketedBinder|($ids* $[: $ty?]? $(annot?)?)) => some (ids, ty?, annot?)
| `(bracketedBinder|{$ids* $[: $ty?]?}) => some (ids, ty?, none)
| `(bracketedBinder|[$id : $ty]) => some (#[id], some ty, none)
| _ => none
then
if (← matchBinderNames ids binderNames) then
if annot?.isSome then
throwError "cannot update binder annotation of variables with default values/tactics"
if explicit then
varDeclsNew := varDeclsNew.push (← `(bracketedBinder| ($ids* $[: $ty?]?)))
else
varDeclsNew := varDeclsNew.push (← `(bracketedBinder| {$ids* $[: $ty?]?}))
found := true
else
varDeclsNew := varDeclsNew.push varDecl
else
varDeclsNew := varDeclsNew.push varDecl
if found then
modifyScope fun scope => { scope with varDecls := varDeclsNew }
return true
else
return false
else
return false
@[builtinCommandElab «variable»] def elabVariable : CommandElab
| `(variable $binders*) => do
-- Try to elaborate `binders` for sanity checking
runTermElabM none fun _ => Term.withAutoBoundImplicit <|
Term.elabBinders binders fun _ => pure ()
for binder in binders do
unless (← replaceBinderAnnotation binder) 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 (some `_check) fun _ => do
let e ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing (ignoreStuckTC := ignoreStuckTC)
let (e, _) ← Term.levelMVarToParam (← instantiateMVars e)
let type ← inferType e
unless e.isSyntheticSorry do
logInfoAt tk m!"{e} : {type}"
| _ => throwUnsupportedSyntax
@[builtinCommandElab Lean.Parser.Command.check] def elabCheck : CommandElab := elabCheckCore (ignoreStuckTC := true)
@[builtinCommandElab Lean.Parser.Command.reduce] def elabReduce : CommandElab
| `(#reduce%$tk $term) => withoutModifyingEnv <| runTermElabM (some `_check) fun _ => 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"
@[builtinCommandElab «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 _ =>
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 n := `_eval
let ctx ← read
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 := n
levelParams := us.toList
type := type
value := value
hints := ReducibilityHints.opaque
safety := DefinitionSafety.unsafe
}
Term.ensureNoUnassignedMVars decl
addAndCompile decl
let elabEvalTerm : TermElabM Expr := do
let e ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing
if (← isProp e) then
mkDecide e
else
return e
let elabMetaEval : CommandElabM Unit := runTermElabM (some n) fun _ => do
let e ← mkRunMetaEval (← elabEvalTerm)
let env ← getEnv
let opts ← getOptions
let act ← try addAndCompile e; evalConst (Environment → Options → IO (String × Except IO.Error Environment)) n finally setEnv env
let (out, res) ← act env opts -- we execute `act` using the environment
logInfoAt tk out
match res with
| Except.error e => throwError e.toString
| Except.ok env => do setEnv env; pure ()
let elabEval : CommandElabM Unit := runTermElabM (some n) fun _ => do
-- fall back to non-meta eval if MetaEval hasn't been defined yet
-- modify e to `runEval e`
let e ← mkRunEval (← elabEvalTerm)
let env ← getEnv
let act ← try addAndCompile e; evalConst (IO (String × Except IO.Error Unit)) n finally setEnv env
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
@[builtinCommandElab «eval», implementedBy elabEvalUnsafe]
constant elabEval : CommandElab
@[builtinCommandElab «synth»] def elabSynth : CommandElab := fun stx => do
let term := stx[1]
withoutModifyingEnv <| runTermElabM `_synth_cmd fun _ => do
let inst ← Term.elabTerm term none
Term.synthesizeSyntheticMVarsNoPostponing
let inst ← instantiateMVars inst
let val ← synthInstance inst
logInfo val
pure ()
@[builtinCommandElab «set_option»] def elabSetOption : CommandElab := fun stx => do
let options ← Elab.elabSetOption stx[1] stx[2]
modify fun s => { s with maxRecDepth := maxRecDepth.get options }
modifyScope fun scope => { scope with opts := options }
@[builtinMacro Lean.Parser.Command.«in»] def expandInCmd : Macro := fun stx => do
let cmd₁ := stx[0]
let cmd₂ := stx[2]
`(section $cmd₁:command $cmd₂:command end)
end Lean.Elab.Command