chore: cleanup

This commit is contained in:
Leonardo de Moura 2020-10-26 07:54:11 -07:00
parent cffa3b0039
commit b4e8862716
12 changed files with 947 additions and 955 deletions

View file

@ -4,59 +4,61 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
import Lean.Meta.ExprDefEq
namespace Lean.Meta
def forallTelescopeCompatibleAux {α} (k : Array Expr → Expr → Expr → MetaM α) : Nat → Expr → Expr → Array Expr → MetaM α
| 0, type₁, type₂, xs => k xs type₁ type₂
| i+1, type₁, type₂, xs => do
let type₁ ← whnf type₁
let type₂ ← whnf type₂
match type₁, type₂ with
| Expr.forallE n₁ d₁ b₁ c₁, Expr.forallE n₂ d₂ b₂ c₂ =>
unless n₁ == n₂ do
throwError! "parameter name mismatch '{n₁}', expected '{n₂}'"
unless (← isDefEq d₁ d₂) do
throwError! "type mismatch at parameter '{n₁}'{indentExpr d₁}\nexpected type{indentExpr d₂}"
unless c₁.binderInfo == c₂.binderInfo do
throwError! "binder annotation mismatch at parameter '{n₁}'"
withLocalDecl n₁ c₁.binderInfo d₁ fun x =>
let type₁ := b₁.instantiate1 x
let type₂ := b₂.instantiate1 x
forallTelescopeCompatibleAux k i type₁ type₂ (xs.push x)
| _, _ => throwError "unexpected number of parameters"
| 0, type₁, type₂, xs => k xs type₁ type₂
| i+1, type₁, type₂, xs => do
let type₁ ← whnf type₁
let type₂ ← whnf type₂
match type₁, type₂ with
| Expr.forallE n₁ d₁ b₁ c₁, Expr.forallE n₂ d₂ b₂ c₂ =>
unless n₁ == n₂ do
throwError! "parameter name mismatch '{n₁}', expected '{n₂}'"
unless (← isDefEq d₁ d₂) do
throwError! "type mismatch at parameter '{n₁}'{indentExpr d₁}\nexpected type{indentExpr d₂}"
unless c₁.binderInfo == c₂.binderInfo do
throwError! "binder annotation mismatch at parameter '{n₁}'"
withLocalDecl n₁ c₁.binderInfo d₁ fun x =>
let type₁ := b₁.instantiate1 x
let type₂ := b₂.instantiate1 x
forallTelescopeCompatibleAux k i type₁ type₂ (xs.push x)
| _, _ => throwError "unexpected number of parameters"
/-- Given two forall-expressions `type₁` and `type₂`, ensure the first `numParams` parameters are compatible, and
then execute `k` with the parameters and remaining types. -/
@[inline] def forallTelescopeCompatible {α m} [Monad m] [MonadControlT MetaM m] (type₁ type₂ : Expr) (numParams : Nat) (k : Array Expr → Expr → Expr → m α) : m α :=
controlAt MetaM fun runInBase => forallTelescopeCompatibleAux (fun xs type₁ type₂ => runInBase $ k xs type₁ type₂) numParams type₁ type₂ #[]
controlAt MetaM fun runInBase =>
forallTelescopeCompatibleAux (fun xs type₁ type₂ => runInBase $ k xs type₁ type₂) numParams type₁ type₂ #[]
end Meta
namespace Elab
def expandOptDeclSig (stx : Syntax) : Syntax × Option Syntax :=
-- many Term.bracketedBinder >> Term.optType
let binders := stx[0]
let optType := stx[1] -- optional (parser! " : " >> termParser)
if optType.isNone then
(binders, none)
else
let typeSpec := optType[0]
(binders, some typeSpec[1])
-- many Term.bracketedBinder >> Term.optType
let binders := stx[0]
let optType := stx[1] -- optional (parser! " : " >> termParser)
if optType.isNone then
(binders, none)
else
let typeSpec := optType[0]
(binders, some typeSpec[1])
def expandDeclSig (stx : Syntax) : Syntax × Syntax :=
-- many Term.bracketedBinder >> Term.typeSpec
let binders := stx[0]
let typeSpec := stx[1]
(binders, typeSpec[1])
-- many Term.bracketedBinder >> Term.typeSpec
let binders := stx[0]
let typeSpec := stx[1]
(binders, typeSpec[1])
def mkFreshInstanceName (env : Environment) (nextIdx : Nat) : Name :=
(env.mainModule ++ `_instance).appendIndexAfter nextIdx
(env.mainModule ++ `_instance).appendIndexAfter nextIdx
def isFreshInstanceName (name : Name) : Bool :=
match name with
| Name.str _ s _ => "_instance".isPrefixOf s
| _ => false
match name with
| Name.str _ s _ => "_instance".isPrefixOf s
| _ => false
/--
Sort the given list of `usedParams` using the following order:
@ -70,13 +72,12 @@ match name with
Remark: `explicitParams` are in reverse declaration order. That is, the head is the last declared parameter. -/
def sortDeclLevelParams (scopeParams : List Name) (allUserParams : List Name) (usedParams : Array Name) : Except String (List Name) :=
match allUserParams.find? $ fun u => !usedParams.contains u && !scopeParams.elem u with
| some u => throw s!"unused universe parameter '{u}'"
| none =>
let result := allUserParams.foldl (fun result levelName => if usedParams.elem levelName then levelName :: result else result) []
let remaining := usedParams.filter (fun levelParam => !allUserParams.elem levelParam)
let remaining := remaining.qsort Name.lt
pure $ result ++ remaining.toList
match allUserParams.find? $ fun u => !usedParams.contains u && !scopeParams.elem u with
| some u => throw s!"unused universe parameter '{u}'"
| none =>
let result := allUserParams.foldl (fun result levelName => if usedParams.elem levelName then levelName :: result else result) []
let remaining := usedParams.filter (fun levelParam => !allUserParams.elem levelParam)
let remaining := remaining.qsort Name.lt
pure $ result ++ remaining.toList
end Elab
end Lean
end Lean.Elab

View file

