Source file completionSuggester.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
open Protocol
module CompactedDecl = Context.Compacted.Declaration
open Printer
open EConstr
open Names
open Types
let Log log = Log.mk_log "completionSuggester"
module TypeCompare = struct
type t = types
let compare = compare
end
let takeSkip n l =
let rec sub_list n accu l =
match l with
| [] -> accu, []
| hd :: tl ->
if n = 0 then accu, tl
else sub_list (n - 1) (hd :: accu) tl
in
let take, skip = sub_list n [] l in
List.rev (take), skip
module Atomics = Set.Make(TypeCompare)
module HypothesisMap = Map.Make (struct type t = Id.t let compare = compare end)
let mk_hyp d (env,l) =
let d' = CompactedDecl.to_named_context d in
let env' = List.fold_right EConstr.push_named d' env in
let ids, typ = match d with
| CompactedDecl.LocalAssum (ids, typ) -> ids, typ
| CompactedDecl.LocalDef (ids,_,typ) -> ids, typ
in
let ids' = List.map (fun id -> id.Context.binder_name) ids in
let m = List.fold_right (fun id acc -> HypothesisMap.add id typ acc) ids' l in
(env', m)
let get_hyps env sigma goal =
let EvarInfo evi = Evd.find sigma goal in
let env = Evd.evar_filtered_env env evi in
let min_env = Environ.reset_context env in
let (_env, hyps) =
Context.Compacted.fold (mk_hyp)
(Termops.compact_named_context sigma (EConstr.named_context env)) ~init:(min_env, HypothesisMap.empty) in
hyps
let type_kind_opt sigma t = try Some (kind_of_type sigma t) with _ -> None
[%%if coq = "8.18" || coq = "8.19"]
let type_size sigma t : int =
let rec aux u =
match kind sigma u with
| Sort _ -> 1
| Cast (c,_,t) -> aux c + aux t
| Prod (_,t,c) -> aux t + aux c
| LetIn (_,b,t,c) -> aux b + aux t + aux c
| App (c,l) -> Array.map aux l |> Array.fold_left (+) (aux c)
| Rel _ -> 1
| (Meta _ | Var _ | Evar _ | Const _
| Proj _ | Case _ | Fix _ | CoFix _ | Ind _) -> 1
| (Lambda _ | Construct _ | Array _ | Int _ | Float _) -> 1
in
aux t
[%%else]
let type_size sigma t : int =
let rec aux u =
match kind sigma u with
| Sort _ -> 1
| Cast (c,_,t) -> aux c + aux t
| Prod (_,t,c) -> aux t + aux c
| LetIn (_,b,t,c) -> aux b + aux t + aux c
| App (c,l) -> Array.map aux l |> Array.fold_left (+) (aux c)
| Rel _ -> 1
| (Meta _ | Var _ | Evar _ | Const _
| Proj _ | Case _ | Fix _ | CoFix _ | Ind _) -> 1
| (Lambda _ | Construct _ | Array _ | Int _ | Float _ | String _) -> 1
in
aux t
[%%endif]
module TypeIntersection = struct
let atomic_types sigma t: Atomics.t =
let rec aux t : types list =
match (type_kind_opt sigma t) with
| Some SortType _ -> []
| Some CastType (tt, t) -> aux tt @ aux t
| Some ProdType (_, t1, t2) -> aux t1 @ aux t2
| Some LetInType _ -> []
| Some AtomicType (t, ta) ->
t :: (Array.map aux ta |> Array.to_list |> List.flatten);
| None -> []
in
aux t |>
Atomics.of_list
let compare_atomics (goal : Atomics.t) (a1, _ : Atomics.t * _) (a2, _ : Atomics.t * _) : int =
match (Atomics.inter a1 goal, Atomics.inter a2 goal) with
| r1, r2 when Atomics.cardinal r1 = Atomics.cardinal r2 ->
compare (Atomics.cardinal a1) (Atomics.cardinal a2)
| r1, r2 ->
compare (Atomics.cardinal r2) (Atomics.cardinal r1)
let split_types sigma c =
let (list, other_c) = decompose_prod sigma c in
list
|> List.map snd
|> List.cons other_c
|> List.map (fun typ -> atomic_types sigma typ)
|> List.fold_left (fun (acc, result) item -> (Atomics.union acc item, Atomics.union acc item :: result)) (Atomics.empty, [])
|> snd
let best_subtype sigma goal c =
c |> split_types sigma |> List.map (fun a -> (a, a)) |> List.stable_sort (compare_atomics goal) |> List.hd |> fst
let rank (goal : Evd.econstr) sigma _ (lemmas : CompletionItems.completion_item list) : CompletionItems.completion_item list =
let goal = atomic_types sigma goal in
let lemmaTypes = List.map (fun (l : CompletionItems.completion_item) ->
let best = best_subtype sigma goal (of_constr l.typ) in
(best, l)
) lemmas in
List.stable_sort (compare_atomics goal) lemmaTypes |>
List.map snd
end
type bruijn_level = int
module Structured = struct
type unifier =
| SortUniType of ESorts.t * bruijn_level
| AtomicUniType of types * unifier array
module UM = Map.Make(struct type t = bruijn_level let compare = compare end)
let filter_options a =
a |> Array.to_list |> Option.List.flatten |> Array.of_list
let unifier_kind sigma (hyps : types HypothesisMap.t) (t : types) : unifier option =
let rec aux bruijn t = match kind sigma t with
| Sort s -> SortUniType (s, List.length bruijn) |> Option.make
| Cast (c,_,_) -> aux bruijn c
| Prod (_,t,c) -> aux (aux bruijn t :: bruijn) c
| LetIn (_,_,t,c) -> aux (aux bruijn t :: bruijn) c
| App (c,l) ->
let l' = Array.map (aux bruijn) l in
AtomicUniType (c, filter_options l') |> Option.make
| Rel i -> List.nth bruijn (i-1)
| Var v when HypothesisMap.mem v hyps -> aux bruijn (HypothesisMap.find v hyps)
| (Meta _ | Var _ | Evar _ | Const _
| Proj _ | Case _ | Fix _ | CoFix _ | Ind _)
-> AtomicUniType (t,[||]) |> Option.make
| (Lambda _ | Construct _ | _ ) -> None
in
aux [] t
let rec matches evd (u1: unifier) (u2: unifier) =
match (u1, u2) with
| SortUniType (s1, _), SortUniType (s2, _) ->
ESorts.equal evd s1 s2
| SortUniType _, _ -> false
| _, SortUniType _ -> false
| AtomicUniType (t1, ua1), AtomicUniType (t2, ua2) ->
let c = EConstr.compare_constr evd (EConstr.eq_constr evd) t1 t2 in
if c then
let rec aux ua1 ua2 =
match (ua1, ua2) with
| [], [] -> true
| [], _ | _, [] -> false
| u1 :: ua1, u2 :: ua2 ->
let c = matches evd u1 u2 in
if c then aux ua1 ua2 else false
in
aux (Array.to_list ua1) (Array.to_list ua2)
else false
let score_unifier atomic_factor evd (goal : unifier) (u : unifier) : float =
let rec aux (um : unifier UM.t) g u : (float * unifier UM.t) =
match (g, u) with
| SortUniType (s1, _), SortUniType (s2, _) -> if ESorts.equal evd s1 s2 then (1., um) else (0., um)
| SortUniType _, _ -> (0., um)
| AtomicUniType (_, _), SortUniType (s, i) ->
if UM.mem i um then
let u = UM.find i um in
matches evd u (SortUniType (s, i)) |> Bool.to_float |> fun x -> (x, um)
else (1., UM.add i u um)
| AtomicUniType (t1, ua1), AtomicUniType (t2, ua2) ->
let c = EConstr.compare_constr evd (EConstr.eq_constr evd) t1 t2 |> Bool.to_float |> (Float.mul atomic_factor) in
let c =
if isVar evd t1 && isVar evd t2 then
Float.mul c 2.0
else c in
if Array.length ua1 <> Array.length ua2 then (c, um)
else
let (score, um) = Array.fold_left (fun (acc, um) (u1, u2) ->
let (s, um) = aux um u1 u2 in
(Float.add acc s, um)
) (c, um) (Array.combine ua1 ua2) in
(score, um)
in
aux UM.empty goal u |> fst
let unpack_unifier u : unifier list =
let res = ref [] in
let rec aux u = match u with
| SortUniType (_, _) -> ();
| AtomicUniType (_, ua) ->
res := u :: !res;
Array.iter aux ua
in
aux u;
!res
let rec size_unifier u = match u with
| SortUniType (_, _) -> 1l
| AtomicUniType (_, ua) -> Array.fold_left (fun acc u -> Int32.add acc (size_unifier u)) 1l ua
let rank (options: Settings.Completion.t) (goal, goal_evar) sigma env lemmas : CompletionItems.completion_item list =
let size_impact = options.sizeFactor in
let atomic_factor = options.atomicFactor in
let finalScore score size = Float.sub size (Float.mul score size_impact) in
let hyps = get_hyps env sigma goal_evar in
match (unifier_kind sigma hyps goal, unifier_kind sigma HypothesisMap.empty goal) with
| None, _ | _, None -> lemmas
| Some goalUnf, Some goalUnfNoHypothesisSub ->
let lemmaUnfs = List.map (fun (l : CompletionItems.completion_item) ->
match (unifier_kind sigma HypothesisMap.empty (of_constr l.typ)) with
| None -> ((Float.min_float), l)
| Some unf ->
let scores = List.map (score_unifier atomic_factor sigma goalUnf) (unpack_unifier unf) in
let scores = scores @ (List.map (score_unifier atomic_factor sigma goalUnfNoHypothesisSub) (unpack_unifier unf)) in
let size = size_unifier unf |> Int32.to_float in
let maxScore = List.fold_left Float.max 0. scores in
let final = finalScore maxScore size in
l.debug_info <- (Printf.sprintf "Score: %f, Size: %f, Final Score: %f" maxScore size final);
(final, l)
) lemmas in
let sorted = List.stable_sort (fun (x, _) (y, _) -> Float.compare x y) lemmaUnfs in
List.map snd sorted
end
module SelectiveUnification = struct
let rankByUnifiability (goal : Evd.econstr) sigma env (lemmas : CompletionItems.completion_item list) : CompletionItems.completion_item list =
let goal_size = type_size sigma goal in
let worst_value = 1000 in
let make_sortable (lemma : CompletionItems.completion_item) =
let flags = Evarconv.default_flags_of TransparentState.full in
let rec aux (iterations: int) (typ : types) : (CompletionItems.completion_item * int)=
if type_size sigma typ + goal_size > 500 then (lemma, worst_value)
else
let res = Evarconv.evar_conv_x flags env sigma Conversion.CONV goal typ in
match res with
| Success _ ->
({lemma with completes = if iterations = 0 then Some Fully else Some Partially}, iterations)
| UnifFailure (_, _) ->
match kind sigma typ with
| Prod (_,_,c) -> aux (iterations + 1) c
| Cast (c,_,_) -> aux iterations c
| _ -> ({lemma with completes = Some No_completion}, worst_value)
in
try
aux 0 (of_constr lemma.typ)
with e ->
Printf.sprintf "Error in Split Unification: %s for %s\n%!" (Printexc.to_string e) (Pp.string_of_ppcmds (pr_global lemma.ref)) |> log;
({lemma with completes = Some No_completion}, worst_value)
in
lemmas
|> List.map make_sortable
|> List.stable_sort (fun a b -> compare (snd a) (snd b))
|> List.map fst
let selectiveRank (options: Settings.Completion.t) (goal : Evd.econstr) sigma env (lemmas : CompletionItems.completion_item list) : CompletionItems.completion_item list =
try
let take, skip = takeSkip options.unificationLimit lemmas in
List.append (rankByUnifiability goal sigma env take) skip
with e ->
log ("Error in Split Unification: %s" ^ (Printexc.to_string e));
lemmas
let rank = selectiveRank
end
let rank_choices (options: Settings.Completion.t) (goal, goal_evar) sigma env lemmas =
let open Settings.Completion.RankingAlgoritm in
match options.algorithm with
| SplitTypeIntersection -> TypeIntersection.rank goal sigma env lemmas
| StructuredSplitUnification -> SelectiveUnification.rank options goal sigma env (Structured.rank options (goal, goal_evar) sigma env lemmas)
let get_goal_type_opt env Proof.{ goals; sigma; _ } =
List.nth_opt goals 0
|> Option.map (fun goal ->
let evi = Evd.find_undefined sigma goal in
let env = Evd.evar_filtered_env env evi in
Evd.evar_concl evi, sigma, env, goal
)
let get_completion_items env proof lemmas options =
try
match get_goal_type_opt env proof with
| None -> lemmas
| Some (goal, sigma, env, goal_evar) ->
rank_choices options (goal, goal_evar) sigma env lemmas
with e ->
log ("Ranking of lemmas failed: " ^ (Printexc.to_string e));
lemmas
[%%if coq = "8.18"]
let generic_search e s f = Search.generic_search e (fun r k e c -> f r k e s c)
[%%else]
let generic_search = Search.generic_search
[%%endif]
let get_lemmas sigma env =
let open CompletionItems in
let results = ref [] in
let display ref _kind env sigma c =
results := mk_completion_item sigma ref env c :: results.contents;
in
generic_search env sigma display;
results.contents
let get_completions options st =
Vernacstate.unfreeze_full_state st;
match st.interp.lemmas with
| None -> None
| Some lemmas ->
let proof = Proof.data (lemmas |> Vernacstate.LemmaStack.with_top ~f:Declare.Proof.get) in
let env = Global.env () in
let sigma = proof.sigma in
let lemmas = get_lemmas sigma env in
Some (get_completion_items env proof lemmas options)