Skip to content

Commit

Permalink
fix: #eval command was leaking auxiliary declarations into the envi…
Browse files Browse the repository at this point in the history
…ronment (#3323)
  • Loading branch information
leodemoura authored Feb 13, 2024
1 parent 56d703d commit 644d426
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 25 deletions.
55 changes: 30 additions & 25 deletions src/Lean/Elab/BuiltinCommand.lean
Original file line number Diff line number Diff line change
Expand Up @@ -656,35 +656,40 @@ unsafe def elabEvalUnsafe : CommandElab
return e
-- Evaluate using term using `MetaEval` class.
let elabMetaEval : CommandElabM Unit := do
-- act? is `some act` if elaborated `term` has type `CommandElabM α`
let act? ← runTermElabM fun _ => Term.withDeclName declName do
let e ← elabEvalTerm
let eType ← instantiateMVars (← inferType e)
if eType.isAppOfArity ``CommandElabM 1 then
let mut stx ← Term.exprToSyntax e
unless (← isDefEq eType.appArg! (mkConst ``Unit)) do
stx ← `($stx >>= fun v => IO.println (repr v))
let act ← Lean.Elab.Term.evalTerm (CommandElabM Unit) (mkApp (mkConst ``CommandElabM) (mkConst ``Unit)) stx
pure <| some act
else
let e ← mkRunMetaEval e
let env ← getEnv
let opts ← getOptions
let act ← try addAndCompile e; evalConst (Environment → Options → IO (String × Except IO.Error Environment)) declName finally setEnv env
let (out, res) ← act env opts -- we execute `act` using the environment
logInfoAt tk out
match res with
| Except.error e => throwError e.toString
| Except.ok env => do setEnv env; pure none
let some act := act? | return ()
act
-- Generate an action without executing it. We use `withoutModifyingEnv` to ensure
-- we don't polute the environment with auxliary declarations.
-- We have special support for `CommandElabM` to ensure `#eval` can be used to execute commands
-- that modify `CommandElabM` state not just the `Environment`.
let act : Sum (CommandElabM Unit) (Environment → Options → IO (String × Except IO.Error Environment)) ←
runTermElabM fun _ => Term.withDeclName declName do withoutModifyingEnv do
let e ← elabEvalTerm
let eType ← instantiateMVars (← inferType e)
if eType.isAppOfArity ``CommandElabM 1 then
let mut stx ← Term.exprToSyntax e
unless (← isDefEq eType.appArg! (mkConst ``Unit)) do
stx ← `($stx >>= fun v => IO.println (repr v))
let act ← Lean.Elab.Term.evalTerm (CommandElabM Unit) (mkApp (mkConst ``CommandElabM) (mkConst ``Unit)) stx
pure <| Sum.inl act
else
let e ← mkRunMetaEval e
addAndCompile e
let act ← evalConst (Environment → Options → IO (String × Except IO.Error Environment)) declName
pure <| Sum.inr act
match act with
| .inl act => act
| .inr act =>
let (out, res) ← act (← getEnv) (← getOptions)
logInfoAt tk out
match res with
| Except.error e => throwError e.toString
| Except.ok env => setEnv env; pure ()
-- Evaluate using term using `Eval` class.
let elabEval : CommandElabM Unit := runTermElabM fun _ => Term.withDeclName declName do
let elabEval : CommandElabM Unit := runTermElabM fun _ => Term.withDeclName declName do withoutModifyingEnv do
-- fall back to non-meta eval if MetaEval hasn't been defined yet
-- modify e to `runEval e`
let e ← mkRunEval (← elabEvalTerm)
let env ← getEnv
let act ← try addAndCompile e; evalConst (IO (String × Except IO.Error Unit)) declName finally setEnv env
addAndCompile e
let act ← evalConst (IO (String × Except IO.Error Unit)) declName
let (out, res) ← liftM (m := IO) act
logInfoAt tk out
match res with
Expand Down
23 changes: 23 additions & 0 deletions tests/lean/evalLeak.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Lean

#eval match true with | true => false | false => true

open Lean Elab Command Meta
#eval show CommandElabM Unit from do
match ([] : List Nat) with
| [] =>
liftCoreM <| addDecl <| Declaration.defnDecl {
name := `foo
type := Lean.mkConst ``Bool
levelParams := []
value := Lean.mkConst ``true
hints := .opaque
safety := .safe
}
| _ => return ()

#check foo
-- The auxiliary declarations created to elaborate the commands above
-- should not leak into the environment
#check _eval.match_1 -- Should fail
#check _eval.match_2 -- Should fail
4 changes: 4 additions & 0 deletions tests/lean/evalLeak.lean.expected.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
false
foo : Bool
evalLeak.lean:22:7-22:20: error: unknown identifier '_eval.match_1'
evalLeak.lean:23:7-23:20: error: unknown identifier '_eval.match_2'

0 comments on commit 644d426

Please sign in to comment.