leanprover / leanprover/lean4

Unification sees through type synonyms when checking instances' types

Open
#14,949 0 comments 0 reactions 1 assignee View on GitHub

@datokrat is already working on this.

Since Aug 28, 2026.

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

Description

Prerequisites
Description

Unification of (?inst).toMyClass =?= i.toMyClass assigns ?inst := i whenever ?inst : A and i : B have the same type at implicit transparency, not instance transparency. In particular, i : B might not be the instance expected for an instance-implicit argument of type A.

Context

This problem occurred in Mathlib/Geometry/Manifold/VectorBundle/CovariantDerivative/LeviCivita.lean.

Steps to Reproduce

Mathlib-free reproducer:

class Topo (α : Type) where nhd : α → α → Prop
class Nrm (α : Type) extends Topo α where norm : α → Nat
structure Total {B : Type} (E : B → Type) where
  base : B
  fib  : E base

class Fib {B : Type} (E : B → Type) [∀ b, Topo (E b)] where chart : B → B

instance totalTopo {B : Type} [Topo B] {E : B → Type} [∀ b, Topo (E b)] [Fib E] :
    Topo (Total E) := ⟨fun _ _ => True⟩

def Diff {M N : Type} [Topo M] [Topo N] (_f : M → N) : Prop := True

theorem inner_bundle {B : Type} [Topo B] {E : B → Type}
    [nE : ∀ b, Nrm (E b)] [Fib E] {f g : ∀ b, E b}
    (_hf : Diff fun b => (⟨b, f b⟩ : Total E))
    (_hg : Diff fun b => (⟨b, g b⟩ : Total E)) :
    ∀ b, (nE b).norm (f b) = (nE b).norm (g b) := sorry

def Tan (E : Type) (B : Type) (_b : B) : Type := E

instance tanTopo (E : Type) [nE : Nrm E] (B : Type) (b : B) : Topo (Tan E B b) := nE.toTopo
instance tanFib (E : Type) [Nrm E] (B : Type) : Fib (Tan E B) := ⟨id⟩

class RB {B : Type} (E : B → Type) [∀ b, Topo (E b)] where g : ∀ b, E b → Nat

instance rbNrm {B : Type} {E : B → Type} [tE : ∀ b, Topo (E b)] [rb : RB E] (b : B) : Nrm (E b) where
  toTopo := tE b
  norm := rb.g b

section
variable (E : Type) [Nrm E] (B : Type) [Topo B] [RB (Tan E B)] (f g : ∀ b, Tan E B b)

/--
error: synthesized type class instance is not definitionally equal to expression inferred by typing rules, synthesized
  fun b => rbNrm b
inferred
  fun b => inst✝²
-/
#guard_msgs in
example (hf : Diff fun b => (⟨b, f b⟩ : Total (Tan E B)))
        (hg : Diff fun b => (⟨b, g b⟩ : Total (Tan E B))) :=
  inner_bundle hf hg

-- works with an explicit expected type, which enforces the expected instance
example (hf : Diff fun b => (⟨b, f b⟩ : Total (Tan E B)))
        (hg : Diff fun b => (⟨b, g b⟩ : Total (Tan E B))) :
    ∀ b, (rbNrm b).norm (f b) = (rbNrm b).norm (g b) :=
  inner_bundle hf hg

end

Reproducer closer to Mathlib:

module

public import Mathlib.Analysis.InnerProductSpace.Dual
public import Mathlib.Geometry.Manifold.VectorBundle.Riemannian
public import Mathlib.Geometry.Manifold.Riemannian.Basic
public import Mathlib.Geometry.Manifold.VectorField.LieBracket
meta import Lean.PostprocessTraces
open Lean.PostprocessTraces

open Bundle FiberBundle Function NormedSpace VectorField

