fix: `grind` pattern validation
This PR fixes the `grind` pattern validator. It covers the case where
an instance is not tagged with the implicit instance binder. This
happens in declarations such as
```lean
ZeroMemClass.zero_mem {S : Type} {M : outParam Type} {inst1 : Zero M} {inst2 : SetLike S M}
[self : @ZeroMemClass S M inst1 inst2] (s : S) : 0 ∈ s
```
diff --git a/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean b/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean
index 29e38c7..5736432 100644
--- a/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean
+++ b/src/Lean/Meta/Tactic/Grind/EMatchTheorem.lean
@@ -662,10 +662,23 @@
if pinfos[idx].hasFwdDeps then return .typeFormer else return .relevant
else
return .typeFormer
- else if (← x.fvarId!.getDecl).binderInfo matches .instImplicit then
- return .instImplicit
else
- return .relevant
+ let xDecl ← x.fvarId!.getDecl
+ if xDecl.binderInfo matches .instImplicit then
+ return .instImplicit
+ /-
+ **Note**: Even if the binder is not marked as instance implicit, we may still
+ synthesize it using type class resolution. The motivation is declarations such as
+ ```
+ ZeroMemClass.zero_mem {S : Type} {M : outParam Type} {inst1 : Zero M} {inst2 : SetLike S M}
+ [self : @ZeroMemClass S M inst1 inst2] (s : S) : 0 ∈ s
+ ```
+ Recall that a similar approach is used in `simp`.
+ -/
+ else if (← isClass? xDecl.type).isSome then
+ return .instImplicit
+ else
+ return .relevant
private def getPatternFn? (pattern : Expr) (inSupport : Bool) (root : Bool) (argKind : PatternArgKind) : M (Option Expr) := do
if !pattern.isApp && !pattern.isConst then
@@ -860,27 +873,34 @@
for x in xs do
let fvarId := x.fvarId!
unless processed.contains fvarId do
- let xType ← inferType x
+ let xDecl ← fvarId.getDecl
+ let xType := xDecl.type
if fvarsFound.contains fvarId then
-- Collect free vars in `x`s type and mark as processed
fvarsFound := update fvarsFound xType
processed := processed.insert fvarId
modified := true
- else if (← fvarId.getDecl).binderInfo matches .instImplicit then
- -- If `x` is instance implicit, check whether
- -- we have found all free variables needed to synthesize instance
- if (← canBeSynthesized thmVars fvarsFound xType) then
- fvarsFound := fvarsFound.insert fvarId
- fvarsFound := update fvarsFound xType
- processed := processed.insert fvarId
- modified := true
- else if (← isProp xType) then
- -- If `x` is a proposition, and all theorem variables in `x`s type have already been found
- -- add it to `fvarsFound` and mark it as processed.
- if checkTypeFVars thmVars fvarsFound xType then
- fvarsFound := fvarsFound.insert fvarId
- processed := processed.insert fvarId
- modified := true
+ else
+ /- **Note**: See comment at `getPatternArgKinds` -/
+ let instImplicit ← if xDecl.binderInfo matches .instImplicit then
+ pure true
+ else
+ pure <| (← isClass? xType).isSome
+ if instImplicit then
+ -- If `x` is instance implicit, check whether
+ -- we have found all free variables needed to synthesize instance
+ if (← canBeSynthesized thmVars fvarsFound xType) then
+ fvarsFound := fvarsFound.insert fvarId
+ fvarsFound := update fvarsFound xType
+ processed := processed.insert fvarId
+ modified := true
+ else if (← isProp xType) then
+ -- If `x` is a proposition, and all theorem variables in `x`s type have already been found
+ -- add it to `fvarsFound` and mark it as processed.
+ if checkTypeFVars thmVars fvarsFound xType then
+ fvarsFound := fvarsFound.insert fvarId
+ processed := processed.insert fvarId
+ modified := true
if fvarsFound.size == numParams then
return .ok
if !modified then
diff --git a/tests/lean/run/grind_pattern_validation_instance.lean b/tests/lean/run/grind_pattern_validation_instance.lean
new file mode 100644
index 0000000..a78519d
--- /dev/null
+++ b/tests/lean/run/grind_pattern_validation_instance.lean
@@ -0,0 +1,58 @@
+/-!
+Test for `grind` pattern validation. It covers the case where
+an instance is not tagged with the implicit instance binder.
+This happens in declarations such as
+```lean
+ZeroMemClass.zero_mem {S : Type} {M : outParam Type} {inst1 : Zero M} {inst2 : SetLike S M}
+ [self : @ZeroMemClass S M inst1 inst2] (s : S) : 0 ∈ s
+```
+-/
+
+def Set (α : Type u) := α → Prop
+
+/-- Membership in a set -/
+protected def Set.Mem (s : Set α) (a : α) : Prop :=
+ s a
+
+instance : Membership α (Set α) :=
+ ⟨Set.Mem⟩
+
+class SetLike (A : Type) (B : outParam Type) where
+ protected coe : A → Set B
+ protected coe_injective : Function.Injective coe
+
+instance [SetLike A B]: CoeTC A (Set B) where coe := SetLike.coe
+
+instance [SetLike A B] : Membership B A :=
+ ⟨fun p x => x ∈ (p : Set B)⟩
+
+class ZeroMemClass (S : Type) (M : outParam Type) [Zero M] [SetLike S M] : Prop where
+ zero_mem : ∀ s : S, (0 : M) ∈ s
+
+class Ring (R : Type) extends Zero R
+
+@[ext]
+structure Subring (M : Type) [Ring M] where
+ carrier : Set M
+ zero_mem' : 0 ∈ carrier
+
+instance {M} [Ring M] : SetLike (Subring M) M where
+ coe := Subring.carrier
+ coe_injective a b h := by ext; assumption
+
+theorem Subring.zero_mem {M} [Ring M] (s : Subring M) : 0 ∈ s :=
+ s.zero_mem'
+
+grind_pattern ZeroMemClass.zero_mem => 0 ∈ s
+
+instance {M} [Ring M] : ZeroMemClass (Subring M) M where
+ zero_mem := Subring.zero_mem
+
+example {R : Type} [Ring R] (S : Subring R) : 0 ∈ S := by
+ grind
+
+example {R : Type} [Ring R] (S : Subring R) : 0 ∈ S := by
+ grind only [ZeroMemClass.zero_mem]
+
+example {R : Type} [Ring R] (S : Subring R) : 0 ∈ S := by
+ grind only [Subring.zero_mem]