leanprover / leanprover/lean4

"failed to synthesize instance" despite local instance

Open
#14,443 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P-low
Dominant language
Lean
Stars
9.2k
Forks
990
Avg merge
1d 17h
Merged PRs (30d)
175

Description

Prerequisites
Description

I'm working with complex instances, and synthesis fails even when I explicitly create a local instance.

Steps to Reproduce

I couldn't reduce the example further without losing the error. Update: I found a smaller reproducer, see the comment below.

Example
class IsNamedTypeDecl (α : Type) (TypeParam : outParam Type) where
  name : α →  String
  typeParams : α → List TypeParam

abbrev IsNamedTypeDecl.numParams {α TypeParam} [IsNamedTypeDecl α TypeParam] (decl : α) : Nat :=
  (IsNamedTypeDecl.typeParams decl).length

-- We have to use `List` rather than `Vector` due to https://www.github.com/leanprover/lean4/issues/1964.
inductive MyType (TypeParam NamedTypeDecl : Type) [IsNamedTypeDecl NamedTypeDecl TypeParam] where
  | typeParam (_ : TypeParam)
  | namedType (decl : NamedTypeDecl) (typeArgs : List (MyType TypeParam NamedTypeDecl))

abbrev MyType.instantiate {TypeParam NamedTypeDecl} [IsNamedTypeDecl NamedTypeDecl TypeParam]
    (decl : NamedTypeDecl) (typeArgs : Vector (MyType TypeParam NamedTypeDecl) (IsNamedTypeDecl.numParams decl)) :
    MyType TypeParam NamedTypeDecl :=
  .namedType decl typeArgs.toList

instance (TypeParam NamedTypeDecl) [IsNamedTypeDecl NamedTypeDecl TypeParam]
    [DecidableEq TypeParam] [DecidableEq NamedTypeDecl] :
    DecidableEq (MyType TypeParam NamedTypeDecl) :=
  go
where
  go l r := by
    cases l <;> cases r
    any_goals exact .isFalse (fun h => MyType.noConfusion rfl rfl (heq_of_eq rfl) (heq_of_eq h))
    next => rw [MyType.typeParam.injEq]; exact inferInstance
    next declₗ typeArgsₗ declᵣ typeArgsᵣ =>
      let := List_hasDecEq typeArgsₗ typeArgsᵣ
      rw [MyType.namedType.injEq]; exact inferInstance
  termination_by structural l
  List_hasDecEq : (a b : List _) → Decidable (Eq a b)
      | .nil,       .nil       => isTrue rfl
      | .cons _ _,  .nil       => isFalse (fun h => List.noConfusion rfl (heq_of_eq h))
      | .nil,       .cons _ _  => isFalse (fun h => List.noConfusion rfl (heq_of_eq h))
      | .cons a as, .cons b bs =>
        match (go a b) with
        | isTrue hab  =>
          match List_hasDecEq as bs with
          | isTrue habs  => isTrue (hab ▸ habs ▸ rfl)
          | isFalse nabs => isFalse (fun h => List.noConfusion rfl (heq_of_eq h) (fun _ habs => absurd (eq_of_heq habs) nabs))
        | isFalse nab => isFalse (fun h => List.noConfusion rfl (heq_of_eq h) (fun hab _ => absurd (eq_of_heq hab)   nab))

-- Somewhat similar to `Vector.instDecidableExistsVectorSucc`, but for a specific form of `P` to allow the search to be pruned (and supports arbitrary `n`).
instance (priority := high) Vector_instDecidableForallVectorIdx' {n α} (Q : Fin n → α → Prop) (P : (xs : Vector α n) → (∀ (i : Fin n), Q i xs[i]) → Prop)
    [∀ i x, Decidable (Q i x)] [∀ xs hQxs, Decidable (P xs hQxs)] [∀ R, [DecidablePred R] → Decidable (∀ (x : α), R x)] :
    Decidable (∀ xs, (hQxs : ∀ (i : Fin n), Q i xs[i]) → P xs hQxs) := by
  sorry

