Skip to content

Commit 0c6ca88

Browse files
committed
feat: library_search using (leanprover#503)
Implement the `library_search using X` clause, which only returns results for which `X` appears as a subexpression. The mathlib3 implementation is still cleverer, but would require full implementation of `solveByElim` with backtracking across multiple goals. I'll get there later. Co-authored-by: Scott Morrison <[email protected]>
1 parent 243f55c commit 0c6ca88

File tree

2 files changed

+53
-28
lines changed

2 files changed

+53
-28
lines changed

Mathlib/Tactic/LibrarySearch.lean

+40-12
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,24 @@ initialize librarySearchLemmas : DeclCache (DiscrTree Name) ←
5353
/-- Shortcut for calling `solveByElimImpl`. -/
5454
def solveByElim (g : MVarId) (depth) := Lean.Tactic.solveByElimImpl false [] depth g
5555

56-
def librarySearch (goal : MVarId) (lemmas : DiscrTree Name) (solveByElimDepth := 6) :
57-
MetaM <| Option (Array <| MetavarContext × List MVarId) := do
56+
/--
57+
Try to solve the goal either by:
58+
* calling `solveByElim`
59+
* or applying a library lemma then calling `solveByElim` on the resulting goals.
60+
61+
If it successfully closes the goal, returns `none`.
62+
Otherwise, it returns `some a`, where `a : Array (MetavarContext × List MVarId)`,
63+
with an entry for each library lemma which was successfully applied,
64+
containing the metavariable context after the application, and a list of the subsidiary goals.
65+
66+
(Always succeeds, and the metavariable context stored in the monad is reverted,
67+
unless the goal was completely solved.)
68+
69+
(Note that if `solveByElim` solves some but not all subsidiary goals,
70+
this is not currently tracked.)
71+
-/
72+
def librarySearch (goal : MVarId) (lemmas : DiscrTree Name) (required : List Expr)
73+
(solveByElimDepth := 6) : MetaM <| Option (Array <| MetavarContext × List MVarId) := do
5874
profileitM Exception "librarySearch" (← getOptions) do
5975
let ty ← goal.getType
6076
withTraceNode `Tactic.librarySearch (return m!"{exceptOptionEmoji ·} {ty}") do
@@ -65,7 +81,10 @@ def librarySearch (goal : MVarId) (lemmas : DiscrTree Name) (solveByElimDepth :=
6581

6682
try
6783
solveByElim goal solveByElimDepth
68-
return none
84+
if (← checkRequired) then
85+
return none
86+
else
87+
set state0
6988
catch _ =>
7089
set state0
7190

@@ -79,11 +98,16 @@ def librarySearch (goal : MVarId) (lemmas : DiscrTree Name) (solveByElimDepth :=
7998
newGoal.withContext do
8099
trace[Tactic.librarySearch] "proving {← addMessageContextFull (mkMVar newGoal)}"
81100
solveByElim newGoal solveByElimDepth
82-
pure $ some $ Sum.inr ()
101+
if (← checkRequired) then
102+
pure $ some $ Sum.inr ()
103+
else
104+
set state0
105+
pure none
83106
catch _ =>
84107
let res := some $ Sum.inl (← getMCtx, newGoals)
108+
let check ← checkRequired
85109
set state0
86-
pure res)
110+
return if check then res else none)
87111
catch _ =>
88112
set state0
89113
pure none
@@ -93,6 +117,10 @@ def librarySearch (goal : MVarId) (lemmas : DiscrTree Name) (solveByElimDepth :=
93117
| some (Sum.inl suggestion) => suggestions := suggestions.push suggestion
94118

95119
pure $ some suggestions
120+
where
121+
/-- Verify that the instantiated goal contains each `Expr` in `required` as a sub-expression.
122+
(Make sure to not reset the state before calling.) -/
123+
checkRequired : MetaM Bool := return required.all (·.occurs (← instantiateMVars (.mvar goal)))
96124

97125
def lines (ls : List MessageData) :=
98126
MessageData.joinSep ls (MessageData.ofFormat Format.line)
@@ -101,22 +129,22 @@ open Lean.Parser.Tactic
101129

102130
-- TODO: implement the additional options for `library_search` from Lean 3,
103131
-- in particular including additional lemmas
104-
-- with `library_search [X, Y, Z]` or `library_search with attr`,
105-
-- or requiring that a particular hypothesis is used in the solution, with `library_search using h`.
132+
-- with `library_search [X, Y, Z]` or `library_search with attr`.
106133
syntax (name := librarySearch') "library_search" (config)? (simpArgs)?
107-
(" using " (colGt binderIdent)+)? : tactic
134+
(" using " (colGt term),+)? : tactic
108135
syntax (name := librarySearch!) "library_search!" (config)? (simpArgs)?
109-
(" using " (colGt binderIdent)+)? : tactic
136+
(" using " (colGt term),+)? : tactic
110137

111138
-- For now we only implement the basic functionality.
112139
-- The full syntax is recognized, but will produce a "Tactic has not been implemented" error.
113140

114141
open Elab.Tactic Elab Tactic in
115-
elab_rules : tactic | `(tactic| library_search%$tk) => do
142+
elab_rules : tactic | `(tactic| library_search%$tk $[using $[$required:term],*]?) => do
116143
let mvar ← getMainGoal
117144
let (_, goal) ← (← getMainGoal).intros
118145
goal.withContext do
119-
if let some suggestions ← librarySearch goal (← librarySearchLemmas.get) then
146+
let required := (← (required.getD #[]).mapM getFVarId).toList.map .fvar
147+
if let some suggestions ← librarySearch goal (← librarySearchLemmas.get) required then
120148
for suggestion in suggestions do
121149
withMCtx suggestion.1 do
122150
addExactSuggestion tk (← instantiateMVars (mkMVar mvar))
@@ -129,7 +157,7 @@ elab tk:"library_search%" : term <= expectedType => do
129157
let goal ← mkFreshExprMVar expectedType
130158
let (_, introdGoal) ← goal.mvarId!.intros
131159
introdGoal.withContext do
132-
if let some suggestions ← librarySearch introdGoal (← librarySearchLemmas.get) then
160+
if let some suggestions ← librarySearch introdGoal (← librarySearchLemmas.get) [] then
133161
for suggestion in suggestions do
134162
withMCtx suggestion.1 do
135163
addTermSuggestion tk (← instantiateMVars goal)

test/librarySearch.lean

+13-16
Original file line numberDiff line numberDiff line change
@@ -89,25 +89,22 @@ axiom F (a b : ℕ) : f a ≤ f b ↔ a ≤ b
8989

9090
-- TODO theorem nonzero_gt_one (n : ℕ) : ¬ n = 0 → n ≥ 1 := by library_search -- `exact nat.pos_of_ne_zero`
9191

92-
/- TODO: using
92+
example (L M : List (List ℕ)) : List ℕ := by library_search using L
9393

94-
example (L : List (List ℕ)) : List ℕ :=
95-
by library_search using L
94+
example (P Q : List ℕ) (h : ℕ) : List ℕ := by library_search using h, P
9695

97-
example (n m : ℕ) : ℕ :=
98-
by library_search using n m
96+
-- These tests for `using` require moving the required subexpressions check deeper into solveByElim
9997

100-
example (P Q : list ℕ) (h : ℕ) : list ℕ :=
101-
by library_search using h Q
98+
-- example (n m : ℕ) : ℕ := by library_search using n, m
10299

103-
example (P Q : list ℕ) (h : ℕ) : list ℕ :=
104-
by library_search using P Q
100+
-- example (P Q : List ℕ) (h : ℕ) : List ℕ :=
101+
-- by library_search using h, Q
105102

106-
-- Make sure `library_search` finds nothing when we list too many hypotheses after `using`.
107-
example (P Q R S T : list ℕ) : list ℕ :=
108-
begin
109-
success_if_fail { library_search using P Q R S T, },
110-
exact []
111-
end
103+
-- example (P Q : List ℕ) (h : ℕ) : List ℕ :=
104+
-- by library_search using P, Q
112105

113-
-/
106+
-- -- Make sure `library_search` finds nothing when we list too many hypotheses after `using`.
107+
-- example (P Q R S T : List ℕ) : List ℕ := by
108+
-- fail_if_success
109+
-- library_search using P, Q, R, S, T
110+
-- exact []

0 commit comments

Comments
 (0)