package rocq-runtime

  1. Overview
  2. Docs
The Rocq Prover -- Core Binaries and Tools

Install

dune-project
 Dependency

Authors

Maintainers

Sources

rocq-9.2.0.tar.gz
sha256=a45280ab4fbaac7540b136a6b073b4a6db15739ec1e149bded43fa6f4fc25f20

doc/src/rocq-runtime.kernel/qGraph.ml.html

Source file qGraph.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
(************************************************************************)
(*         *      The Rocq Prover / The Rocq Development Team           *)
(*  v      *         Copyright INRIA, CNRS and contributors             *)
(* <O___,, * (see version control and CREDITS file for authors & dates) *)
(*   \VV/  **************************************************************)
(*    //   *    This file is distributed under the terms of the         *)
(*         *     GNU Lesser General Public License Version 2.1          *)
(*         *     (see LICENSE file for the text of the license)         *)
(************************************************************************)
open Sorts

module ElimTable = struct
  open Quality

  let const_eliminates_to q q' =
    match q, q' with
    | QType, _ -> true
    | QProp, (QProp | QSProp) -> true
    | QSProp, QSProp -> true
    | (QProp | QSProp), _ -> false

  let eliminates_to q q' =
    match q, q' with
    | QConstant QType, _ -> true
    | QConstant q, QConstant q' -> const_eliminates_to q q'
    | QVar q, QVar q' -> QVar.equal q q'
    | (QConstant _ | QVar _), _ -> false
end

module G = AcyclicGraph.Make(struct
    type t = Quality.t
    module Set = Quality.Set
    module Map = Quality.Map

    let equal = Quality.equal
    let compare = Quality.compare

    let raw_pr = Quality.raw_pr

    let anomaly_err q = Pp.(str "Quality " ++ Quality.raw_pr q ++ str " undefined.")
  end)
(** We only use G to track connected components of the eliminability relation.
    In particular, G |= q1 = q2 does not mean that they are equal, but just
    equi-eliminable. As a result, one should not use the check_eq / enforce_eq
    functions in the remainder of this module.

    Note that we also abuse the Lt relation to get some rigidity for free for
    the built-in sorts, but this is a hack that should go away at some point. *)

module RigidPaths = struct
  type t = (Quality.t list) Quality.Map.t

  let add_elim_to q1 q2 g =
    let upd ls =
      Some (match ls with
        | Some ls -> q2 :: ls
        | None -> [q2])
    in
    Quality.Map.update q1 upd g

  let empty : t = Quality.Map.empty

  let dfs q g =
    let rec aux seen q =
      if Quality.Set.mem q seen
      then seen
      else
        let seen = Quality.Set.add q seen in
        match Quality.Map.find_opt q g with
        | None -> seen
        | Some ls -> List.fold_left aux seen ls
  in aux Quality.Set.empty q

  let check_forbidden_path (map : Quality.Set.t Quality.Map.t) g =
    let fold q s ls =
      let allowed = dfs q g in
      let diff = Quality.Set.diff s allowed in
      if Quality.Set.is_empty diff
      then ls
      else List.append (List.map (fun q' -> (q, q')) (List.of_seq @@ Quality.Set.to_seq diff)) ls in
    let forbidden = Quality.Map.fold fold map [] in
    match forbidden with
    | [] -> None
    | p :: _ -> Some p
end

module QMap = QVar.Map
module QSet = QVar.Set

type t =
  { graph: G.t;
    (** Connected components of the eliminability graph *)
    rigid_paths: RigidPaths.t;
    ground_and_global_sorts: Quality.Set.t;
    dominant: Quality.t QMap.t;
    delayed_check: QSet.t QMap.t;
  }

type path_explanation = G.explanation Lazy.t

type explanation =
  | Path of path_explanation
  | Other of Pp.t

type quality_inconsistency =
  ((QVar.t -> Pp.t) option) *
    (ElimConstraint.kind * Quality.t * Quality.t * explanation option)

(* If s can eliminate to s', we want an edge between s and s'.
   In the acyclic graph, it means setting s to be lower or equal than s'.
   This function ensures a uniform behaviour between [check] and [enforce]. *)
