Zulip Chat Archive
Stream: batteries
Topic: Strange 'import all' bug
cmlsharp (Jan 06 2026 at 23:52):
Here's an isolated example, this experimentation actually led me to find some _very_ odd behavior (including that the declaration order of independent functions matters)
First, as minimal of an example as I can get:
Test/Foo.lean
module
public def increment? (i : Nat) (sz: Nat) : Option Nat :=
if _ : i < sz - 1 then some (i + 1) else none
--@[expose]
public def iterate (a : Array Nat) (i : Nat) : Array Nat :=
match _ : increment? i a.size with
| none => a
| some j =>
have h: i < a.size := by simp_all[increment?]; omega
iterate (a.set i a[i]) j
termination_by a.size - i
decreasing_by
simp_all [increment?]
omega
Test/Bar.lean
module
import all Test.Foo
public import Test.Foo
theorem iterate_does_nothing (a :Array Nat) (i : Nat) :
iterate a i = a := by
induction a, i using iterate.induct with grind [iterate]
This will compile only if @[expose] is uncommented. I get the following error:
▼ 7:44-7:58: error:
invalid pattern(s) for `iterate.match_1.congr_eq_2`
[iterate.match_1✝ #5 #4 #3 #2]
the following theorem parameters cannot be instantiated:
j : Nat
heq_1 : x✝ = some j
▼ 7:44-7:58: error:
invalid pattern(s) for `iterate.match_1.congr_eq_2`
[iterate.match_1✝ #5 #4 #3 #2]
the following theorem parameters cannot be instantiated:
j : Nat
heq_1 : x✝ = some j
However, I discovered even more weirdness.
Let's augment Foo and Bar with identical copies of iterate at varying visibilities:
Test/Foo.lean
module
public def increment? (i : Nat) (sz: Nat) : Option Nat :=
if _ : i < sz - 1 then some (i + 1) else none
public def iterate (a : Array Nat) (i : Nat) : Array Nat :=
match _ : increment? i a.size with
| none => a
| some j =>
have h: i < a.size := by simp_all[increment?]; omega
iterate (a.set i a[i]) j
termination_by a.size - i
decreasing_by
simp_all [increment?]
omega
@[expose]
public def iterate_expose (a : Array Nat) (i : Nat) : Array Nat :=
match _ : increment? i a.size with
| none => a
| some j =>
have h: i < a.size := by simp_all[increment?]; omega
iterate_expose (a.set i a[i]) j
termination_by a.size - i
decreasing_by
simp_all [increment?]
omega
def iterate_priv (a : Array Nat) (i : Nat) : Array Nat :=
match _ : increment? i a.size with
| none => a
| some j =>
have h: i < a.size := by simp_all[increment?]; omega
iterate_priv (a.set i a[i]) j
termination_by a.size - i
decreasing_by
simp_all [increment?]
omega
And lets modify Test/Bar.lean to have the corresponding theorems:
module
import all Test.Foo
public import Test.Foo
theorem iterate_does_nothing (a :Array Nat) (i : Nat) :
iterate a i = a := by
induction a, i using iterate.induct with grind [iterate]
theorem iterate_expose_does_nothing (a :Array Nat) (i : Nat) :
iterate_expose a i = a := by
induction a, i using iterate_expose.induct with grind [iterate_expose]
theorem iterate_priv_does_nothing (a :Array Nat) (i : Nat) :
iterate_priv a i = a := by
induction a, i using iterate_priv.induct with grind [iterate_priv]
At first, it's as before, only iterate_expose_does_nothing compiles.
Two strange facts about this setup:
- Removing the
public import Test.Foo(or replacing it withimport Test.Foo) causes all three theorems to fail to compile. The reported error foriterateanditerate_privare the same as before, but theiterate_exposetheorem now fails with a standard grind failure. Investigating further reveals that, 'iterate_expose' cannot be unfolded (or more accurately, unfolding it doesn't give you its body, it gives you stuff that if you keep unfolding you get the recursion in terms ofWellFounded.fixetc). - Even more bizarrely, changing the declaration order of these functions in
Test/Foo.leanchanges the result. Ifiterate_exposeis declared _before_iterateanditerate_priv, then all three theorems compile! - If
iterate_exposeis declared first _and_public import Test.Foois removed, theniterate_does_nothinganditerate_priv_does_nothingwill compile whileiterate_expose_does_nothingwill not.
I did try to simplify this match statement into e.g. a dependent if-then-else, but I was not able to replicate the issue, so the match seems important.
Edit: also as an additional note, the use of iterate.induct doesn't seem important here. Just replacing the whole proof with 'grind [iterate]' shows the same error "invalid patterns" error when iterate is non-exposed and just reports an expected failure to prove when it is.
Notification Bot (Jan 08 2026 at 18:24):
A message was moved here from #batteries > Verifying BinaryHeap (PR Review) by cmlsharp.
cmlsharp (Jan 12 2026 at 14:53):
github issue:
https://github.com/leanprover/lean4/issues/11948
Last updated: Feb 28 2026 at 14:05 UTC