@ -7,28 +7,29 @@ import Lean.InternalExceptionId
import Lean.Meta.Basic
namespace Lean.Elab
builtin_initialize postponeExceptionId : InternalExceptionId ← registerInternalExceptionId `postpone
builtin_initialize unsupportedSyntaxExceptionId : InternalExceptionId ← registerInternalExceptionId `unsupportedSyntax
builtin_initialize abortExceptionId : InternalExceptionId ← registerInternalExceptionId `abortElab
def throwPostpone {α m} [MonadExceptOf Exception m] : m α :=
throw $ Exception.internal postponeExceptionId
throw $ Exception.internal postponeExceptionId
def throwUnsupportedSyntax {α m} [MonadExceptOf Exception m] : m α :=
throw $ Exception.internal unsupportedSyntaxExceptionId
throw $ Exception.internal unsupportedSyntaxExceptionId
def throwIllFormedSyntax {α m} [Monad m] [MonadExceptOf Exception m] [Ref m] [AddErrorMessageContext m] : m α :=
throwError "ill-formed syntax"
throwError "ill-formed syntax"
def throwAlreadyDeclaredUniverseLevel {α m} [Monad m] [MonadExceptOf Exception m] [Ref m] [AddErrorMessageContext m] (u : Name) : m α :=
throwError! "a universe level named '{u}' has already been declared"
throwError! "a universe level named '{u}' has already been declared"
-- Throw exception to abort elaboration without producing any error message
def throwAbort {α m} [MonadExcept Exception m] : m α :=
throw $ Exception.internal abortExceptionId
throw $ Exception.internal abortExceptionId
def mkMessageCore (fileName : String) (fileMap : FileMap) (msgData : MessageData) (severity : MessageSeverity) (pos : String.Pos) : Message :=
let pos := fileMap.toPosition pos
{ fileName := fileName, pos := pos, data := msgData, severity := severity }
let pos := fileMap.toPosition pos
{ fileName := fileName, pos := pos, data := msgData, severity := severity }
end Lean.Elab

View file

@ -8,34 +8,35 @@ import Lean.Elab.Command
import Lean.Util.Profile
namespace Lean.Elab.Frontend
structure State :=
(commandState : Command.State)
(parserState : Parser.ModuleParserState)
(cmdPos : String.Pos)
(commands : Array Syntax := #[])
(commandState : Command.State)
(parserState : Parser.ModuleParserState)
(cmdPos : String.Pos)
(commands : Array Syntax := #[])
structure Context :=
(inputCtx : Parser.InputContext)
(inputCtx : Parser.InputContext)
abbrev FrontendM := ReaderT Context $ StateRefT State IO
def setCommandState (commandState : Command.State) : FrontendM Unit :=
modify fun s => { s with commandState := commandState }
modify fun s => { s with commandState := commandState }
@[inline] def runCommandElabM (x : Command.CommandElabM Unit) : FrontendM Unit := do
let ctx ← read
let s ← get
let cmdCtx : Command.Context := { cmdPos := s.cmdPos, fileName := ctx.inputCtx.fileName, fileMap := ctx.inputCtx.fileMap }
let sNew? ← liftM $ EIO.toIO (fun _ => IO.Error.userError "unexpected error") (do (_, s) ← (x cmdCtx).run s.commandState; pure $ some s)
match sNew? with
| some sNew => setCommandState sNew
| none => pure ()
let ctx ← read
let s ← get
let cmdCtx : Command.Context := { cmdPos := s.cmdPos, fileName := ctx.inputCtx.fileName, fileMap := ctx.inputCtx.fileMap }
let sNew? ← liftM $ EIO.toIO (fun _ => IO.Error.userError "unexpected error") (do (_, s) ← (x cmdCtx).run s.commandState; pure $ some s)
match sNew? with
| some sNew => setCommandState sNew
| none => pure ()
def elabCommandAtFrontend (stx : Syntax) : FrontendM Unit := do
runCommandElabM (Command.elabCommand stx)
runCommandElabM (Command.elabCommand stx)
def updateCmdPos : FrontendM Unit := do
modify fun s => { s with cmdPos := s.parserState.pos }
modify fun s => { s with cmdPos := s.parserState.pos }
def getParserState : FrontendM Parser.ModuleParserState := do pure (← get).parserState
def getCommandState : FrontendM Command.State := do pure (← get).commandState
@ -44,53 +45,53 @@ def setMessages (msgs : MessageLog) : FrontendM Unit := modify fun s => { s with
def getInputContext : FrontendM Parser.InputContext := do pure (← read).inputCtx
def processCommand : FrontendM Bool := do
updateCmdPos
let cmdState ← getCommandState
let ictx ← getInputContext
let pstate ← getParserState
let pos := ictx.fileMap.toPosition pstate.pos
match profileit "parsing" pos fun _ => Parser.parseCommand cmdState.env ictx pstate cmdState.messages with
| (cmd, ps, messages) =>
modify fun s => { s with commands := s.commands.push cmd }
setParserState ps
setMessages messages
if Parser.isEOI cmd || Parser.isExitCommand cmd then
pure true -- Done
else
profileitM IO.Error "elaboration" pos $ elabCommandAtFrontend cmd
pure false
updateCmdPos
let cmdState ← getCommandState
let ictx ← getInputContext
let pstate ← getParserState
let pos := ictx.fileMap.toPosition pstate.pos
match profileit "parsing" pos fun _ => Parser.parseCommand cmdState.env ictx pstate cmdState.messages with
| (cmd, ps, messages) =>
modify fun s => { s with commands := s.commands.push cmd }
setParserState ps
setMessages messages
if Parser.isEOI cmd || Parser.isExitCommand cmd then
pure true -- Done
else
profileitM IO.Error "elaboration" pos $ elabCommandAtFrontend cmd
pure false
partial def processCommands : FrontendM Unit := do
let done ← processCommand
unless done do
processCommands
let done ← processCommand
unless done do
processCommands
end Frontend
open Frontend
def IO.processCommands (inputCtx : Parser.InputContext) (parserState : Parser.ModuleParserState) (commandState : Command.State) : IO State := do
let (_, s) ← (Frontend.processCommands.run { inputCtx := inputCtx }).run { commandState := commandState, parserState := parserState, cmdPos := parserState.pos }
pure s
let (_, s) ← (Frontend.processCommands.run { inputCtx := inputCtx }).run { commandState := commandState, parserState := parserState, cmdPos := parserState.pos }
pure s
def process (input : String) (env : Environment) (opts : Options) (fileName : Option String := none) : IO (Environment × MessageLog) := do
let fileName := fileName.getD "<input>"
let inputCtx := Parser.mkInputContext input fileName
let s ← IO.processCommands inputCtx { : Parser.ModuleParserState } (Command.mkState env {} opts)
pure (s.commandState.env, s.commandState.messages)
let fileName := fileName.getD "<input>"
let inputCtx := Parser.mkInputContext input fileName
let s ← IO.processCommands inputCtx { : Parser.ModuleParserState } (Command.mkState env {} opts)
pure (s.commandState.env, s.commandState.messages)
@[export lean_process_input]
def processExport (env : Environment) (input : String) (opts : Options) (fileName : String) : IO (Environment × List Message) := do
let (env, messages) ← process input env opts fileName
pure (env, messages.toList)
let (env, messages) ← process input env opts fileName
pure (env, messages.toList)
@[export lean_run_frontend]
def runFrontend (input : String) (opts : Options) (fileName : String) (mainModuleName : Name) : IO (Environment × List Message × Module) := do
let inputCtx := Parser.mkInputContext input fileName
let (header, parserState, messages) ← Parser.parseHeader inputCtx
let (env, messages) ← processHeader header opts messages inputCtx
let env := env.setMainModule mainModuleName
let s ← IO.processCommands inputCtx parserState (Command.mkState env messages opts)
pure (s.commandState.env, s.commandState.messages.toList, { header := header, commands := s.commands })
let inputCtx := Parser.mkInputContext input fileName
let (header, parserState, messages) ← Parser.parseHeader inputCtx
let (env, messages) ← processHeader header opts messages inputCtx
let env := env.setMainModule mainModuleName
let s ← IO.processCommands inputCtx parserState (Command.mkState env messages opts)
pure (s.commandState.env, s.commandState.messages.toList, { header := header, commands := s.commands })
end Lean.Elab

View file

@ -7,39 +7,39 @@ import Lean.Parser.Module
namespace Lean.Elab
def headerToImports (header : Syntax) : List Import :=
let imports := if header[0].isNone then [{ module := `Init : Import }] else []
imports ++ header[1].getArgs.toList.map fun stx =>
-- `stx` is of the form `(Module.import "import" "runtime"? id)
let runtime := !stx[1].isNone
let id := stx[2].getId
{ module := id, runtimeOnly := runtime }
let imports := if header[0].isNone then [{ module := `Init : Import }] else []
imports ++ header[1].getArgs.toList.map fun stx =>
-- `stx` is of the form `(Module.import "import" "runtime"? id)
let runtime := !stx[1].isNone
let id := stx[2].getId
{ module := id, runtimeOnly := runtime }
def processHeader (header : Syntax) (opts : Options) (messages : MessageLog) (inputCtx : Parser.InputContext) (trustLevel : UInt32 := 0)
: IO (Environment × MessageLog) := do
try
let env ← importModules (headerToImports header) opts trustLevel
pure (env, messages)
catch e =>
let env ← mkEmptyEnvironment
let spos := header.getPos.getD 0
let pos := inputCtx.fileMap.toPosition spos
pure (env, messages.add { fileName := inputCtx.fileName, data := toString e, pos := pos })
try
let env ← importModules (headerToImports header) opts trustLevel
pure (env, messages)
catch e =>
let env ← mkEmptyEnvironment
let spos := header.getPos.getD 0
let pos := inputCtx.fileMap.toPosition spos
pure (env, messages.add { fileName := inputCtx.fileName, data := toString e, pos := pos })
def parseImports (input : String) (fileName : Option String := none) : IO (List Import × Position × MessageLog) := do
let fileName := fileName.getD "<input>"
let inputCtx := Parser.mkInputContext input fileName
let (header, parserState, messages) ← Parser.parseHeader inputCtx
pure (headerToImports header, inputCtx.fileMap.toPosition parserState.pos, messages)
let fileName := fileName.getD "<input>"
let inputCtx := Parser.mkInputContext input fileName
let (header, parserState, messages) ← Parser.parseHeader inputCtx
pure (headerToImports header, inputCtx.fileMap.toPosition parserState.pos, messages)
@[export lean_parse_imports]
def parseImportsExport (input : String) (fileName : Option String) : IO (List Import × Position × List Message) := do
let (imports, pos, log) ← parseImports input fileName
pure (imports, pos, log.toList)
let (imports, pos, log) ← parseImports input fileName
pure (imports, pos, log.toList)
@[export lean_print_deps]
def printDeps (deps : List Import) : IO Unit := do
for dep in deps do
let fname ← findOLean dep.module;
IO.println fname
for dep in deps do
let fname ← findOLean dep.module;
IO.println fname
end Lean.Elab

View file

@ -7,106 +7,106 @@ import Lean.Elab.Attributes
import Lean.Elab.Binders
import Lean.Elab.DeclModifiers
import Lean.Elab.SyntheticMVars
namespace Lean.Elab.Term
open Meta
structure LetRecDeclView :=
(ref : Syntax)
(attrs : Array Attribute)
(shortDeclName : Name)
(declName : Name)
(numParams : Nat)
(type : Expr)
(mvar : Expr) -- auxiliary metavariable used to lift the 'let rec'
(valStx : Syntax)
(ref : Syntax)
(attrs : Array Attribute)
(shortDeclName : Name)
(declName : Name)
(numParams : Nat)
(type : Expr)
(mvar : Expr) -- auxiliary metavariable used to lift the 'let rec'
(valStx : Syntax)
structure LetRecView :=
(decls : Array LetRecDeclView)
(body : Syntax)
(decls : Array LetRecDeclView)
(body : Syntax)
/- group ("let " >> nonReservedSymbol "rec ") >> sepBy1 (group (optional «attributes» >> letDecl)) ", " >> "; " >> termParser -/
private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
let decls ← letRec[1].getSepArgs.mapM fun (attrDeclStx : Syntax) => do
let attrOptStx := attrDeclStx[0]
let attrs ← if attrOptStx.isNone then pure #[] else elabDeclAttrs attrOptStx[0]
let decl := attrDeclStx[1][0]
if decl.isOfKind `Lean.Parser.Term.letPatDecl then
throwErrorAt decl "patterns are not allowed in 'let rec' expressions"
else if decl.isOfKind `Lean.Parser.Term.letIdDecl || decl.isOfKind `Lean.Parser.Term.letEqnsDecl then
let shortDeclName := decl[0].getId
let currDeclName? ← getDeclName?
let declName := currDeclName?.getD Name.anonymous ++ shortDeclName
checkNotAlreadyDeclared declName
applyAttributesAt declName attrs AttributeApplicationTime.beforeElaboration
let binders := decl[1].getArgs
let typeStx := expandOptType decl decl[2]
let (type, numParams) ← elabBinders binders fun xs => do
let type ← elabType typeStx
registerCustomErrorIfMVar type typeStx "failed to infer 'let rec' declaration type"
let type ← mkForallFVars xs type
pure (type, xs.size)
let mvar ← mkFreshExprMVar type MetavarKind.syntheticOpaque
let valStx ←
if decl.isOfKind `Lean.Parser.Term.letIdDecl then
pure decl[4]
else
liftMacroM $ expandMatchAltsIntoMatch decl decl[3]
pure {
ref := decl,
attrs := attrs,
shortDeclName := shortDeclName,
declName := declName,
numParams := numParams,
type := type,
mvar := mvar,
valStx := valStx
: LetRecDeclView }
else
throwUnsupportedSyntax
pure {
decls := decls,
body := letRec[3]
}
let decls ← letRec[1].getSepArgs.mapM fun (attrDeclStx : Syntax) => do
let attrOptStx := attrDeclStx[0]
let attrs ← if attrOptStx.isNone then pure #[] else elabDeclAttrs attrOptStx[0]
let decl := attrDeclStx[1][0]
if decl.isOfKind `Lean.Parser.Term.letPatDecl then
throwErrorAt decl "patterns are not allowed in 'let rec' expressions"
else if decl.isOfKind `Lean.Parser.Term.letIdDecl || decl.isOfKind `Lean.Parser.Term.letEqnsDecl then
let shortDeclName := decl[0].getId
let currDeclName? ← getDeclName?
let declName := currDeclName?.getD Name.anonymous ++ shortDeclName
checkNotAlreadyDeclared declName
applyAttributesAt declName attrs AttributeApplicationTime.beforeElaboration
let binders := decl[1].getArgs
let typeStx := expandOptType decl decl[2]
let (type, numParams) ← elabBinders binders fun xs => do
let type ← elabType typeStx
registerCustomErrorIfMVar type typeStx "failed to infer 'let rec' declaration type"
let type ← mkForallFVars xs type
pure (type, xs.size)
let mvar ← mkFreshExprMVar type MetavarKind.syntheticOpaque
let valStx ←
if decl.isOfKind `Lean.Parser.Term.letIdDecl then
pure decl[4]
else
liftMacroM $ expandMatchAltsIntoMatch decl decl[3]
pure {
ref := decl,
attrs := attrs,
shortDeclName := shortDeclName,
declName := declName,
numParams := numParams,
type := type,
mvar := mvar,
valStx := valStx
: LetRecDeclView }
else
throwUnsupportedSyntax
pure {
decls := decls,
body := letRec[3]
}
private partial def withAuxLocalDecls {α} (views : Array LetRecDeclView) (k : Array Expr → TermElabM α) : TermElabM α :=
let rec loop (i : Nat) (fvars : Array Expr) : TermElabM α :=
if h : i < views.size then
let view := views.get ⟨i, h⟩
withLetDecl view.shortDeclName view.type view.mvar fun fvar => loop (i+1) (fvars.push fvar)
else
k fvars
loop 0 #[]
let rec loop (i : Nat) (fvars : Array Expr) : TermElabM α :=
if h : i < views.size then
let view := views.get ⟨i, h⟩
withLetDecl view.shortDeclName view.type view.mvar fun fvar => loop (i+1) (fvars.push fvar)
else
k fvars
loop 0 #[]
private def elabLetRecDeclValues (view : LetRecView) : TermElabM (Array Expr) :=
view.decls.mapM fun view => do
forallBoundedTelescope view.type view.numParams fun xs type =>
withDeclName view.declName do
let value ← elabTermEnsuringType view.valStx type
mkLambdaFVars xs value
view.decls.mapM fun view => do
forallBoundedTelescope view.type view.numParams fun xs type =>
withDeclName view.declName do
let value ← elabTermEnsuringType view.valStx type
mkLambdaFVars xs value
private def abortIfContainsSyntheticSorry (e : Expr) : TermElabM Unit := do
let e ← instantiateMVars e
if e.hasSyntheticSorry then throwAbort
let e ← instantiateMVars e
if e.hasSyntheticSorry then throwAbort
private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array Expr) (values : Array Expr) : TermElabM Unit := do
let lctx ← getLCtx
let localInsts ← getLocalInstances
let toLift := views.mapIdx fun i view => {
ref := view.ref,
fvarId := fvars[i].fvarId!,
attrs := view.attrs,
shortDeclName := view.shortDeclName,
declName := view.declName,
lctx := lctx,
localInstances := localInsts,
type := view.type,
val := values[i],
mvarId := view.mvar.mvarId!
: LetRecToLift }
modify fun s => { s with letRecsToLift := toLift.toList ++ s.letRecsToLift }
let lctx ← getLCtx
let localInsts ← getLocalInstances
let toLift := views.mapIdx fun i view => {
ref := view.ref,
fvarId := fvars[i].fvarId!,
attrs := view.attrs,
shortDeclName := view.shortDeclName,
declName := view.declName,
lctx := lctx,
localInstances := localInsts,
type := view.type,
val := values[i],
mvarId := view.mvar.mvarId!
: LetRecToLift }
modify fun s => { s with letRecsToLift := toLift.toList ++ s.letRecsToLift }
@[builtinTermElab «letrec»] def elabLetRec : TermElab :=
fun stx expectedType? => do
@[builtinTermElab «letrec»] def elabLetRec : TermElab := fun stx expectedType? => do
let view ← mkLetRecDeclView stx
withAuxLocalDecls view.decls fun fvars => do
let values ← elabLetRecDeclValues view
@ -114,6 +114,4 @@ fun stx expectedType? => do
registerLetRecsToLift view.decls fvars values
mkLetFVars fvars body
end Term
end Elab
end Lean
end Lean.Elab.Term

View file

@ -10,66 +10,69 @@ import Lean.Elab.Log
namespace Lean.Elab.Level
structure Context :=
(ref : Syntax)
(levelNames : List Name)
(ref : Syntax)
(levelNames : List Name)
structure State :=
(ngen : NameGenerator)
(mctx : MetavarContext)
(ngen : NameGenerator)
(mctx : MetavarContext)
abbrev LevelElabM := ReaderT Context (EStateM Exception State)
instance : Ref LevelElabM :=
{ getRef := do return (← read).ref,
withRef := fun ref x => withReader (fun ctx => { ctx with ref := ref }) x }
instance : Ref LevelElabM := {
getRef := do return (← read).ref,
withRef := fun ref x => withReader (fun ctx => { ctx with ref := ref }) x
}
instance : AddMessageContext LevelElabM :=
{ addMessageContext := fun msg => pure msg }
instance : AddMessageContext LevelElabM := {
addMessageContext := fun msg => pure msg
}
instance : MonadNameGenerator LevelElabM :=
{ getNGen := do return (← get).ngen,
setNGen := fun ngen => modify fun s => { s with ngen := ngen } }
instance : MonadNameGenerator LevelElabM := {
getNGen := do return (← get).ngen,
setNGen := fun ngen => modify fun s => { s with ngen := ngen }
}
def mkFreshLevelMVar : LevelElabM Level := do
let mvarId ← mkFreshId
modify fun s => { s with mctx := s.mctx.addLevelMVarDecl mvarId }
return mkLevelMVar mvarId
let mvarId ← mkFreshId
modify fun s => { s with mctx := s.mctx.addLevelMVarDecl mvarId }
return mkLevelMVar mvarId
partial def elabLevel (stx : Syntax) : LevelElabM Level := withRef stx do
let kind := stx.getKind
if kind == `Lean.Parser.Level.paren then
elabLevel (stx.getArg 1)
else if kind == `Lean.Parser.Level.max then
let args := stx.getArg 1 $.getArgs
let lvl ← elabLevel args.back
for arg in args[:args.size-1] do
let arg ← elabLevel arg
lvl := mkLevelMax lvl arg
return lvl
else if kind == `Lean.Parser.Level.imax then
let args := stx.getArg 1 $.getArgs
let lvl ← elabLevel args.back
for arg in args[:args.size-1] do
let arg ← elabLevel arg
lvl := mkLevelIMax lvl arg
return lvl
else if kind == `Lean.Parser.Level.hole then
mkFreshLevelMVar
else if kind == numLitKind then
match stx.isNatLit? with
| some val => return Level.ofNat val
| none => throwIllFormedSyntax
else if kind == identKind then
let paramName := stx.getId
unless (← read).levelNames.contains paramName do
throwError ("unknown universe level " ++ paramName)
return mkLevelParam paramName
else if kind == `Lean.Parser.Level.addLit then
let lvl ← elabLevel (stx.getArg 0)
match stx.getArg 2 $.isNatLit? with
| some val => return lvl.addOffset val
| none => throwIllFormedSyntax
else
throwError "unexpected universe level syntax kind"
let kind := stx.getKind
if kind == `Lean.Parser.Level.paren then
elabLevel (stx.getArg 1)
else if kind == `Lean.Parser.Level.max then
let args := stx.getArg 1 $.getArgs
let lvl ← elabLevel args.back
for arg in args[:args.size-1] do
let arg ← elabLevel arg
lvl := mkLevelMax lvl arg
return lvl
else if kind == `Lean.Parser.Level.imax then
let args := stx.getArg 1 $.getArgs
let lvl ← elabLevel args.back
for arg in args[:args.size-1] do
let arg ← elabLevel arg
lvl := mkLevelIMax lvl arg
return lvl
else if kind == `Lean.Parser.Level.hole then
mkFreshLevelMVar
else if kind == numLitKind then
match stx.isNatLit? with
| some val => return Level.ofNat val
| none => throwIllFormedSyntax
else if kind == identKind then
let paramName := stx.getId
unless (← read).levelNames.contains paramName do
throwError ("unknown universe level " ++ paramName)
return mkLevelParam paramName
else if kind == `Lean.Parser.Level.addLit then
let lvl ← elabLevel (stx.getArg 0)
match stx.getArg 2 $.isNatLit? with
| some val => return lvl.addOffset val
| none => throwIllFormedSyntax
else
throwError "unexpected universe level syntax kind"
end Lean.Elab.Level

View file

@ -9,74 +9,75 @@ import Lean.Elab.Exception
namespace Lean.Elab
class MonadLog (m : Type → Type) :=
(getRef : m Syntax)
(getFileMap : m FileMap)
(getFileName : m String)
(logMessage : Message → m Unit)
(getRef : m Syntax)
(getFileMap : m FileMap)
(getFileName : m String)
(logMessage : Message → m Unit)
instance (m n) [MonadLog m] [MonadLift m n] : MonadLog n :=
{ getRef := liftM (MonadLog.getRef : m _),
instance (m n) [MonadLog m] [MonadLift m n] : MonadLog n := {
getRef := liftM (MonadLog.getRef : m _),
getFileMap := liftM (MonadLog.getFileMap : m _),
getFileName := liftM (MonadLog.getFileName : m _),
logMessage := fun msg => liftM (MonadLog.logMessage msg : m _ ) }
logMessage := fun msg => liftM (MonadLog.logMessage msg : m _ )
}
export MonadLog (getFileMap getFileName logMessage)
variables {m : Type → Type} [Monad m] [MonadLog m] [AddMessageContext m]
def getRefPos : m String.Pos := do
let ref ← MonadLog.getRef
return ref.getPos.getD 0
let ref ← MonadLog.getRef
return ref.getPos.getD 0
def getRefPosition : m Position := do
let fileMap ← getFileMap
return fileMap.toPosition (← getRefPos)
let fileMap ← getFileMap
return fileMap.toPosition (← getRefPos)
def logAt (ref : Syntax) (msgData : MessageData) (severity : MessageSeverity := MessageSeverity.error): m Unit := do
let ref := replaceRef ref (← MonadLog.getRef)
let pos := ref.getPos.getD 0
let fileMap ← getFileMap
let msgData ← addMessageContext msgData
logMessage { fileName := (← getFileName), pos := fileMap.toPosition pos, data := msgData, severity := severity }
let ref := replaceRef ref (← MonadLog.getRef)
let pos := ref.getPos.getD 0
let fileMap ← getFileMap
let msgData ← addMessageContext msgData
logMessage { fileName := (← getFileName), pos := fileMap.toPosition pos, data := msgData, severity := severity }
def logErrorAt (ref : Syntax) (msgData : MessageData) : m Unit :=
logAt ref msgData MessageSeverity.error
logAt ref msgData MessageSeverity.error
def logWarningAt (ref : Syntax) (msgData : MessageData) : m Unit :=
logAt ref msgData MessageSeverity.warning
logAt ref msgData MessageSeverity.warning
def logInfoAt (ref : Syntax) (msgData : MessageData) : m Unit :=
logAt ref msgData MessageSeverity.information
logAt ref msgData MessageSeverity.information
def log (msgData : MessageData) (severity : MessageSeverity := MessageSeverity.error): m Unit := do
let ref ← MonadLog.getRef
logAt ref msgData severity
let ref ← MonadLog.getRef
logAt ref msgData severity
def logError (msgData : MessageData) : m Unit :=
log msgData MessageSeverity.error
log msgData MessageSeverity.error
def logWarning (msgData : MessageData) : m Unit :=
log msgData MessageSeverity.warning
log msgData MessageSeverity.warning
def logInfo (msgData : MessageData) : m Unit :=
log msgData MessageSeverity.information
log msgData MessageSeverity.information
def logException [MonadIO m] (ex : Exception) : m Unit := do
match ex with
| Exception.error ref msg => logErrorAt ref msg
| Exception.internal id =>
unless id == abortExceptionId do
let name ← liftIO $ id.getName
logError ("internal exception: " ++ name)
match ex with
| Exception.error ref msg => logErrorAt ref msg
| Exception.internal id =>
unless id == abortExceptionId do
let name ← liftIO $ id.getName
logError ("internal exception: " ++ name)
def logTrace (cls : Name) (msgData : MessageData) : m Unit := do
logInfo (MessageData.tagged cls msgData)
logInfo (MessageData.tagged cls msgData)
@[inline] def trace [MonadOptions m] (cls : Name) (msg : Unit → MessageData) : m Unit := do
if checkTraceOption (← getOptions) cls then
logTrace cls (msg ())
if checkTraceOption (← getOptions) cls then
logTrace cls (msg ())
def logDbgTrace [MonadOptions m] (msg : MessageData) : m Unit := do
trace `Elab.debug fun _ => msg
trace `Elab.debug fun _ => msg
end Lean.Elab

View file

@ -9,66 +9,64 @@ import Lean.Elab.Command
namespace Lean.Elab.Command
private def throwUnknownId (id : Name) : CommandElabM Unit :=
throwError! "unknown identifier '{id}'"
throwError! "unknown identifier '{id}'"
private def lparamsToMessageData (lparams : List Name) : MessageData :=
match lparams with
| [] => ""
| u::us => do
let m : MessageData := ".{" ++ u
for u in us do
m := m ++ ", " ++ u
return m ++ "}"
match lparams with
| [] => ""
| u::us => do
let m : MessageData := ".{" ++ u
for u in us do
m := m ++ ", " ++ u
return m ++ "}"
private def mkHeader (kind : String) (id : Name) (lparams : List Name) (type : Expr) (isUnsafe : Bool) : CommandElabM MessageData := do
let m : MessageData := if isUnsafe then "unsafe " else ""
let m := if isProtected (← getEnv) id then m ++ "protected " else m
let (m, id) := match privateToUserName? id with
| some id => (m ++ "private ", id)
| none => (m, id)
let m := m ++ kind ++ " " ++ id ++ lparamsToMessageData lparams ++ " : " ++ type
pure m
let m : MessageData := if isUnsafe then "unsafe " else ""
let m := if isProtected (← getEnv) id then m ++ "protected " else m
let (m, id) := match privateToUserName? id with
| some id => (m ++ "private ", id)
| none => (m, id)
let m := m ++ kind ++ " " ++ id ++ lparamsToMessageData lparams ++ " : " ++ type
pure m
private def printDefLike (kind : String) (id : Name) (lparams : List Name) (type : Expr) (value : Expr) (isUnsafe := false) : CommandElabM Unit := do
let m ← mkHeader kind id lparams type isUnsafe
let m := m ++ " :=" ++ Format.line ++ value
logInfo m
let m ← mkHeader kind id lparams type isUnsafe
let m := m ++ " :=" ++ Format.line ++ value
logInfo m
private def printAxiomLike (kind : String) (id : Name) (lparams : List Name) (type : Expr) (isUnsafe := false) : CommandElabM Unit := do
let m ← mkHeader kind id lparams type isUnsafe
logInfo m
logInfo (← mkHeader kind id lparams type isUnsafe)
private def printQuot (kind : QuotKind) (id : Name) (lparams : List Name) (type : Expr) : CommandElabM Unit := do
printAxiomLike "Quotient primitive" id lparams type
printAxiomLike "Quotient primitive" id lparams type
private def printInduct (id : Name) (lparams : List Name) (nparams : Nat) (nindices : Nat) (type : Expr)
(ctors : List Name) (isUnsafe : Bool) : CommandElabM Unit := do
let m ← mkHeader "inductive" id lparams type isUnsafe
let m := m ++ Format.line ++ "constructors:"
for ctor in ctors do
let cinfo ← getConstInfo ctor
m := m ++ Format.line ++ ctor ++ " : " ++ cinfo.type
logInfo m
let m ← mkHeader "inductive" id lparams type isUnsafe
let m := m ++ Format.line ++ "constructors:"
for ctor in ctors do
let cinfo ← getConstInfo ctor
m := m ++ Format.line ++ ctor ++ " : " ++ cinfo.type
logInfo m
private def printIdCore (id : Name) : CommandElabM Unit := do
match (← getEnv).find? id with
| ConstantInfo.axiomInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "axiom" id us t u
| ConstantInfo.defnInfo { lparams := us, type := t, value := v, isUnsafe := u, .. } => printDefLike "def" id us t v u
| ConstantInfo.thmInfo { lparams := us, type := t, value := v, .. } => printDefLike "theorem" id us t v
| ConstantInfo.opaqueInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "constant" id us t u
| ConstantInfo.quotInfo { kind := kind, lparams := us, type := t, .. } => printQuot kind id us t
| ConstantInfo.ctorInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "constructor" id us t u
| ConstantInfo.recInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "recursor" id us t u
| ConstantInfo.inductInfo { lparams := us, nparams := nparams, nindices := nindices, type := t, ctors := ctors, isUnsafe := u, .. } =>
printInduct id us nparams nindices t ctors u
| none => throwUnknownId id
match (← getEnv).find? id with
| ConstantInfo.axiomInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "axiom" id us t u
| ConstantInfo.defnInfo { lparams := us, type := t, value := v, isUnsafe := u, .. } => printDefLike "def" id us t v u
| ConstantInfo.thmInfo { lparams := us, type := t, value := v, .. } => printDefLike "theorem" id us t v
| ConstantInfo.opaqueInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "constant" id us t u
| ConstantInfo.quotInfo { kind := kind, lparams := us, type := t, .. } => printQuot kind id us t
| ConstantInfo.ctorInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "constructor" id us t u
| ConstantInfo.recInfo { lparams := us, type := t, isUnsafe := u, .. } => printAxiomLike "recursor" id us t u
| ConstantInfo.inductInfo { lparams := us, nparams := nparams, nindices := nindices, type := t, ctors := ctors, isUnsafe := u, .. } =>
printInduct id us nparams nindices t ctors u
| none => throwUnknownId id
private def printId (id : Name) : CommandElabM Unit := do
let cs ← resolveGlobalConst id
cs.forM printIdCore
let cs ← resolveGlobalConst id
cs.forM printIdCore
@[builtinCommandElab «print»] def elabPrint : CommandElab :=
fun stx =>
@[builtinCommandElab «print»] def elabPrint : CommandElab := fun stx =>
let numArgs := stx.getNumArgs
if numArgs == 2 then
let arg := stx[1]
@ -83,13 +81,12 @@ fun stx =>
namespace CollectAxioms
structure State :=
(visited : NameSet := {})
(axioms : Array Name := #[])
(visited : NameSet := {})
(axioms : Array Name := #[])
abbrev M := ReaderT Environment $ StateM State
partial def collect : Name → M Unit
| c => do
partial def collect (c : Name) : M Unit := do
let collectExpr (e : Expr) : M Unit := e.getUsedConstants.forM collect
let s ← get
unless s.visited.contains c do
@ -109,15 +106,14 @@ partial def collect : Name → M Unit
end CollectAxioms
private def printAxiomsOf (constName : Name) : CommandElabM Unit := do
let env ← getEnv
let (_, s) := ((CollectAxioms.collect constName).run env).run {}
if s.axioms.isEmpty then
logInfo msg!"'{constName}' does not depend on any axioms"
else
logInfo msg!"'{constName}' depends on axioms: {s.axioms.toList}"
let env ← getEnv
let (_, s) := ((CollectAxioms.collect constName).run env).run {}
if s.axioms.isEmpty then
logInfo msg!"'{constName}' does not depend on any axioms"
else
logInfo msg!"'{constName}' depends on axioms: {s.axioms.toList}"
@[builtinCommandElab «printAxioms»] def elabPrintAxioms : CommandElab :=
fun stx => do
@[builtinCommandElab «printAxioms»] def elabPrintAxioms : CommandElab := fun stx => do
let id := stx[2].getId
let cs ← resolveGlobalConst id
cs.forM printAxiomsOf

View file

@ -27,105 +27,106 @@ open Meta
-- Antiquotations can be escaped as in `$$x`, which is useful for nesting macros.
def isEscapedAntiquot (stx : Syntax) : Bool :=
!stx[1].getArgs.isEmpty
!stx[1].getArgs.isEmpty
def unescapeAntiquot (stx : Syntax) : Syntax :=
if isAntiquot stx then
stx.setArg 1 $ mkNullNode stx[1].getArgs.pop
else stx
if isAntiquot stx then
stx.setArg 1 $ mkNullNode stx[1].getArgs.pop
else
stx
def getAntiquotTerm (stx : Syntax) : Syntax :=
let e := stx[2]
if e.isIdent then e
else
-- `e` is from `"(" >> termParser >> ")"`
e[1]
let e := stx[2]
if e.isIdent then e
else
-- `e` is from `"(" >> termParser >> ")"`
e[1]
def antiquotKind? : Syntax → Option SyntaxNodeKind
| Syntax.node (Name.str k "antiquot" _) args =>
if args[3].isOfKind `antiquotName then some k
else
-- we treat all antiquotations where the kind was left implicit (`$e`) the same (see `elimAntiquotChoices`)
some Name.anonymous
| _ => none
| Syntax.node (Name.str k "antiquot" _) args =>
if args[3].isOfKind `antiquotName then some k
else
-- we treat all antiquotations where the kind was left implicit (`$e`) the same (see `elimAntiquotChoices`)
some Name.anonymous
| _ => none
-- `$e*` is an antiquotation "splice" matching an arbitrary number of syntax nodes
def isAntiquotSplice (stx : Syntax) : Bool :=
isAntiquot stx && stx[4].getOptional?.isSome
isAntiquot stx && stx[4].getOptional?.isSome
-- If any item of a `many` node is an antiquotation splice, its result should
-- be substituted into the `many` node's children
def isAntiquotSplicePat (stx : Syntax) : Bool :=
stx.isOfKind nullKind && stx.getArgs.any fun arg => isAntiquotSplice arg && !isEscapedAntiquot arg
stx.isOfKind nullKind && stx.getArgs.any fun arg => isAntiquotSplice arg && !isEscapedAntiquot arg
/-- A term like `($e) is actually ambiguous: the antiquotation could be of kind `term`,
or `ident`, or ... . But it shouldn't really matter because antiquotations without
explicit kinds behave the same at runtime. So we replace `choice` nodes that contain
at least one implicit antiquotation with that antiquotation. -/
private partial def elimAntiquotChoices : Syntax → Syntax
| Syntax.node `choice args => match args.find? fun arg => antiquotKind? arg == Name.anonymous with
| some anti => anti
| none => Syntax.node `choice $ args.map elimAntiquotChoices
| Syntax.node k args => Syntax.node k $ args.map elimAntiquotChoices
| stx => stx
| Syntax.node `choice args => match args.find? fun arg => antiquotKind? arg == Name.anonymous with
| some anti => anti
| none => Syntax.node `choice $ args.map elimAntiquotChoices
| Syntax.node k args => Syntax.node k $ args.map elimAntiquotChoices
| stx => stx
-- Elaborate the content of a syntax quotation term
private partial def quoteSyntax : Syntax → TermElabM Syntax
| Syntax.ident info rawVal val preresolved => do
-- Add global scopes at compilation time (now), add macro scope at runtime (in the quotation).
-- See the paper for details.
let r ← resolveGlobalName val
let preresolved := r ++ preresolved
let val := quote val
-- `scp` is bound in stxQuot.expand
`(Syntax.ident (SourceInfo.mk none none none) $(quote rawVal) (addMacroScope mainModule $val scp) $(quote preresolved))
-- if antiquotation, insert contents as-is, else recurse
| stx@(Syntax.node k _) => do
if isAntiquot stx && !isEscapedAntiquot stx then
-- splices must occur in a `many` node
if isAntiquotSplice stx then throwErrorAt stx "unexpected antiquotation splice"
else pure $ getAntiquotTerm stx
else
let empty ← `(Array.empty);
-- if escaped antiquotation, decrement by one escape level
let stx := unescapeAntiquot stx
let args ← stx.getArgs.foldlM (fun args arg =>
if k == nullKind && isAntiquotSplice arg then
-- antiquotation splice pattern: inject args array
`(Array.append $args $(getAntiquotTerm arg))
else do
let arg ← quoteSyntax arg;
`(Array.push $args $arg)) empty
`(Syntax.node $(quote k) $args)
| Syntax.atom info val =>
`(Syntax.atom (SourceInfo.mk none none none) $(quote val))
| Syntax.missing => unreachable!
| Syntax.ident info rawVal val preresolved => do
-- Add global scopes at compilation time (now), add macro scope at runtime (in the quotation).
-- See the paper for details.
let r ← resolveGlobalName val
let preresolved := r ++ preresolved
let val := quote val
-- `scp` is bound in stxQuot.expand
`(Syntax.ident (SourceInfo.mk none none none) $(quote rawVal) (addMacroScope mainModule $val scp) $(quote preresolved))
-- if antiquotation, insert contents as-is, else recurse
| stx@(Syntax.node k _) => do
if isAntiquot stx && !isEscapedAntiquot stx then
-- splices must occur in a `many` node
if isAntiquotSplice stx then throwErrorAt stx "unexpected antiquotation splice"
else pure $ getAntiquotTerm stx
else
let empty ← `(Array.empty);
-- if escaped antiquotation, decrement by one escape level
let stx := unescapeAntiquot stx
let args ← stx.getArgs.foldlM (fun args arg =>
if k == nullKind && isAntiquotSplice arg then
-- antiquotation splice pattern: inject args array
`(Array.append $args $(getAntiquotTerm arg))
else do
let arg ← quoteSyntax arg;
`(Array.push $args $arg)) empty
`(Syntax.node $(quote k) $args)
| Syntax.atom info val =>
`(Syntax.atom (SourceInfo.mk none none none) $(quote val))
| Syntax.missing => unreachable!
def stxQuot.expand (stx : Syntax) : TermElabM Syntax := do
let quoted := stx[1]
/- Syntax quotations are monadic values depending on the current macro scope. For efficiency, we bind
the macro scope once for each quotation, then build the syntax tree in a completely pure computation
depending on this binding. Note that regular function calls do not introduce a new macro scope (i.e.
we preserve referential transparency), so we can refer to this same `scp` inside `quoteSyntax` by
including it literally in a syntax quotation. -/
-- TODO: simplify to `(do scp ← getCurrMacroScope; pure $(quoteSyntax quoted))
stx ← quoteSyntax (elimAntiquotChoices quoted);
`(bind getCurrMacroScope (fun scp => bind getMainModule (fun mainModule => pure $stx)))
/- NOTE: It may seem like the newly introduced binding `scp` may accidentally
capture identifiers in an antiquotation introduced by `quoteSyntax`. However,
note that the syntax quotation above enjoys the same hygiene guarantees as
anywhere else in Lean; that is, we implement hygienic quotations by making
use of the hygienic quotation support of the bootstrapped Lean compiler!
let quoted := stx[1]
/- Syntax quotations are monadic values depending on the current macro scope. For efficiency, we bind
the macro scope once for each quotation, then build the syntax tree in a completely pure computation
depending on this binding. Note that regular function calls do not introduce a new macro scope (i.e.
we preserve referential transparency), so we can refer to this same `scp` inside `quoteSyntax` by
including it literally in a syntax quotation. -/
-- TODO: simplify to `(do scp ← getCurrMacroScope; pure $(quoteSyntax quoted))
stx ← quoteSyntax (elimAntiquotChoices quoted);
`(bind getCurrMacroScope (fun scp => bind getMainModule (fun mainModule => pure $stx)))
/- NOTE: It may seem like the newly introduced binding `scp` may accidentally
capture identifiers in an antiquotation introduced by `quoteSyntax`. However,
note that the syntax quotation above enjoys the same hygiene guarantees as
anywhere else in Lean; that is, we implement hygienic quotations by making
use of the hygienic quotation support of the bootstrapped Lean compiler!
Aside: While this might sound "dangerous", it is in fact less reliant on a
"chain of trust" than other bootstrapping parts of Lean: because this
implementation itself never uses `scp` (or any other identifier) both inside
and outside quotations, it can actually correctly be compiled by an
unhygienic (but otherwise correct) implementation of syntax quotations. As
long as it is then compiled again with the resulting executable (i.e. up to
stage 2), the result is a correct hygienic implementation. In this sense the
implementation is "self-stabilizing". It was in fact originally compiled
by an unhygienic prototype implementation. -/
Aside: While this might sound "dangerous", it is in fact less reliant on a
"chain of trust" than other bootstrapping parts of Lean: because this
implementation itself never uses `scp` (or any other identifier) both inside
and outside quotations, it can actually correctly be compiled by an
unhygienic (but otherwise correct) implementation of syntax quotations. As
long as it is then compiled again with the resulting executable (i.e. up to
stage 2), the result is a correct hygienic implementation. In this sense the
implementation is "self-stabilizing". It was in fact originally compiled
by an unhygienic prototype implementation. -/
@[builtinTermElab Parser.Level.quot] def elabLevelQuot : TermElab := adaptExpander stxQuot.expand
@[builtinTermElab Parser.Term.quot] def elabTermQuot : TermElab := adaptExpander stxQuot.expand
@ -143,173 +144,173 @@ private abbrev Alt := List Syntax × Syntax
/-- Information on a pattern's head that influences the compilation of a single
match step. -/
structure HeadInfo :=
-- Node kind to match, if any
(kind : Option SyntaxNodeKind := none)
-- Nested patterns for each argument, if any. In a single match step, we only
-- check that the arity matches. The arity is usually implied by the node kind,
-- but not in the case of `many` nodes.
(argPats : Option (Array Syntax) := none)
-- Function to apply to the right-hand side in case the match succeeds. Used to
-- bind pattern variables.
(rhsFn : Syntax → TermElabM Syntax := pure)
-- Node kind to match, if any
(kind : Option SyntaxNodeKind := none)
-- Nested patterns for each argument, if any. In a single match step, we only
-- check that the arity matches. The arity is usually implied by the node kind,
-- but not in the case of `many` nodes.
(argPats : Option (Array Syntax) := none)
-- Function to apply to the right-hand side in case the match succeeds. Used to
-- bind pattern variables.
(rhsFn : Syntax → TermElabM Syntax := pure)
instance : Inhabited HeadInfo := ⟨{}⟩
/-- `h1.generalizes h2` iff h1 is equal to or more general than h2, i.e. it matches all nodes
h2 matches. This induces a partial ordering. -/
def HeadInfo.generalizes : HeadInfo → HeadInfo → Bool
| { kind := none, .. }, _ => true
| { kind := some k1, argPats := none, .. },
{ kind := some k2, .. } => k1 == k2
| { kind := some k1, argPats := some ps1, .. },
{ kind := some k2, argPats := some ps2, .. } => k1 == k2 && ps1.size == ps2.size
| _, _ => false
| { kind := none, .. }, _ => true
| { kind := some k1, argPats := none, .. },
{ kind := some k2, .. } => k1 == k2
| { kind := some k1, argPats := some ps1, .. },
{ kind := some k2, argPats := some ps2, .. } => k1 == k2 && ps1.size == ps2.size
| _, _ => false
private def getHeadInfo (alt : Alt) : HeadInfo :=
let pat := alt.fst.head!;
let unconditional (rhsFn) := { rhsFn := rhsFn : HeadInfo };
-- variable pattern
if pat.isIdent then unconditional $ fun rhs => `(let $pat := discr; $rhs)
-- wildcard pattern
else if pat.isOfKind `Lean.Parser.Term.hole then unconditional pure
-- quotation pattern
else if isQuot pat then
let quoted := pat[1]
if quoted.isAtom then
-- We assume that atoms are uniquely determined by the node kind and never have to be checked
unconditional pure
else if isAntiquot quoted && !isEscapedAntiquot quoted then
-- quotation contains a single antiquotation
let k := antiquotKind? quoted;
-- Antiquotation kinds like `$id:ident` influence the parser, but also need to be considered by
-- match_syntax (but not by quotation terms). For example, `($id:ident) and `($e) are not
-- distinguishable without checking the kind of the node to be captured. Note that some
-- antiquotations like the latter one for terms do not correspond to any actual node kind
-- (signified by `k == Name.anonymous`), so we would only check for `ident` here.
--
-- if stx.isOfKind `ident then
-- let id := stx; ...
-- else
-- let e := stx; ...
let kind := if k == Name.anonymous then none else k
let anti := getAntiquotTerm quoted
-- Splices should only appear inside a nullKind node, see next case
if isAntiquotSplice quoted then unconditional $ fun _ => throwErrorAt quoted "unexpected antiquotation splice"
else if anti.isIdent then { kind := kind, rhsFn := fun rhs => `(let $anti := discr; $rhs) }
else unconditional fun _ => throwErrorAt anti ("match_syntax: antiquotation must be variable " ++ toString anti)
else if isAntiquotSplicePat quoted && quoted.getArgs.size == 1 then
-- quotation is a single antiquotation splice => bind args array
let anti := getAntiquotTerm quoted[0]
unconditional fun rhs => `(let $anti := Syntax.getArgs discr; $rhs)
-- TODO: support for more complex antiquotation splices
let pat := alt.fst.head!;
let unconditional (rhsFn) := { rhsFn := rhsFn : HeadInfo };
-- variable pattern
if pat.isIdent then unconditional $ fun rhs => `(let $pat := discr; $rhs)
-- wildcard pattern
else if pat.isOfKind `Lean.Parser.Term.hole then unconditional pure
-- quotation pattern
else if isQuot pat then
let quoted := pat[1]
if quoted.isAtom then
-- We assume that atoms are uniquely determined by the node kind and never have to be checked
unconditional pure
else if isAntiquot quoted && !isEscapedAntiquot quoted then
-- quotation contains a single antiquotation
let k := antiquotKind? quoted;
-- Antiquotation kinds like `$id:ident` influence the parser, but also need to be considered by
-- match_syntax (but not by quotation terms). For example, `($id:ident) and `($e) are not
-- distinguishable without checking the kind of the node to be captured. Note that some
-- antiquotations like the latter one for terms do not correspond to any actual node kind
-- (signified by `k == Name.anonymous`), so we would only check for `ident` here.
--
-- if stx.isOfKind `ident then
-- let id := stx; ...
-- else
-- let e := stx; ...
let kind := if k == Name.anonymous then none else k
let anti := getAntiquotTerm quoted
-- Splices should only appear inside a nullKind node, see next case
if isAntiquotSplice quoted then unconditional $ fun _ => throwErrorAt quoted "unexpected antiquotation splice"
else if anti.isIdent then { kind := kind, rhsFn := fun rhs => `(let $anti := discr; $rhs) }
else unconditional fun _ => throwErrorAt anti ("match_syntax: antiquotation must be variable " ++ toString anti)
else if isAntiquotSplicePat quoted && quoted.getArgs.size == 1 then
-- quotation is a single antiquotation splice => bind args array
let anti := getAntiquotTerm quoted[0]
unconditional fun rhs => `(let $anti := Syntax.getArgs discr; $rhs)
-- TODO: support for more complex antiquotation splices
else
-- not an antiquotation or escaped antiquotation: match head shape
let quoted := unescapeAntiquot quoted
let argPats := quoted.getArgs.map (pat.setArg 1);
{ kind := quoted.getKind, argPats := argPats }
else
-- not an antiquotation or escaped antiquotation: match head shape
let quoted := unescapeAntiquot quoted
let argPats := quoted.getArgs.map (pat.setArg 1);
{ kind := quoted.getKind, argPats := argPats }
else
unconditional $ fun _ => throwErrorAt pat ("match_syntax: unexpected pattern kind " ++ toString pat)
unconditional $ fun _ => throwErrorAt pat ("match_syntax: unexpected pattern kind " ++ toString pat)
-- Assuming that the first pattern of the alternative is taken, replace it with patterns (if any) for its
-- child nodes.
-- Ex: `($a + (- $b)) => `($a), `(+), `(- $b)
-- Note: The atom pattern `(+) will be discarded in a later step
private def explodeHeadPat (numArgs : Nat) : HeadInfo × Alt → TermElabM Alt
| (info, (pat::pats, rhs)) => do
let newPats := match info.argPats with
| some argPats => argPats.toList
| none => List.replicate numArgs $ Unhygienic.run `(_)
let rhs ← info.rhsFn rhs
pure (newPats ++ pats, rhs)
| _ => unreachable!
| (info, (pat::pats, rhs)) => do
let newPats := match info.argPats with
| some argPats => argPats.toList
| none => List.replicate numArgs $ Unhygienic.run `(_)
let rhs ← info.rhsFn rhs
pure (newPats ++ pats, rhs)
| _ => unreachable!
private partial def compileStxMatch : List Syntax → List Alt → TermElabM Syntax
| [], ([], rhs)::_ => pure rhs -- nothing left to match
| _, [] => throwError "non-exhaustive 'match_syntax'"
| discr::discrs, alts => do
let alts := (alts.map getHeadInfo).zip alts;
-- Choose a most specific pattern, ie. a minimal element according to `generalizes`.
-- If there are multiple minimal elements, the choice does not matter.
let (info, alt) := alts.tail!.foldl (fun (min : HeadInfo × Alt) (alt : HeadInfo × Alt) => if min.1.generalizes alt.1 then alt else min) alts.head!;
-- introduce pattern matches on the discriminant's children if there are any nested patterns
let newDiscrs ← match info.argPats with
| some pats => (List.range pats.size).mapM fun i => `(Syntax.getArg discr $(quote i))
| none => pure []
-- collect matching alternatives and explode them
let yesAlts := alts.filter fun (alt : HeadInfo × Alt) => alt.1.generalizes info
let yesAlts ← yesAlts.mapM $ explodeHeadPat newDiscrs.length
-- NOTE: use fresh macro scopes for recursive call so that different `discr`s introduced by the quotations below do not collide
let yes ← withFreshMacroScope $ compileStxMatch (newDiscrs ++ discrs) yesAlts
let some kind ← pure info.kind
-- unconditional match step
| `(let discr := $discr; $yes)
-- conditional match step
let noAlts := (alts.filter $ fun (alt : HeadInfo × Alt) => !info.generalizes alt.1).map (·.2)
let no ← withFreshMacroScope $ compileStxMatch (discr::discrs) noAlts
let cond ← match info.argPats with
| some pats => `(Syntax.isOfKind discr $(quote kind) && Array.size (Syntax.getArgs discr) == $(quote pats.size))
| none => `(Syntax.isOfKind discr $(quote kind))
`(let discr := $discr; if $cond = true then $yes else $no)
| _, _ => unreachable!
| [], ([], rhs)::_ => pure rhs -- nothing left to match
| _, [] => throwError "non-exhaustive 'match_syntax'"
| discr::discrs, alts => do
let alts := (alts.map getHeadInfo).zip alts;
-- Choose a most specific pattern, ie. a minimal element according to `generalizes`.
-- If there are multiple minimal elements, the choice does not matter.
let (info, alt) := alts.tail!.foldl (fun (min : HeadInfo × Alt) (alt : HeadInfo × Alt) => if min.1.generalizes alt.1 then alt else min) alts.head!;
-- introduce pattern matches on the discriminant's children if there are any nested patterns
let newDiscrs ← match info.argPats with
| some pats => (List.range pats.size).mapM fun i => `(Syntax.getArg discr $(quote i))
| none => pure []
-- collect matching alternatives and explode them
let yesAlts := alts.filter fun (alt : HeadInfo × Alt) => alt.1.generalizes info
let yesAlts ← yesAlts.mapM $ explodeHeadPat newDiscrs.length
-- NOTE: use fresh macro scopes for recursive call so that different `discr`s introduced by the quotations below do not collide
let yes ← withFreshMacroScope $ compileStxMatch (newDiscrs ++ discrs) yesAlts
let some kind ← pure info.kind
-- unconditional match step
| `(let discr := $discr; $yes)
-- conditional match step
let noAlts := (alts.filter $ fun (alt : HeadInfo × Alt) => !info.generalizes alt.1).map (·.2)
let no ← withFreshMacroScope $ compileStxMatch (discr::discrs) noAlts
let cond ← match info.argPats with
| some pats => `(Syntax.isOfKind discr $(quote kind) && Array.size (Syntax.getArgs discr) == $(quote pats.size))
| none => `(Syntax.isOfKind discr $(quote kind))
`(let discr := $discr; if $cond = true then $yes else $no)
| _, _ => unreachable!
private partial def getPatternVarsAux : Syntax → List Syntax
| stx@(Syntax.node k args) =>
if isAntiquot stx && !isEscapedAntiquot stx then
let anti := getAntiquotTerm stx
if anti.isIdent then [anti]
else []
else
List.join $ args.toList.map getPatternVarsAux
| _ => []
| stx@(Syntax.node k args) =>
if isAntiquot stx && !isEscapedAntiquot stx then
let anti := getAntiquotTerm stx
if anti.isIdent then [anti]
else []
else
List.join $ args.toList.map getPatternVarsAux
| _ => []
-- Get all pattern vars (as `Syntax.ident`s) in `stx`
partial def getPatternVars (stx : Syntax) : List Syntax :=
if isQuot stx then do
let quoted := stx.getArg 1;
getPatternVarsAux stx
else if stx.isIdent then
[stx]
else []
if isQuot stx then do
let quoted := stx.getArg 1;
getPatternVarsAux stx
else if stx.isIdent then
[stx]
else []
-- Transform alternatives by binding all right-hand sides to outside the match_syntax in order to prevent
-- code duplication during match_syntax compilation
private def letBindRhss (cont : List Alt → TermElabM Syntax) : List Alt → List Alt → TermElabM Syntax
| [], altsRev' => cont altsRev'.reverse
| (pats, rhs)::alts, altsRev' => do
let vars := List.join $ pats.map getPatternVars
match vars with
-- no antiquotations => introduce Unit parameter to preserve evaluation order
| [] =>
-- NOTE: references binding below
let rhs' ← `(rhs ())
-- NOTE: new macro scope so that introduced bindings do not collide
let stx ← withFreshMacroScope $ letBindRhss cont alts ((pats, rhs')::altsRev')
`(let rhs := fun _ => $rhs; $stx)
| _ =>
-- rhs ← `(fun $vars* => $rhs)
let rhs := Syntax.node `Lean.Parser.Term.fun #[mkAtom "fun", Syntax.node `null vars.toArray, mkAtom "=>", rhs]
let rhs' ← `(rhs)
let stx ← withFreshMacroScope $ letBindRhss cont alts ((pats, rhs')::altsRev')
`(let rhs := $rhs; $stx)
| [], altsRev' => cont altsRev'.reverse
| (pats, rhs)::alts, altsRev' => do
let vars := List.join $ pats.map getPatternVars
match vars with
-- no antiquotations => introduce Unit parameter to preserve evaluation order
| [] =>
-- NOTE: references binding below
let rhs' ← `(rhs ())
-- NOTE: new macro scope so that introduced bindings do not collide
let stx ← withFreshMacroScope $ letBindRhss cont alts ((pats, rhs')::altsRev')
`(let rhs := fun _ => $rhs; $stx)
| _ =>
-- rhs ← `(fun $vars* => $rhs)
let rhs := Syntax.node `Lean.Parser.Term.fun #[mkAtom "fun", Syntax.node `null vars.toArray, mkAtom "=>", rhs]
let rhs' ← `(rhs)
let stx ← withFreshMacroScope $ letBindRhss cont alts ((pats, rhs')::altsRev')
`(let rhs := $rhs; $stx)
def match_syntax.expand (stx : Syntax) : TermElabM Syntax := do
let discr := stx[1]
let alts := stx[3][1]
let alts ← alts.getSepArgs.mapM $ fun alt => do
let pats := alt.getArg 0;
let pat ←
if pats.getArgs.size == 1 then pure pats[0]
else throwError "match_syntax: expected exactly one pattern per alternative"
let pat := if isQuot pat then pat.setArg 1 $ elimAntiquotChoices $ pat[1] else pat
match pat.find? $ fun stx => stx.getKind == choiceKind with
| some choiceStx => throwErrorAt choiceStx "invalid pattern, nested syntax has multiple interpretations"
| none =>
let rhs := alt.getArg 2
pure ([pat], rhs)
-- letBindRhss (compileStxMatch stx [discr]) alts.toList []
compileStxMatch [discr] alts.toList
let discr := stx[1]
let alts := stx[3][1]
let alts ← alts.getSepArgs.mapM $ fun alt => do
let pats := alt.getArg 0;
let pat ←
if pats.getArgs.size == 1 then pure pats[0]
else throwError "match_syntax: expected exactly one pattern per alternative"
let pat := if isQuot pat then pat.setArg 1 $ elimAntiquotChoices $ pat[1] else pat
match pat.find? $ fun stx => stx.getKind == choiceKind with
| some choiceStx => throwErrorAt choiceStx "invalid pattern, nested syntax has multiple interpretations"
| none =>
let rhs := alt.getArg 2
pure ([pat], rhs)
-- letBindRhss (compileStxMatch stx [discr]) alts.toList []
compileStxMatch [discr] alts.toList
@[builtinTermElab «match_syntax»] def elabMatchSyntax : TermElab :=
adaptExpander match_syntax.expand
adaptExpander match_syntax.expand
end Lean.Elab.Term.Quotation

View file

@ -12,10 +12,10 @@ We want to support a more general approach, but we need to provide
the strategy selection attributes while we rely on the Lean3 elaborator.
-/
inductive ElaboratorStrategy
| simple | withExpectedType | asEliminator
| simple | withExpectedType | asEliminator
instance : Inhabited ElaboratorStrategy :=
⟨ElaboratorStrategy.withExpectedType⟩
⟨ElaboratorStrategy.withExpectedType⟩
builtin_initialize elaboratorStrategyAttrs : EnumAttributes ElaboratorStrategy ←
registerEnumAttributes `elaboratorStrategy
@ -25,6 +25,6 @@ builtin_initialize elaboratorStrategyAttrs : EnumAttributes ElaboratorStrategy
@[export lean_get_elaborator_strategy]
def getElaboratorStrategy (env : Environment) (n : Name) : Option ElaboratorStrategy :=
elaboratorStrategyAttrs.getValue env n
elaboratorStrategyAttrs.getValue env n
end Lean

View file

@ -13,58 +13,58 @@ Expand `optional «precedence»` where
precedenceLit : Parser := numLit <|> maxSymbol
maxSymbol := parser! nonReservedSymbol "max" -/
def expandOptPrecedence (stx : Syntax) : Option Nat :=
if stx.isNone then none
else match stx[0][1].isNatLit? with
| some v => some v
| _ => some Parser.maxPrec
if stx.isNone then
none
else match stx[0][1].isNatLit? with
| some v => some v
| _ => some Parser.maxPrec
private def mkParserSeq (ds : Array Syntax) : TermElabM Syntax := do
if ds.size == 0 then
throwUnsupportedSyntax
else if ds.size == 1 then
pure ds[0]
else
let r := ds[0]
for d in ds[1:ds.size] do
r ← `(ParserDescr.andthen $r $d)
return r
if ds.size == 0 then
throwUnsupportedSyntax
else if ds.size == 1 then
pure ds[0]
else
let r := ds[0]
for d in ds[1:ds.size] do
r ← `(ParserDescr.andthen $r $d)
return r
structure ToParserDescrContext :=
(catName : Name)
(first : Bool)
(leftRec : Bool) -- true iff left recursion is allowed
/- When `leadingIdentAsSymbol == true` we convert
`Lean.Parser.Syntax.atom` into `Lean.ParserDescr.nonReservedSymbol`
See comment at `Parser.ParserCategory`. -/
(leadingIdentAsSymbol : Bool)
(catName : Name)
(first : Bool)
(leftRec : Bool) -- true iff left recursion is allowed
/- When `leadingIdentAsSymbol == true` we convert
`Lean.Parser.Syntax.atom` into `Lean.ParserDescr.nonReservedSymbol`
See comment at `Parser.ParserCategory`. -/
(leadingIdentAsSymbol : Bool)
abbrev ToParserDescrM := ReaderT ToParserDescrContext (StateRefT Bool TermElabM)
private def markAsTrailingParser : ToParserDescrM Unit := set true
@[inline] private def withNotFirst {α} (x : ToParserDescrM α) : ToParserDescrM α :=
withReader (fun ctx => { ctx with first := false }) x
withReader (fun ctx => { ctx with first := false }) x
@[inline] private def withoutLeftRec {α} (x : ToParserDescrM α) : ToParserDescrM α :=
withReader (fun ctx => { ctx with leftRec := false }) x
withReader (fun ctx => { ctx with leftRec := false }) x
def checkLeftRec (stx : Syntax) : ToParserDescrM Bool := do
let ctx ← read
if ctx.first && stx.getKind == `Lean.Parser.Syntax.cat then
let cat := stx[0].getId.eraseMacroScopes
if cat == ctx.catName then
let prec? : Option Nat := expandOptPrecedence stx[1]
unless prec?.isNone do throwErrorAt stx[1] ("invalid occurrence of ':<num>' modifier in head")
unless ctx.leftRec do
throwErrorAt! stx[3] "invalid occurrence of '{cat}', parser algorithm does not allow this form of left recursion"
markAsTrailingParser -- mark as trailing par
pure true
let ctx ← read
if ctx.first && stx.getKind == `Lean.Parser.Syntax.cat then
let cat := stx[0].getId.eraseMacroScopes
if cat == ctx.catName then
let prec? : Option Nat := expandOptPrecedence stx[1]
unless prec?.isNone do throwErrorAt stx[1] ("invalid occurrence of ':<num>' modifier in head")
unless ctx.leftRec do
throwErrorAt! stx[3] "invalid occurrence of '{cat}', parser algorithm does not allow this form of left recursion"
markAsTrailingParser -- mark as trailing par
pure true
else
pure false
else
pure false
else
pure false
partial def toParserDescrAux : Syntax → ToParserDescrM Syntax
| stx => do
partial def toParserDescrAux (stx : Syntax) : ToParserDescrM Syntax := do
let kind := stx.getKind
if kind == nullKind then
let args := stx.getArgs
@ -172,31 +172,30 @@ partial def toParserDescrAux : Syntax → ToParserDescrM Syntax
where `newStx` is of category `term`. After elaboration, `newStx` should have type
`TrailingParserDescr` if `trailingParser == true`, and `ParserDescr` otherwise. -/
def toParserDescr (stx : Syntax) (catName : Name) : TermElabM (Syntax × Bool) := do
let env ← getEnv
let leadingIdentAsSymbol := Parser.leadingIdentAsSymbol env catName
(toParserDescrAux stx { catName := catName, first := true, leftRec := true, leadingIdentAsSymbol := leadingIdentAsSymbol }).run false
let env ← getEnv
let leadingIdentAsSymbol := Parser.leadingIdentAsSymbol env catName
(toParserDescrAux stx { catName := catName, first := true, leftRec := true, leadingIdentAsSymbol := leadingIdentAsSymbol }).run false
end Term
namespace Command
private def getCatSuffix (catName : Name) : String :=
match catName with
| Name.str _ s _ => s
| _ => unreachable!
match catName with
| Name.str _ s _ => s
| _ => unreachable!
private def declareSyntaxCatQuotParser (catName : Name) : CommandElabM Unit := do
let quotSymbol := "`(" ++ getCatSuffix catName ++ "|"
let kind := catName ++ `quot
let cmd ← `(
@[termParser] def $(mkIdent kind) : Lean.ParserDescr :=
Lean.ParserDescr.node $(quote kind) $(quote Lean.Parser.maxPrec)
(Lean.ParserDescr.andthen (Lean.ParserDescr.symbol $(quote quotSymbol))
(Lean.ParserDescr.andthen (Lean.ParserDescr.cat $(quote catName) 0) (Lean.ParserDescr.symbol ")"))))
elabCommand cmd
let quotSymbol := "`(" ++ getCatSuffix catName ++ "|"
let kind := catName ++ `quot
let cmd ← `(
@[termParser] def $(mkIdent kind) : Lean.ParserDescr :=
Lean.ParserDescr.node $(quote kind) $(quote Lean.Parser.maxPrec)
(Lean.ParserDescr.andthen (Lean.ParserDescr.symbol $(quote quotSymbol))
(Lean.ParserDescr.andthen (Lean.ParserDescr.cat $(quote catName) 0) (Lean.ParserDescr.symbol ")"))))
elabCommand cmd
@[builtinCommandElab syntaxCat] def elabDeclareSyntaxCat : CommandElab :=
fun stx => do
@[builtinCommandElab syntaxCat] def elabDeclareSyntaxCat : CommandElab := fun stx => do
let catName := stx[1].getId
let attrName := catName.appendAfter "Parser"
let env ← getEnv
@ -205,43 +204,43 @@ fun stx => do
declareSyntaxCatQuotParser catName
def mkKindName (catName : Name) : Name :=
`_kind ++ catName
`_kind ++ catName
def mkFreshKind (catName : Name) : CommandElabM Name := do
let scp ← getCurrMacroScope
let mainModule ← getMainModule
pure $ Lean.addMacroScope mainModule (mkKindName catName) scp
let scp ← getCurrMacroScope
let mainModule ← getMainModule
pure $ Lean.addMacroScope mainModule (mkKindName catName) scp
def Macro.mkFreshKind (catName : Name) : MacroM Name :=
Macro.addMacroScope (mkKindName catName)
Macro.addMacroScope (mkKindName catName)
def mkKind (stx : Syntax) : CommandElabM Name := do
let kind := stx.getId
if kind.hasMacroScopes then
pure kind
else
let currNamespace ← getCurrNamespace
pure (currNamespace ++ kind)
let kind := stx.getId
if kind.hasMacroScopes then
pure kind
else
let currNamespace ← getCurrNamespace
pure (currNamespace ++ kind)
private def elabKindPrio (stx : Syntax) (catName : Name) : CommandElabM (Name × Nat) := do
if stx.isNone then
let k ← mkFreshKind catName
pure (k, 0)
else
let arg := stx[1]
if arg.getKind == `Lean.Parser.Command.parserKind then
let k ← mkKind arg[0]
pure (k, 0)
else if arg.getKind == `Lean.Parser.Command.parserPrio then
if stx.isNone then
let k ← mkFreshKind catName
let prio := arg[0].isNatLit?.getD 0
pure (k, prio)
else if arg.getKind == `Lean.Parser.Command.parserKindPrio then
let k ← mkKind arg[0]
let prio := arg[2].isNatLit?.getD 0
pure (k, prio)
pure (k, 0)
else
throwError "unexpected syntax kind/priority"
let arg := stx[1]
if arg.getKind == `Lean.Parser.Command.parserKind then
let k ← mkKind arg[0]
pure (k, 0)
else if arg.getKind == `Lean.Parser.Command.parserPrio then
let k ← mkFreshKind catName
let prio := arg[0].isNatLit?.getD 0
pure (k, prio)
else if arg.getKind == `Lean.Parser.Command.parserKindPrio then
let k ← mkKind arg[0]
let prio := arg[2].isNatLit?.getD 0
pure (k, prio)
else
throwError "unexpected syntax kind/priority"
/- We assume a new syntax can be treated as an atom when it starts and ends with a token.
Here are examples of atom-like syntax.
@ -251,8 +250,7 @@ else
syntax "foo" : term
```
-/
private partial def isAtomLikeSyntax : Syntax → Bool
| stx =>
private partial def isAtomLikeSyntax (stx : Syntax) : Bool :=
let kind := stx.getKind
if kind == nullKind then
isAtomLikeSyntax stx[0] && isAtomLikeSyntax stx[stx.getNumArgs - 1]
@ -266,8 +264,7 @@ private partial def isAtomLikeSyntax : Syntax → Bool
/-
def «syntax» := parser! "syntax " >> optPrecedence >> optKindPrio >> many1 syntaxParser >> " : " >> ident
-/
@[builtinCommandElab «syntax»] def elabSyntax : CommandElab :=
fun stx => do
@[builtinCommandElab «syntax»] def elabSyntax : CommandElab := fun stx => do
let env ← getEnv
let cat := stx[5].getId.eraseMacroScopes
unless (Parser.isParserCategory env cat) do throwErrorAt stx[5] ("unknown category '" ++ cat ++ "'")
@ -291,179 +288,176 @@ fun stx => do
/-
def syntaxAbbrev := parser! "syntax " >> ident >> " := " >> many1 syntaxParser
-/
@[builtinCommandElab «syntaxAbbrev»] def elabSyntaxAbbrev : CommandElab :=
fun stx => do
@[builtinCommandElab «syntaxAbbrev»] def elabSyntaxAbbrev : CommandElab := fun stx => do
let declName := stx[1]
let (val, _) ← runTermElabM none $ fun _ => Term.toParserDescr stx[3] Name.anonymous
let stx' ← `(def $declName : Lean.ParserDescr := $val)
withMacroExpansion stx stx' $ elabCommand stx'
def elabMacroRulesAux (k : SyntaxNodeKind) (alts : Array Syntax) : CommandElabM Syntax := do
alts ← alts.mapSepElemsM fun alt => do
alts ← alts.mapSepElemsM fun alt => do
let lhs := alt[0]
let pat := lhs[0]
if !pat.isQuot then
throwUnsupportedSyntax
let quot := pat[1]
let k' := quot.getKind
if k' == k then
pure alt
else if k' == choiceKind then
match quot.getArgs.find? fun quotAlt => quotAlt.getKind == k with
| none => throwErrorAt! alt "invalid macro_rules alternative, expected syntax node kind '{k}'"
| some quot =>
let pat := pat.setArg 1 quot
let lhs := lhs.setArg 0 pat
pure $ alt.setArg 0 lhs
else
throwErrorAt! alt "invalid macro_rules alternative, unexpected syntax node kind '{k'}'"
`(@[macro $(Lean.mkIdent k)] def myMacro : Macro :=
fun stx => match_syntax stx with $alts:matchAlt* | _ => throw Lean.Macro.Exception.unsupportedSyntax)
def inferMacroRulesAltKind (alt : Syntax) : CommandElabM SyntaxNodeKind := do
let lhs := alt[0]
let pat := lhs[0]
if !pat.isQuot then
throwUnsupportedSyntax
let quot := pat[1]
let k' := quot.getKind
if k' == k then
pure alt
else if k' == choiceKind then
match quot.getArgs.find? fun quotAlt => quotAlt.getKind == k with
| none => throwErrorAt! alt "invalid macro_rules alternative, expected syntax node kind '{k}'"
| some quot =>
let pat := pat.setArg 1 quot
let lhs := lhs.setArg 0 pat
pure $ alt.setArg 0 lhs
else
throwErrorAt! alt "invalid macro_rules alternative, unexpected syntax node kind '{k'}'"
`(@[macro $(Lean.mkIdent k)] def myMacro : Macro :=
fun stx => match_syntax stx with $alts:matchAlt* | _ => throw Lean.Macro.Exception.unsupportedSyntax)
def inferMacroRulesAltKind (alt : Syntax) : CommandElabM SyntaxNodeKind := do
let lhs := alt[0]
let pat := lhs[0]
if !pat.isQuot then
throwUnsupportedSyntax
let quot := pat[1]
pure quot.getKind
pure quot.getKind
def elabNoKindMacroRulesAux (alts : Array Syntax) : CommandElabM Syntax := do
let k ← inferMacroRulesAltKind (alts.get! 0)
if k == choiceKind then
throwErrorAt! alts[0]
"invalid macro_rules alternative, multiple interpretations for pattern (solution: specify node kind using `macro_rules [<kind>] ...`)"
else
let altsK ← alts.filterSepElemsM fun alt => do pure $ k == (← inferMacroRulesAltKind alt)
let altsNotK ← alts.filterSepElemsM fun alt => do pure $ k != (← inferMacroRulesAltKind alt)
let defCmd ← elabMacroRulesAux k altsK
if altsNotK.isEmpty then
pure defCmd
let k ← inferMacroRulesAltKind (alts.get! 0)
if k == choiceKind then
throwErrorAt! alts[0]
"invalid macro_rules alternative, multiple interpretations for pattern (solution: specify node kind using `macro_rules [<kind>] ...`)"
else
`($defCmd:command macro_rules $altsNotK:matchAlt*)
let altsK ← alts.filterSepElemsM fun alt => do pure $ k == (← inferMacroRulesAltKind alt)
let altsNotK ← alts.filterSepElemsM fun alt => do pure $ k != (← inferMacroRulesAltKind alt)
let defCmd ← elabMacroRulesAux k altsK
if altsNotK.isEmpty then
pure defCmd
else
`($defCmd:command macro_rules $altsNotK:matchAlt*)
@[builtinCommandElab «macro_rules»] def elabMacroRules : CommandElab :=
adaptExpander $ fun stx => match_syntax stx with
| `(macro_rules $alts:matchAlt*) => elabNoKindMacroRulesAux alts
| `(macro_rules | $alts:matchAlt*) => elabNoKindMacroRulesAux alts
| `(macro_rules [$kind] $alts:matchAlt*) => do elabMacroRulesAux (← mkKind kind) alts
| `(macro_rules [$kind] | $alts:matchAlt*) => do elabMacroRulesAux (← mkKind kind) alts
| _ => throwUnsupportedSyntax
adaptExpander fun stx => match_syntax stx with
| `(macro_rules $alts:matchAlt*) => elabNoKindMacroRulesAux alts
| `(macro_rules | $alts:matchAlt*) => elabNoKindMacroRulesAux alts
| `(macro_rules [$kind] $alts:matchAlt*) => do elabMacroRulesAux (← mkKind kind) alts
| `(macro_rules [$kind] | $alts:matchAlt*) => do elabMacroRulesAux (← mkKind kind) alts
| _ => throwUnsupportedSyntax
@[builtinMacro Lean.Parser.Command.mixfix] def expandMixfix : Macro :=
fun stx => match_syntax stx with
| `(infix:$prec $op => $f) => `(infixl:$prec $op => $f)
| `(infixr:$prec $op => $f) => `(notation:$prec lhs $op:strLit rhs:$prec => $f lhs rhs)
| `(infixl:$prec $op => $f) => let prec1 : Syntax := quote (prec.toNat+1); `(notation:$prec lhs $op:strLit rhs:$prec1 => $f lhs rhs)
| `(prefix:$prec $op => $f) => `(notation:$prec $op:strLit arg:$prec => $f arg)
| `(postfix:$prec $op => $f) => `(notation:$prec arg $op:strLit => $f arg)
| _ => Macro.throwUnsupported
fun stx => match_syntax stx with
| `(infix:$prec $op => $f) => `(infixl:$prec $op => $f)
| `(infixr:$prec $op => $f) => `(notation:$prec lhs $op:strLit rhs:$prec => $f lhs rhs)
| `(infixl:$prec $op => $f) => let prec1 : Syntax := quote (prec.toNat+1); `(notation:$prec lhs $op:strLit rhs:$prec1 => $f lhs rhs)
| `(prefix:$prec $op => $f) => `(notation:$prec $op:strLit arg:$prec => $f arg)
| `(postfix:$prec $op => $f) => `(notation:$prec arg $op:strLit => $f arg)
| _ => Macro.throwUnsupported
/- Wrap all occurrences of the given `ident` nodes in antiquotations -/
private partial def antiquote (vars : Array Syntax) : Syntax → Syntax
| stx => match_syntax stx with
| `($id:ident) =>
if (vars.findIdx? (fun var => var.getId == id.getId)).isSome then
Syntax.node `antiquot #[mkAtom "$", mkNullNode, id, mkNullNode, mkNullNode]
else
stx
| _ => match stx with
| Syntax.node k args => Syntax.node k (args.map (antiquote vars))
| stx => stx
| stx => match_syntax stx with
| `($id:ident) =>
if (vars.findIdx? (fun var => var.getId == id.getId)).isSome then
Syntax.node `antiquot #[mkAtom "$", mkNullNode, id, mkNullNode, mkNullNode]
else
stx
| _ => match stx with
| Syntax.node k args => Syntax.node k (args.map (antiquote vars))
| stx => stx
/- Convert `notation` command lhs item into a `syntax` command item -/
def expandNotationItemIntoSyntaxItem (stx : Syntax) : MacroM Syntax :=
let k := stx.getKind
if k == `Lean.Parser.Command.identPrec then
pure $ Syntax.node `Lean.Parser.Syntax.cat #[mkIdentFrom stx `term, stx[1]]
else if k == quotedSymbolKind then
match stx[1] with
| Syntax.atom info val => pure $ Syntax.node `Lean.Parser.Syntax.atom #[mkStxStrLit val info]
| _ => Macro.throwUnsupported
else if k == strLitKind then
pure $ Syntax.node `Lean.Parser.Syntax.atom #[stx]
else
Macro.throwUnsupported
let k := stx.getKind
if k == `Lean.Parser.Command.identPrec then
pure $ Syntax.node `Lean.Parser.Syntax.cat #[mkIdentFrom stx `term, stx[1]]
else if k == quotedSymbolKind then
match stx[1] with
| Syntax.atom info val => pure $ Syntax.node `Lean.Parser.Syntax.atom #[mkStxStrLit val info]
| _ => Macro.throwUnsupported
else if k == strLitKind then
pure $ Syntax.node `Lean.Parser.Syntax.atom #[stx]
else
Macro.throwUnsupported
def strLitToPattern (stx: Syntax) : MacroM Syntax :=
match stx.isStrLit? with
| some str => pure $ mkAtomFrom stx str
| none => Macro.throwUnsupported
match stx.isStrLit? with
| some str => pure $ mkAtomFrom stx str
| none => Macro.throwUnsupported
/- Convert `notation` command lhs item a pattern element -/
def expandNotationItemIntoPattern (stx : Syntax) : MacroM Syntax :=
let k := stx.getKind
if k == `Lean.Parser.Command.identPrec then
let item := stx[0]
pure $ mkNode `antiquot #[mkAtom "$", mkNullNode, item, mkNullNode, mkNullNode]
else if k == quotedSymbolKind then
pure stx[1]
else if k == strLitKind then
strLitToPattern stx
else
Macro.throwUnsupported
let k := stx.getKind
if k == `Lean.Parser.Command.identPrec then
let item := stx[0]
pure $ mkNode `antiquot #[mkAtom "$", mkNullNode, item, mkNullNode, mkNullNode]
else if k == quotedSymbolKind then
pure stx[1]
else if k == strLitKind then
strLitToPattern stx
else
Macro.throwUnsupported
private def expandNotationAux (ref : Syntax) (prec? : Option Syntax) (items : Array Syntax) (rhs : Syntax) : MacroM Syntax := do
let kind ← Macro.mkFreshKind `term
-- build parser
let syntaxParts ← items.mapM expandNotationItemIntoSyntaxItem
let cat := mkIdentFrom ref `term
-- build macro rules
let vars := items.filter fun item => item.getKind == `Lean.Parser.Command.identPrec
let vars := vars.map fun var => var[0]
let rhs := antiquote vars rhs
let patArgs ← items.mapM expandNotationItemIntoPattern
let pat := Syntax.node kind patArgs
match prec? with
| none => `(syntax [$(mkIdentFrom ref kind):ident] $syntaxParts* : $cat macro_rules | `($pat) => `($rhs))
| some prec => `(syntax:$prec [$(mkIdentFrom ref kind):ident] $syntaxParts* : $cat macro_rules | `($pat) => `($rhs))
let kind ← Macro.mkFreshKind `term
-- build parser
let syntaxParts ← items.mapM expandNotationItemIntoSyntaxItem
let cat := mkIdentFrom ref `term
-- build macro rules
let vars := items.filter fun item => item.getKind == `Lean.Parser.Command.identPrec
let vars := vars.map fun var => var[0]
let rhs := antiquote vars rhs
let patArgs ← items.mapM expandNotationItemIntoPattern
let pat := Syntax.node kind patArgs
match prec? with
| none => `(syntax [$(mkIdentFrom ref kind):ident] $syntaxParts* : $cat macro_rules | `($pat) => `($rhs))
| some prec => `(syntax:$prec [$(mkIdentFrom ref kind):ident] $syntaxParts* : $cat macro_rules | `($pat) => `($rhs))
@[builtinMacro Lean.Parser.Command.notation] def expandNotation : Macro :=
fun stx =>
match_syntax stx with
| `(notation:$prec $items* => $rhs) => expandNotationAux stx prec items rhs
| `(notation $items:notationItem* => $rhs) => expandNotationAux stx none items rhs
| _ => Macro.throwUnsupported
@[builtinMacro Lean.Parser.Command.notation] def expandNotation : Macro := fun stx =>
match_syntax stx with
| `(notation:$prec $items* => $rhs) => expandNotationAux stx prec items rhs
| `(notation $items:notationItem* => $rhs) => expandNotationAux stx none items rhs
| _ => Macro.throwUnsupported
/- Convert `macro` argument into a `syntax` command item -/
def expandMacroArgIntoSyntaxItem (stx : Syntax) : MacroM Syntax :=
let k := stx.getKind
if k == `Lean.Parser.Command.macroArgSimple then
pure stx[2]
else if k == strLitKind then
pure $ Syntax.node `Lean.Parser.Syntax.atom #[stx]
else
Macro.throwUnsupported
let k := stx.getKind
if k == `Lean.Parser.Command.macroArgSimple then
pure stx[2]
else if k == strLitKind then
pure $ Syntax.node `Lean.Parser.Syntax.atom #[stx]
else
Macro.throwUnsupported
/- Convert `macro` head into a `syntax` command item -/
def expandMacroHeadIntoSyntaxItem (stx : Syntax) : MacroM Syntax :=
if stx.isIdent then
let info := stx.getHeadInfo.getD {}
let id := stx.getId
pure $ Syntax.node `Lean.Parser.Syntax.atom #[mkStxStrLit (toString id) info]
else
expandMacroArgIntoSyntaxItem stx
if stx.isIdent then
let info := stx.getHeadInfo.getD {}
let id := stx.getId
pure $ Syntax.node `Lean.Parser.Syntax.atom #[mkStxStrLit (toString id) info]
else
expandMacroArgIntoSyntaxItem stx
/- Convert `macro` arg into a pattern element -/
def expandMacroArgIntoPattern (stx : Syntax) : MacroM Syntax :=
let k := stx.getKind
if k == `Lean.Parser.Command.macroArgSimple then
let item := stx[0]
pure $ mkNode `antiquot #[mkAtom "$", mkNullNode, item, mkNullNode, mkNullNode]
else if k == strLitKind then
strLitToPattern stx
else
Macro.throwUnsupported
let k := stx.getKind
if k == `Lean.Parser.Command.macroArgSimple then
let item := stx[0]
pure $ mkNode `antiquot #[mkAtom "$", mkNullNode, item, mkNullNode, mkNullNode]
else if k == strLitKind then
strLitToPattern stx
else
Macro.throwUnsupported
/- Convert `macro` head into a pattern element -/
def expandMacroHeadIntoPattern (stx : Syntax) : MacroM Syntax :=
if stx.isIdent then
pure $ mkAtomFrom stx (toString stx.getId)
else
expandMacroArgIntoPattern stx
if stx.isIdent then
pure $ mkAtomFrom stx (toString stx.getId)
else
expandMacroArgIntoPattern stx
@[builtinMacro Lean.Parser.Command.macro] def expandMacro : Macro :=
fun stx => do
@[builtinMacro Lean.Parser.Command.macro] def expandMacro : Macro := fun stx => do
let prec := stx[1].getArgs
let head := stx[2]
let args := stx[3].getArgs
@ -488,22 +482,20 @@ fun stx => do
`(syntax $prec* [$(mkIdentFrom stx kind):ident] $stxParts* : $cat
macro_rules | `($pat) => `($rhsBody))
@[builtinInit] private def regTraceClasses : IO Unit := do
registerTraceClass `Elab.syntax
pure ()
builtin_initialize
registerTraceClass `Elab.syntax
@[inline] def withExpectedType (expectedType? : Option Expr) (x : Expr → TermElabM Expr) : TermElabM Expr := do
Term.tryPostponeIfNoneOrMVar expectedType?
let some expectedType ← pure expectedType?
| throwError "expected type must be known"
x expectedType
Term.tryPostponeIfNoneOrMVar expectedType?
let some expectedType ← pure expectedType?
| throwError "expected type must be known"
x expectedType
/-
def elabTail := try (" : " >> ident) >> darrow >> termParser
parser! "elab " >> optPrecedence >> elabHead >> many elabArg >> elabTail
-/
@[builtinMacro Lean.Parser.Command.elab] def expandElab : Macro :=
fun stx => do
@[builtinMacro Lean.Parser.Command.elab] def expandElab : Macro := fun stx => do
let ref := stx
let prec := stx[1].getArgs
let head := stx[2]

View file

@ -6,183 +6,181 @@ Authors: Leonardo de Moura, Sebastian Ullrich
import Lean.Elab.Term
import Lean.Elab.Tactic.Basic
namespace Lean
namespace Elab
namespace Term
namespace Lean.Elab.Term
open Tactic (TacticM evalTactic getUnsolvedGoals)
open Meta
def liftTacticElabM {α} (mvarId : MVarId) (x : TacticM α) : TermElabM α :=
withMVarContext mvarId do
let s ← get
let savedSyntheticMVars := s.syntheticMVars
modify fun s => { s with syntheticMVars := [] }
try
x.run' { main := mvarId } { goals := [mvarId] }
finally
modify fun s => { s with syntheticMVars := savedSyntheticMVars }
withMVarContext mvarId do
let s ← get
let savedSyntheticMVars := s.syntheticMVars
modify fun s => { s with syntheticMVars := [] }
try
x.run' { main := mvarId } { goals := [mvarId] }
finally
modify fun s => { s with syntheticMVars := savedSyntheticMVars }
def ensureAssignmentHasNoMVars (mvarId : MVarId) : TermElabM Unit := do
let val ← instantiateMVars (mkMVar mvarId)
if val.hasExprMVar then throwError! "tactic failed, result still contain metavariables{indentExpr val}"
let val ← instantiateMVars (mkMVar mvarId)
if val.hasExprMVar then throwError! "tactic failed, result still contain metavariables{indentExpr val}"
def runTactic (mvarId : MVarId) (tacticCode : Syntax) : TermElabM Unit := do
/- Recall, `tacticCode` is the whole `by ...` expression.
We store the `by` because in the future we want to save the initial state information at the `by` position. -/
let code := tacticCode[1]
modifyThe Meta.State fun s => { s with mctx := s.mctx.instantiateMVarDeclMVars mvarId }
let remainingGoals ← liftTacticElabM mvarId do evalTactic code; getUnsolvedGoals
unless remainingGoals.isEmpty do reportUnsolvedGoals remainingGoals
ensureAssignmentHasNoMVars mvarId
/- Recall, `tacticCode` is the whole `by ...` expression.
We store the `by` because in the future we want to save the initial state information at the `by` position. -/
let code := tacticCode[1]
modifyThe Meta.State fun s => { s with mctx := s.mctx.instantiateMVarDeclMVars mvarId }
let remainingGoals ← liftTacticElabM mvarId do evalTactic code; getUnsolvedGoals
unless remainingGoals.isEmpty do reportUnsolvedGoals remainingGoals
ensureAssignmentHasNoMVars mvarId
/-- Auxiliary function used to implement `synthesizeSyntheticMVars`. -/
private def resumeElabTerm (stx : Syntax) (expectedType? : Option Expr) (errToSorry := true) : TermElabM Expr :=
-- Remark: if `ctx.errToSorry` is already false, then we don't enable it. Recall tactics disable `errToSorry`
withReader (fun ctx => { ctx with errToSorry := ctx.errToSorry && errToSorry }) do
elabTerm stx expectedType? false
-- Remark: if `ctx.errToSorry` is already false, then we don't enable it. Recall tactics disable `errToSorry`
withReader (fun ctx => { ctx with errToSorry := ctx.errToSorry && errToSorry }) do
elabTerm stx expectedType? false
/--
Try to elaborate `stx` that was postponed by an elaboration method using `Expection.postpone`.
It returns `true` if it succeeded, and `false` otherwise.
It is used to implement `synthesizeSyntheticMVars`. -/
private def resumePostponed (macroStack : MacroStack) (declName? : Option Name) (stx : Syntax) (mvarId : MVarId) (postponeOnError : Bool) : TermElabM Bool :=
withRef stx $ withMVarContext mvarId do
let s ← get
try
withReader (fun ctx => { ctx with macroStack := macroStack, declName? := declName? }) do
let mvarDecl ← getMVarDecl mvarId
let expectedType ← instantiateMVars mvarDecl.type
let result ← resumeElabTerm stx expectedType (!postponeOnError)
/- We must ensure `result` has the expected type because it is the one expected by the method that postponed stx.
That is, the method does not have an opportunity to check whether `result` has the expected type or not. -/
let result ← withRef stx $ ensureHasType expectedType result
assignExprMVar mvarId result
pure true
catch
| ex@(Exception.internal id) =>
if id == postponeExceptionId then
set s
pure false
else
throw ex
| ex@(Exception.error _ _) =>
if postponeOnError then
set s; pure false
else
logException ex
pure true
withRef stx $ withMVarContext mvarId do
let s ← get
try
withReader (fun ctx => { ctx with macroStack := macroStack, declName? := declName? }) do
let mvarDecl ← getMVarDecl mvarId
let expectedType ← instantiateMVars mvarDecl.type
let result ← resumeElabTerm stx expectedType (!postponeOnError)
/- We must ensure `result` has the expected type because it is the one expected by the method that postponed stx.
That is, the method does not have an opportunity to check whether `result` has the expected type or not. -/
let result ← withRef stx $ ensureHasType expectedType result
assignExprMVar mvarId result
pure true
catch
| ex@(Exception.internal id) =>
if id == postponeExceptionId then
set s
pure false
else
throw ex
| ex@(Exception.error _ _) =>
if postponeOnError then
set s; pure false
else
logException ex
pure true
/--
Similar to `synthesizeInstMVarCore`, but makes sure that `instMVar` local context and instances
are used. It also logs any error message produced. -/
private def synthesizePendingInstMVar (instMVar : MVarId) : TermElabM Bool :=
withMVarContext instMVar do
try
synthesizeInstMVarCore instMVar
catch
| ex@(Exception.error _ _) => logException ex; pure true
| _ => unreachable!
withMVarContext instMVar do
try
synthesizeInstMVarCore instMVar
catch
| ex@(Exception.error _ _) => logException ex; pure true
| _ => unreachable!
/--
Similar to `synthesizePendingInstMVar`, but generates type mismatch error message. -/
private def synthesizePendingCoeInstMVar
(instMVar : MVarId) (errorMsgHeader? : Option String) (expectedType : Expr) (eType : Expr) (e : Expr) (f? : Option Expr) : TermElabM Bool :=
withMVarContext instMVar do
try
synthesizeInstMVarCore instMVar
catch
| Exception.error _ msg => throwTypeMismatchError errorMsgHeader? expectedType eType e f? msg
| _ => unreachable!
withMVarContext instMVar do
try
synthesizeInstMVarCore instMVar
catch
| Exception.error _ msg => throwTypeMismatchError errorMsgHeader? expectedType eType e f? msg
| _ => unreachable!
/--
Return `true` iff `mvarId` is assigned to a term whose the
head is not a metavariable. We use this method to process `SyntheticMVarKind.withDefault`. -/
private def checkWithDefault (mvarId : MVarId) : TermElabM Bool := do
let val ← instantiateMVars (mkMVar mvarId)
pure $ !val.getAppFn.isMVar
let val ← instantiateMVars (mkMVar mvarId)
pure $ !val.getAppFn.isMVar
/-- Try to synthesize the given pending synthetic metavariable. -/
private def synthesizeSyntheticMVar (mvarSyntheticDecl : SyntheticMVarDecl) (postponeOnError : Bool) (runTactics : Bool) : TermElabM Bool :=
withRef mvarSyntheticDecl.stx do
match mvarSyntheticDecl.kind with
| SyntheticMVarKind.typeClass => synthesizePendingInstMVar mvarSyntheticDecl.mvarId
| SyntheticMVarKind.coe header? expectedType eType e f? => synthesizePendingCoeInstMVar mvarSyntheticDecl.mvarId header? expectedType eType e f?
-- NOTE: actual processing at `synthesizeSyntheticMVarsAux`
| SyntheticMVarKind.withDefault _ => checkWithDefault mvarSyntheticDecl.mvarId
| SyntheticMVarKind.postponed macroStack declName? => resumePostponed macroStack declName? mvarSyntheticDecl.stx mvarSyntheticDecl.mvarId postponeOnError
| SyntheticMVarKind.tactic declName? tacticCode =>
withReader (fun ctx => { ctx with declName? := declName? }) do
if runTactics then
runTactic mvarSyntheticDecl.mvarId tacticCode
pure true
else
pure false
withRef mvarSyntheticDecl.stx do
match mvarSyntheticDecl.kind with
| SyntheticMVarKind.typeClass => synthesizePendingInstMVar mvarSyntheticDecl.mvarId
| SyntheticMVarKind.coe header? expectedType eType e f? => synthesizePendingCoeInstMVar mvarSyntheticDecl.mvarId header? expectedType eType e f?
-- NOTE: actual processing at `synthesizeSyntheticMVarsAux`
| SyntheticMVarKind.withDefault _ => checkWithDefault mvarSyntheticDecl.mvarId
| SyntheticMVarKind.postponed macroStack declName? => resumePostponed macroStack declName? mvarSyntheticDecl.stx mvarSyntheticDecl.mvarId postponeOnError
| SyntheticMVarKind.tactic declName? tacticCode =>
withReader (fun ctx => { ctx with declName? := declName? }) do
if runTactics then
runTactic mvarSyntheticDecl.mvarId tacticCode
pure true
else
pure false
/--
Try to synthesize the current list of pending synthetic metavariables.
Return `true` if at least one of them was synthesized. -/
private def synthesizeSyntheticMVarsStep (postponeOnError : Bool) (runTactics : Bool) : TermElabM Bool := do
let ctx ← read
traceAtCmdPos `Elab.resuming $ fun _ =>
fmt "resuming synthetic metavariables, mayPostpone: " ++ fmt ctx.mayPostpone ++ ", postponeOnError: " ++ toString postponeOnError
let s ← get
let syntheticMVars := s.syntheticMVars
let numSyntheticMVars := syntheticMVars.length
-- We reset `syntheticMVars` because new synthetic metavariables may be created by `synthesizeSyntheticMVar`.
modify fun s => { s with syntheticMVars := [] }
-- Recall that `syntheticMVars` is a list where head is the most recent pending synthetic metavariable.
-- We use `filterRevM` instead of `filterM` to make sure we process the synthetic metavariables using the order they were created.
-- It would not be incorrect to use `filterM`.
let remainingSyntheticMVars ← syntheticMVars.filterRevM fun mvarDecl => do
-- We use `traceM` because we want to make sure the metavar local context is used to trace the message
traceM `Elab.postpone (withMVarContext mvarDecl.mvarId do addMessageContext msg!"resuming {mkMVar mvarDecl.mvarId}")
let succeeded ← synthesizeSyntheticMVar mvarDecl postponeOnError runTactics
trace[Elab.postpone]! if succeeded then fmt "succeeded" else fmt "not ready yet"
pure !succeeded
-- Merge new synthetic metavariables with `remainingSyntheticMVars`, i.e., metavariables that still couldn't be synthesized
modify fun s => { s with syntheticMVars := s.syntheticMVars ++ remainingSyntheticMVars }
pure $ numSyntheticMVars != remainingSyntheticMVars.length
let ctx ← read
traceAtCmdPos `Elab.resuming $ fun _ =>
fmt "resuming synthetic metavariables, mayPostpone: " ++ fmt ctx.mayPostpone ++ ", postponeOnError: " ++ toString postponeOnError
let s ← get
let syntheticMVars := s.syntheticMVars
let numSyntheticMVars := syntheticMVars.length
-- We reset `syntheticMVars` because new synthetic metavariables may be created by `synthesizeSyntheticMVar`.
modify fun s => { s with syntheticMVars := [] }
-- Recall that `syntheticMVars` is a list where head is the most recent pending synthetic metavariable.
-- We use `filterRevM` instead of `filterM` to make sure we process the synthetic metavariables using the order they were created.
-- It would not be incorrect to use `filterM`.
let remainingSyntheticMVars ← syntheticMVars.filterRevM fun mvarDecl => do
-- We use `traceM` because we want to make sure the metavar local context is used to trace the message
traceM `Elab.postpone (withMVarContext mvarDecl.mvarId do addMessageContext msg!"resuming {mkMVar mvarDecl.mvarId}")
let succeeded ← synthesizeSyntheticMVar mvarDecl postponeOnError runTactics
trace[Elab.postpone]! if succeeded then fmt "succeeded" else fmt "not ready yet"
pure !succeeded
-- Merge new synthetic metavariables with `remainingSyntheticMVars`, i.e., metavariables that still couldn't be synthesized
modify fun s => { s with syntheticMVars := s.syntheticMVars ++ remainingSyntheticMVars }
pure $ numSyntheticMVars != remainingSyntheticMVars.length
/--
Apply default value to any pending synthetic metavariable of kind `SyntheticMVarKind.withDefault`
Return true if something was synthesized. -/
def synthesizeUsingDefault : TermElabM Bool := do
let s ← get
let len := s.syntheticMVars.length
let newSyntheticMVars ← s.syntheticMVars.filterM fun mvarDecl =>
withRef mvarDecl.stx $
match mvarDecl.kind with
| SyntheticMVarKind.withDefault defaultVal => withMVarContext mvarDecl.mvarId do
let val ← instantiateMVars (mkMVar mvarDecl.mvarId)
if val.getAppFn.isMVar && !(← isDefEq val defaultVal) then
-- TODO: better error message
throwError! "failed to assign default value to metavariable{indentExpr val}\ndefault value{indentExpr defaultVal}"
pure false
| _ => pure true
modify fun s => { s with syntheticMVars := newSyntheticMVars }
pure $ newSyntheticMVars.length != len
let s ← get
let len := s.syntheticMVars.length
let newSyntheticMVars ← s.syntheticMVars.filterM fun mvarDecl =>
withRef mvarDecl.stx $
match mvarDecl.kind with
| SyntheticMVarKind.withDefault defaultVal => withMVarContext mvarDecl.mvarId do
let val ← instantiateMVars (mkMVar mvarDecl.mvarId)
if val.getAppFn.isMVar && !(← isDefEq val defaultVal) then
-- TODO: better error message
throwError! "failed to assign default value to metavariable{indentExpr val}\ndefault value{indentExpr defaultVal}"
pure false
| _ => pure true
modify fun s => { s with syntheticMVars := newSyntheticMVars }
pure $ newSyntheticMVars.length != len
/-- Report an error for each synthetic metavariable that could not be resolved. -/
private def reportStuckSyntheticMVars : TermElabM Unit := do
let s ← get
for mvarSyntheticDecl in s.syntheticMVars do
withRef mvarSyntheticDecl.stx do
match mvarSyntheticDecl.kind with
| SyntheticMVarKind.typeClass =>
withMVarContext mvarSyntheticDecl.mvarId do
let mvarDecl ← getMVarDecl mvarSyntheticDecl.mvarId
logError $ "failed to create type class instance for " ++ indentExpr mvarDecl.type
| SyntheticMVarKind.coe header expectedType eType e f? =>
withMVarContext mvarSyntheticDecl.mvarId do
let mvarDecl ← getMVarDecl mvarSyntheticDecl.mvarId
throwTypeMismatchError header expectedType eType e f? (some ("failed to create type class instance for " ++ indentExpr mvarDecl.type))
| _ => unreachable! -- TODO handle other cases.
let s ← get
for mvarSyntheticDecl in s.syntheticMVars do
withRef mvarSyntheticDecl.stx do
match mvarSyntheticDecl.kind with
| SyntheticMVarKind.typeClass =>
withMVarContext mvarSyntheticDecl.mvarId do
let mvarDecl ← getMVarDecl mvarSyntheticDecl.mvarId
logError $ "failed to create type class instance for " ++ indentExpr mvarDecl.type
| SyntheticMVarKind.coe header expectedType eType e f? =>
withMVarContext mvarSyntheticDecl.mvarId do
let mvarDecl ← getMVarDecl mvarSyntheticDecl.mvarId
throwTypeMismatchError header expectedType eType e f? (some ("failed to create type class instance for " ++ indentExpr mvarDecl.type))
| _ => unreachable! -- TODO handle other cases.
private def getSomeSynthethicMVarsRef : TermElabM Syntax := do
let s ← get
match s.syntheticMVars.find? $ fun (mvarDecl : SyntheticMVarDecl) => !mvarDecl.stx.getPos.isNone with
| some mvarDecl => pure mvarDecl.stx
| none => pure Syntax.missing
let s ← get
match s.syntheticMVars.find? $ fun (mvarDecl : SyntheticMVarDecl) => !mvarDecl.stx.getPos.isNone with
| some mvarDecl => pure mvarDecl.stx
| none => pure Syntax.missing
/--
Try to process pending synthetic metavariables. If `mayPostpone == false`,
@ -194,67 +192,67 @@ match s.syntheticMVars.find? $ fun (mvarDecl : SyntheticMVarDecl) => !mvarDecl.s
with `mayPostpone == false`. That is, we force them to produce error messages and/or commit to
a "best option". If, after that, we still haven't made progress, we report "stuck" errors. -/
partial def synthesizeSyntheticMVars (mayPostpone := true) : TermElabM Unit :=
let rec loop (u : Unit) : TermElabM Unit := do
let ref ← getSomeSynthethicMVarsRef
withRef ref $ withIncRecDepth do
let s ← get
unless s.syntheticMVars.isEmpty do
if ← synthesizeSyntheticMVarsStep false false then
loop ()
else if !mayPostpone then
/- Resume pending metavariables with "elaboration postponement" disabled.
We postpone elaboration errors in this step by setting `postponeOnError := true`.
Example:
```
#check let x := ⟨1, 2⟩; Prod.fst x
```
The term `⟨1, 2⟩` can't be elaborated because the expected type is not know.
The `x` at `Prod.fst x` is not elaborated because the type of `x` is not known.
When we execute the following step with "elaboration postponement" disabled,
the elaborator fails at `⟨1, 2⟩` and postpones it, and succeeds at `x` and learns
that its type must be of the form `Prod ?α ?β`.
let rec loop (u : Unit) : TermElabM Unit := do
let ref ← getSomeSynthethicMVarsRef
withRef ref $ withIncRecDepth do
let s ← get
unless s.syntheticMVars.isEmpty do
if ← synthesizeSyntheticMVarsStep false false then
loop ()
else if !mayPostpone then
/- Resume pending metavariables with "elaboration postponement" disabled.
We postpone elaboration errors in this step by setting `postponeOnError := true`.
Example:
```
#check let x := ⟨1, 2⟩; Prod.fst x
```
The term `⟨1, 2⟩` can't be elaborated because the expected type is not know.
The `x` at `Prod.fst x` is not elaborated because the type of `x` is not known.
When we execute the following step with "elaboration postponement" disabled,
the elaborator fails at `⟨1, 2⟩` and postpones it, and succeeds at `x` and learns
that its type must be of the form `Prod ?α ?β`.
Recall that we postponed `x` at `Prod.fst x` because its type it is not known.
We the type of `x` may learn later its type and it may contain implicit and/or auto arguments.
By disabling postponement, we are essentially giving up the opportunity of learning `x`s type
and assume it does not have implict and/or auto arguments. -/
if ← withoutPostponing (synthesizeSyntheticMVarsStep true false) then
loop ()
else if ← synthesizeUsingDefault then
loop ()
else if ← withoutPostponing (synthesizeSyntheticMVarsStep false false) then
loop ()
else if ← synthesizeSyntheticMVarsStep false true then
loop ()
else
reportStuckSyntheticMVars
loop ()
Recall that we postponed `x` at `Prod.fst x` because its type it is not known.
We the type of `x` may learn later its type and it may contain implicit and/or auto arguments.
By disabling postponement, we are essentially giving up the opportunity of learning `x`s type
and assume it does not have implict and/or auto arguments. -/
if ← withoutPostponing (synthesizeSyntheticMVarsStep true false) then
loop ()
else if ← synthesizeUsingDefault then
loop ()
else if ← withoutPostponing (synthesizeSyntheticMVarsStep false false) then
loop ()
else if ← synthesizeSyntheticMVarsStep false true then
loop ()
else
reportStuckSyntheticMVars
loop ()
def synthesizeSyntheticMVarsNoPostponing : TermElabM Unit :=
synthesizeSyntheticMVars (mayPostpone := false)
synthesizeSyntheticMVars (mayPostpone := false)
/--
Execute `k`, and synthesize pending synthetic metavariables created while executing `k` are solved.
If `mayPostpone == false`, then all of them must be synthesized.
Remark: even if `mayPostpone == true`, the method still uses `synthesizeUsingDefault` -/
def withSynthesize {α} (k : TermElabM α) (mayPostpone := false) : TermElabM α := do
let s ← get
let syntheticMVarsSaved := s.syntheticMVars
modify fun s => { s with syntheticMVars := [] }
try
let a ← k
synthesizeSyntheticMVars mayPostpone
if mayPostpone then
if (← synthesizeUsingDefault) then
synthesizeSyntheticMVars mayPostpone
pure a
finally
modify fun s => { s with syntheticMVars := s.syntheticMVars ++ syntheticMVarsSaved }
let s ← get
let syntheticMVarsSaved := s.syntheticMVars
modify fun s => { s with syntheticMVars := [] }
try
let a ← k
synthesizeSyntheticMVars mayPostpone
if mayPostpone then
if (← synthesizeUsingDefault) then
synthesizeSyntheticMVars mayPostpone
pure a
finally
modify fun s => { s with syntheticMVars := s.syntheticMVars ++ syntheticMVarsSaved }
/-- Elaborate `stx`, and make sure all pending synthetic metavariables created while elaborating `stx` are solved. -/
def elabTermAndSynthesize (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr :=
withRef stx do
let v ← withSynthesize $ elabTerm stx expectedType?
instantiateMVars v
withRef stx do
let v ← withSynthesize $ elabTerm stx expectedType?
instantiateMVars v
end Lean.Elab.Term