open scoped Manifold ContDiff Bundle
variable
  {E : Type*} [NormedAddCommGroup E] [NormedSpace ℝ E]
  {H : Type*} [TopologicalSpace H] {I : ModelWithCorners ℝ E H}
  {M : Type*} [EMetricSpace M] [ChartedSpace H M] [IsManifold I ∞ M]
  [RiemannianBundle (fun (x : M) ↦ TangentSpace I x)]
  [IsContMDiffRiemannianBundle I ∞ E (fun (x : M) ↦ TangentSpace I x)]
  [IsRiemannianManifold I M]
  {X Y Z : Π x : M, TangentSpace I x}

local notation "⟪" X ", " Y "⟫" => fun x ↦ inner ℝ (X x) (Y x)

lemma _root_.MDifferentiableAt.inner_bundle' {x : M} {X Y : Π x : M, TangentSpace I x}
    (hX : MDiffAt (T% X) x) (hY : MDiffAt (T% Y) x) :
    MDiffAt ⟪X, Y⟫ x :=
  MDifferentiableAt.inner_bundle hX hY


variable {x : M} (hY : MDiffAt (T% Y) x)  (hZ : MDiffAt (T% Z) x)

postprocess_traces
  filterSubtrees (ofClass `Meta.isDefEq.assign.checkTypes)
  >=> exposeSubtrees (fun x => ofClass `Meta.isDefEq.assign.checkTypes x <&&> containsString "NormedAddCommGroup (TangentSpace I x)" x)
in
set_option trace.Meta.isDefEq.assign.checkTypes true in
set_option trace.Meta.isDefEq.printTransparency true in
set_option trace.Meta.isDefEq true in
set_option trace.Meta.synthInstance true in
#check hY.inner_bundle hZ

Expected behavior:

  • Lean's errors should be easy to understand and fix.
  • Possibly: Lean should not assign instances of the wrong type (at instance transparency) to an instance-implicit metavariable during unification.
  • Lean should not assign instances during unification that it will reject at a later point.

Actual behavior:

From the Mathlib reproducer's trace:

``lean
[] ❌️ [default] (instNormedAddCommGroupOfRiemannianBundleOfIsTopologicalAddGroupOfContinuousConstSMulReal
b).toMetricSpace =?= inst✝⁸.toMetricSpace ▶
[] ✅️ [default] NormedAddCommGroup.toMetricSpace.1 =?= inst✝⁸.toMetricSpace.1 ▼
[] ✅️ [default] TangentSpace I b =?= E ▶
[synthInstance] ✅️ (x : M) → NormedAddCommGroup (TangentSpace I x) ▶
[] ❌️ [default] instNormedAddCommGroupOfRiemannianBundleOfIsTopologicalAddGroupOfContinuousConstSMulReal b =?= inst✝⁸ ▶
[] ✅️ [default] (?m.114 b).3 =?= inst✝⁸.3 ▼
[] ?m.114 b [assignable] =?= inst✝⁸ [nonassignable]
[assign.checkTypes] ✅️ (?m.114 : (x : M) → NormedAddCommGroup (TangentSpace I x)) := (fun b => inst✝⁸ : M → NormedAddCommGroup E) ▼


Confronted with the unification problem `(someInstanceDerivedFrom ?i).toMetricSpace =?= (someInstanceDerivedFrom j).toMetricSpace` and speculatively tries `?i =?= j`. The assignment succeeds. Later, the term elaborator checks whether `?i`'s assigned instance matches the synthesized instance, which it doesn't, and it fails. The instance assigned by unification is an unexpected, wrong, instance.

This does *not* happen during instance search, in contrast to issue #9077.

### Versions

Lean 4.35.0-nightly-2026-08-27
Target: x86_64-unknown-linux-gnu Linux


### Additional Information

I'm already working on finding a fix for this issue and opened this for documentation purposes.

### Impact

Add :+1: to [issues you consider important](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). If others are impacted by this issue, please ask them to add :+1: to it.

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.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.