Rewrite applies the wrong equational rule
Closed this issue · 8 comments
Prerequisites
Please put an X between the brackets as you perform the following steps:
- Check that your issue is not already filed:
https://github.com/leanprover/lean4/issues - Reduce the issue to a minimal, self-contained, reproducible test case.
Avoid dependencies to Mathlib or Batteries. - Test your test case against the latest nightly release, for example on
https://live.lean-lang.org/#project=lean-nightly
(You can also use the settings there to switch to “Lean nightly”)
Description
On the following example, the rw
tactic does not unfold a function definition (as it used to prior to 4.12.0).
structure LoopRange where
start: Nat
end_: Option Nat
namespace LoopRange
def isZero (r: LoopRange): Prop :=
r = ⟨0, some 0⟩
def isZeroVar2 (r: LoopRange): Prop :=
match r with
| ⟨0, some 0⟩ => True
| _ => False
example (r: LoopRange): r.isZeroVar2 ↔ r.isZero := by
rw [isZeroVar2]
sorry
end LoopRange
Instead, it looks as if rw [isZeroVar2]
applies isZeroVar2.eq_2
which results in two unprovable subgoals:
Tactic state
2 goals
r : LoopRange
⊢ False ↔ r.isZero
case x_1
r : LoopRange
⊢ r = { start := 0, end_ := some 0 } → False
This variant does the right thing::
example (r: LoopRange): r.isZeroVar2 ↔ r.isZero := by
rw [isZeroVar2.eq_def]
sorry
Versions
4.12.0
Additional Information
[Additional information, configuration or data that might be necessary to reproduce the issue]
Impact
Add 👍 to issues you consider important. If others are impacted by this issue, please ask them to add 👍 to it.
I fear that's now the expected behavior. It always has been like this for recursive functions, and has become more uniform. Using rw [foo.eq_def]
is the right work around.
In other places, I've seen an error message like this
failed to rewrite using equation theorems for 'Option.isSome'. Try rewriting with 'Option.isSome.eq_def'.
But here, it's silently applying the wrong rewrite and there's no warning or hint at what to do.
Yes, this only happens with function definitions that have a catch-all clause in the pattern match. These lead to rewrite rules that always apply, but introduce side goals. With simp
that's fine, it won't apply the rule unless it can discharge these side goals. But rw
is a bit too primitive for that, at least at the moment.
Closing as (new) expected behavior
Is this documented somewhere? What is the new expected behavior of rw [<function-name>]
?
It's not new behavior of rw
; it has always behaved like this with functions with multiple equational rules and overlapping patterns. Until 0.11 only recursive functions has multiple equational rules, since 0.12 all functions are treated the same in this regard. See breaking changes section in https://github.com/leanprover/lean4/releases/tag/v4.12.0 (although it's not explicitly calling out the this particular effect.)
Yes, I saw the release notes. Let me try to clarify my question.
My function definition looks like this:
def isZeroVar2 (r: LoopRange): Prop :=
match r with
| ⟨0, some 0⟩ => True
| _ => False
There are four equational rules for this definition:
LoopRange.isZeroVar2.eq_1 : { start := 0, end_ := some 0 }.isZeroVar2 = True
LoopRange.isZeroVar2.eq_2 (r : LoopRange) (x_1 : r = { start := 0, end_ := some 0 } → False) : r.isZeroVar2 = False
LoopRange.isZeroVar2.eq_def (r : LoopRange) :
r.isZeroVar2 =
match r with
| { start := 0, end_ := some 0 } => True
| x => False
LoopRange.isZeroVar2.eq_unfold :
isZeroVar2 = fun r =>
match r with
| { start := 0, end_ := some 0 } => True
| x => False
How does the tactic rw [isZeroVar2]
decide which of these four rules to apply?
It tries .eq_1
and then .eq_2
.
Unfortunately the second applies even when the user probably doesn't want it to apply, because it's a conditional rule, and rw
will apply it always, leaving the side-condition as a new goal (unlike simp, which only applies it if it can discharge the side-condition).