Source file wp_eauto.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
open Auto
open Context.Named
open EConstr
open Hints
open Names
open Pp
open Proofview
open Proofview.Notations
open Tactics
open Termops
open Util
open Backtracking
open Proofutils
open Wp_auto
let eauto_unif_flags: Unification.unify_flags = auto_flags_of_state TransparentState.full
let e_give_exact ?(flags: Unification.unify_flags = eauto_unif_flags) (c: types): unit tactic =
Goal.enter begin fun gl ->
let sigma, t1 = Tacmach.pf_type_of gl c in
let t2 = Tacmach.pf_concl gl in
if occur_existential sigma t1 || occur_existential sigma t2 then
Tacticals.tclTHENLIST [
Unsafe.tclEVARS sigma;
Clenv.unify ~flags t1;
exact_no_check c
]
else exact_check c
end
let e_assumption: trace tactic =
TraceTactics.typedGoalEnter begin fun gl ->
let hyps = Goal.hyps gl in
let sigma = Goal.sigma gl in
let concl = Tacmach.pf_concl gl in
if List.is_empty hyps then
Tacticals.tclZEROMSG (str "No applicable tactic.")
else
let not_ground = occur_existential sigma concl in
let map (decl: ('a, types) Declaration.pt): trace tactic =
let id = Declaration.get_id decl in
let t = Declaration.get_type decl in
begin
if not_ground || occur_existential sigma t
then Clenv.unify ~flags:eauto_unif_flags t <*> exact_no_check (mkVar id)
else exact_check (mkVar id)
end <*> tclUNIT @@ singleton_trace true (str "eassumption") (str "")
in
tclTraceFirst (List.map map hyps)
end
let unify_e_resolve (flags: Unification.unify_flags) (h: hint): unit tactic =
Hints.hint_res_pf ~with_evars:true ~with_classes:true ~flags h
let e_exact (h: hint): unit tactic =
Goal.enter begin fun gl ->
let env = Goal.env gl in
let sigma = Goal.sigma gl in
let sigma, c = Hints.fresh_hint env sigma h in
Unsafe.tclEVARS sigma <*> e_give_exact c
end
(**
Cost to solve a hint
*)
type cost = {
cost_priority : int;
cost_subgoals : int;
}
(**
Type alias containing functions that will return a [Hints.hint_db]
*)
type delayed_db = Environ.env -> Evd.evar_map -> hint_db
(**
State of the search
*)
type search_state = {
depth: int; (** Depth remaining before failing *)
tactics_resolution: (Proofview_monad.goal_with_state * delayed_db) list;
trace: trace; (** Current trace *)
}
let search_tactics_factory (initial_state: search_state): (module Mergeable with type elt = search_state) = (
module struct
type elt = search_state
let empty = initial_state
let merge state1 state2 = { state1 with trace = merge_traces state1.trace state2.trace }
end: Mergeable with type elt = search_state
)
(**
Creates an initial state
*)
let initial_state (log: bool) (evk: Proofview_monad.goal_with_state) (local_db: delayed_db) (n: int): search_state =
{
depth = n;
tactics_resolution = [(evk, local_db)];
trace = new_trace log
}
(**
Prints a debug header
*)
let () = Feedback.msg_notice (str "(* info wp_eauto: *)")
let tclTraceComplete (t: trace tactic): trace tactic =
t >>= fun res ->
(
tclINDEPENDENT
(let info = Exninfo.reify () in
tclZERO ~info (SearchBound no_trace))
) <*>
tclUNIT res
let rec e_trivial_fail_db (db_list: hint_db list) (local_db: hint_db) (forbidden_tactics: Pp.t list): trace tactic =
let next = TraceTactics.typedGoalEnter begin fun gl ->
let d = Declaration.get_id @@ Tacmach.pf_last_hyp gl in
let local_db = push_resolve_hyp (Tacmach.pf_env gl) (Tacmach.project gl) d local_db in
e_trivial_fail_db db_list local_db forbidden_tactics
end in
TraceTactics.typedGoalEnter begin fun gl ->
let secvars = compute_secvars gl in
let tacl =
e_assumption ::
(Tactics.intro <*> next) ::
(e_trivial_resolve (Tacmach.pf_env gl) (Tacmach.project gl) db_list local_db secvars (Tacmach.pf_concl gl) forbidden_tactics)
in
tclTraceFirst (List.map tclTraceComplete tacl)
end
and esearch_find (env: Environ.env) (sigma: Evd.evar_map) (db_list: hint_db list) (local_db: hint_db) (secvars: Id.Pred.t) (concl: types) (forbidden_tactics: Pp.t list): (trace tactic * cost * Pp.t) list =
let hint_of_db: hint_db -> FullHint.t list = hintmap_of env sigma secvars concl in
let flagged_hints: (Unification.unify_flags * FullHint.t) list =
List.map_append (fun db ->
let flags = auto_flags_of_state (Hint_db.transparent_state db) in
List.map (fun x -> (flags, x)) (hint_of_db db)
) (local_db::db_list)
in
let tac_of_hint ((state, hint): (Unification.unify_flags * FullHint.t)): trace tactic * cost * Pp.t =
let priority = match FullHint.repr hint with
| Unfold_nth _ -> 1
| _ -> FullHint.priority hint
in let tac: hint hint_ast -> trace tactic = function
| Res_pf h -> unify_e_resolve state h <*> tclUNIT @@ no_trace
| ERes_pf h -> unify_e_resolve state h <*> tclUNIT @@ no_trace
| Give_exact h -> e_exact h <*> tclUNIT @@ singleton_trace true (str "exact") (str "")
| Res_pf_THEN_trivial_fail h ->
(unify_e_resolve state h) <*>
(e_trivial_fail_db db_list local_db forbidden_tactics)
| Unfold_nth c ->
TraceTactics.typedGoalEnter begin fun gl ->
if exists_evaluable_reference (Goal.env gl) c then
Tacticals.tclPROGRESS (reduce (Genredexpr.Unfold [Locus.AllOccurrences, c]) Locusops.onConcl) <*>
tclUNIT @@ singleton_trace true (str "unfold") (str "")
else
let info = Exninfo.reify () in
Tacticals.tclFAIL ~info (str"Unbound reference")
end
| Extern (pat, tacast) -> conclPattern concl pat tacast <*> tclUNIT @@ singleton_trace true (str "") (str "extern")
in
let subgoals = match FullHint.subgoals hint with
| Some subgoals -> subgoals
| None -> priority
in let cost = { cost_priority = priority; cost_subgoals = subgoals } in
let pr_hint (h: FullHint.t) (env: Environ.env) (sigma: Evd.evar_map): t * t =
let origin = match FullHint.database h with
| None -> mt ()
| Some n -> str n
in
(Proofutils.pr_hint env sigma h, origin)
in
let tactic = tclLOG (pr_hint hint) (FullHint.run hint tac) forbidden_tactics in
(tactic, cost, FullHint.print env sigma hint)
in
(List.map tac_of_hint flagged_hints)
and e_trivial_resolve (env: Environ.env) (sigma: Evd.evar_map) (db_list: hint_db list) (local_db: hint_db) (secvars: Id.Pred.t) (gl: types) (forbidden_tactics: Pp.t list): trace tactic list =
let filter (tac, pr, _) = if pr.cost_priority = 0 then Some tac else None in
try List.map_filter filter (esearch_find env sigma db_list local_db secvars gl forbidden_tactics)
with Not_found -> []
(**
The goal is solved if the cost of solving is null
*)
let is_solved (cost: cost): bool = cost.cost_subgoals = 0
let solve_order (c1: cost) (c2: cost): int = match (is_solved c1, is_solved c2) with
| (true, true) | (false, false) -> 0
| (false, true) -> 1
| (true, false) -> -1
(**
Compare two states
Ordering of states is lexicographic:
1. tactics known to solve the goal
2. priority
3. number of generated goals
*)
let compare ((_, c1, _): (trace tactic * cost * Pp.t)) ((_, c2, _): (trace tactic * cost * Pp.t)) =
let solve_ordering = solve_order c1 c2 in
let priority_ordering = Int.compare c1.cost_priority c2.cost_priority in
if solve_ordering != 0 then solve_ordering
else if priority_ordering != 0 then priority_ordering
else Int.compare c1.cost_subgoals c2.cost_subgoals
(**
Returns the list of tactics that will be tried for the proof search
It always begins with [assumption] and [intros] (not exactly them but equivalent with evar support), then uses the hint databases
*)
let branching (n: int) (delayed_database: delayed_db) (dblist: hint_db list) (local_lemmas: Tactypes.delayed_open_constr list) (forbidden_tactics: Pp.t list): (bool * delayed_db * trace tactic * Pp.t) list tactic =
Goal.enter_one
begin fun gl ->
let env = Goal.env gl in
let sigma = Goal.sigma gl in
let concl = Goal.concl gl in
let hyps = EConstr.named_context env in
let db = delayed_database env sigma in
let secvars = secvars_of_hyps hyps in
let assumption_tacs : (bool * delayed_db * trace tactic * Pp.t) list =
let mkdb (env: Environ.env) (sigma: Evd.evar_map): 'a = assert false in
let map_assum (id: variable): (bool * delayed_db * trace tactic * Pp.t) =
let hint = str "exact" ++ str " " ++ Id.print id in
(false, mkdb, tclLOG (fun _ _ -> (hint, str "")) (e_give_exact (mkVar id) <*> tclUNIT no_trace) forbidden_tactics, hint)
in List.map map_assum (ids_of_named_context hyps)
in
let intro_tac: (bool * delayed_db * trace tactic * Pp.t) =
let mkdb (env: Environ.env) (sigma: Evd.evar_map): hint_db =
push_resolve_hyp env sigma (Declaration.get_id (List.hd (EConstr.named_context env))) db
in (false, mkdb, tclLOG (fun _ _ -> (str "intro", str "")) (Tactics.intro <*> tclUNIT no_trace) forbidden_tactics, str "intro")
in
let rec_tacs: (bool * delayed_db * trace tactic * Pp.t) list tactic =
let mkdb (env: Environ.env) (sigma: Evd.evar_map): hint_db =
let hyps' = EConstr.named_context env in
if hyps' == hyps
then db
else make_local_hint_db env sigma ~ts:TransparentState.full true local_lemmas
in try
esearch_find env sigma dblist db secvars concl forbidden_tactics
|> List.sort compare
|> List.map (fun (tac, _, pp) -> (true, mkdb, tac, pp))
|> tclUNIT
with Not_found -> tclUNIT []
in
rec_tacs >>= fun rec_tacs ->
tclUNIT (assumption_tacs @ intro_tac :: rec_tacs)
end
(**
Actual search function
*)
let resolve_esearch (max_depth: int) (dblist: hint_db list) (local_lemmas: Tactypes.delayed_open_constr list) (state: search_state) (must_use_tactics: Pp.t list) (forbidden_tactics: Pp.t list): search_state tactic =
let rec explore (state: search_state) (previous_envs: (EConstr.named_context * EConstr.constr) list) =
if state.depth = 0
then tclZERO (SearchBound no_trace)
else match state.tactics_resolution with
| [] -> tclUNIT state
| (gl, db) :: rest ->
tclEVARMAP >>= fun sigma ->
match Unsafe.undefined sigma [gl] with
| [] -> explore { state with tactics_resolution = rest; trace = incr_trace_depth state.trace } previous_envs
| gl :: _ ->
Unsafe.tclSETGOALS [gl] <*>
branching state.depth db dblist local_lemmas forbidden_tactics >>= fun tacs ->
let cast ((isrec, mkdb, tac, pp): (bool * delayed_db * trace tactic * Pp.t)): search_state tactic =
tclONCE tac >>= fun trace ->
Unsafe.tclGETGOALS >>= fun lgls ->
tclEVARMAP >>= fun sigma ->
let join (gl: Proofview_monad.goal_with_state): (Proofview_monad.goal_with_state * delayed_db) = (gl, mkdb) in
let depth =
if isrec && not @@ List.is_empty lgls
then pred state.depth
else state.depth
in tclUNIT { depth; tactics_resolution = List.map join lgls @ rest; trace = merge_traces state.trace trace }
in
tacs
|> List.map cast
|> explore_many previous_envs
>>= fun s ->
if state.depth = max_depth
then trace_check_used must_use_tactics s.trace >>= fun trace -> tclUNIT s
else tclUNIT s
and explore_many (previous_envs: (EConstr.named_context * EConstr.constr) list) (tactic_list: search_state tactic list): search_state tactic = match tactic_list with
| [] -> tclZERO (SearchBound no_trace)
| tac :: l ->
tclORELSE
(tac >>= fun state ->
let module SearchState = (val (search_tactics_factory {state with tactics_resolution = []})) in
let module StateTactics = TypedTactics(SearchState) in
let search_goal_enter: (Goal.t -> search_state tactic) -> search_state tactic = StateTactics.typedGoalEnter in
search_goal_enter @@ fun goal ->
if List.mem (Goal.hyps goal, Goal.concl goal) previous_envs
then tclZERO (SearchBound no_trace)
else explore state @@ (Goal.hyps goal, Goal.concl goal)::previous_envs
)
(
fun (e, _) -> match e with
| SearchBound trace -> explore_many previous_envs l
| _ -> explore_many previous_envs l
)
in explore state []
(**
Searches a sequence of at most [n] tactics within [db_list] and [lems] that solves the goal
The goal can contain evars
*)
let esearch (log: bool) (depth: int) (lems: Tactypes.delayed_open_constr list) (db_list: hint_db list) (must_use_tactics: Pp.t list) (forbidden_tactics: Pp.t list): trace tactic =
TraceTactics.typedGoalEnter begin fun gl ->
let local_db env sigma = make_local_hint_db env sigma ~ts:TransparentState.full true lems in
let tac (s: search_state): search_state tactic = resolve_esearch depth db_list lems s must_use_tactics forbidden_tactics in
tclORELSE
begin
let evk = goal_with_state (Goal.goal gl) (Goal.state gl) in
tac (initial_state log evk local_db depth) >>= fun s ->
assert (List.is_empty s.tactics_resolution);
Unsafe.tclSETGOALS [] <*> tclUNIT s.trace
end
begin fun (exn, info) -> match exn with
| SearchBound trace -> tclUNIT trace
| _ -> tclZERO ~info exn
end
end
(**
Generates the {! wp_eauto} function
*)
let gen_wp_eauto (log: bool) ?(n: int = 5) (lems: Tactypes.delayed_open_constr list) (dbnames: hint_db_name list option) (must_use_tactics: Pp.t list) (forbidden_tactics: Pp.t list): trace tactic =
wrap_hint_warning @@
TraceTactics.typedGoalEnter begin fun gl ->
let db_list =
match dbnames with
| Some dbnames -> make_db_list dbnames
| None -> current_pure_db ()
in
tclTryDbg pr_dbg_header @@ TraceTactics.typedThen (tclUNIT @@ new_trace log) @@ esearch log n lems db_list must_use_tactics forbidden_tactics
end
(**
Waterproof eauto
This function is a rewrite around {! Eauto.eauto} with the same arguments to be able to retrieve which hints have been used in case of success.
The code structure has been rearranged to match the one of [wp_auto.wp_auto].
*)
let wp_eauto (log: bool) (n: int) (lems: Tactypes.delayed_open_constr list) (db_names: hint_db_name list): trace tactic =
gen_wp_eauto log ~n lems (Some db_names) [] []
(**
Restricted Waterproof eauto
This function acts the same as {! wp_eauto} but will fail if all proof found contain at least one must-use lemma that is unused or one hint that is in the [forbidden] list.
*)
let rwp_eauto (log: bool) (n: int) (lems: Tactypes.delayed_open_constr list) (dbnames: hint_db_name list) (must_use_tactics: Pp.t list) (forbidden_tactics: Pp.t list): trace tactic =
gen_wp_eauto log ~n lems (Some dbnames) must_use_tactics forbidden_tactics