let to_graph_cstr k =
  let open ElimConstraint in
  match k with
  | ElimTo -> AcyclicGraph.Le

type elimination_error =
  | IllegalConstraintFromSProp of Quality.t
  | IllegalConstantConstraint of Quality.constant * Quality.constant
  | CreatesForbiddenPath of Quality.t * Quality.t
  | MultipleDominance of Quality.t * Quality.t * Quality.t
  | QualityInconsistency of quality_inconsistency

exception EliminationError of elimination_error

let non_refl_pairs l =
  let fold x =
    List.fold_right (fun y acc -> if x <> y then (x,y) :: acc else acc) l in
  List.fold_right fold l []

let get_new_rigid_paths g p dom =
  let add (q1,_,q2) accu =
    Quality.Map.update q1
      (fun ls -> Some (match ls with Some ls -> Quality.Set.add q2 ls | None -> Quality.Set.singleton q2))
      accu in
  let all_paths = G.constraints_for ~kept:dom g add Quality.Map.empty in
  RigidPaths.check_forbidden_path all_paths p

let set_dominant g qv q =
  { g with dominant = QMap.add qv q g.dominant }

(* Set the dominant sort of qv to the minimum between q1 and q2 if they are related.
   [q1] is the dominant of qv in [g]. *)
let update_dominant_if_related g qv q1 q2 =
  if G.check_leq g.graph q1 q2 then Some (set_dominant g qv q2)
  else if G.check_leq g.graph q2 q1 then Some g
  else None

(* If [qv] is not dominated, set dominance to [q].
   Otherwise, checks whether the dominant of [qv] and [q] are related.
   If so, puts the smallest of the two as the dominant of [qv]. *)
let rec update_dominance g q qv =
  let g' = match QMap.find_opt qv g.dominant with
    | None -> Some (set_dominant g qv q)
    | Some q' -> update_dominant_if_related g qv q' q in
  match QMap.find_opt qv g.delayed_check with
  | None -> g'
  | Some qs ->
     let g' = QSet.fold (fun v g -> Option.bind g (fun g -> update_dominance g q v)) qs g' in
     match g' with
     | Some graph -> Some { graph with delayed_check = QMap.set qv QSet.empty g.delayed_check }
     | None -> None

let update_dominance_if_valid g (q1,k,q2) =
  match k with
  | ElimConstraint.ElimTo ->
     (* if the constraint is s ~> g, dominants are not modified. *)
     if Quality.is_qconst q2 then Some g
     else
       match q1, q2 with
       | (Quality.QConstant _ | Quality.QVar _), Quality.QConstant _ -> assert false
       | Quality.QVar qv1, Quality.QVar qv2 ->
          (* 3 cases:
             - if [qv1] is a global, treat as constants.
             - if [qv1] is not dominated, delay the check to when [qv1] gets dominated.
             - if [qv1] is dominated, try to update the dominance of [qv2]. *)
          if Quality.is_qglobal q1 then update_dominance g q1 qv2
          else
            (match QMap.find_opt qv1 g.dominant with
            | None ->
               let add_delayed qs =
                 Some { g with delayed_check = QMap.set qv1 (QSet.add qv2 qs) g.delayed_check }
               in
               (match QMap.find_opt qv1 g.delayed_check with
               | None -> add_delayed QSet.empty
               | Some qs -> add_delayed qs)
            | Some q' -> update_dominance g q' qv2)
       | Quality.QConstant _, Quality.QVar qv -> update_dominance g q1 qv

let dominance_check g (q1,_,q2 as cstr) =
  let dom_q1 () = match q1 with
    | Quality.QConstant _ -> q1
    | Quality.QVar qv ->
       if Quality.is_qglobal q1 then q1
       else QMap.find qv g.dominant in
  let dom_q2 () = match q2 with
    | Quality.QConstant _ -> assert false
    | Quality.QVar qv -> QMap.find qv g.dominant in
  match update_dominance_if_valid g cstr with
  | None -> raise (EliminationError (MultipleDominance (dom_q2() , q2, dom_q1())))
  | Some g -> g

