lean4-htt/tests/elab/isDefEqProjIssue.lean
Wojciech Różowski 3fc99eef10
feat: add instance validation checks in addInstance (#13389)
This PR adds two validation checks to `addInstance` that provide early
feedback for common mistakes in instance declarations:

1. **Non-class instance check**: errors when an instance target type is
not a type class. This catches the common mistake of writing `instance`
for a plain structure. Previously handled by the `nonClassInstance`
linter in Batteries (`Batteries.Tactic.Lint.TypeClass`), this is now
checked directly at declaration time.

2. **Impossible argument check**: errors when an instance has arguments
that cannot be inferred by instance synthesis. Specifically, it flags
arguments that are not instance-implicit and do not appear in any
subsequent instance-implicit argument or in the return type. Previously
such instances would be silently accepted but could never be
synthesised.

Supersedes #13237 and #13333.
2026-04-16 17:48:16 +00:00

105 lines
3.1 KiB
Text

import Lean
open Lean
-- We need a structure as this is related to isDefEq problems of the form `e1.proj =?= e2.proj`.
structure Test where
x : Nat
-- We need a data structure with functions that are not meant for reduction purposes.
abbrev Cache := Std.HashMap Nat Test
def Cache.insert (cache : Cache) (key : Nat) (val : Test) : Cache :=
Std.HashMap.insert cache key val
def Cache.find? (cache : Cache) (key : Nat) : Option Test :=
cache[key]?
-- This function just contains a call to a function that we definitely do not want to reduce.
-- To illustrate that the problem is actually noticeable there are multiple implementations provided.
-- Each of these implementations does additional modifications on the cache before looking things up,
-- as one might expect in irl functions.
-- Each version has a lot of additional complexity from the type checkers POV.
def barImpl1 (cache : Cache) (key : Nat) : Test :=
match cache.find? key with
| some val => val
| none => ⟨0⟩
def barImpl2 (cache : Cache) (key : Nat) : Test :=
match (cache.insert key ⟨0⟩).find? key with
| some val => val
| none => ⟨0⟩
def barImpl3 (cache : Cache) (key : Nat) : Test :=
match ((cache.insert key ⟨0⟩).insert 0 ⟨0⟩).find? key with
| some val => val
| none => ⟨0⟩
def barImpl4 (cache : Cache) (key : Nat) : Test :=
match (((cache.insert key ⟨0⟩).insert 0 ⟨0⟩).insert key ⟨key⟩).find? key with
| some val => val
| none => ⟨0⟩
def bar := barImpl4
set_option maxHeartbeats 400 in
def test (c1 : Cache) (key : Nat) : Nat :=
go c1 key
where
go (c1 : Cache) (key : Nat) : Nat :=
let val : Test := bar c1 key
have : val.x = (bar c1 key).x := rfl
val.x
/--
error: (deterministic) timeout at `whnf`, maximum number of heartbeats (400) has been reached
Note: Use `set_option maxHeartbeats <num>` to set the limit.
Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command.
-/
#guard_msgs in
set_option backward.isDefEq.lazyWhnfCore false in
set_option maxHeartbeats 400 in
def test' (c1 : Cache) (key : Nat) : Nat :=
go c1 key
where
go (c1 : Cache) (key : Nat) : Nat :=
let val : Test := bar c1 key
have : val.x = (bar c1 key).x := rfl
val.x
def ack : Nat → Nat → Nat
| 0, y => y+1
| x+1, 0 => ack x 1
| x+1, y+1 => ack x (ack (x+1) y)
class Foo where
x : Nat
y : Nat
@[reducible]
def f (x : Nat) : Foo :=
{ x, y := ack 10 10 }
@[reducible]
def g (x : Nat) : Foo :=
{ x, y := ack 10 11 }
open Lean Meta
set_option maxHeartbeats 500 in
run_meta do
withLocalDeclD `x (mkConst ``Nat) fun x => do
let lhs := Expr.proj ``Foo 0 <| mkApp (mkConst ``f) x
let rhs := Expr.proj ``Foo 0 <| mkApp (mkConst ``g) x
assert! (← isDefEq lhs rhs)
run_meta do
withLocalDeclD `x (mkConst ``Nat) fun x => do
let lhs := Expr.proj ``Foo 0 <| mkApp (mkConst ``f) x
let rhs := Expr.proj ``Foo 0 <| mkApp (mkConst ``g) x
match Kernel.isDefEq (← getEnv) {} lhs rhs with
| .ok b => assert! b
| .error _ => throwError "failed"
example : (f x).1 = (g x).1 :=
rfl