Zulip Chat Archive

Stream: lean4

Topic: unfold triggers infinite loop


Alex Keizer (Aug 08 2023 at 10:16):

In the following MWE, uncommenting the unfold seems to trigger an infinite loop, burning 100% of my CPU without being caught by maxHeartBeats.

inductive Ty
  | nat

def Ctxt : Type :=
  List Ty


def Ctxt.Var (Γ : Ctxt) (t : Ty) : Type :=
  { i : Nat // Γ.get? i = some t }


def matchVar {t : Ty} : {Δ : Ctxt}  Δ.Var t  Option Bool
  | _::Δ, w+1, h => -- w† = Var.toSnoc w
      let w : Ctxt.Var Δ t := w, by simp_all[List.get?]⟩
      matchVar w
  | _, _ =>
      none


example {Δ : Ctxt } {w : Δ.Var t} :
  matchVar w = none
    := by
        -- uncommenting the `unfold` triggers an infinite loop of sorts,
        -- consuming 100% CPU without being caught by `maxHeartbeats` timeout
        -- unfold matchVar
        sorry

Running under gdb, we find that we are in:

#6203 0x00007fc263bd9f24 in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop___lambda__2 () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6204 0x00007fc263bda95d in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6205 0x00007fc263bd9f24 in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop___lambda__2 () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6206 0x00007fc263bda95d in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6207 0x00007fc263bd9f24 in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop___lambda__2 () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6208 0x00007fc263bda95d in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6209 0x00007fc263bd9f24 in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop___lambda__2 () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6210 0x00007fc263bda95d in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6211 0x00007fc263bd9f24 in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop___lambda__2 () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6212 0x00007fc263bda95d in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop () from /home/grosser/.elan/toolchains/leanprover--lean4---nightly-2023-07-12/bin/../lib/lean/libleanshared.so
#6213 0x00007fc263bd9f24 in l___private_Lean_Meta_Match_MatchEqs_0__Lean_Meta_Match_mkSplitterProof_proveSubgoalLoop___lambda__2 ()

Which seems to correspond to:

  proveSubgoalLoop (mvarId : MVarId) : MetaM Unit := do
    trace[Meta.Match.matchEqs] "proveSubgoalLoop\n{mvarId}"
    match ( injectionAny mvarId) with
    | InjectionAnyResult.solved => return ()
    | InjectionAnyResult.failed =>
      let mvarId'  substVars mvarId
      if mvarId' == mvarId then
        if ( mvarId.contradictionCore {}) then
          return ()
        throwError "failed to generate splitter for match auxiliary declaration '{matchDeclName}', unsolved subgoal:\n{MessageData.ofGoal mvarId}"
      else
        proveSubgoalLoop mvarId'
    | InjectionAnyResult.subgoal mvarId => proveSubgoalLoop mvarId

  proveSubgoal (mvarId : MVarId) : MetaM Unit := do
    trace[Meta.Match.matchEqs] "subgoal {mkMVar mvarId}, {repr (← mvarId.getDecl).kind}, {← mvarId.isAssigned}\n{MessageData.ofGoal mvarId}"
    let (_, mvarId)  mvarId.intros
    let mvarId  mvarId.tryClearMany (alts.map (·.fvarId!))
    proveSubgoalLoop mvarId

My original code threw a failed to generate splitter for match auxiliary declaration error, instead of the infinite looping behaviour; this code suggests that the two problems are related.

Does anybody have any idea of what triggered this, and how to circumvent it?

Alex Keizer (Aug 08 2023 at 10:36):

Aha, explicitly spelling out the second case seems to work!

inductive Ty
  | nat

def Ctxt : Type :=
  List Ty


def Ctxt.Var (Γ : Ctxt) (t : Ty) : Type :=
  { i : Nat // Γ.get? i = some t }


def matchVar {t : Ty} : {Δ : Ctxt}  Δ.Var t  Option Bool
  | _::Δ, w+1, h => -- w† = Var.toSnoc w
      let w : Ctxt.Var Δ t := w, by simp_all[List.get?]⟩
      matchVar w
  | _, 0, _ =>
      none


example {Δ : Ctxt } {w : Δ.Var t} :
  matchVar w = none
    := by
        unfold matchVar -- Yay, it works
        sorry

Alex Keizer (Aug 08 2023 at 12:27):

This insight did not help to solve the original error I was seeing, here is another attempt at an MWE. This one depends on Mathlib and does showcase the exact error I'm having trouble with.

import Mathlib.Data.Erased

inductive Ty
  | nat

def Ctxt : Type :=
  Erased <| List Ty

noncomputable def Ctxt.snoc (Γ : Ctxt) (t : Ty) : Ctxt :=
  Erased.mk <| t :: Γ.out

def Ctxt.Var (Γ : Ctxt) (t : Ty) : Type :=
  { i : Nat // Γ.out.get? i = some t }

inductive CtxtProp : Ctxt  Type
  | nil : CtxtProp (Erased.mk [])
  | snoc : CtxtProp Δ  CtxtProp (Δ.snoc t)


noncomputable def matchVar {t : Ty} {Δ : Ctxt} : CtxtProp Δ  Δ.Var t  Option Bool
  | .snoc d, w+1, h => -- w† = Var.toSnoc w
      let w : Ctxt.Var _ t := w, by simp_all[List.get?, Ctxt.snoc]⟩
      matchVar d w
  -- Neither spelling out all cases, nor having a single fallback case work
  | .snoc _, 0, _ | .nil, 0, _ | .nil, _+1, h => none
  -- | _, _ => none


example {Δ : Ctxt} (d : CtxtProp Δ) {w : Δ.Var t} :
  matchVar d w = none
    := by
        unfold matchVar /- throws error:
          failed to generate splitter for match auxiliary declaration 'matchVar.match_1', unsolved subgoal:
            case x
            t: Ty
            motive: (Δ : Ctxt) → CtxtProp Δ → Ctxt.Var Δ t → Sort u_1
            h_1: (Γ : Ctxt) →
              (t_2 : Ty) →
                (d : CtxtProp Γ) →
                  (w : ℕ) →
                    (h : List.get? (Erased.out (Ctxt.snoc Γ t_2)) (w + 1) = some t) →
                      motive (Ctxt.snoc Γ t_2) (CtxtProp.snoc d) { val := Nat.succ w, property := h }
            h_2: (Γ : Ctxt) →
              (t_2 : Ty) →
                (a : CtxtProp Γ) →
                  (property : List.get? (Erased.out (Ctxt.snoc Γ t_2)) 0 = some t) →
                    (∀ (Γ_1 : Ctxt) (t_1 : Ty) (d : CtxtProp Γ_1) (w : ℕ)
                        (h : List.get? (Erased.out (Ctxt.snoc Γ_1 t_1)) (w + 1) = some t),
                        ((fun b => t_2 :: Erased.out Γ = b) = fun b => t_1 :: Erased.out Γ_1 = b) →
                          HEq (_ : ∃ a, (fun b => a = b) = fun b => t_2 :: Erased.out Γ = b)
                              (_ : ∃ a, (fun b => a = b) = fun b => t_1 :: Erased.out Γ_1 = b) →
                            HEq (CtxtProp.snoc a) (CtxtProp.snoc d) →
                              HEq { val := 0, property := property } { val := Nat.succ w, property := h } → False) →
                      motive (Ctxt.snoc Γ t_2) (CtxtProp.snoc a) { val := 0, property := property }
            h_3: (property : List.get? (Erased.out (Erased.mk [])) 0 = some t) →
              motive (Erased.mk []) CtxtProp.nil { val := 0, property := property }
            h_4: (n : ℕ) →
              (h : List.get? (Erased.out (Erased.mk [])) (n + 1) = some t) →
                motive (Erased.mk []) CtxtProp.nil { val := Nat.succ n, property := h }
            Δ✝: Ctxt
            t✝: Ty
            a✝: CtxtProp Δ✝
            x✝²: Ctxt.Var (Ctxt.snoc Δ✝ t✝) t
            val✝: ℕ
            property✝¹: List.get? (Erased.out (Ctxt.snoc Δ✝ t✝)) val✝ = some t
            property✝: List.get? (Erased.out (Ctxt.snoc Δ✝ t✝)) Nat.zero = some t
            Γ: Ctxt
            t_1: Ty
            d: CtxtProp Γ
            w: ℕ
            h: List.get? (Erased.out (Ctxt.snoc Γ t_1)) (w + 1) = some t
            fst_eq: (fun b => t✝ :: Erased.out Δ✝ = b) = fun b => t_1 :: Erased.out Γ = b
            snd_eq: HEq (_ : ∃ a, (fun b => a = b) = fun b => t✝ :: Erased.out Δ✝ = b)
              (_ : ∃ a, (fun b => a = b) = fun b => t_1 :: Erased.out Γ = b)
            x_1: HEq (CtxtProp.snoc a✝) (CtxtProp.snoc d)
            x_2: HEq { val := 0, property := property✝ } { val := Nat.succ w, property := h }
            x✝¹: CtxtProp (Ctxt.snoc Δ✝ t✝)
            x✝: Ctxt.Var (Ctxt.snoc Δ✝ t✝) t
            ⊢ False
        -/
        sorry

Alex Keizer (Aug 08 2023 at 12:31):

Tracing with trace.Meta.Match.matchEqs tells us Lean is repeatedly trying to apply injective to the fst_eq hypothesis of that goal, which fails because it's not an injective type.

Is there some attribute I can use to add a custom injectivity theorem for the injective tactic to use?


Last updated: Dec 20 2023 at 11:08 UTC