let check_rigid_paths g =
  let paths = get_new_rigid_paths g.graph g.rigid_paths g.ground_and_global_sorts in
  match paths with
  | None -> ()
  | Some (q1, q2) -> raise (EliminationError (CreatesForbiddenPath (q1, q2)))

let add_rigid_path q1 q2 g =
  let open Quality in
  match q1, q2 with
  (* Adding a constraint from SProp -> * is not allowed *)
  | QConstant QSProp, _ -> raise (EliminationError (IllegalConstraintFromSProp q2))
  (* Adding constraints between constants is not allowed *)
  | QConstant qc1, QConstant qc2 -> raise (EliminationError (IllegalConstantConstraint (qc1, qc2)))
  | _, _ -> { g with rigid_paths = RigidPaths.add_elim_to q1 q2 g.rigid_paths }

let enforce_func k q1 q2 g = match k with
| ElimConstraint.ElimTo ->
  begin match G.enforce_leq q1 q2 g.graph with
  | None -> None
  | Some graph -> Some { g with graph }
  end

let enforce_constraint (q1, k, q2) g =
  match enforce_func k q1 q2 g with
  | None ->
     let e = lazy (G.get_explanation (q1,to_graph_cstr k,q2) g.graph) in
     raise @@ EliminationError (QualityInconsistency (None, (k, q1, q2, Some (Path e))))
  | Some g ->
    dominance_check g (q1, k, q2)

let merge_constraints csts g = ElimConstraints.fold enforce_constraint csts g

let check_constraint g (q1, k, q2) = match k with
| ElimConstraint.ElimTo -> G.check_leq g.graph q1 q2

let check_constraints csts g = ElimConstraints.for_all (check_constraint g) csts

exception AlreadyDeclared = G.AlreadyDeclared

let add_quality q g =
  let graph = G.add q g.graph in
  let g = enforce_constraint (Quality.qtype, ElimConstraint.ElimTo, q) { g with graph } in
  let (paths,ground_and_global_sorts) =
    if Quality.is_qglobal q
    then (RigidPaths.add_elim_to Quality.qtype q g.rigid_paths, Quality.Set.add q g.ground_and_global_sorts)
    else (g.rigid_paths,g.ground_and_global_sorts) in
  (* As Type ~> s, set Type to be the dominant sort of q if q is a variable. *)
  let dominant = match q with
    | Quality.QVar qv -> QMap.add qv Quality.qtype g.dominant
    | Quality.QConstant _ -> g.dominant in
  { g with rigid_paths = paths; ground_and_global_sorts; dominant }

let enforce_eliminates_to s1 s2 g =
  enforce_constraint (s1, ElimConstraint.ElimTo, s2) g

