Skip to content

Commit 47dbcd4

Browse files
authored
feat: finish? and grind? infrastructure (#10747)
This PR implements infrastructure for `finish?` and `grind?` tactics.
1 parent 4f7d3bb commit 47dbcd4

File tree

10 files changed

+100
-35
lines changed

10 files changed

+100
-35
lines changed

src/Init/Meta.lean

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@ Authors: Leonardo de Moura and Sebastian Ullrich
66
Additional goodies for writing macros
77
-/
88
module
9-
109
prelude
1110
public import Init.Meta.Defs
1211
public meta import Init.Meta.Defs
1312
public import Init.Tactics
14-
1513
public section
16-
1714
namespace Lean
1815

1916
macro_rules

src/Lean/Meta/Tactic/Grind/AC.lean

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ builtin_initialize registerTraceClass `grind.debug.ac.eq
3434
builtin_initialize
3535
acExt.setMethods
3636
(internalize := AC.internalize)
37-
(newEq := AC.processNewEq)
38-
(newDiseq := AC.processNewDiseq)
39-
(check := AC.check)
40-
(checkInv := AC.checkInvariants)
37+
(newEq := AC.processNewEq)
38+
(newDiseq := AC.processNewDiseq)
39+
(check := AC.check)
40+
(checkInv := AC.checkInvariants)
41+
(mkTactic? := return some (← `(grind| ac)))
4142

4243
end Lean.Meta.Grind.AC

src/Lean/Meta/Tactic/Grind/Arith/CommRing.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,6 @@ builtin_initialize
5555
(newDiseq := CommRing.processNewDiseq)
5656
(check := CommRing.check)
5757
(checkInv := CommRing.checkInvariants)
58+
(mkTactic? := return some (← `(grind| ring)))
5859

5960
end Lean.Meta.Grind.Arith.CommRing

src/Lean/Meta/Tactic/Grind/Arith/Cutsat.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,6 @@ builtin_initialize
5252
(check := Cutsat.check)
5353
(checkInv := Cutsat.checkInvariants)
5454
(mbtc := Cutsat.mbtc)
55+
(mkTactic? := return some (← `(grind| lia)))
5556

5657
end Lean.Meta.Grind.Arith.Cutsat