def Sound {TypeParam NamedTypeDecl} [IsNamedTypeDecl NamedTypeDecl TypeParam]
    (alg : MyType TypeParam NamedTypeDecl → List String)
    (decl : NamedTypeDecl) :
    Prop :=
  ∀ (argsₗ : Vector (MyType ..) (IsNamedTypeDecl.numParams decl))
    (h_argsₗ : ∀ (i : Fin (IsNamedTypeDecl.numParams decl)), alg argsₗ[i] ⊆ alg (.typeParam (IsNamedTypeDecl.typeParams decl)[i]))
    (argsᵤ : Vector (MyType ..) (IsNamedTypeDecl.numParams decl))
    (h_argsᵤ : ∀ (i : Fin (IsNamedTypeDecl.numParams decl)), alg argsᵤ[i] ⊆ alg (.typeParam (IsNamedTypeDecl.typeParams decl)[i]) ∧ (argsᵤ[i] matches .typeParam _ ∨ argsᵤ[i] = argsₗ[i])),
  alg (.instantiate decl argsₗ) ⊆ alg (.instantiate decl argsᵤ)

set_option synthInstance.maxHeartbeats 1000000
set_option synthInstance.maxSize 10000
--set_option trace.Meta.synthInstance true

instance {TypeParam NamedTypeDecl} [IsNamedTypeDecl NamedTypeDecl TypeParam]
    [DecidableEq TypeParam] [DecidableEq NamedTypeDecl]
    [∀ (R : MyType TypeParam NamedTypeDecl → Prop), Decidable (∀ x, R x)]
    (alg : MyType TypeParam NamedTypeDecl → List String)
    (decl : NamedTypeDecl) :
    Decidable (Sound alg decl) := by
  unfold Sound
  let localInstance : -- The type was copied directly from the Lean error message.
      (xs : Vector (MyType TypeParam NamedTypeDecl) (IsNamedTypeDecl.numParams decl)) →
      (hQxs :
          ∀ (i : Fin (IsNamedTypeDecl.numParams decl)),
            alg xs[i] ⊆ alg (MyType.typeParam (IsNamedTypeDecl.typeParams decl)[i])) →
        Decidable
          (∀ (argsᵤ : Vector (MyType TypeParam NamedTypeDecl) (IsNamedTypeDecl.numParams decl)),
            (∀ (i : Fin (IsNamedTypeDecl.numParams decl)),
                alg argsᵤ[i] ⊆ alg (MyType.typeParam (IsNamedTypeDecl.typeParams decl)[i]) ∧
                  ((match argsᵤ[i] with
                      | MyType.typeParam x => true
                      | x => false) =
                      true ∨
                    argsᵤ[i] = xs[i])) →
              alg (MyType.instantiate decl xs) ⊆ alg (MyType.instantiate decl argsᵤ)) :=
    fun xs hQxs => by
      apply Vector_instDecidableForallVectorIdx' (Q := fun i x => alg x ⊆ alg _ ∧ ((x matches .typeParam _ ∨ x = _)))
  apply @Vector_instDecidableForallVectorIdx' _ _
    (Q := fun i x => alg x ⊆ alg (.typeParam (IsNamedTypeDecl.typeParams decl)[i]))
    (P := fun argsₗ h_argsₗ =>
      ∀ (argsᵤ : Vector (MyType TypeParam NamedTypeDecl) (IsNamedTypeDecl.numParams decl))
      (h_argsᵤ :
        ∀ (i : Fin (IsNamedTypeDecl.numParams decl)),
          alg argsᵤ[i] ⊆ alg (MyType.typeParam (IsNamedTypeDecl.typeParams decl)[i]) ∧
            ((match argsᵤ[i] with
                | MyType.typeParam x => true
                | x => false) =
                true ∨
              argsᵤ[i] = argsₗ[i])),
      alg (.instantiate decl argsₗ) ⊆ alg (.instantiate decl argsᵤ))
    _
    _ -- changing this underscore to `localInstance` makes the error go away
    _

Error message: "failed to synthesize instance of type class ..."

Changing the second-to-last underscore to localInstance makes the error go away.

I tried increasing synthInstance.maxHeartbeats and synthInstance.maxSize as shown but it didn't help.

Versions

Nightly 2026-07-18, v4.32.0, 4.28.1.

Additional Information

In my original example (before I minimised it), I saw a very puzzling message from trace.Meta.synthInstance, it said "apply localInstance to ... result type X is not definitionally equal to Y" where X and Y are long but identical. I can post this example if it's useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the self-contained reproducer in the issue, comparing the second-to-last underscore with localInstance, and enabling trace.Meta.synthInstance if needed. Investigate the typeclass synthesis path and the reported definitionally-equal result types; done means the behavior is explained and the reproducer has a verified resolution or regression test.

Written by the indexing model from the issue text.

Assessment

Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.