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:
Joachim Breitner 2025-01-26 12:14:12 +01:00 committed by GitHub
parent 6278839534
commit ba95dbc36b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 94 additions and 6 deletions

View file

@ -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

View file

@ -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.

View 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