src/Lean/Meta/Tactic/Grind/Arith/Linear.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ builtin_initialize
5050
(check := Linear.check)
5151
(checkInv := Linear.checkInvariants)
5252
(mbtc := Linear.mbtc)
53+
(mkTactic? := return some (← `(grind| linarith)))
5354

5455
end Lean.Meta.Grind.Arith.Linear

src/Lean/Meta/Tactic/Grind/Canon.lean

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ Authors: Leonardo de Moura
55
-/
66
module
77
prelude
8-
public import Init.Grind.Util
9-
public import Lean.Meta.Basic
10-
public import Lean.Meta.FunInfo
11-
public import Lean.Util.FVarSubset
12-
public import Lean.Util.PtrSet
13-
public import Lean.Util.FVarSubset
148
public import Lean.Meta.Tactic.Grind.Types
9+
import Init.Grind.Util
10+
import Lean.Meta.Basic
11+
import Lean.Meta.FunInfo
12+
import Lean.Util.FVarSubset
13+
import Lean.Util.PtrSet
14+
import Lean.Util.FVarSubset
1515
import Lean.Meta.IntInstTesters
1616
import Lean.Meta.NatInstTesters
1717
public section

src/Lean/Meta/Tactic/Grind/SearchM.lean

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,33 @@ public import Lean.Meta.Tactic.Grind.Types
99
import Lean.Util.ForEachExpr
1010
public section
1111
namespace Lean.Meta.Grind
12+
13+
/-- Helper type for implementing `finish?` and `grind?` -/
14+
inductive ProofStep where
15+
| solver (id : Nat)
16+
| lookahead | mbtc
17+
| instantiate (thms : List EMatchTheorem) (usedThms : List EMatchTheorem)
18+
deriving Inhabited
19+
20+
/-- Helper type for implementing `finish?` and `grind?` -/
21+
inductive ProofTrace where
22+
| done
23+
| sep (s : ProofStep) (k : ProofTrace)
24+
| cases (info : SplitInfo) (alts : List ProofTrace)
25+
deriving Inhabited
26+
1227
/--
1328
A `choice` (aka backtracking) point in the search tree.
1429
-/
1530
structure Choice where
31+
info? : Option SplitInfo
1632
/--
1733
Goal where the case-split was performed.
18-
Invariant: `goalOld.mvarId` is not assigned.
34+
Invariant: `goalPending.mvarId` is not assigned.
1935
-/
2036
goalPending : Goal
2137
/--
22-
Expression to be assigned to `goalOld.mvarId` if it is not possible to perform
38+
Expression to be assigned to `goalPending.mvarId` if it is not possible to perform
2339
non-chronological backtracking.
2440
`proof` is often a `casesOn` application containing meta-variables.
2541
-/
@@ -28,11 +44,14 @@ structure Choice where
2844
Subgoals that still need to be processed.
2945
-/
3046
todo : List Goal
47+
traces : Array ProofTrace := #[]
3148
generation : Nat
3249
deriving Inhabited
3350

3451
structure SearchM.State where
35-
goal : Goal
52+
goal : Goal
53+
steps : Array ProofStep := #[]
54+
trace? : Option ProofTrace := none
3655
choiceStack : List Choice := []
3756

3857
abbrev SearchM := StateRefT SearchM.State GrindM
@@ -66,7 +85,7 @@ update current goal using `s`.
6685
- If there are more than one `s :: ss`, we create a choice point using the current
6786
goal as the pending goal, and update the current goal with `s`.
6887
-/
69-
def mkChoice (proof : Expr) (subgoals : List Goal) (generation : Nat) : SearchM Unit := do
88+
def mkChoice (proof : Expr) (subgoals : List Goal) (generation : Nat) (info? : Option SplitInfo := none) : SearchM Unit := do
7089
assert! !(← isInconsistent)
7190
match subgoals with
7291
| [] =>
@@ -79,7 +98,7 @@ def mkChoice (proof : Expr) (subgoals : List Goal) (generation : Nat) : SearchM
7998
let goalPending ← getGoal
8099
modify fun s => { s with
81100
goal := subgoal
82-
choiceStack := { goalPending, proof, generation, todo := subgoals } :: s.choiceStack
101+
choiceStack := { info?, goalPending, proof, generation, todo := subgoals } :: s.choiceStack
83102
}
84103

85104
/--
@@ -94,6 +113,9 @@ def mkAuxMVarForCurrGoal : SearchM MVarId := withCurrGoalContext do
94113
let mvarNew ← mkFreshExprSyntheticOpaqueMVar type tag
95114
return mvarNew.mvarId!
96115

116+
/--
117+
Returns the maximum free variable id occurring in `e`
118+
-/
97119
private def findMaxFVarIdx? (e : Expr) : MetaM (Option Nat) := do
98120
let go (e : Expr) : StateT (Option Nat) MetaM Bool := do
99121
unless e.hasFVar do return false
@@ -187,14 +209,14 @@ def nextGoal? : SearchM (Option Nat) := do
187209
let mvarDecl ← choice.goalPending.mvarId.getDecl
188210
let numIndices := mvarDecl.lctx.numIndices
189211
if maxFVarIdx < numIndices then
190-
-- `falseProof` can close `choice.goalOld` since all its free-variables are in scope.
212+
-- `falseProof` can close `choice.goalPending` since all its free-variables are in scope.
191213
choice.goalPending.mvarId.assignFalseProof falseProof
192214
-- keep looking at next choice point
193215
-- Remark: we may be able to find other choice points using falseProof.
194216
choices := choices'
195217
else match choice.todo with
196218
| [] =>
197-
-- All subgoals have been solved. We can finally assign `choice.proof` to `goalOld.mvarId`.
219+
-- All subgoals have been solved. We can finally assign `choice.proof` to `goalPending.mvarId`.
198220
let proof ← instantiateMVars choice.proof
199221
choice.goalPending.mvarId.assign proof
200222
if (← isTargetFalse choice.goalPending.mvarId) then

src/Lean/Meta/Tactic/Grind/Solve.lean

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,36 @@ import Lean.Meta.Tactic.Grind.Lookahead
1313
import Lean.Meta.Tactic.Grind.Intro
1414
public section
1515
namespace Lean.Meta.Grind
16+
17+
def checkSolvers : SearchM Bool := do
18+
if (← getConfig).trace then
19+
let some solverIds ← Solvers.check? | return false
20+
modify fun s => { s with steps := s.steps ++ solverIds.map (.solver ·) }
21+
return true
22+
else
23+
Solvers.check
24+
25+
def ematchStep : SearchM Bool := do
26+
if (← getConfig).trace then
27+
let .true ← ematch | return false
28+
-- TODO: Collect instances
29+
modify fun s => { s with steps := s.steps.push <| .instantiate [] [] }
30+
return true
31+
else
32+
ematch
33+
34+
def mbtcStep : SearchM Bool := do
35+
let .true ← Solvers.mbtc | return false
36+
if (← getConfig).trace then
37+
modify fun s => { s with steps := s.steps.push .mbtc }
38+
return true
39+
40+
def lookaheadStep : SearchM Bool := do
41+
let .true ← lookahead | return false
42+
if (← getConfig).trace then
43+
modify fun s => { s with steps := s.steps.push .lookahead }
44+
return true
45+
1646
/--
1747
Try to solve/close the given goal.
1848
Returns `some goal` if this subgoal failed to be closed,
@@ -39,8 +69,8 @@ where
3969
intros gen
4070
else
4171
break
42-
if (← assertAll <||> Solvers.check <||> ematch <||> lookahead <||> splitNext
43-
<||> Solvers.mbtc) then
72+
if (← assertAll <||> checkSolvers <||> ematchStep <||> lookaheadStep <||> splitNext
73+
<||> mbtcStep) then
4474
continue
4575
return some (← getGoal) -- failed
4676
return none -- solved

src/Lean/Meta/Tactic/Grind/Split.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,11 @@ Selects a case-split from the list of candidates, and adds new choice point
275275
(aka backtracking point). Returns true if successful.
276276
-/
277277
def splitNext : SearchM Bool := withCurrGoalContext do
278-
let .some c numCases isRec _ ← selectNextSplit?
278+
let .some info numCases isRec _ ← selectNextSplit?
279279
| return false
280280
let mvarId ← mkAuxMVarForCurrGoal
281-
let (goals, genNew) ← splitCore mvarId c numCases isRec
282-
mkChoice (mkMVar mvarId) goals genNew
281+
let (goals, genNew) ← splitCore mvarId info numCases isRec
282+
mkChoice (mkMVar mvarId) goals genNew (info? := some info)
283283
intros genNew
284284
return true
285285

src/Lean/Meta/Tactic/Grind/Types.lean

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,7 @@ structure SolverExtension (σ : Type) where private mk ::
14611461
mbtc : GoalM Bool
14621462
check : GoalM Bool
14631463
checkInv : GoalM Unit
1464+
mkTactic? : CoreM (Option (TSyntax `grind))
14641465
deriving Inhabited
14651466

