Zulip Chat Archive
Stream: new members
Topic: A subtle detail about Lean `rw` tactic
Shuhao Song (Aug 03 2024 at 05:31):
Hello! I'm new to the Lean community. I spent almost whole forenoon time trying to understand some subtle behavior of rw
tactic. Consider the following example code:
axiom α : Type
axiom β : Type
axiom p : α → Prop
axiom q : α → Prop
axiom r : α → Prop
axiom rp : {x : α} → r x ↔ p x
axiom rq : {x : α} → r x ↔ q x
axiom f : (x : α) → p x → β
axiom g : (x : α) → q x → β
axiom y : β
axiom fx : (x : α) → (hx : r x) → f x (rp.mp hx) = g x (rq.mp hx)
example (x : α) (hx : r x) : g x (rq.mp hx) = y := by
have test : y = f x (rp.mp hx) := by sorry
have test₂ : 0 = 0 := by rfl
rw [fx _] at test
exact test.symm
The exact test.symm
reports an error:
application type mismatch
Eq.symm test
argument
test
has type
y = g x ⋯ : Prop
but is expected to have type
y = ?m.184 : Prop
That may seem strange, because the metavariable ?m.184
can be unified with g x
. So why the assignment step failed? I read the Lean source code trying to understand it. The rw
tactic changes a hypothesis (FVar
) called test
, and because the original hypothesis can't be safely deleted, it was daggered (test✝
). However, after rw
, the FVar test₂
was then became a new FVar
with different FVarId
but the same name (test₂
), that may be the reason.
The hole in rw [fx _] at test
creates a new metavariable, say ?m.149
(thats the value in https://live.lean-lang.org). The expression g x ...
contains the metavariable ?m.149
, and it will be assigned to ?m.184
. In function processAssignment
of file Lean.Meta.ExprDefEq
, it will call checkAssignment
to check if an expression can be assigned to a metavariable, and if not it will try first-order unification (processAssignmentFOApprox
and then processConstApprox
).
In checkAssignment
, it will use checkMVar
to check if a metavariable (like ?m.149
, we use to denote it) is suitable for assignmented to another metavariable (like ?m.184
, use to denote). If the context of is part of current context (to speak rigorously, "part of" means function Lean.LocalContext.isSubPrefixOf
), the check would succeed. If config.ctxApprox
is true
and the current context is part of context of p
, it also succeeds. However, it's not both the case: ?m.149
has context test
and test₂
, the current context is test
(display with dagger), test
(new, rewrited) and test₂
. Note that two test₂
have different FVarId
, so neither context are part of each other.
Then it is easy to understand why the exact test.symm
would succeed, if you remove have test₂ : 0 = 0 := by rfl
, even these two tactics seems not relevant to each other.
Shuhao Song (Aug 03 2024 at 23:23):
Can we modify the implementation of rw
to make this better?
Eric Wieser (Aug 04 2024 at 00:54):
I think this example might be worth filing as a bug report.
Eric Wieser (Aug 04 2024 at 00:57):
let n := 1
is also enough to trigger the issue, instead of have test₂ : 0 = 0 := by rfl
Eric Wieser (Aug 04 2024 at 00:59):
Even more surprisingly, have test2 := test
instead of exact test.symm
still fails!
Shuhao Song (Aug 04 2024 at 01:51):
Yes because it modifies the local context. The difference of have
and let
is just .cdecl
and .ldecl
in LocalDecl
type, so they don't difference a lot.
Eric Wieser said:
let n := 1
is also enough to trigger the issue, instead ofhave test₂ : 0 = 0 := by rfl
Shuhao Song (Aug 04 2024 at 01:51):
Eric Wieser said:
I think this example might be worth filing as a bug report.
How can I submit a bug report?
Shuhao Song (Aug 04 2024 at 02:14):
Eric Wieser said:
have test2 := test
Because it need to fill the metavariable of type of test2
.
Eric Wieser (Aug 04 2024 at 12:01):
github.com/leanprover/lean4/issues
Last updated: May 02 2025 at 03:31 UTC