Zulip Chat Archive
Stream: lean4
Topic: bug: mut + match + dependent types
Floris van Doorn (Nov 24 2025 at 11:09):
There is a bug when match changes the type of a mutable variable: the mutable variable gets an old value within the match statement.
import Lean
open Lean Meta
structure Foo (n : Nat) where
(l : List Nat)
(h : n = n)
def foo (n : Nat) : MetaM Unit := do
let mut result : Foo n := ⟨[7], rfl⟩
trace[Meta.Tactic.simp] "Initial value of result: {result.l}"
result := ⟨List.range n, rfl⟩
trace[Meta.Tactic.simp] "We have a new value of result {result.l}"
match n with
| _ => trace[Meta.Tactic.simp] "The value of result reverted back to the original value {result.l}"
set_option trace.Meta.Tactic.simp true
run_meta do
foo 5
-- [Meta.Tactic.simp] Initial value of result: [7]
-- [Meta.Tactic.simp] We have a new value of result [0, 1, 2, 3, 4]
-- [Meta.Tactic.simp] The value of result reverted back to the original value [7]
-- **Expected**: -- [Meta.Tactic.simp] The value of result reverted back to the original value [0, 1, 2, 3, 4]
Floris van Doorn (Nov 24 2025 at 11:10):
Note: replacing trace by logInfo changes the behavior:
import Lean
open Lean Meta
structure Foo (n : Nat) where
(l : List Nat)
(h : n = n)
def foo' (n : Nat) : MetaM Unit := do
let mut result : Foo n := ⟨[7], rfl⟩
logInfo m!"Initial value of result: {result.l}"
result := ⟨List.range n, rfl⟩
logInfo m!"We have a new value of result {result.l}"
match n with
| _ => logInfo m!"Now we keep the new value {result.l}"
run_meta do
foo' 5
-- Initial value of result: [7]
-- We have a new value of result [0, 1, 2, 3, 4]
-- Now we keep the new value [0, 1, 2, 3, 4]
Floris van Doorn (Nov 24 2025 at 11:10):
In another (non-MWE) instance, adding a trace caused match to find the "right" value for the mutable variable, but with logInfo we got the wrong value.
Floris van Doorn (Nov 24 2025 at 11:15):
Jovan Gerbscheid (Jan 28 2026 at 12:31):
I ran into another let mut bug that was really hard to debug, because adding logInfo in some places would remove the bug(!!!). Here's a MWE:
import Std
local instance {α β cmp} [Append β] : Append (Std.TreeMap α β cmp) :=
⟨.mergeWith (fun _ ↦ (· ++ ·))⟩
def foo (hyp? : Option Unit) (a : α) : IO (Std.TreeMap Nat (Array α)) := do
let mut tree := {}
if true then
pure ()
tree := tree ++ (.insert {} 0 #[a])
if let some _ := hyp? then
tree := tree
return tree
-- We expect `foo none 3` to return `Std.TreeMap.ofList [(0, #[3])]`
#eval foo none 3 -- Std.TreeMap.ofList []
Lean automatically infers the type of tree to be Std.TreeMap Nat (Array α), but if I give the type annotation myself, replacing let mut tree := {} with let mut tree : Std.TreeMap Nat (Array α) := {}, the bug goes away!
Kim Morrison (Jan 29 2026 at 02:40):
Could you open an issue for this one?
Floris van Doorn (Jan 29 2026 at 11:29):
@Jovan Gerbscheid: I noticed similar behavior, where adding/removing logging would change how match and the mutable variable interact.
Last updated: Feb 28 2026 at 14:05 UTC