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:
parent
42c4a770c2
commit
59a09fb4e7
3 changed files with 61 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
41
tests/lean/run/eqnsPrio.lean
Normal file
41
tests/lean/run/eqnsPrio.lean
Normal 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]
|
||||
Loading…
Add table
Reference in a new issue