feat: zetaUnused option (implementation) (#6755)
This PR implements the `zetaUnused` simp and reduction option (added in #6754). True by default, and implied by `zeta`, this can be turned off to make simp even more careful about preserving the expression structure, including unused let and have expressions. Breaking change: The `split` tactic no longer removes unused let and have expressions as a side-effect, in rare cases this may break proofs. `dsimp only` can be used to remove unused have and let expressions.
This commit is contained in:
parent
6278839534
commit
ba95dbc36b
3 changed files with 94 additions and 6 deletions
|
|
@ -1634,11 +1634,18 @@ private partial def consumeLet : Expr → Expr
|
|||
else
|
||||
e
|
||||
|
||||
private partial def consumeLetIfZeta (e : Expr) : MetaM Expr := do
|
||||
let cfg ← getConfig
|
||||
if cfg.zeta || cfg.zetaUnused then
|
||||
return consumeLet e
|
||||
else
|
||||
return e
|
||||
|
||||
mutual
|
||||
|
||||
private partial def isDefEqQuick (t s : Expr) : MetaM LBool :=
|
||||
let t := consumeLet t
|
||||
let s := consumeLet s
|
||||
private partial def isDefEqQuick (t s : Expr) : MetaM LBool := do
|
||||
let t ← consumeLetIfZeta t
|
||||
let s ← consumeLetIfZeta s
|
||||
match t, s with
|
||||
| .lit l₁, .lit l₂ => return (l₁ == l₂).toLBool
|
||||
| .sort u, .sort v => toLBoolM <| isLevelDefEqAux u v
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ private partial def dsimpImpl (e : Expr) : SimpM Expr := do
|
|||
let pre := m.dpre >> doNotVisitOfNat >> doNotVisitOfScientific >> doNotVisitCharLit
|
||||
let post := m.dpost >> dsimpReduce
|
||||
withInDSimp do
|
||||
transform (usedLetOnly := cfg.zeta) e (pre := pre) (post := post)
|
||||
transform (usedLetOnly := cfg.zeta || cfg.zetaUnused) e (pre := pre) (post := post)
|
||||
|
||||
def visitFn (e : Expr) : SimpM Result := do
|
||||
let f := e.getAppFn
|
||||
|
|
@ -623,6 +623,7 @@ It attempts to minimize the quadratic overhead imposed by
|
|||
the locally nameless discipline.
|
||||
-/
|
||||
partial def simpNonDepLetFun (e : Expr) : SimpM Result := do
|
||||
let cfg ← getConfig
|
||||
let rec go (xs : Array Expr) (e : Expr) : SimpM SimpLetFunResult := do
|
||||
/-
|
||||
Helper function applied when `e` is not a `let_fun` or
|
||||
|
|
@ -648,9 +649,9 @@ partial def simpNonDepLetFun (e : Expr) : SimpM Result := do
|
|||
-/
|
||||
if alpha.hasLooseBVars || !betaFun.isLambda || !body.isLambda || betaFun.bindingBody!.hasLooseBVars then
|
||||
stop
|
||||
else if !body.bindingBody!.hasLooseBVar 0 then
|
||||
else if (cfg.zeta || cfg.zetaUnused) && !body.bindingBody!.hasLooseBVar 0 then
|
||||
/-
|
||||
Redundant `let_fun`. The simplifier will remove it.
|
||||
Redundant `let_fun`. The simplifier will remove it when `zetaUnused := true`.
|
||||
Remark: the `hasLooseBVar` check here may introduce a quadratic overhead in the worst case.
|
||||
If observe that in practice, we may use a separate step for removing unused variables.
|
||||
|
||||
|
|
|
|||
80
tests/lean/run/zetaUnused.lean
Normal file
80
tests/lean/run/zetaUnused.lean
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
/--
|
||||
info: b : Bool
|
||||
⊢ if b = true then
|
||||
let_fun unused := ();
|
||||
True
|
||||
else False
|
||||
---
|
||||
warning: declaration uses 'sorry'
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
trace_state; sorry
|
||||
|
||||
/--
|
||||
info: b : Bool
|
||||
⊢ b = true
|
||||
---
|
||||
warning: declaration uses 'sorry'
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
simp; trace_state; sorry
|
||||
|
||||
/--
|
||||
info: b : Bool
|
||||
⊢ b = true ∧
|
||||
let_fun unused := ();
|
||||
True
|
||||
---
|
||||
warning: declaration uses 'sorry'
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
simp (config := Lean.Meta.Simp.neutralConfig); trace_state; sorry
|
||||
|
||||
/-- error: simp made no progress -/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
simp (config := Lean.Meta.Simp.neutralConfig) only; trace_state; sorry
|
||||
|
||||
/--
|
||||
info: b : Bool
|
||||
⊢ if b = true then True else False
|
||||
---
|
||||
warning: declaration uses 'sorry'
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
simp (config := Lean.Meta.Simp.neutralConfig) +zeta only; trace_state; sorry
|
||||
|
||||
|
||||
/--
|
||||
info: b : Bool
|
||||
⊢ if b = true then True else False
|
||||
---
|
||||
warning: declaration uses 'sorry'
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
simp (config := Lean.Meta.Simp.neutralConfig) +zetaUnused only; trace_state; sorry
|
||||
|
||||
|
||||
-- Before the introduction of zetaUnused, split would do collateral damage to unused letFuns.
|
||||
-- Now they are preserved:
|
||||
|
||||
/--
|
||||
info: case isTrue
|
||||
b : Bool
|
||||
h✝ : b = true
|
||||
⊢ let_fun unused := ();
|
||||
True
|
||||
---
|
||||
warning: declaration uses 'sorry'
|
||||
-/
|
||||
#guard_msgs in
|
||||
example (b : Bool) : if b then have unused := (); True else False := by
|
||||
split
|
||||
· trace_state; sorry
|
||||
· sorry
|
||||
Loading…
Add table
Reference in a new issue