Source file diagnostics.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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
(** Explaining why a solve failed or gave an unexpected answer. *)
module List = Solver_core.List
let pf = Format.fprintf
module Make (Results : S.SOLVER_RESULT) = struct
module Model = Results.Input
module RoleMap = Results.RoleMap
let format_role = Model.Role.pp
let format_restrictions r = String.concat ", " (List.map Model.string_of_restriction r)
module Note = struct
type t =
| UserRequested of Model.restriction
| ReplacesConflict of Model.Role.t
| ReplacedByConflict of Model.Role.t
| Restricts of Model.Role.t * Model.impl * Model.restriction list
| RequiresCommand of Model.Role.t * Model.impl * Model.command_name
| Feed_problem of string
let pp f = function
| UserRequested r -> pf f "User requested %s" (format_restrictions [r])
| ReplacesConflict old -> pf f "Replaces (and therefore conflicts with) %a" format_role old
| ReplacedByConflict replacement -> pf f "Replaced by (and therefore conflicts with) %a" format_role replacement
| Restricts (other_role, impl, r) ->
pf f "%a %a requires %s" format_role other_role Model.pp_version impl (format_restrictions r)
| RequiresCommand (other_role, impl, command) ->
pf f "%a %a requires '%s' command" format_role other_role Model.pp_version impl (command :> string)
| Feed_problem msg -> pf f "%s" msg
end
(** Represents a single interface in the example (failed) selections produced by the solver.
It partitions the implementations into good and bad based (initially) on the split from the
impl_provider. As we explore the example selections, we further filter the candidates. *)
module Component = struct
type rejection_reason = [
| `Model_rejection of Model.rejection
| `FailsRestriction of Model.restriction
| `DepFailsRestriction of Model.dependency * Model.restriction
| `MachineGroupConflict of Model.Role.t * Model.impl
| `ClassConflict of Model.Role.t * Model.conflict_class
| `ConflictsRole of Model.Role.t
| `MissingCommand of Model.command_name
| `DiagnosticsFailure of string
]
type reject = Model.impl * rejection_reason
type t = {
role : Model.Role.t;
replacement : Model.Role.t option;
diagnostics : string Lazy.t;
selected_impl : Model.impl option;
selected_commands : Model.command_name list;
orig_good : Model.impl list;
orig_bad : (Model.impl * Model.rejection) list;
mutable good : Model.impl list;
mutable bad : (Model.impl * rejection_reason) list;
mutable notes : Note.t list;
}
let create
~role
(candidates, orig_bad, feed_problems)
(diagnostics:string Lazy.t)
(selected_impl:Model.impl option)
(selected_commands:Model.command_name list) =
let {Model.impls; Model.replacement} = candidates in
let notes = List.map (fun x -> Note.Feed_problem x) feed_problems in
{
role;
replacement;
orig_good = impls;
orig_bad;
good = impls;
bad = List.map (fun (impl, reason) -> (impl, `Model_rejection reason)) orig_bad;
notes; diagnostics; selected_impl; selected_commands
}
let note t note = t.notes <- note :: t.notes
let notes t = List.rev t.notes
let affected_selection t impl =
match t.selected_impl with
| Some selected when Model.compare_version selected impl > 0 -> false
| _ -> true
let filter_impls_ref ~note:n t get_problem =
let old_good = List.rev t.good in
t.good <- [];
old_good |> List.iter (fun impl ->
match get_problem impl with
| None -> t.good <- impl :: t.good
| Some problem ->
!n |> Option.iter (fun info ->
if affected_selection t impl then (
note t info;
n := None;
)
);
t.bad <- (impl, problem) :: t.bad
)
let filter_impls ?note t get_problem =
let note = ref note in
filter_impls_ref ~note t get_problem
let apply_restrictions ~note t restrictions =
let note = ref (Some note) in
restrictions |> List.iter (fun r ->
filter_impls_ref ~note t (fun impl ->
if Model.meets_restriction impl r then None
else Some (`FailsRestriction r)
)
)
let apply_user_restriction t r =
note t (UserRequested r);
filter_impls t (fun impl ->
if Model.meets_restriction impl r then None
else Some (`FailsRestriction r)
);
let new_bad = t.bad |> List.filter (fun (impl, _) ->
if Model.meets_restriction impl r then true
else false
)
in
if new_bad <> [] || t.good <> [] then t.bad <- new_bad
let reject_all t reason =
t.bad <- List.map (fun impl -> (impl, reason)) t.good @ t.bad;
t.good <- []
let replacement t = t.replacement
let selected_impl t = t.selected_impl
let selected_commands t = t.selected_commands
let reject_self_conflicts t =
filter_impls t (fun impl ->
let deps, _ = Model.requires t.role impl in
deps |> List.find_map (fun dep ->
let { Model.dep_role; _ } = Model.dep_info dep in
if Model.Role.compare dep_role t.role <> 0 then None else (
Model.restrictions dep |> List.find_map (fun r ->
if Model.meets_restriction impl r then None
else Some (`DepFailsRestriction (dep, r))
)
)
)
)
let finalise t =
if t.selected_impl = None then (
reject_self_conflicts t;
reject_all t (`DiagnosticsFailure (Lazy.force t.diagnostics))
)
let pp_reject f ((impl, reason) : reject) =
match reason with
| `Model_rejection r -> Format.pp_print_string f (Model.describe_problem impl r)
| `FailsRestriction r -> pf f "Incompatible with restriction: %s" (Model.string_of_restriction r)
| `DepFailsRestriction (dep, restriction) ->
let dep_info = Model.dep_info dep in
pf f "Requires %a %s" format_role dep_info.Model.dep_role (format_restrictions [restriction])
| `MachineGroupConflict (other_role, other_impl) ->
pf f "Can't use %s with selection of %a (%s)"
(Model.format_machine impl)
format_role other_role
(Model.format_machine other_impl)
| `ClassConflict (other_role, cl) ->
pf f "In same conflict class (%s) as %a"
(cl :> string)
format_role other_role
| `ConflictsRole other_role -> pf f "Conflicts with %a" format_role other_role
| `MissingCommand command -> pf f "No %s command" (command : Model.command_name :> string)
| `DiagnosticsFailure msg -> pf f "Reason for rejection unknown: %s" msg
let show_rejections ~verbose f rejected =
let by_version (a, _) (b, _) = Model.compare_version b a in
let rejected = List.sort by_version rejected in
let rec aux i = function
| [] -> ()
| _ when i = 5 && not verbose -> pf f "@,..."
| (impl, problem) :: xs ->
pf f "@,%a: %a" Model.pp_impl_long impl pp_reject (impl, problem);
aux (i + 1) xs
in
aux 0 rejected
let rejects t =
let summary =
if t.orig_good = [] then (
if t.orig_bad = [] then `No_candidates
else `All_unusable
) else `Conflicts
in t.bad, summary
let pp_candidates ~verbose f t =
if t.selected_impl = None then (
match rejects t with
| _, `No_candidates -> pf f "@,No known implementations at all"
| bad, `All_unusable -> pf f "@,@[<v2>No usable implementations:%a@]" (show_rejections ~verbose) bad
| bad, `Conflicts -> pf f "@,@[<v2>Rejected candidates:%a@]" (show_rejections ~verbose) bad
)
let pp_notes f t =
match notes t with
| [] -> ()
| notes -> pf f "@,%a" Format.(pp_print_list ~pp_sep:pp_print_cut Note.pp) notes
let pp_outcome f t =
match t.selected_impl with
| Some sel -> Model.pp_impl_long f sel
| None -> Format.pp_print_string f "(problem)"
let pp ~verbose f t =
pf f "@[<v2>%a -> %a%a%a@]"
format_role t.role
pp_outcome t
pp_notes t
(pp_candidates ~verbose) t
end
type t = Component.t RoleMap.t
let find_component_ex role report =
match RoleMap.find_opt role report with
| Some c -> c
| None -> failwith (Format.asprintf "Can't find component %a!" format_role role)
let get_dependency_problem role report impl =
let check_dep dep =
let dep_info = Model.dep_info dep in
match RoleMap.find_opt dep_info.Model.dep_role report with
| None -> None
| Some required_component ->
match Component.selected_impl required_component with
| None -> None
| Some dep_impl ->
let check_restriction r =
if Model.meets_restriction dep_impl r then None
else Some (`DepFailsRestriction (dep, r)) in
List.find_map check_restriction (Model.restrictions dep) in
let deps, commands_needed = Model.requires role impl in
commands_needed |> List.find_map (fun command ->
if Model.get_command impl command <> None then None
else Some (`MissingCommand command : Component.rejection_reason)
)
|> function
| Some _ as r -> r
| None -> List.find_map check_dep deps
(** A selected component has [dep] as a dependency. Use this to explain why some implementations
of the required interface were rejected. *)
let examine_dep requiring_role requiring_impl report dep =
let {Model.dep_role = other_role; dep_importance = _; dep_required_commands} = Model.dep_info dep in
match RoleMap.find_opt other_role report with
| None -> ()
| Some required_component ->
let dep_restrictions = Model.restrictions dep in
if dep_restrictions <> [] then (
Component.apply_restrictions required_component dep_restrictions
~note:(Restricts (requiring_role, requiring_impl, dep_restrictions))
);
dep_required_commands |> List.iter (fun command ->
let note = Note.RequiresCommand (requiring_role, requiring_impl, command) in
Component.filter_impls ~note required_component (fun impl ->
if Model.get_command impl command <> None then None
else Some (`MissingCommand command)
)
)
let examine_selection report role component =
let () =
match Component.replacement component with
| Some replacement when RoleMap.mem replacement report -> (
Component.note component (ReplacedByConflict replacement);
Component.reject_all component (`ConflictsRole replacement);
match RoleMap.find_opt replacement report with
| Some replacement_component ->
Component.note replacement_component (ReplacesConflict role);
Component.reject_all replacement_component (`ConflictsRole role)
| None -> ()
)
| _ -> () in
match Component.selected_impl component with
| Some our_impl ->
let deps, _commands_needed = Model.requires role our_impl in
List.iter (examine_dep role our_impl report) deps;
Component.selected_commands component |> List.iter (fun name ->
match Model.get_command our_impl name with
| None -> failwith "BUG: missing command!"
| Some command ->
let deps, _commands_needed = Model.command_requires role command in
List.iter (examine_dep role our_impl report) deps;
)
| None ->
Component.filter_impls component (get_dependency_problem role report)
let report =
report |> RoleMap.iter (fun role component ->
Model.user_restrictions role |> Option.iter (fun restriction ->
Component.apply_user_restriction component restriction
)
)
(** If we wanted a command on the root, add that as a restriction. *)
let process_root_req report = function
| {Model.command = Some root_command; role = root_role} ->
let component = find_component_ex root_role report in
Component.filter_impls component (fun impl ->
if Model.get_command impl root_command <> None then None
else Some (`MissingCommand root_command)
)
| _ -> ()
(** Find an implementation which requires a machine group. Use this to
explain the rejection of all implementations requiring other groups. *)
exception Found of (Model.Role.t * Model.impl * Model.machine_group)
let check_machine_groups report =
let check role compoment =
match Component.selected_impl compoment with
| None -> ()
| Some impl ->
match Model.machine_group impl with
| None -> ()
| Some group -> raise (Found (role, impl, group)) in
try RoleMap.iter check report
with Found (example_role, example_impl, example_group) ->
let filter _key component = Component.filter_impls component (fun impl ->
match Model.machine_group impl with
| Some group when group <> example_group -> Some (`MachineGroupConflict (example_role, example_impl))
| _ -> None
) in
RoleMap.iter filter report
module Classes = Map.Make(struct
type t = Model.conflict_class
let compare = compare
end)
(** For each selected implementation with a conflict class, reject all candidates
with the same class. *)
let check_conflict_classes report =
let classes =
RoleMap.fold (fun role component acc ->
match Component.selected_impl component with
| None -> acc
| Some impl -> Model.conflict_class impl |> List.fold_left (fun acc x -> Classes.add x role acc) acc
) report Classes.empty
in
report |> RoleMap.iter @@ fun role component ->
Component.filter_impls component @@ fun impl ->
let rec aux = function
| [] -> None
| cl :: cls ->
match Classes.find_opt cl classes with
| Some other_role when Model.Role.compare role other_role <> 0 -> Some (`ClassConflict (other_role, cl))
| _ -> aux cls
in
aux (Model.conflict_class impl)
let of_result result =
let impls = Results.to_map result in
let root_req = Results.requirements result in
let report =
let get_selected role sel =
let impl = Results.unwrap sel in
let diagnostics = lazy (Results.explain result role) in
let impl = if impl == Model.dummy_impl then None else Some impl in
let impl_candidates = Model.implementations role in
let rejects, feed_problems = Model.rejects role in
let selected_commands = Results.selected_commands sel in
Component.create ~role (impl_candidates, rejects, feed_problems) diagnostics impl selected_commands in
RoleMap.mapi get_selected impls
in
process_root_req report root_req;
examine_extra_restrictions report;
check_machine_groups report;
check_conflict_classes report;
RoleMap.iter (examine_selection report) report;
RoleMap.iter (fun _ c -> Component.finalise c) report;
report
let pp_rolemap ~verbose f reasons =
let pp_item f (_, c) = pf f "- @[%a@]" (Component.pp ~verbose) c in
Format.(pp_print_list ~pp_sep:pp_print_cut) pp_item f (RoleMap.bindings reasons)
(** Return a message explaining why the solve failed. *)
let get_failure_reason ?(verbose=false) result =
let reasons = of_result result in
Format.asprintf "Can't find all required implementations:@\n@[<v0>%a@]" (pp_rolemap ~verbose) reasons
end