Skip to content

Regression in v4.33.0-rc1: simp changes the type of hoisted proof #14467

Open

Description

@TWal

Prerequisites

Description

When updating DyLean to v4.33.0-rc1, I noticed the following regression:
when writing a proof inside a function definition, it seems that Lean hoists the proof in an auxiliary ._proof_1 theorem, however when proving this theorem using simp, the type of the hoisted theorem can change. See the example below.

opaque Bytes: Type
opaque Bytes.length: Bytes → Nat

axiom foo
  {a: Type}
  (f: a → Bytes)
  (len: Nat)
  (h: ∀ x, (f x).length = len) 
  : a

def f {len: Nat} (b: { b: Bytes // b.length = len}): Bytes :=
  b.val

noncomputable
def bar (len: Nat) : ({ b: Bytes // b.length = len}) :=
  foo f len (by
    simp [f]
    -- fix: instead use grind [f]
  )


set_option linter.tacticCheckInstances true

/--
warning: produced tactic goal is not type-correct at `.implicit` transparency; consider using propositional rewriting or marking some of the following as `@[implicit_reducible]`:
  f
Full error:
  Application type mismatch: The argument
    bar._proof_1 len
  has type
    ∀ (x : { a // a.length = len }), @Eq Nat x.val.length len
  but is expected to have type
    ∀ (x : { b // b.length = len }), @Eq Nat (f x).length len
  in the application
    foo f len ⋯

Note: This linter can be disabled with `set_option linter.tacticCheckInstances false`
-/
#guard_msgs in
example (len: Nat): bar len = bar len := by
  unfold bar
  rfl

-- diagnostic: bar._proof_1 has the wrong type!
-- should be: .................................................. : (f x).length = len -/
/-- info: bar._proof_1 (len : Nat) (x : { a // a.length = len }) : x.val.length = len -/
#guard_msgs in
#check bar._proof_1

Context

Unlikely to stem from #13895? Because adding the option set_option backward.isDefEq.respectTransparency.types false does not fix the issue.

Steps to Reproduce

Expected behavior: using simp or any other tactic does not change the type of the hoisted theorem

Actual behavior: using simp changes the type of the hoisted theorem, and leaks the definition of f

Versions

  • "4.33.0-rc1"
  • "4.34.0, commit 323137b" (on live.lean-lang.org)

Impact

Add 👍 to issues you consider important. If others are impacted by this issue, please ask them to add 👍 to it.

Activity

  1. added
    bugSomething isn't working
    on Jul 20, 2026
  2. Kha commented on Jul 21, 2026

    @Kha
    Member

    The impact of this issue is not clear to me, is the actual problem that the linter is triggered? How did you determine that previous version used a different type?

  3. TWal commented on Jul 21, 2026

    @TWal
    ContributorAuthor

    Ah indeed, you are correct: it is not a regression, in that v4.32.0 and v4.31.0 also show the same behavior.

    However, this behavior combined with #13895 creates problems down the line, for example:

    • deriving instance does not work
    • some simple theorems proved by simp do not work anymore

    Theses are a reproduction of the actual problems I encountered in DyLean, but I am sure we can find many more. For now, my workaround has been to use grind instead of simp for my proof internal to bar (shown below). But given that Lean comes with proof irrelevance, I find it weird that the tactics chosen for this inner proof actually impacts downstream proofs abour bar.

    opaque Bytes: Type
    opaque Bytes.length: Bytes → Nat
    
    structure F (α: Type) where
      serialize: α → Bytes
    class C {α: Type} (β: F α) where
    axiom wf {α: Type} (β: F α): Prop
    
    axiom foo {a: Type} (f: F a) (len: Nat) (h: ∀ x, (f.serialize x).length = len): F a
    
    instance {a: Type} (f: F a) (len: Nat) (h: ∀ x, (f.serialize x).length = len): C (foo f len h) where
    
    axiom foo_wf {a: Type} (f: F a) (len: Nat) (h: ∀ x, (f.serialize x).length = len): wf (foo f len h) = wf f
    
    def f (len: Nat): F { b: Bytes // b.length = len} where
      serialize b := b.val
    
    axiom f_wf (len: Nat): wf (f len)
    
    section WithSimp
    
    /--
    error: failed to synthesize instance of type class
      C (foo (f len) len ⋯)
    
    Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
    ---
    error: Failed to delta derive `C` instance for `bar`.
    
    Note: Delta deriving tries the following strategies: (1) inserting the definition into each explicit non-out-param parameter of a class and (2) unfolding definitions further.
    -/
    #guard_msgs in
    noncomputable
    def bar (len: Nat) : F {b: Bytes // b.length = len} :=
      foo (f len) len (by
        simp [f]
      )
    deriving C
    
    /--
    error: unsolved goals
    len : Nat
    ⊢ wf (foo (f len) len ⋯)
    ---
    warning: This simp argument is unused:
      foo_wf
    
    Hint: Omit it from the simp argument list.
      [apply] simp only [bar, f_wf]
    
    Note: This linter can be disabled with `set_option linter.unusedSimpArgs false`
    ---
    warning: This simp argument is unused:
      f_wf
    
    Hint: Omit it from the simp argument list.
      [apply] simp only [bar, foo_wf]
    
    Note: This linter can be disabled with `set_option linter.unusedSimpArgs false`
    -/
    #guard_msgs in
    theorem bar_wf (len: Nat): wf (bar len) := by
      simp only [bar, foo_wf, f_wf]
    
    end WithSimp
    
    section WithGrind
    
    noncomputable
    def baz (len: Nat) : F {b: Bytes // b.length = len} :=
      foo (f len) len (by
        grind [f]
      )
    deriving C
    
    theorem baz_wf (len: Nat): wf (baz len) := by
      simp only [baz, foo_wf, f_wf]
    
    end WithGrind
  4. TWal commented on Jul 21, 2026

    @TWal
    ContributorAuthor

    I found a general workaround: start the tactic with apply id, like this:

    noncomputable
    def bar (len: Nat) : F {b: Bytes // b.length = len} :=
      foo (f len) len (by
        apply id
        simp [f]
      )
    deriving C
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions