fix: E-matching patterns containing ground universe polymorphic patterns in grind (#9857)

This PR ensures `grind` can E-match patterns containing universe
polymorphic ground sub-patterns. For example, given
```
set_option pp.universes true in
attribute [grind?] Id.run_pure
```
the pattern
```
Id.run_pure.{u_1}: [@Id.run.{u_1} #1 (@pure.{u_1, u_1} `[Id.{u_1}] `[Applicative.toPure.{u_1, u_1}] _ #0)]
```
contains two nested universe polymorphic ground patterns
- `Id.{u_1}`
- `Applicative.toPure.{u_1, u_1}`

This kind of pattern is not common, but it occurs in core.
This commit is contained in:
Leonardo de Moura 2025-08-11 14:12:57 -07:00 committed by GitHub
parent 5abf4bb651
commit a157abbbc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 21 deletions

View file

@ -131,6 +131,33 @@ protected def _root_.Lean.Meta.Grind.GenPatternInfo.assign? (genInfo : GenPatter
let c ← assignDelayedEqProof? c genInfo.hIdx
return c
private def matchGroundPattern (pArg eArg : Expr) : GoalM Bool := do
/-
1) Remark:
We need to use `withReducibleAndInstances` because ground patterns are often instances.
Here is an example
```
instance : Max Nat where
max := Nat.max -- Redefined the instance
example (a : Nat) : max a a = a := by
grind
```
Possible future improvements:
- When `diagnostics` is true, try with `withDefault` and report issue if it succeeds.
- (minor) Only use `withReducibleAndInstances` if the argument is an implicit instance.
Potential issue: some user write `{_ : Class α}` when the instance can be inferred from
explicit arguments.
2) Remark:
If `pArg` contains universe metavariables, we use `withoutModifyingMCtx` to ensure the metavariables
are not assigned. These universe metavariables are created at `internalizePattern` for universe polymorphic
ground patterns. They are not common, but they occur in practice.
-/
if pArg.hasLevelMVar then
withoutModifyingMCtx <| withReducibleAndInstances <| isDefEq pArg eArg
else
isEqv pArg eArg <||> withReducibleAndInstances (isDefEq pArg eArg)
/-- Matches a pattern argument. See `matchArgs?`. -/
private def matchArg? (c : Choice) (pArg : Expr) (eArg : Expr) : OptionT GoalM Choice := do
if isPatternDontCare pArg then
@ -138,23 +165,7 @@ private def matchArg? (c : Choice) (pArg : Expr) (eArg : Expr) : OptionT GoalM C
else if pArg.isBVar then
assign? c pArg.bvarIdx! eArg
else if let some pArg := groundPattern? pArg then
/-
We need to use `withReducibleAndInstances` because ground patterns are often instances.
Here is an example
```
instance : Max Nat where
max := Nat.max -- Redefined the instance
example (a : Nat) : max a a = a := by
grind
```
Possible future improvements:
- When `diagnostics` is true, try with `withDefault` and report issue if it succeeds.
- (minor) Only use `withReducibleAndInstances` if the argument is an implicit instance.
Potential issue: some user write `{_ : Class α}` when the instance can be inferred from
explicit arguments.
-/
guard (← isEqv pArg eArg <||> withReducibleAndInstances (isDefEq pArg eArg))
guard (← matchGroundPattern pArg eArg)
return c
else if let some (pArg, k) := isOffsetPattern? pArg then
assert! Option.isNone <| isOffsetPattern? pArg
@ -165,7 +176,7 @@ private def matchArg? (c : Choice) (pArg : Expr) (eArg : Expr) : OptionT GoalM C
let c ← assign? c pArg.bvarIdx! eArg
genInfo.assign? c eArg
else if let some pArg := groundPattern? pArg then
guard (← isEqv pArg eArg <||> withReducibleAndInstances (isDefEq pArg eArg))
guard (← matchGroundPattern pArg eArg)
genInfo.assign? c eArg
else if let some (pArg, k) := isOffsetPattern? pArg then
return { c with cnstrs := .offset (some genInfo) pArg k eArg :: c.cnstrs }

View file

@ -12,6 +12,7 @@ public import Lean.Meta.LitValues
public import Lean.Meta.Match.MatcherInfo
public import Lean.Meta.Match.MatchEqsExt
public import Lean.Meta.Match.MatchEqs
public import Lean.Util.CollectLevelParams
public import Lean.Meta.Tactic.Grind.Types
public import Lean.Meta.Tactic.Grind.Util
public import Lean.Meta.Tactic.Grind.Canon
@ -151,7 +152,7 @@ private def mkENode' (e : Expr) (generation : Nat) : GoalM Unit :=
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
/-- Internalizes the nested ground terms in the given pattern. -/
private partial def internalizePattern (pattern : Expr) (generation : Nat) : GoalM Expr := do
private partial def internalizePattern (pattern : Expr) (generation : Nat) (origin : Origin) : GoalM Expr := do
-- Recall that it is important to ensure patterns are maximally shared since
-- we assume that in functions such as `getAppsOf` in `EMatch.lean`
go (← shareCommon pattern)
@ -161,7 +162,21 @@ where
return pattern
else if let some e := groundPattern? pattern then
let e ← preprocessLight e
internalize e generation none
let e ← if e.hasLevelParam && origin matches .decl _ then
/-
If `e` has universe parameters and it is **not** local. That is,
it contains the universe parameters of some global theorem.
Then, we convert `e`'s universe parameters into universe meta-variables.
Remark: it is pointless to internalize the result because it contains these helper meta-variables.
Remark: universe polymorphic ground patterns are not common, but they do occur in the
core library.
-/
let ps := collectLevelParams {} e |>.params
let us ← ps.mapM fun _ => mkFreshLevelMVar
pure <| e.instantiateLevelParamsArray ps us
else
internalize e generation none
pure e
return mkGroundPattern e
else pattern.withApp fun f args => do
return mkAppN f (← args.mapM go)
@ -203,7 +218,7 @@ def activateTheorem (thm : EMatchTheorem) (generation : Nat) : GoalM Unit := do
-- Recall that we use the proof as part of the key for a set of instances found so far.
-- We don't want to use structural equality when comparing keys.
let proof ← shareCommon thm.proof
let thm := { thm with proof, patterns := (← thm.patterns.mapM (internalizePattern · generation)) }
let thm := { thm with proof, patterns := (← thm.patterns.mapM (internalizePattern · generation thm.origin)) }
trace_goal[grind.ematch] "activated `{thm.origin.key}`, {thm.patterns.map ppPattern}"
modify fun s => { s with ematch.newThms := s.ematch.newThms.push thm }

View file

@ -0,0 +1,3 @@
/-! Test for E-matching patterns containing nested universe polymorphic ground patterns. -/
example : Id.run (pure true) = true := by
grind only [Id.run_pure]