let initial_graph =
  let g = G.empty in
  let g = List.fold_left (fun g q -> G.add q g) g Quality.all_constants in
  (* Enforces the constant constraints defined in the table of
     [Constants.eliminates_to] without reflexivity (should be consistent,
     otherwise the [Option.get] will fail). *)
  let fold (g,p) (q,q') =
    if ElimTable.eliminates_to q q'
    then (Option.get @@ G.enforce_lt q q' g, RigidPaths.add_elim_to q q' p)
    else (g,p)
  in
  let (g,p) = List.fold_left fold (g,RigidPaths.empty) @@ non_refl_pairs Quality.all_constants in
  { graph = g;
    rigid_paths = p;
    ground_and_global_sorts = Quality.Set.of_list Quality.all_constants;
    dominant = QMap.empty;
    delayed_check = QMap.empty; }

let eliminates_to g q q' =
  G.check_leq g.graph q q'

let update_rigids g g' =
  { g' with rigid_paths = g.rigid_paths }

let sort_eliminates_to g s1 s2 =
  eliminates_to g (quality s1) (quality s2)

let eliminates_to_prop g q = eliminates_to g q Quality.qprop

let domain g = G.domain g.graph

let qvar_domain g =
  Quality.Set.fold
    (fun q acc -> match q with Quality.QVar q -> QVar.Set.add q acc | _ -> acc)
    (domain g) QVar.Set.empty

(* XXX the function below does not handle equivalence classes, but it's only used for printing *)
let merge g g' =
  let qs = domain g' in
  let g = Quality.Set.fold
             (fun q acc -> try add_quality q acc with _ -> acc) qs g in
  Quality.Set.fold
    (fun q -> Quality.Set.fold
             (fun q' acc -> if eliminates_to g' q q'
                         then enforce_eliminates_to q q' acc
                         else acc) qs) qs g

let is_empty g = QVar.Set.is_empty (qvar_domain g)

(* Pretty printing *)

let pr_pmap sep pr map =
  let cmp (q1, _) (q2, _) = Quality.compare q1 q2 in
  Pp.prlist_with_sep sep pr (List.sort cmp (Quality.Map.bindings map))

let pr_arc prq =
  let open Pp in
  function
  | q1, G.Node ltle ->
    if Quality.Map.is_empty ltle then mt ()
    else
      prq q1 ++ spc () ++
      v 0
        (pr_pmap spc (fun (q2, _) ->
              str "-> " ++ prq q2)
            ltle) ++
      fnl ()
  | q1, G.Alias q2 ->
    prq q1  ++ str " <-> " ++ prq q2 ++ fnl ()

let repr g = G.repr g.graph

let is_declared q g = match G.check_declared g.graph (Quality.Set.singleton q) with
| Result.Ok _ -> true
| Result.Error _ -> false

let pr_qualities prq g = pr_pmap Pp.mt (pr_arc prq) (repr g)

let explain_quality_inconsistency prv r =
  let open Pp in
  let pr_cst = function
    | AcyclicGraph.Eq -> str"="
    | AcyclicGraph.Le -> str"->"
    | AcyclicGraph.Lt -> str"->" (* Yes, it's the same as above. *)
  in
  match r with
  | None -> mt()
  | Some (Other p) -> p
  | Some (Path p) ->
     let pstart, p = Lazy.force p in
     if p = [] then mt ()
     else
       let qualities = pstart :: List.map snd p in
       let constants = List.filter Quality.is_qconst qualities in
       str "because it would identify " ++
         pr_enum (Quality.pr prv) constants ++
         spc() ++ str"which is inconsistent." ++ spc() ++
         str"This is introduced by the constraints " ++
         prlist (fun (r, v) -> Quality.pr prv v ++ spc() ++ pr_cst r ++ spc()) p ++
         Quality.pr prv pstart

let explain_elimination_error defprv err =
  let open Pp in
  match err with
  | IllegalConstraintFromSProp q -> str "Enforcing elimination constraints from SProp to any other sort is not allowed. " ++ brk (1, 0) ++
    str "This expression would enforce that SProp eliminates to " ++ Quality.pr defprv q ++ str "."
  | IllegalConstantConstraint (q1, q2) -> str "Enforcing elimination constraints between constant sorts (Type, Prop or SProp) is not allowed." ++ brk (1, 0) ++
    str "Here: " ++ Quality.Constants.pr q1 ++ str" and " ++ Quality.Constants.pr q2 ++ str "."
  | CreatesForbiddenPath (q1,q2) ->
     str "This expression would enforce a non-declared elimination constraint between" ++
       spc() ++ Quality.pr defprv q1 ++ spc() ++ str"and" ++ spc() ++ Quality.pr defprv q2
  | MultipleDominance (q1,qv,q2) ->
     let pr_elim q = Quality.pr defprv q ++ spc() ++ str"->" ++ spc() ++ Quality.pr defprv qv in
     str "This expression enforces" ++ spc() ++ pr_elim q1 ++ spc() ++ str"and" ++ spc() ++
       pr_elim q2 ++ spc() ++ str"which might make type-checking undecidable"
  | QualityInconsistency (prv, (k, q1, q2, r)) ->
     let prv = match prv with Some prv -> prv | None -> defprv in
     str"The quality constraints are inconsistent: " ++
       str "cannot enforce" ++ spc() ++ Quality.pr prv q1 ++ spc() ++
       ElimConstraint.pr_kind k ++ spc() ++ Quality.pr prv q2 ++ spc() ++
       explain_quality_inconsistency prv r