14661467
private builtin_initialize solverExtensionsRef : IO.Ref (Array (SolverExtension SolverExtensionState)) ← IO.mkRef #[]
@@ -1470,7 +1471,7 @@ Registers a new solver extension for `grind`.
14701471
Solver extensions can only be registered during initialization.
14711472
Reason: We do not use any synchronization primitive to access `solverExtensionsRef`.
14721473
-/
1473-
def registerSolverExtension {σ : Type} (mkInitial : IO σ) : IO (SolverExtension σ) := do
1474+
def registerSolverExtension {σ : Type} (mkInitial : IO σ) : IO (SolverExtension σ) := do
14741475
unless (← initializing) do
14751476
throw (IO.userError "failed to register `grind` solver, extensions can only be registered during initialization")
14761477
let exts ← solverExtensionsRef.get
@@ -1483,6 +1484,7 @@ def registerSolverExtension {σ : Type} (mkInitial : IO σ) : IO (SolverExtens
14831484
check := fun _ _ => return false
14841485
checkInv := fun _ _ => return ()
14851486
mbtc := fun _ _ => return false
1487+
mkTactic? := return none
14861488
}
14871489
solverExtensionsRef.modify fun exts => exts.push (unsafe unsafeCast ext)
14881490
return ext
@@ -1498,13 +1500,15 @@ def SolverExtension.setMethods (ext : SolverExtension σ)
14981500
(newDiseq : Expr → Expr → GoalM Unit := fun _ _ => return ())
14991501
(mbtc : GoalM Bool := return false)
15001502
(check : GoalM Bool := return false)
1501-
(checkInv : GoalM Unit := return ()) : IO Unit := do
1503+
(checkInv : GoalM Unit := return ())
1504+
(mkTactic? : CoreM (Option (TSyntax `grind)) := return none)
1505+
: IO Unit := do
15021506
unless (← initializing) do
15031507
throw (IO.userError "failed to register `grind` solver, extensions can only be registered during initialization")
15041508
unless ext.id < (← solverExtensionsRef.get).size do
15051509
throw (IO.userError "failed to register `grind` solver methods, invalid solver id")
15061510
solverExtensionsRef.modify fun exts => exts.modify ext.id fun s => { s with
1507-
internalize, newEq, newDiseq, mbtc, check, checkInv
1511+
internalize, newEq, newDiseq, mbtc, check, checkInv, mkTactic?
15081512
}
15091513

15101514
/-- Returns initial state for registered solvers. -/
@@ -1540,17 +1544,25 @@ def Solvers.internalize (e : Expr) (parent? : Option Expr) : GoalM Unit := do
15401544
def Solvers.checkInvariants : GoalM Unit := do
15411545
(← solverExtensionsRef.get).forM fun ext => ext.checkInv
15421546

1543-
/-- Performs (expensive) satisfiability checks in all registered solvers. -/
1544-
def Solvers.check : GoalM Bool := do
1545-
let mut result := false
1547+
/--
1548+
Performs (expensive) satisfiability checks in all registered solvers,
1549+
and returns the solver ids that made progress.
1550+
-/
1551+
def Solvers.check? : GoalM (Option (Array Nat)) := do
1552+
let mut result := #[]
15461553
for ext in (← solverExtensionsRef.get) do
15471554
if (← isInconsistent) then
1548-
return true
1555+
return some result
15491556
if (← ext.check) then
1550-
result := true
1551-
if result then
1557+
result := result.push ext.id
1558+
if !result.isEmpty then
15521559
processNewFacts
1553-
return result
1560+
return some result
1561+
else
1562+
return none
1563+
1564+
def Solvers.check : GoalM Bool := do
1565+
return !(← check?).isNone
15541566

15551567
/-- Invokes model-based theory combination extensions in all registered solvers. -/
15561568
def Solvers.mbtc : GoalM Bool := do

0 commit comments

Comments
 (0)