Zulip Chat Archive
Stream: lean4
Topic: termination compiler app error
Mario Carneiro (Dec 15 2021 at 21:41):
I think the error here is somewhere in the termination checker, but I haven't minimized it further than this:
def heapifyDown (lt : α → α → Bool) (a : Array α) (i : Fin a.size) : Array α :=
  let left := 2 * i.1 + 1
  let right := left + 1
  have left_le : i ≤ left := sorry
  have right_le : i ≤ right := sorry
  have i_le : i ≤ i := Nat.le_refl _
  have j : {j : Fin a.size // i ≤ j} := if h : left < a.size then
    if lt (a.get i) (a.get ⟨left, h⟩) then ⟨⟨left, h⟩, left_le⟩ else ⟨i, i_le⟩ else ⟨i, i_le⟩
  have j := if h : right < a.size then
    if lt (a.get j) (a.get ⟨right, h⟩) then ⟨⟨right, h⟩, right_le⟩ else j else j
  if h : i ≠ j then
    let a' := a.swap i j
    have : a'.size - j < a.size - i := sorry
    heapifyDown lt a' ⟨j.1.1, a.size_swap i j ▸ j.1.2⟩
  else
    a
termination_by measure fun ⟨_, _, a, i⟩ => a.size - i.1
decreasing_by assumption
gives an error on the def heapifyDown saying:
application type mismatch
  LE.le snd✝
argument has type
  Fin (Array.size fst✝)
but function has type
  Fin x✝ → Fin x✝ → Prop
Leonardo de Moura (Dec 15 2021 at 21:46):
@Mario Carneiro Thanks for reporting the issue. I will be very grateful if you manage to minimize the example.
Mario Carneiro (Dec 15 2021 at 21:47):
smaller version
def heapifyDown (a : Array α) (i : Fin a.size) : Array α :=
  have : i < i := sorry
  heapifyDown a ⟨i.1, a.size_swap i i ▸ i.2⟩
termination_by measure fun ⟨_, a, i⟩ => i.1
decreasing_by assumption
Leonardo de Moura (Dec 15 2021 at 21:47):
Thanks! :)
Mario Carneiro (Dec 15 2021 at 21:47):
removing the a.size_swap i i ▸ makes the error go away. I think the version of the goal in the assumption context is ill typed
Mario Carneiro (Dec 15 2021 at 21:58):
Replacing assumption with done and setting pp.all reveals the context:
unsolved goals
fst✝¹ : Type u_1
fst✝ : Array.{u_1} fst✝¹
snd✝ : Fin (@Array.size.{u_1} fst✝¹ fst✝)
this : @LT.lt.{0} (Fin (@Array.size.{u_1} fst✝¹ fst✝)) (@instLTFin (@Array.size.{u_1} fst✝¹ fst✝)) snd✝ snd✝
⊢ (@measure.{u_1 + 2}
        (@PSigma.{u_1 + 2, u_1 + 1} (Type u_1)
          fun {α : Type u_1} =>
            @PSigma.{u_1 + 1, 1} (Array.{u_1} α) fun (a : Array.{u_1} α) => Fin (@Array.size.{u_1} α a))
        fun
          (x :
          @PSigma.{u_1 + 2, u_1 + 1} (Type u_1)
            fun {α : Type u_1} =>
              @PSigma.{u_1 + 1, 1} (Array.{u_1} α) fun (a : Array.{u_1} α) => Fin (@Array.size.{u_1} α a)) =>
          heapifyDown._unary.match_1.{u_1, 1}
            (fun
              (x :
              @PSigma.{u_1 + 2, u_1 + 1} (Type u_1)
                fun {α : Type u_1} =>
                  @PSigma.{u_1 + 1, 1} (Array.{u_1} α) fun (a : Array.{u_1} α) => Fin (@Array.size.{u_1} α a)) =>
              Nat)
            x
            fun (x : Type u_1) (a : Array.{u_1} x) (i : Fin (@Array.size.{u_1} x a)) =>
              @Fin.val (@Array.size.{u_1} x a) i).1
    (@PSigma.mk.{u_1 + 2, u_1 + 1} (Type u_1)
      (fun {α : Type u_1} =>
        @PSigma.{u_1 + 1, 1} (Array.{u_1} α) fun (a : Array.{u_1} α) => Fin (@Array.size.{u_1} α a))
      fst✝¹
      (@PSigma.mk.{u_1 + 1, 1} (Array.{u_1} fst✝¹) (fun (a : Array.{u_1} fst✝¹) => Fin (@Array.size.{u_1} fst✝¹ a)) fst✝
        (@Fin.mk (@Array.size.{u_1} fst✝¹ fst✝) (@Fin.val (@Array.size.{u_1} fst✝¹ fst✝) snd✝)
          (@Eq.ndrec.{0, 1} Nat (@Array.size.{u_1} fst✝¹ (@Array.swap.{u_1} fst✝¹ fst✝ snd✝ snd✝))
            (fun (x : Nat) => @LT.lt.{0} Nat instLTNat (@Fin.val x snd✝) x)
            (@Eq.ndrec.{0, 1} Nat (@Array.size.{u_1} fst✝¹ fst✝)
              (fun (x : Nat) => @LT.lt.{0} Nat instLTNat (@Fin.val x snd✝) x)
              (@Fin.isLt (@Array.size.{u_1} fst✝¹ fst✝) snd✝)
              (@Array.size.{u_1} fst✝¹ (@Array.swap.{u_1} fst✝¹ fst✝ snd✝ snd✝))
              (@Eq.symm.{1} Nat (@Array.size.{u_1} fst✝¹ (@Array.swap.{u_1} fst✝¹ fst✝ snd✝ snd✝))
                (@Array.size.{u_1} fst✝¹ fst✝) (@Array.size_swap.{u_1} fst✝¹ fst✝ snd✝ snd✝)))
            (@Array.size.{u_1} fst✝¹ fst✝) (@Array.size_swap.{u_1} fst✝¹ fst✝ snd✝ snd✝)))))
    (@PSigma.mk.{u_1 + 2, u_1 + 1} (Type u_1)
      (fun {α : Type u_1} =>
        @PSigma.{u_1 + 1, 1} (Array.{u_1} α) fun (a : Array.{u_1} α) => Fin (@Array.size.{u_1} α a))
      fst✝¹
      (@PSigma.mk.{u_1 + 1, 1} (Array.{u_1} fst✝¹) (fun (a : Array.{u_1} fst✝¹) => Fin (@Array.size.{u_1} fst✝¹ a)) fst✝
        snd✝))
