Zulip Chat Archive

Stream: general

Topic: 'unexpected occurrence of recursive application' with omega


Aaron Hill (Apr 09 2025 at 03:22):

The following code (extracted from the equational_theories project) compiles with Lean 4.18.0, but fails with 4.19.0-rc2 and above:

@[irreducible] noncomputable def g_enumerate : Option Nat := sorry

noncomputable def f_data (n: Nat): Nat := by
  match hn: n with
  | 0 => exact 0
  | a + 1 =>
    have new_vals_not_supp: g_enumerate < Option.some (f_data a) := by
      have supp_max_lt: (g_enumerate).getD 0 < (f_data a) - 1 := sorry
      match h_supp_max: (g_enumerate) with
      | Option.some max_val =>
        rw [Option.some_lt_some]
        rw [h_supp_max, Option.getD_some] at supp_max_lt
        omega
      | None => sorry
    exact 1

produces:

fail to show termination for
  f_data
with errors
failed to infer structural recursion:
Cannot use parameter n:
  unexpected occurrence of recursive application
    f_data


failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal

I've had difficulty minimizing it further than this, as omega seems to be required to trigger the error (changing supp_max_lt to (g_enumerate).getD 0 < (f_data a) and using assumption will allow the proof to compile).

Kim Morrison (Apr 09 2025 at 03:29):

Making it clearer that the problem involves omega (specifically, probably the fact that omega is abstracting a proof into a separate declartion), this works fine:

@[irreducible] noncomputable def g_enumerate : Option Nat := sorry

theorem foo (h : x < y - 1) : x < y := by omega

noncomputable def f_data (n: Nat): Nat := by
  match hn: n with
  | 0 => exact 0
  | a + 1 =>
    have new_vals_not_supp: g_enumerate < Option.some (f_data a) := by
      have supp_max_lt: (g_enumerate).getD 0 < (f_data a) - 1 := sorry
      match h_supp_max: (g_enumerate) with
      | Option.some max_val =>
        rw [Option.some_lt_some]
        rw [h_supp_max, Option.getD_some] at supp_max_lt
        exact foo supp_max_lt
      | None => sorry
    exact 1

Kim Morrison (Apr 09 2025 at 03:33):

Let's ping @Joachim Breitner, who is away this week, in case no one works out a fix in the meantime. :-)

Aaron Hill (Apr 09 2025 at 19:39):

It looks like it's being caused by omega's usage of mkAuxTheorem (https://github.com/leanprover/lean4/pull/5998) - I can reproduce the issue by using as_aux_lemma:

def f_data (n: Nat): True := by
  match hn: n with
  | 0 => trivial
  | a + 1 =>
    have my_proof: trivial = (f_data a) := by
      as_aux_lemma => trivial
    trivial

Aaron Hill (Apr 09 2025 at 20:00):

replacing as_aux_lemma => trivial by trivial makes it compile

Joachim Breitner (Apr 11 2025 at 11:19):

Uff, this will be hard to avoid. Aux theorems abstract over all free variables, and then the call is unsaturated.

A work around sometimes is to let- or have-bind the recursive call, so that the omega proof is properly abstract in it.

Joachim Breitner (Apr 11 2025 at 11:22):

Workaround:

@[irreducible] noncomputable def g_enumerate : Option Nat := sorry


noncomputable def f_data (n: Nat): Nat := by
  match hn: n with
  | 0 => exact 0
  | a + 1 =>
    have fa := f_data a
    have new_vals_not_supp: g_enumerate < Option.some fa := by
      have supp_max_lt: (g_enumerate).getD 0 < fa - 1 := sorry
      match h_supp_max: (g_enumerate) with
      | Option.some max_val =>
        rw [Option.some_lt_some]
        rw [h_supp_max, Option.getD_some] at supp_max_lt
        omega
      | None => sorry
    exact 1

Aaron Hill (Apr 11 2025 at 17:12):

Thanks!

Aaron Hill (Apr 11 2025 at 17:15):

Is there any way for omega to detect that the current goal references an "outer" function, and skip creating an aux theorem?

Aaron Hill (Apr 11 2025 at 21:59):

It took me a while to minimize the original error, as I wasn't expecting omega to have anything to do with proving termination

Joachim Breitner (Apr 12 2025 at 19:29):

A more proper solution that I have been wondering about is to abstract over all calls to free variables, but I didn't complete this thought yet (what if the arguments are large and complex and contain proofs?). So it's not clear to me what the really best way is here. Or maybe the proofs should be unfolded during derecursification.


Last updated: May 02 2025 at 03:31 UTC