Skip to content

Commit 6a900dc

Browse files
kim-emclaude
andauthored
fix: strip nested mdata in grind preprocessing (#11412)
This PR fixes an issue where `grind` would fail after multiple `norm_cast` calls with the error "unexpected metadata found during internalization". The `norm_cast` tactic adds mdata nodes to expressions, and when called multiple times it creates nested mdata. The `eraseIrrelevantMData` preprocessing function was using `.continue e` when stripping mdata, which causes `Core.transform` to reconstruct the mdata node around the visited children. By changing to `.visit e`, the inner expression is passed back to `pre` for another round of processing, allowing all nested mdata layers to be stripped. Closes #11411 🤖 Prepared with Claude Code Co-authored-by: Claude <[email protected]>
1 parent b21cef3 commit 6a900dc

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

src/Lean/Meta/Tactic/Grind/Util.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def eraseIrrelevantMData (e : Expr) : CoreM Expr := do
153153
let pre (e : Expr) := do
154154
match e with
155155
| .letE .. | .lam .. => return .done e
156-
| .mdata _ e => return .continue e
156+
| .mdata _ e => return .visit e
157157
| _ => return .continue e
158158
Core.transform e (pre := pre)
159159

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/-!
2+
# Test that `grind` handles nested mdata from multiple `norm_cast` calls
3+
4+
This is a regression test for https://github.com/leanprover/lean4/issues/11411
5+
6+
The issue was that `norm_cast` adds mdata nodes to expressions, and when called
7+
multiple times it creates nested mdata. The `grind` preprocessing step
8+
`eraseIrrelevantMData` was only stripping one layer of mdata instead of all layers.
9+
-/
10+
11+
-- Minimal WithTop definition
12+
def WithTop (α : Type u) := Option α
13+
14+
namespace WithTop
15+
16+
variable {α : Type u}
17+
18+
@[coe] def some : α → WithTop α := Option.some
19+
20+
instance : Coe α (WithTop α) := ⟨some⟩
21+
22+
instance : NatCast (WithTop Nat) where
23+
natCast n := some n
24+
25+
end WithTop
26+
27+
-- AtLeastTwo infrastructure (from Mathlib.Data.Nat.Init)
28+
class Nat.AtLeastTwo (n : Nat) : Prop where
29+
prop : n ≥ 2
30+
31+
instance Nat.instAtLeastTwo : Nat.AtLeastTwo (n + 2) where
32+
prop := Nat.le_add_left 2 n
33+
34+
-- OfNat instance for numeric literals ≥ 2 (from Mathlib.Data.Nat.Cast.Defs)
35+
instance instOfNatAtLeastTwo {R : Type u} [NatCast R] (n : Nat) [Nat.AtLeastTwo n] : OfNat R n where
36+
ofNat := n
37+
38+
-- The key norm_cast lemma (from Mathlib.Data.Nat.Cast.Defs)
39+
@[simp, norm_cast]
40+
theorem Nat.cast_ofNat {R : Type u} {n : Nat} [NatCast R] [Nat.AtLeastTwo n] :
41+
(Nat.cast (no_index (OfNat.ofNat n)) : R) = no_index (OfNat.ofNat n) := rfl
42+
43+
variable {Foo : WithTop Nat → Prop} {Bar : Prop}
44+
45+
-- Works: grind without norm_cast
46+
example (foo_imp_bar : ∀ x, Foo x → Bar) (h : Foo 2) : Bar := by
47+
grind
48+
49+
-- Works: grind after one norm_cast
50+
example (foo_imp_bar : ∀ x, Foo x → Bar) (h : Foo 2) : Bar := by
51+
norm_cast at h
52+
grind
53+
54+
-- Previously failed: grind after two norm_casts
55+
-- This used to fail with "unexpected metadata found during internalization"
56+
example (foo_imp_bar : ∀ x, Foo x → Bar) (h : Foo 2) : Bar := by
57+
norm_cast at h
58+
norm_cast at h
59+
grind

0 commit comments

Comments
 (0)