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 pp to denote it) is suitable for assignmented to another metavariable (like ?m.184, use qq to denote). If the context of pp 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 of have 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