feat: use priorities to ensure simp applies eqational lemmas in order (#4434)

This assigns priorities to the equational lemmas so that more specific
ones
are tried first before a possible catch-all with possible
side-conditions.

We assign very low priorities to match the simplifiers behavior when
unfolding
a definition, which happens in `simpLoop`’ `visitPreContinue` after
applying
rewrite rules.

Definitions with more than 100 equational theorems will use priority 1
for all
but the last (a heuristic, not perfect).

fixes #4173, to some extent.
This commit is contained in:
Joachim Breitner 2024-06-17 20:22:28 +02:00 committed by GitHub
parent 42c4a770c2
commit 59a09fb4e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 61 additions and 2 deletions

View file

@ -1459,6 +1459,7 @@ have been simplified by using the modifier `↓`. Here is an example
```
When multiple simp theorems are applicable, the simplifier uses the one with highest priority.
The equational theorems of function are applied at very low priority (100 and below).
If there are several with the same priority, it is uses the "most recent one". Example:
```lean
@[simp high] theorem cond_true (a b : α) : cond true a b = a := rfl

View file

@ -466,8 +466,25 @@ def SimpTheorems.add (s : SimpTheorems) (id : Origin) (levelParams : Array Name)
def SimpTheorems.addDeclToUnfold (d : SimpTheorems) (declName : Name) : MetaM SimpTheorems := do
if let some eqns ← getEqnsFor? declName then
let mut d := d
for eqn in eqns do
d ← SimpTheorems.addConst d eqn
for h : i in [:eqns.size] do
let eqn := eqns[i]
/-
We assign priorities to the equational lemmas so that more specific ones
are tried first before a possible catch-all with possible side-conditions.
We assign very low priorities to match the simplifiers behavior when unfolding
a definition, which happens in `simpLoop` `visitPreContinue` after applying
rewrite rules.
Definitions with more than 100 equational theorems will use priority 1 for all
but the last (a heuristic, not perfect).
-/
let prio := if eqns.size > 100 then
if i + 1 = eqns.size then 0 else 1
else
100 - i
-- We assign very low priority to equational le
d ← SimpTheorems.addConst d eqn (prio := prio)
/-
Even if a function has equation theorems,
we also store it in the `toUnfold` set in the following two cases:

View file

@ -0,0 +1,41 @@
import Lean
/-!
Tests that simp applies the equational lemmas in order. In particular,
a catch-all at the end is tried afte the others
-/
def foo : Bool → Nat → Nat
| _, 0 => 0
| .true, n+1 => foo .true n
| _, n+1 => foo .false n
termination_by _ n => n
/-- info: foo.eq_1 : ∀ (x : Bool), foo x 0 = 0 -/
#guard_msgs in
#check foo.eq_1
/-- info: foo.eq_2 (n : Nat) : foo true n.succ = foo true n -/
#guard_msgs in
#check foo.eq_2
/-- info: foo.eq_3 : ∀ (x : Bool) (n : Nat), (x = true → False) → foo x n.succ = foo false n -/
#guard_msgs in
#check foo.eq_3
-- In order to reliably check if simp is not attempting to rewrite with a certain lemma
-- we can look at te diagnostics. But simply dumping all diangostics is too noisy for a test,
-- so here we try to get our hands at the `Simp.Stats` and look there.
open Lean Meta Elab Tactic in
elab "simp_foo_with_check" : tactic =>
withOptions (fun o => diagnostics.set o true) do withMainContext do
let stx ← `(tactic|simp [foo])
let { ctx, simprocs, dischargeWrapper } ← mkSimpContext stx (eraseLocal := false)
let stats ← dischargeWrapper.with fun discharge? => do
simpLocation ctx simprocs discharge? (expandOptLocation stx.raw[5])
unless stats.diag.triedThmCounter.toList.any (fun (o, _n) => o.key = ``foo.eq_2) do
throwError "Simp did not try to use foo.eq_2, is this test still working?"
for (origin, n) in stats.diag.triedThmCounter.toList do
if origin.key = ``foo.eq_3 then
throwError m!"Bad: Simp used foo.eq_3 {n} times"
example : foo true 1 = 0 := by
simp_foo_with_check -- essentially simp [foo]