Skip to content

Commit 447af45

Browse files
committed
feat: sparse sparse casesOn splitting in match equations
This PR makes sure that when a matcher is compiled using a sparse cases, that equation generation also uses sparse cases to split. This fixes #11665.
1 parent 7f46f4e commit 447af45

File tree

4 files changed

+98
-2
lines changed

4 files changed

+98
-2
lines changed

src/Lean/Meta/Constructions/SparseCasesOn.lean

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ public def mkSparseCasesOn (indName : Name) (ctors : Array Name) : MetaM Name :=
136136
addDecl (.defnDecl decl)
137137
modifyEnv fun env => sparseCasesOnCacheExt.modifyState env fun s => s.insert key declName
138138
setReducibleAttribute declName
139-
modifyEnv fun env => markAuxRecursor env declName -- TODO: is this right?
140139
modifyEnv fun env => markSparseCasesOn env declName
141140
modifyEnv fun env => sparseCasesOnInfoExt.insert env declName {
142141
indName

src/Lean/Meta/Match/MatchEqs.lean

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import Lean.Meta.Tactic.SplitIf
1414
import Lean.Meta.Tactic.CasesOnStuckLHS
1515
import Lean.Meta.Match.SimpH
1616
import Lean.Meta.Match.AltTelescopes
17-
import Lean.Meta.Match.SolveOverlap
1817
import Lean.Meta.Match.NamedPatterns
18+
import Lean.Meta.SplitSparseCasesOn
1919

2020
public section
2121

@@ -95,6 +95,10 @@ where
9595
<|>
9696
(casesOnStuckLHS mvarId)
9797
<|>
98+
(reduceSparseCasesOn mvarId)
99+
<|>
100+
(splitSparseCasesOn mvarId)
101+
<|>
98102
(do let mvarId' ← simpIfTarget mvarId (useDecide := true) (useNewSemantics := true)
99103
if mvarId' == mvarId then throwError "simpIf failed"
100104
return #[mvarId'])
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Joachim Breitner
5+
-/
6+
7+
module
8+
prelude
9+
public import Lean.Meta.Basic
10+
import Lean.Meta.Tactic.Delta
11+
import Lean.Meta.Tactic.Rewrite
12+
import Lean.Meta.Constructions.SparseCasesOn
13+
import Lean.Meta.Constructions.SparseCasesOnEq
14+
import Lean.Meta.HasNotBit
15+
import Lean.Meta.Tactic.Cases
16+
17+
namespace Lean.Meta
18+
19+
private def rewriteGoalUsingEq (goal : MVarId) (eq : Expr) (symm : Bool := false) : MetaM MVarId := do
20+
let rewriteResult ← goal.rewrite (←goal.getType) eq symm
21+
goal.replaceTargetEq rewriteResult.eNew rewriteResult.eqProof
22+
23+
/--
24+
Reduces a sparse casesOn applied to a concrete constructor.
25+
-/
26+
public def reduceSparseCasesOn (mvarId : MVarId) : MetaM (Array MVarId) := do
27+
let some (_, lhs) ← matchEqHEqLHS? (← mvarId.getType) | throwError "Target not an equality"
28+
lhs.withApp fun f xs => do
29+
let .const matchDeclName _ := f | throwError "Not a const application"
30+
let some sparseCasesOnInfo ← getSparseCasesOnInfo matchDeclName | throwError "Not a sparse casesOn application"
31+
withTraceNode `Meta.Match.matchEqs (msg := (return m!"{exceptEmoji ·} splitSparseCasesOn")) do
32+
if xs.size < sparseCasesOnInfo.arity then
33+
throwError "Not enough arguments for sparse casesOn application"
34+
let majorIdx := sparseCasesOnInfo.majorPos
35+
let major := xs[majorIdx]!
36+
let some ctorInfo ← isConstructorApp'? major
37+
| throwError "Major premise is not a constructor application:{indentExpr major}"
38+
if sparseCasesOnInfo.insterestingCtors.contains ctorInfo.name then
39+
let mvarId' ← mvarId.modifyTargetEqLHS fun lhs =>
40+
unfoldDefinition lhs
41+
return #[mvarId']
42+
else
43+
let sparseCasesOnEqName ← getSparseCasesOnEq matchDeclName
44+
let eqProof := mkConst sparseCasesOnEqName lhs.getAppFn.constLevels!
45+
let eqProof := mkAppN eqProof lhs.getAppArgs[:sparseCasesOnInfo.arity]
46+
let eqProof := mkApp eqProof (← mkHasNotBitProof (mkRawNatLit ctorInfo.cidx) (← sparseCasesOnInfo.insterestingCtors.mapM (fun n => return (← getConstInfoCtor n).cidx)))
47+
let mvarId' ← rewriteGoalUsingEq mvarId eqProof
48+
return #[mvarId']
49+
50+
public def splitSparseCasesOn (mvarId : MVarId) : MetaM (Array MVarId) := do
51+
let some (_, lhs) ← matchEqHEqLHS? (← mvarId.getType) | throwError "Target not an equality"
52+
lhs.withApp fun f xs => do
53+
let .const matchDeclName _ := f | throwError "Not a const application"
54+
let some sparseCasesOnInfo ← getSparseCasesOnInfo matchDeclName | throwError "Not a sparse casesOn application"
55+
withTraceNode `Meta.Match.matchEqs (msg := (return m!"{exceptEmoji ·} splitSparseCasesOn")) do
56+
try
57+
trace[Meta.Match.matchEqs] "splitSparseCasesOn running on\n{mvarId}"
58+
if xs.size < sparseCasesOnInfo.arity then
59+
throwError "Not enough arguments for sparse casesOn application"
60+
let majorIdx := sparseCasesOnInfo.majorPos
61+
unless xs[majorIdx]!.isFVar do
62+
throwError "Major premise is not a free variable:{indentExpr xs[majorIdx]!}"
63+
let fvarId := xs[majorIdx]!.fvarId!
64+
let subgoals ← mvarId.cases fvarId (interestingCtors? := sparseCasesOnInfo.insterestingCtors)
65+
subgoals.mapM fun s => s.mvarId.withContext do
66+
if s.ctorName.isNone then
67+
unless s.fields.size = 1 do
68+
throwError "Unexpected number of fields for catch-all branch: {s.fields}"
69+
let sparseCasesOnEqName ← getSparseCasesOnEq matchDeclName
70+
let some (_, lhs) ← matchEqHEqLHS? (← s.mvarId.getType) | throwError "Target not an equality"
71+
let eqProof := mkConst sparseCasesOnEqName lhs.getAppFn.constLevels!
72+
let eqProof := mkAppN eqProof lhs.getAppArgs[:sparseCasesOnInfo.arity]
73+
let eqProof := mkApp eqProof s.fields[0]!
74+
rewriteGoalUsingEq s.mvarId eqProof
75+
else
76+
s.mvarId.modifyTargetEqLHS fun lhs =>
77+
unfoldDefinition lhs
78+
catch e =>
79+
trace[Meta.Match.matchEqs] "splitSparseCasesOn failed{indentD e.toMessageData}"
80+
throw e

tests/lean/run/issue11665.lean

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
opaque opq : Nat → Nat
2+
3+
/-- A particular tricky inductive type -/
4+
inductive T : Nat → Type where
5+
| mk3 n : T (n + 1)
6+
| mk4 n : T (opq n) -- Removing this constructor makes the issue go away
7+
8+
def test1 : (n : Nat) → T n → Unit
9+
| Nat.succ _, T.mk3 _ => ()
10+
| _, _ => ()
11+
12+
def eqns := @test1.match_1.eq_1 -- used to fail
13+
def congreqns := @test1.match_1.congr_eq_1 -- used to faile

0 commit comments

Comments
 (0)