Skip to content

Commit f6e580c

Browse files
authored
refactor: extract functionality from Match.MatchEqs (#11236)
This PR extracts two modules from `Match.MatchEqs`, in preparation of #11220 and to use the module system to draw clear boundaries between concerns here.
1 parent 51ed5f2 commit f6e580c

File tree

4 files changed

+272
-241
lines changed

4 files changed

+272
-241
lines changed

src/Lean/Elab/PreDefinition/WF/Unfold.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ matcherArgPusher params motive {α} {β} (f : ∀ (x : α), β x) rel alt1 .. x1
9292
def mkMatchArgPusher (matcherName : Name) (matcherInfo : MatcherInfo) : MetaM Name := do
9393
let name := (mkPrivateName (← getEnv) matcherName) ++ `_arg_pusher
9494
realizeConst matcherName name do
95+
prependError m!"Cannot create match arg pusher for {matcherName}" do
9596
let matcherVal ← getConstVal matcherName
9697
forallBoundedTelescope matcherVal.type (some (matcherInfo.numParams + 1)) fun xs _ => do
9798
let params := xs[*...matcherInfo.numParams]

src/Lean/Meta/Match/MatchEqs.lean

Lines changed: 4 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,13 @@ public import Lean.Meta.Match.MatchEqsExt
1111
public import Lean.Meta.Tactic.Refl
1212
public import Lean.Meta.Tactic.Delta
1313
import Lean.Meta.Tactic.SplitIf
14+
import Lean.Meta.Match.SimpH
15+
import Lean.Meta.Match.SolveOverlap
1416

1517
public section
1618

1719
namespace Lean.Meta
1820

19-
/--
20-
A custom, approximated, and quick `contradiction` tactic.
21-
It only finds contradictions of the form `(h₁ : p)` and `(h₂ : ¬ p)` where
22-
`p`s are structurally equal. The procedure is not quadratic like `contradiction`.
23-
24-
We use it to improve the performance of `proveSubgoalLoop` at `mkSplitterProof`.
25-
We will eventually have to write more efficient proof automation for this module.
26-
The new proof automation should exploit the structure of the generated goals and avoid general purpose tactics
27-
such as `contradiction`.
28-
-/
29-
private def _root_.Lean.MVarId.contradictionQuick (mvarId : MVarId) : MetaM Bool := do
30-
mvarId.withContext do
31-
let mut posMap : Std.HashMap Expr FVarId := {}
32-
let mut negMap : Std.HashMap Expr FVarId := {}
33-
for localDecl in (← getLCtx) do
34-
unless localDecl.isImplementationDetail do
35-
if let some p ← matchNot? localDecl.type then
36-
if let some pFVarId := posMap[p]? then
37-
mvarId.assign (← mkAbsurd (← mvarId.getType) (mkFVar pFVarId) localDecl.toExpr)
38-
return true
39-
negMap := negMap.insert p localDecl.fvarId
40-
if (← isProp localDecl.type) then
41-
if let some nFVarId := negMap[localDecl.type]? then
42-
mvarId.assign (← mkAbsurd (← mvarId.getType) localDecl.toExpr (mkFVar nFVarId))
43-
return true
44-
posMap := posMap.insert localDecl.type localDecl.fvarId
45-
pure ()
46-
return false
47-
4821
/--
4922
Helper method for `proveCondEqThm`. Given a goal of the form `C.rec ... xMajor = rhs`,
5023
apply `cases xMajor`. -/
@@ -215,152 +188,6 @@ where
215188
else
216189
return e
217190

218-
namespace SimpH
219-
220-
/--
221-
State for the equational theorem hypothesis simplifier.
222-
223-
Recall that each equation contains additional hypotheses to ensure the associated case does not taken by previous cases.
224-
We have one hypothesis for each previous case.
225-
226-
Each hypothesis is of the form `forall xs, eqs → False`
227-
228-
We use tactics to minimize code duplication.
229-
-/
230-
structure State where
231-
mvarId : MVarId -- Goal representing the hypothesis
232-
xs : List FVarId -- Pattern variables for a previous case
233-
eqs : List FVarId -- Equations to be processed
234-
eqsNew : List FVarId := [] -- Simplified (already processed) equations
235-
236-
abbrev M := StateRefT State MetaM
237-
238-
/--
239-
Apply the given substitution to `fvarIds`.
240-
This is an auxiliary method for `substRHS`.
241-
-/
242-
private def applySubst (s : FVarSubst) (fvarIds : List FVarId) : List FVarId :=
243-
fvarIds.filterMap fun fvarId => match s.apply (mkFVar fvarId) with
244-
| Expr.fvar fvarId .. => some fvarId
245-
| _ => none
246-
247-
/--
248-
Given an equation of the form `lhs = rhs` where `rhs` is variable in `xs`,
249-
replace it everywhere with `lhs`.
250-
-/
251-
private def substRHS (eq : FVarId) (rhs : FVarId) : M Unit := do
252-
assert! (← get).xs.contains rhs
253-
let (subst, mvarId) ← substCore (← get).mvarId eq (symm := true)
254-
modify fun s => { s with
255-
mvarId,
256-
xs := applySubst subst (s.xs.erase rhs)
257-
eqs := applySubst subst s.eqs
258-
eqsNew := applySubst subst s.eqsNew
259-
}
260-
261-
private def isDone : M Bool :=
262-
return (← get).eqs.isEmpty
263-
264-
/-- Customized `contradiction` tactic for `simpH?` -/
265-
private def contradiction (mvarId : MVarId) : MetaM Bool :=
266-
mvarId.contradictionCore { genDiseq := false, emptyType := false }
267-
268-
/--
269-
Auxiliary tactic that tries to replace as many variables as possible and then apply `contradiction`.
270-
We use it to discard redundant hypotheses.
271-
-/
272-
partial def trySubstVarsAndContradiction (mvarId : MVarId) (forbidden : FVarIdSet := {}) : MetaM Bool :=
273-
commitWhen do
274-
let mvarId ← substVars mvarId
275-
match (← injections mvarId (forbidden := forbidden)) with
276-
| .solved => return true -- closed goal
277-
| .subgoal mvarId' _ forbidden =>
278-
if mvarId' == mvarId then
279-
contradiction mvarId
280-
else
281-
trySubstVarsAndContradiction mvarId' forbidden
282-
283-
private def processNextEq : M Bool := do
284-
let s ← get
285-
s.mvarId.withContext do
286-
if let eq :: eqs := s.eqs then
287-
modify fun s => { s with eqs }
288-
let eqType ← inferType (mkFVar eq)
289-
-- See `substRHS`. Recall that if `rhs` is a variable then if must be in `s.xs`
290-
if let some (_, lhs, rhs) ← matchEq? eqType then
291-
-- Common case: Different constructors
292-
match (← isConstructorApp? lhs), (← isConstructorApp? rhs) with
293-
| some lhsCtor, some rhsCtor =>
294-
if lhsCtor.name != rhsCtor.name then
295-
return false -- If the constructors are different, we can discard the hypothesis even if it a heterogeneous equality
296-
| _,_ => pure ()
297-
if (← isDefEq lhs rhs) then
298-
return true
299-
if rhs.isFVar && s.xs.contains rhs.fvarId! then
300-
substRHS eq rhs.fvarId!
301-
return true
302-
if let some (α, lhs, β, rhs) ← matchHEq? eqType then
303-
-- Try to convert `HEq` into `Eq`
304-
if (← isDefEq α β) then
305-
let (eqNew, mvarId) ← heqToEq s.mvarId eq (tryToClear := true)
306-
modify fun s => { s with mvarId, eqs := eqNew :: s.eqs }
307-
return true
308-
-- If it is not possible, we try to show the hypothesis is redundant by substituting even variables that are not at `s.xs`, and then use contradiction.
309-
else
310-
match (← isConstructorApp? lhs), (← isConstructorApp? rhs) with
311-
| some lhsCtor, some rhsCtor =>
312-
if lhsCtor.name != rhsCtor.name then
313-
return false -- If the constructors are different, we can discard the hypothesis even if it a heterogeneous equality
314-
else if (← trySubstVarsAndContradiction s.mvarId) then
315-
return false
316-
| _, _ =>
317-
if (← trySubstVarsAndContradiction s.mvarId) then
318-
return false
319-
try
320-
-- Try to simplify equation using `injection` tactic.
321-
match (← injection s.mvarId eq) with
322-
| InjectionResult.solved => return false
323-
| InjectionResult.subgoal mvarId eqNews .. =>
324-
modify fun s => { s with mvarId, eqs := eqNews.toList ++ s.eqs }
325-
catch _ =>
326-
modify fun s => { s with eqsNew := eq :: s.eqsNew }
327-
return true
328-
329-
partial def go : M Bool := do
330-
if (← isDone) then
331-
return true
332-
else if (← processNextEq) then
333-
go
334-
else
335-
return false
336-
337-
end SimpH
338-
339-
/--
340-
Auxiliary method for simplifying equational theorem hypotheses.
341-
342-
Recall that each equation contains additional hypotheses to ensure the associated case was not taken by previous cases.
343-
We have one hypothesis for each previous case.
344-
-/
345-
private partial def simpH? (h : Expr) (numEqs : Nat) : MetaM (Option Expr) := withDefault do
346-
let numVars ← forallTelescope h fun ys _ => pure (ys.size - numEqs)
347-
let mvarId := (← mkFreshExprSyntheticOpaqueMVar h).mvarId!
348-
let (xs, mvarId) ← mvarId.introN numVars
349-
let (eqs, mvarId) ← mvarId.introN numEqs
350-
let (r, s) ← SimpH.go |>.run { mvarId, xs := xs.toList, eqs := eqs.toList }
351-
if r then
352-
s.mvarId.withContext do
353-
let eqs := s.eqsNew.reverse.toArray.map mkFVar
354-
let mut r ← mkForallFVars eqs (mkConst ``False)
355-
/- We only include variables in `xs` if there is a dependency. -/
356-
for x in s.xs.reverse do
357-
if (← dependsOn r x) then
358-
r ← mkForallFVars #[mkFVar x] r
359-
trace[Meta.Match.matchEqs] "simplified hypothesis{indentExpr r}"
360-
check r
361-
return some r
362-
else
363-
return none
364191

365192
private def substSomeVar (mvarId : MVarId) : MetaM (Array MVarId) := mvarId.withContext do
366193
for localDecl in (← getLCtx) do
@@ -443,45 +270,6 @@ private partial def withSplitterAlts (altTypes : Array Expr) (f : Array Expr →
443270
f xs
444271
go 0 #[]
445272

446-
inductive InjectionAnyResult where
447-
| solved
448-
| failed
449-
/-- `fvarId` refers to the local declaration selected for the application of the `injection` tactic. -/
450-
| subgoal (fvarId : FVarId) (mvarId : MVarId)
451-
452-
private def injectionAnyCandidate? (type : Expr) : MetaM (Option (Expr × Expr)) := do
453-
if let some (_, lhs, rhs) ← matchEq? type then
454-
return some (lhs, rhs)
455-
else if let some (α, lhs, β, rhs) ← matchHEq? type then
456-
if (← isDefEq α β) then
457-
return some (lhs, rhs)
458-
return none
459-
460-
/--
461-
Try applying `injection` to a local declaration that is not in `forbidden`.
462-
463-
We use `forbidden` because the `injection` tactic might fail to clear the variable if there are forward dependencies.
464-
See `proveSubgoalLoop` for additional details.
465-
-/
466-
private def injectionAny (mvarId : MVarId) (forbidden : FVarIdSet := {}) : MetaM InjectionAnyResult := do
467-
mvarId.withContext do
468-
for localDecl in (← getLCtx) do
469-
unless forbidden.contains localDecl.fvarId do
470-
if let some (lhs, rhs) ← injectionAnyCandidate? localDecl.type then
471-
unless (← isDefEq lhs rhs) do
472-
let lhs ← whnf lhs
473-
let rhs ← whnf rhs
474-
unless lhs.isRawNatLit && rhs.isRawNatLit do
475-
try
476-
match (← injection mvarId localDecl.fvarId) with
477-
| InjectionResult.solved => return InjectionAnyResult.solved
478-
| InjectionResult.subgoal mvarId .. => return InjectionAnyResult.subgoal localDecl.fvarId mvarId
479-
catch ex =>
480-
trace[Meta.Match.matchEqs] "injectionAnyFailed at {localDecl.userName}, error\n{ex.toMessageData}"
481-
pure ()
482-
return InjectionAnyResult.failed
483-
484-
485273
private abbrev ConvertM := ReaderT (FVarIdMap (Expr × Nat × Array Bool)) $ StateRefT (Array MVarId) MetaM
486274

487275
/--
@@ -498,7 +286,8 @@ private partial def mkSplitterProof (matchDeclName : Name) (template : Expr) (al
498286
let (proof, mvarIds) ← convertTemplate template |>.run map |>.run #[]
499287
trace[Meta.Match.matchEqs] "splitter proof: {proof}"
500288
for mvarId in mvarIds do
501-
proveSubgoal mvarId
289+
let mvarId ← mvarId.tryClearMany (alts.map (·.fvarId!))
290+
solveOverlap mvarId
502291
instantiateMVars proof
503292
where
504293
mkMap : FVarIdMap (Expr × Nat × Array Bool) := Id.run do
@@ -680,32 +469,6 @@ where
680469
let eNew := mkAppN eNew mvars
681470
return TransformStep.done eNew
682471

683-
/-
684-
`forbidden` tracks variables that we have already applied `injection`.
685-
Recall that the `injection` tactic may not be able to eliminate them when
686-
they have forward dependencies.
687-
-/
688-
proveSubgoalLoop (mvarId : MVarId) (forbidden : FVarIdSet) : MetaM Unit := do
689-
trace[Meta.Match.matchEqs] "proveSubgoalLoop\n{mvarId}"
690-
if (← mvarId.contradictionQuick) then
691-
return ()
692-
match (← injectionAny mvarId forbidden) with
693-
| .solved => return ()
694-
| .failed =>
695-
let mvarId' ← substVars mvarId
696-
if mvarId' == mvarId then
697-
if (← mvarId.contradictionCore {}) then
698-
return ()
699-
throwError "failed to generate splitter for match auxiliary declaration `{matchDeclName}`, unsolved subgoal:\n{MessageData.ofGoal mvarId}"
700-
else
701-
proveSubgoalLoop mvarId' forbidden
702-
| .subgoal fvarId mvarId => proveSubgoalLoop mvarId (forbidden.insert fvarId)
703-
704-
proveSubgoal (mvarId : MVarId) : MetaM Unit := do
705-
trace[Meta.Match.matchEqs] "subgoal {mkMVar mvarId}, {repr (← mvarId.getDecl).kind}, {← mvarId.isAssigned}\n{MessageData.ofGoal mvarId}"
706-
let (_, mvarId) ← mvarId.intros
707-
let mvarId ← mvarId.tryClearMany (alts.map (·.fvarId!))
708-
proveSubgoalLoop mvarId {}
709472

710473
/--
711474
Create new alternatives (aka minor premises) by replacing `discrs` with `patterns` at `alts`.

0 commit comments

Comments
 (0)