/- Copyright (c) 2019 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura -/ import Lean.Elab.Alias import Lean.Elab.Log import Lean.Elab.ResolveName import Lean.Elab.Term import Lean.Elab.Binders import Lean.Elab.SyntheticMVars namespace Lean namespace Elab namespace Command structure Scope := (kind : String) (header : String) (opts : Options := {}) (currNamespace : Name := Name.anonymous) (openDecls : List OpenDecl := []) (levelNames : List Name := []) (varDecls : Array Syntax := #[]) instance Scope.inhabited : Inhabited Scope := ⟨{ kind := "", header := "" }⟩ structure State := (env : Environment) (messages : MessageLog := {}) (scopes : List Scope := [{ kind := "root", header := "" }]) (nextMacroScope : Nat := firstFrontendMacroScope + 1) (maxRecDepth : Nat) (ngen : NameGenerator := {}) instance State.inhabited : Inhabited State := ⟨{ env := arbitrary _, maxRecDepth := 0 }⟩ def mkState (env : Environment) (messages : MessageLog := {}) (opts : Options := {}) : State := { env := env, messages := messages, scopes := [{ kind := "root", header := "", opts := opts }], maxRecDepth := getMaxRecDepth opts } structure Context := (fileName : String) (fileMap : FileMap) (stateRef : IO.Ref State) (currRecDepth : Nat := 0) (cmdPos : String.Pos := 0) (macroStack : MacroStack := []) (currMacroScope : MacroScope := firstFrontendMacroScope) instance Exception.inhabited : Inhabited Exception := ⟨Exception.error $ arbitrary _⟩ abbrev CommandElabCoreM (ε) := ReaderT Context (EIO ε) abbrev CommandElabM := CommandElabCoreM Exception abbrev CommandElab := Syntax → CommandElabM Unit def mkMessageAux (ctx : Context) (ref : Syntax) (msgData : MessageData) (severity : MessageSeverity) : Message := mkMessageCore ctx.fileName ctx.fileMap msgData severity (ref.getPos.getD ctx.cmdPos) private def ioErrorToMessage (ctx : Context) (ref : Syntax) (err : IO.Error) : Message := let ref := getBetterRef ref ctx.macroStack; mkMessageAux ctx ref (addMacroStack (toString err) ctx.macroStack) MessageSeverity.error @[inline] def liftIOCore {α} (ctx : Context) (ref : Syntax) (x : IO α) : EIO Exception α := EIO.adaptExcept (fun ex => Exception.error $ ioErrorToMessage ctx ref ex) x @[inline] def liftIO {α} (ref : Syntax) (x : IO α) : CommandElabM α := fun ctx => liftIOCore ctx ref x private def getState : CommandElabM State := fun ctx => liftIOCore ctx Syntax.missing $ ctx.stateRef.get private def setState (s : State) : CommandElabM Unit := fun ctx => liftIOCore ctx Syntax.missing $ ctx.stateRef.set s @[inline] private def modifyGetState {α} (f : State → α × State) : CommandElabM α := do s ← getState; let (a, s) := f s; setState s; pure a instance CommandElabCoreM.monadState : MonadState State CommandElabM := { get := getState, set := setState, modifyGet := @modifyGetState } def getEnv : CommandElabM Environment := do s ← get; pure s.env def getScope : CommandElabM Scope := do s ← get; pure s.scopes.head! def getOptions : CommandElabM Options := do scope ← getScope; pure scope.opts def addContext (msg : MessageData) : CommandElabM MessageData := do env ← getEnv; opts ← getOptions; pure (MessageData.withContext { env := env, mctx := {}, lctx := {}, opts := opts } msg) instance CommandElabM.monadLog : MonadLog CommandElabM := { getCmdPos := do ctx ← read; pure ctx.cmdPos, getFileMap := do ctx ← read; pure ctx.fileMap, getFileName := do ctx ← read; pure ctx.fileName, addContext := addContext, logMessage := fun msg => modify $ fun s => { s with messages := s.messages.add msg } } /-- Throws an error with the given `msgData` and extracting position information from `ref`. If `ref` does not contain position information, then use `cmdPos` -/ def throwError {α} (ref : Syntax) (msgData : MessageData) : CommandElabM α := do ctx ← read; let ref := getBetterRef ref ctx.macroStack; let msgData := addMacroStack msgData ctx.macroStack; msg ← mkMessage msgData MessageSeverity.error ref; throw (Exception.error msg) def logTrace (cls : Name) (ref : Syntax) (msg : MessageData) : CommandElabM Unit := do msg ← addContext $ MessageData.tagged cls msg; logInfo ref msg @[inline] def trace (cls : Name) (ref : Syntax) (msg : Unit → MessageData) : CommandElabM Unit := do opts ← getOptions; when (checkTraceOption opts cls) $ logTrace cls ref (msg ()) def throwUnsupportedSyntax {α} : CommandElabM α := throw Elab.Exception.unsupportedSyntax protected def getCurrMacroScope : CommandElabM Nat := do ctx ← read; pure ctx.currMacroScope protected def getMainModule : CommandElabM Name := do env ← getEnv; pure env.mainModule @[inline] protected def withFreshMacroScope {α} (x : CommandElabM α) : CommandElabM α := do fresh ← modifyGet (fun st => (st.nextMacroScope, { st with nextMacroScope := st.nextMacroScope + 1 })); adaptReader (fun (ctx : Context) => { ctx with currMacroScope := fresh }) x instance CommandElabM.MonadQuotation : MonadQuotation CommandElabM := { getCurrMacroScope := Command.getCurrMacroScope, getMainModule := Command.getMainModule, withFreshMacroScope := @Command.withFreshMacroScope } unsafe def mkCommandElabAttribute : IO (KeyedDeclsAttribute CommandElab) := mkElabAttribute CommandElab `Lean.Elab.Command.commandElabAttribute `builtinCommandElab `commandElab `Lean.Parser.Command `Lean.Elab.Command.CommandElab "command" @[init mkCommandElabAttribute] constant commandElabAttribute : KeyedDeclsAttribute CommandElab := arbitrary _ @[inline] def withIncRecDepth {α} (ref : Syntax) (x : CommandElabM α) : CommandElabM α := do ctx ← read; s ← get; when (ctx.currRecDepth == s.maxRecDepth) $ throwError ref maxRecDepthErrorMessage; adaptReader (fun (ctx : Context) => { ctx with currRecDepth := ctx.currRecDepth + 1 }) x private def elabCommandUsing (s : State) (stx : Syntax) : List CommandElab → CommandElabM Unit | [] => do let refFmt := stx.prettyPrint; throwError stx ("unexpected syntax" ++ MessageData.nest 2 (Format.line ++ refFmt)) | (elabFn::elabFns) => catch (elabFn stx) (fun ex => match ex with | Exception.error _ => throw ex | Exception.unsupportedSyntax => do set s; elabCommandUsing elabFns) /- Elaborate `x` with `stx` on the macro stack -/ @[inline] def withMacroExpansion {α} (beforeStx afterStx : Syntax) (x : CommandElabM α) : CommandElabM α := adaptReader (fun (ctx : Context) => { ctx with macroStack := { before := beforeStx, after := afterStx } :: ctx.macroStack }) x instance : MonadMacroAdapter CommandElabM := { getEnv := getEnv, getCurrMacroScope := getCurrMacroScope, getNextMacroScope := do s ← get; pure s.nextMacroScope, setNextMacroScope := fun next => modify $ fun s => { s with nextMacroScope := next }, getCurrRecDepth := do ctx ← read; pure ctx.currRecDepth, getMaxRecDepth := do s ← get; pure s.maxRecDepth, throwError := @throwError, throwUnsupportedSyntax := @throwUnsupportedSyntax} partial def elabCommand : Syntax → CommandElabM Unit | stx => withIncRecDepth stx $ withFreshMacroScope $ match stx with | Syntax.node k args => if k == nullKind then -- list of commands => elaborate in order -- The parser will only ever return a single command at a time, but syntax quotations can return multiple ones args.forM elabCommand else do trace `Elab.step stx $ fun _ => stx; s ← get; stxNew? ← catch (do newStx ← adaptMacro (getMacros s.env) stx; pure (some newStx)) (fun ex => match ex with | Exception.unsupportedSyntax => pure none | _ => throw ex); match stxNew? with | some stxNew => withMacroExpansion stx stxNew $ elabCommand stxNew | _ => do let table := (commandElabAttribute.ext.getState s.env).table; let k := stx.getKind; match table.find? k with | some elabFns => elabCommandUsing s stx elabFns | none => throwError stx ("elaboration function for '" ++ toString k ++ "' has not been implemented") | _ => throwError stx "unexpected command" /-- Adapt a syntax transformation to a regular, command-producing elaborator. -/ def adaptExpander (exp : Syntax → CommandElabM Syntax) : CommandElab := fun stx => do stx' ← exp stx; withMacroExpansion stx stx' $ elabCommand stx' private def mkTermContext (ctx : Context) (s : State) (declName? : Option Name) : Term.Context := let scope := s.scopes.head!; { config := { opts := scope.opts, foApprox := true, ctxApprox := true, quasiPatternApprox := true, isDefEqStuckEx := true }, fileName := ctx.fileName, fileMap := ctx.fileMap, currRecDepth := ctx.currRecDepth, maxRecDepth := s.maxRecDepth, cmdPos := ctx.cmdPos, declName? := declName?, macroStack := ctx.macroStack, currMacroScope := ctx.currMacroScope, currNamespace := scope.currNamespace, levelNames := scope.levelNames, openDecls := scope.openDecls } private def mkTermState (s : State) : Term.State := { env := s.env, messages := s.messages, nextMacroScope := s.nextMacroScope, ngen := s.ngen } private def getVarDecls (s : State) : Array Syntax := s.scopes.head!.varDecls instance CommandElabM.inhabited {α} : Inhabited (CommandElabM α) := ⟨throw $ arbitrary _⟩ @[inline] def liftTermElabM {α} (declName? : Option Name) (x : TermElabM α) : CommandElabM α := do ctx ← read; s ← get; match (x $ mkTermContext ctx s declName?).run (mkTermState s) with | EStateM.Result.ok a newS => do modify $ fun s => { s with env := newS.env, messages := newS.messages, ngen := newS.ngen }; pure a | EStateM.Result.error (Term.Exception.ex ex) newS => do modify $ fun s => { s with env := newS.env, messages := newS.messages, ngen := newS.ngen }; throw ex | EStateM.Result.error Term.Exception.postpone newS => unreachable! @[inline] def runTermElabM {α} (declName? : Option Name) (elabFn : Array Expr → TermElabM α) : CommandElabM α := do s ← get; liftTermElabM declName? (Term.elabBinders (getVarDecls s) elabFn) @[inline] def withLogging (x : CommandElabM Unit) : CommandElabM Unit := catch x (fun ex => match ex with | Exception.error ex => do logMessage ex; pure () | Exception.unsupportedSyntax => unreachable!) @[inline] def catchExceptions (x : CommandElabM Unit) : CommandElabCoreM Empty Unit := fun ctx => EIO.catchExceptions (withLogging x ctx) (fun _ => pure ()) def dbgTrace {α} [HasToString α] (a : α) : CommandElabM Unit := _root_.dbgTrace (toString a) $ fun _ => pure () def setEnv (newEnv : Environment) : CommandElabM Unit := modify $ fun s => { s with env := newEnv } @[inline] def modifyEnv (f : Environment → Environment) : CommandElabM Unit := modify $ fun s => { s with env := f s.env } def getCurrNamespace : CommandElabM Name := do scope ← getScope; pure scope.currNamespace private def addScope (kind : String) (header : String) (newNamespace : Name) : CommandElabM Unit := modify $ fun s => { s with env := s.env.registerNamespace newNamespace, scopes := { s.scopes.head! with kind := kind, header := header, currNamespace := newNamespace } :: s.scopes } private def addScopes (ref : Syntax) (kind : String) (updateNamespace : Bool) : Name → CommandElabM Unit | Name.anonymous => pure () | Name.str p header _ => do addScopes p; currNamespace ← getCurrNamespace; addScope kind header (if updateNamespace then currNamespace ++ header else currNamespace) | _ => throwError ref "invalid scope" private def addNamespace (ref : Syntax) (header : Name) : CommandElabM Unit := addScopes ref "namespace" true header @[builtinCommandElab «namespace»] def elabNamespace : CommandElab := fun stx => match_syntax stx with | `(namespace $n) => addNamespace stx n.getId | _ => throw Exception.unsupportedSyntax @[builtinCommandElab «section»] def elabSection : CommandElab := fun stx => match_syntax stx with | `(section $header:ident) => addScopes stx "section" false header.getId | `(section) => do currNamespace ← getCurrNamespace; addScope "section" "" currNamespace | _ => throw Exception.unsupportedSyntax def getScopes : CommandElabM (List Scope) := do s ← get; pure s.scopes private def checkAnonymousScope : List Scope → Bool | { header := "", .. } :: _ => true | _ => false private def checkEndHeader : Name → List Scope → Bool | Name.anonymous, _ => true | Name.str p s _, { header := h, .. } :: scopes => h == s && checkEndHeader p scopes | _, _ => false @[builtinCommandElab «end»] def elabEnd : CommandElab := fun stx => do let header? := (stx.getArg 1).getOptionalIdent?; let endSize := match header? with | none => 1 | some n => n.getNumParts; scopes ← getScopes; if endSize < scopes.length then modify $ fun s => { s with scopes := s.scopes.drop endSize } else do { -- we keep "root" scope modify $ fun s => { s with scopes := s.scopes.drop (s.scopes.length - 1) }; throwError stx "invalid 'end', insufficient scopes" }; match header? with | none => unless (checkAnonymousScope scopes) $ throwError stx "invalid 'end', name is missing" | some header => unless (checkEndHeader header scopes) $ throwError stx "invalid 'end', name mismatch" @[inline] def withNamespace {α} (ref : Syntax) (ns : Name) (elabFn : CommandElabM α) : CommandElabM α := do addNamespace ref ns; a ← elabFn; modify $ fun s => { s with scopes := s.scopes.drop ns.getNumParts }; pure a @[specialize] def modifyScope (f : Scope → Scope) : CommandElabM Unit := modify $ fun s => { s with scopes := match s.scopes with | h::t => f h :: t | [] => unreachable! } def getLevelNames : CommandElabM (List Name) := do scope ← getScope; pure scope.levelNames def throwAlreadyDeclaredUniverseLevel {α} (ref : Syntax) (u : Name) : CommandElabM α := throwError ref ("a universe level named '" ++ toString u ++ "' has already been declared") def addUnivLevel (idStx : Syntax) : CommandElabM Unit := do let id := idStx.getId; levelNames ← getLevelNames; if levelNames.elem id then throwAlreadyDeclaredUniverseLevel idStx id else modifyScope $ fun scope => { scope with levelNames := id :: scope.levelNames } partial def elabChoiceAux (cmds : Array Syntax) : Nat → CommandElabM Unit | i => if h : i < cmds.size then let cmd := cmds.get ⟨i, h⟩; catch (elabCommand cmd) (fun ex => match ex with | Exception.unsupportedSyntax => elabChoiceAux (i+1) | _ => throw ex) else throwUnsupportedSyntax @[builtinCommandElab choice] def elbChoice : CommandElab := fun stx => elabChoiceAux stx.getArgs 0 @[builtinCommandElab «universe»] def elabUniverse : CommandElab := fun n => do addUnivLevel (n.getArg 1) @[builtinCommandElab «universes»] def elabUniverses : CommandElab := fun n => do let idsStx := n.getArg 1; idsStx.forArgsM addUnivLevel @[builtinCommandElab «init_quot»] def elabInitQuot : CommandElab := fun stx => do env ← getEnv; match env.addDecl Declaration.quotDecl with | Except.ok env => setEnv env | Except.error ex => do opts ← getOptions; throwError stx (ex.toMessageData opts) def getOpenDecls : CommandElabM (List OpenDecl) := do scope ← getScope; pure scope.openDecls def logUnknownDecl (stx : Syntax) (declName : Name) : CommandElabM Unit := logError stx ("unknown declaration '" ++ toString declName ++ "'") def resolveNamespace (id : Name) : CommandElabM Name := do env ← getEnv; currNamespace ← getCurrNamespace; openDecls ← getOpenDecls; match Elab.resolveNamespace env currNamespace openDecls id with | some ns => pure ns | none => throw Exception.unsupportedSyntax @[builtinCommandElab «export»] def elabExport : CommandElab := fun stx => do -- `stx` is of the form (Command.export "export" "(" (null *) ")") let id := stx.getIdAt 1; ns ← resolveNamespace id; currNamespace ← getCurrNamespace; when (ns == currNamespace) $ throwError stx "invalid 'export', self export"; env ← getEnv; let ids := (stx.getArg 3).getArgs; aliases ← ids.foldlM (fun (aliases : List (Name × Name)) (idStx : Syntax) => do { let id := idStx.getId; let declName := ns ++ id; if env.contains declName then pure $ (currNamespace ++ id, declName) :: aliases else do logUnknownDecl idStx declName; pure aliases }) []; modify $ fun s => { s with env := aliases.foldl (fun env p => addAlias env p.1 p.2) s.env } def addOpenDecl (d : OpenDecl) : CommandElabM Unit := modifyScope $ fun scope => { scope with openDecls := d :: scope.openDecls } def elabOpenSimple (n : SyntaxNode) : CommandElabM Unit := -- `open` id+ let nss := n.getArg 0; nss.forArgsM $ fun ns => do ns ← resolveNamespace ns.getId; addOpenDecl (OpenDecl.simple ns []) -- `open` id `(` id+ `)` def elabOpenOnly (n : SyntaxNode) : CommandElabM Unit := do let ns := n.getIdAt 0; ns ← resolveNamespace ns; let ids := n.getArg 2; ids.forArgsM $ fun idStx => do let id := idStx.getId; let declName := ns ++ id; env ← getEnv; if env.contains declName then addOpenDecl (OpenDecl.explicit id declName) else logUnknownDecl idStx declName -- `open` id `hiding` id+ def elabOpenHiding (n : SyntaxNode) : CommandElabM Unit := do let ns := n.getIdAt 0; ns ← resolveNamespace ns; let idsStx := n.getArg 2; env ← getEnv; ids : List Name ← idsStx.foldArgsM (fun idStx ids => do let id := idStx.getId; let declName := ns ++ id; if env.contains declName then pure (id::ids) else do logUnknownDecl idStx declName; pure ids) []; addOpenDecl (OpenDecl.simple ns ids) -- `open` id `renaming` sepBy (id `->` id) `,` def elabOpenRenaming (n : SyntaxNode) : CommandElabM Unit := do let ns := n.getIdAt 0; ns ← resolveNamespace ns; let rs := (n.getArg 2); rs.forSepArgsM $ fun stx => do let fromId := stx.getIdAt 0; let toId := stx.getIdAt 2; let declName := ns ++ fromId; env ← getEnv; if env.contains declName then addOpenDecl (OpenDecl.explicit toId declName) else logUnknownDecl stx declName @[builtinCommandElab «open»] def elabOpen : CommandElab := fun n => do let body := (n.getArg 1).asNode; let k := body.getKind; if k == `Lean.Parser.Command.openSimple then elabOpenSimple body else if k == `Lean.Parser.Command.openOnly then elabOpenOnly body else if k == `Lean.Parser.Command.openHiding then elabOpenHiding body else elabOpenRenaming body @[builtinCommandElab «variable»] def elabVariable : CommandElab := fun n => do -- `variable` bracketedBinder let binder := n.getArg 1; -- Try to elaborate `binder` for sanity checking runTermElabM none $ fun _ => Term.elabBinder binder $ fun _ => pure (); modifyScope $ fun scope => { scope with varDecls := scope.varDecls.push binder } @[builtinCommandElab «variables»] def elabVariables : CommandElab := fun n => do -- `variables` bracketedBinder+ let binders := (n.getArg 1).getArgs; -- Try to elaborate `binders` for sanity checking runTermElabM none $ fun _ => Term.elabBinders binders $ fun _ => pure (); modifyScope $ fun scope => { scope with varDecls := scope.varDecls ++ binders } @[inline] def withoutModifyingEnv {α} (x : CommandElabM α) : CommandElabM α := do env ← getEnv; finally x (setEnv env) @[builtinCommandElab «check»] def elabCheck : CommandElab := fun stx => do let term := stx.getArg 1; withoutModifyingEnv $ runTermElabM (some `_check) $ fun _ => do e ← Term.elabTerm term none; Term.synthesizeSyntheticMVars false; type ← Term.inferType stx e; logInfo stx (e ++ " : " ++ type); pure () def hasNoErrorMessages : CommandElabM Bool := do s ← get; pure $ !s.messages.hasErrors def failIfSucceeds (ref : Syntax) (x : CommandElabM Unit) : CommandElabM Unit := do let resetMessages : CommandElabM MessageLog := do { s ← get; let messages := s.messages; modify $ fun s => { s with messages := {} }; pure messages }; let restoreMessages (prevMessages : MessageLog) : CommandElabM Unit := do { modify $ fun s => { s with messages := prevMessages ++ s.messages.errorsToWarnings } }; prevMessages ← resetMessages; succeeded ← finally (catch (do x; hasNoErrorMessages) (fun ex => match ex with | Exception.error msg => do modify (fun s => { s with messages := s.messages.add msg }); pure false | Exception.unsupportedSyntax => do logError ref "unsupported syntax"; pure false)) (restoreMessages prevMessages); when succeeded $ throwError ref "unexpected success" @[builtinCommandElab «check_failure»] def elabCheckFailure : CommandElab := fun stx => failIfSucceeds stx $ elabCheck stx def addDecl (ref : Syntax) (decl : Declaration) : CommandElabM Unit := liftTermElabM none $ Term.addDecl ref decl def compileDecl (ref : Syntax) (decl : Declaration) : CommandElabM Unit := liftTermElabM none $ Term.compileDecl ref decl def addInstance (ref : Syntax) (declName : Name) : CommandElabM Unit := do env ← getEnv; env ← liftIO ref $ Meta.addGlobalInstance env declName; setEnv env unsafe def elabEvalUnsafe : CommandElab := fun stx => withoutModifyingEnv do let ref := stx; let term := stx.getArg 1; let n := `_eval; ctx ← read; env ← getEnv; let addAndCompile (value : Expr) : TermElabM Unit := do { type ← Term.inferType ref value; let decl := Declaration.defnDecl { name := n, lparams := [], type := type, value := value, hints := ReducibilityHints.opaque, isUnsafe := true }; Term.addDecl ref decl; Term.compileDecl ref decl }; let elabMetaEval : CommandElabM Unit := do { act : IO Environment ← runTermElabM (some n) fun _ => do { e ← Term.elabTerm term none; Term.synthesizeSyntheticMVars false; e ← Term.withLocalDecl ref `env BinderInfo.default (mkConst `Lean.Environment) fun env => Term.withLocalDecl ref `opts BinderInfo.default (mkConst `Lean.Options) fun opts => do { e ← Term.mkAppM ref `Lean.MetaHasEval.eval #[env, opts, e, toExpr false]; Term.mkLambda ref #[env, opts] e }; addAndCompile e; env ← Term.getEnv; opts ← Term.getOptions; match env.evalConst (Environment → Options → IO Environment) n with | Except.error e => Term.throwError ref e | Except.ok act => pure $ act env opts }; (out, res) ← liftIO ref $ IO.Prim.withIsolatedStreams act; logInfo ref out; match res with | Except.error e => throw $ Exception.error $ ioErrorToMessage ctx ref e | Except.ok env => do setEnv env; pure () }; let elabEval : CommandElabM Unit := do { -- fall back to non-meta eval if MetaHasEval hasn't been defined yet -- modify e to `HasEval.eval (hideUnit := false) e` act : IO Unit ← runTermElabM (some n) fun _ => do { e ← Term.elabTerm term none; Term.synthesizeSyntheticMVars false; e ← Term.mkAppM ref `Lean.HasEval.eval #[e, toExpr false]; addAndCompile e; env ← Term.getEnv; match env.evalConst (IO Unit) n with | Except.error e => Term.throwError ref e | Except.ok act => pure act }; (out, res) ← liftIO ref $ IO.Prim.withIsolatedStreams act; logInfo ref out; match res with | Except.error e => throw $ Exception.error $ ioErrorToMessage ctx ref e | Except.ok _ => pure () }; if env.contains `Lean.MetaHasEval then do elabMetaEval else elabEval @[builtinCommandElab «eval», implementedBy elabEvalUnsafe] constant elabEval : CommandElab := arbitrary _ @[builtinCommandElab «synth»] def elabSynth : CommandElab := fun stx => do let ref := stx; let term := stx.getArg 1; withoutModifyingEnv $ runTermElabM `_synth_cmd $ fun _ => do inst ← Term.elabTerm term none; Term.synthesizeSyntheticMVars false; inst ← Term.instantiateMVars ref inst; val ← Term.liftMetaM ref $ Meta.synthInstance inst; logInfo stx val; pure () def setOption (ref : Syntax) (optionName : Name) (val : DataValue) : CommandElabM Unit := do decl ← liftIO ref $ getOptionDecl optionName; unless (decl.defValue.sameCtor val) $ throwError ref "type mismatch at set_option"; modifyScope $ fun scope => { scope with opts := scope.opts.insert optionName val }; match optionName, val with | `maxRecDepth, DataValue.ofNat max => modify $ fun s => { s with maxRecDepth := max } | _, _ => pure () @[builtinCommandElab «set_option»] def elabSetOption : CommandElab := fun stx => do let ref := stx; let optionName := stx.getIdAt 1; let val := stx.getArg 2; match val.isStrLit? with | some str => setOption ref optionName (DataValue.ofString str) | none => match val.isNatLit? with | some num => setOption ref optionName (DataValue.ofNat num) | none => match val with | Syntax.atom _ "true" => setOption ref optionName (DataValue.ofBool true) | Syntax.atom _ "false" => setOption ref optionName (DataValue.ofBool false) | _ => logError val ("unexpected set_option value " ++ toString val) /- `declId` is of the form ``` parser! ident >> optional (".{" >> sepBy1 ident ", " >> "}") ``` but we also accept a single identifier to users to make macro writing more convenient . -/ def expandDeclId (declId : Syntax) : Name × Syntax := if declId.isIdent then (declId.getId, mkNullNode) else let id := declId.getIdAt 0; let optUnivDeclStx := declId.getArg 1; (id, optUnivDeclStx) def withDeclId {α} (declId : Syntax) (f : Name → CommandElabM α) : CommandElabM α := do -- ident >> optional (".{" >> sepBy1 ident ", " >> "}") let (id, optUnivDeclStx) := expandDeclId declId; savedLevelNames ← getLevelNames; levelNames ← if optUnivDeclStx.isNone then pure savedLevelNames else do { let extraLevels := (optUnivDeclStx.getArg 1).getArgs.getEvenElems; extraLevels.foldlM (fun levelNames idStx => let id := idStx.getId; if levelNames.elem id then throwAlreadyDeclaredUniverseLevel idStx id else pure (id :: levelNames)) savedLevelNames }; let ref := declId; -- extract (optional) namespace part of id, after decoding macro scopes that would interfere with the check let scpView := extractMacroScopes id; match scpView.name with | Name.str pre s _ => /- Add back macro scopes. We assume a declaration like `def a.b[1,2] ...` with macro scopes `[1,2]` is always meant to mean `namespace a def b[1,2] ...`. -/ let id := { scpView with name := mkNameSimple s }.review; withNamespace ref pre $ do modifyScope $ fun scope => { scope with levelNames := levelNames }; finally (f id) (modifyScope $ fun scope => { scope with levelNames := savedLevelNames }) | _ => throwError ref "invalid declaration name" /-- Sort the given list of `usedParams` using the following order: - If it is an explicit level `allUserParams`, then use user given order. - Otherwise, use lexicographical. Remark: `scopeParams` are the universe params introduced using the `universe` command. `allUserParams` contains the universe params introduced using the `universe` command *and* the `.{...}` notation. Remark: this function return an exception if there is an `u` not in `usedParams`, that is in `allUserParams` but not in `scopeParams`. 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 ("unused universe parameter '" ++ toString 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 Command end Elab end Lean