which contains a subterm fun (x : Nat) => @LT.lt.{0} Nat instLTNat (@Fin.val x snd✝) x which is ill typed because snd✝ : Fin (@Array.size.{u_1} fst✝¹ fst✝) instead of Fin x. This looks like the kind of replacement that rw will sometimes do that yields the motive is not type correct error in lean 3
Mario Carneiro (Dec 15 2021 at 22:01):
Oh, the termination compiler is a red herring. This has the same error:
def heapifyDown' (a : Array α) (i : Fin a.size) : Array α := sorry
def heapifyDown (a : Array α) (i : Fin a.size) : Array α :=
  heapifyDown' a ⟨i.1, a.size_swap i i ▸ i.2⟩
Mario Carneiro (Dec 15 2021 at 22:03):
minimized more:
example (a : Array α) (i : Fin a.size) : i.1 < a.size := a.size_swap i i ▸ i.2
Mario Carneiro (Dec 15 2021 at 22:15):
minimized more:
variable (a : Nat) (i : Fin a) (h : 1 = a)
#check (h ▸ i.2 : i < a) -- h ▸ Eq.symm h ▸ i.isLt : i.val < a
example : i < a := h ▸ i.2
-- application type mismatch
--   i.val
-- argument has type
--   Fin a
-- but function has type
--   Fin x✝ → Nat
The check result is very weird (and there are in fact two Eq.ndrec nodes being generated), but I don't think it is the issue. It seems like this actually is a motive is not type correct error, but it passes elaboration and gets caught late
Leonardo de Moura (Dec 16 2021 at 00:19):
I pushed a fix for this. The ▸ notation now type checks the generated motives like the rw tactic.
Last updated: May 02 2025 at 03:31 UTC