package pidgin

  1. Overview
  2. Docs

Source file kind.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
(* Copyright (c) 2026, Cargocut and the Pidgin developers.
   All rights reserved.

   SPDX-License-Identifier: BSD-3-Clause *)

(* KLUDGE: This approach is probably a little too complicated and ad
   hoc for reporting purposes. *)

(* NOTE: Kinds are a little more complicated than one might expect
   (to provide more feedback on errors). *)
type t =
  | Any
  (* NOTE: It's a little annoying, but it is possible to
     generate values that cannot be unified. For example, an
     empty list. *)
  | Or of t list (* Empty or leads to any. *)
  | Null
  | Bool
  | Int
  | Float
  | String
  | List of t
  (* NOTE: Since we have [Or], we can perform unification on
     complicated list. *)
  | Branch of string * t
  (* NOTE: A special case for handling sum-types. *)
  | Pair of t * t
  (* NOTE: A special case for handling pairs *)
  | Record of (string * t) list

let rec equal a b =
  match a, b with
  | Any, Any
  | Null, Null
  | Bool, Bool
  | Int, Int
  | Float, Float
  | String, String -> true
  | Or a, Or b -> List.equal equal a b
  | Pair (a, x), Pair (b, y) -> equal a b && equal x y
  | Branch (ka, va), Branch (kb, vb) -> equal_with_keys (ka, va) (kb, vb)
  | List a, List b -> equal a b
  | Record a, Record b -> List.equal equal_with_keys a b
  (* NOTE: Ensure that the pattern matching is exhaustive. *)
  | Any, _
  | Or _, _
  | Null, _
  | Bool, _
  | Int, _
  | Float, _
  | String, _
  | List _, _
  | Branch (_, _), _
  | Pair (_, _), _
  | Record _, _ -> false

and equal_with_keys (ka, va) (kb, vb) =
  let c = String.equal ka kb in
  if c then equal va vb else c
;;

let weight
  =
  (* NOTE: The function is internal and just useful for having a
     consistent compare function. Values are discarded because in the
     case of composite kind, order is checked at the compare function
     level. *)
  function
  | Any -> 0
  | Null -> 1
  | Bool -> 2
  | Int -> 3
  | Float -> 4
  | String -> 5
  | Or _ -> 6
  | List _ -> 7
  | Branch (_, _) -> 8
  | Pair (_, _) -> 9
  | Record _ -> 10
;;

let rec compare a b =
  match a, b with
  | List a, List b -> compare a b
  | Or a, Or b -> List.compare compare a b
  | Pair (a, b), Pair (x, y) ->
    (* NOTE: Lexicographic order is sufficient here, and already
       implemented in List. *)
    List.compare compare [ a; b ] [ x; y ]
  | Branch (a, b), Branch (x, y) -> compare_with_keys (a, b) (x, y)
  | Record a, Record b -> List.compare compare_with_keys a b
  | a, b ->
    (* NOTE: here we are using weight. *)
    Int.compare (weight a) (weight b)

and compare_with_keys (ka, va) (kb, vb) =
  let c = String.compare ka kb in
  if Int.equal c 0 then compare va vb else c
;;

(* NOTE: The set is just used to ensure that [or] expression contains
   only uniq kinds. *)
module S = Stdlib.Set.Make (struct
    type nonrec t = t

    let compare = compare
  end)

let uniq x = x |> S.of_list |> S.to_list

let or_ a b =
  (* NOTE: we try to keep the smallest shape. i,e: or a a = a. *)
  match a, b with
  | Any, _ | _, Any ->
    (* We lose the value, Any absorb *)
    Any
  | a, b when equal a b -> a
  | Or a, Or b -> Or (List.append a b |> uniq)
  | a, Or b | Or b, a -> Or (List.cons a b |> uniq)
  | a, b ->
    (* NOTE: be consistent with the set *)
    Or [ a; b ]
;;

let from_list_aux f = function
  | [] -> (* We lose the kind information *) Any
  | [ x ] -> f x
  | terms ->
    let s =
      terms
      |>
      (* NOTE: Here we do not use [S.of_list] in order to reuse the
         function for term inference. *)
      List.fold_left (fun set t -> S.add (f t) set) S.empty
    in
    (match S.find_first_opt (fun _ -> true) s with
     | None -> Any
     | Some t ->
       (* It is ok to iter on the full set, since [t] will be
          collapsed. *)
       S.fold or_ s t)
;;

let or_ a b = from_list_aux Fun.id [ a; b ]
let any = Any
let null = Null
let bool = Bool
let int = Int
let float = Float
let string = String
let list t = List t

let record ?(normalize_keys = true) assoc =
  (* NOTE: Empty records are allowed. *)
  let assoc =
    if normalize_keys
    then List.map (fun (k, v) -> Misc.strim k, v) assoc
    else assoc
  in
  Record assoc
;;

let unify Nel.(x :: xs) =
  match xs with
  | [] -> x
  | _ -> from_list_aux (fun x -> x) (x :: xs)
;;

let branch k v = Branch (Misc.strim k, v)
let sum Nel.(x :: xs) = from_list_aux (fun (k, v) -> branch k v) (x :: xs)
let pair a b = Pair (a, b)
let triple a b c = pair a (pair b c)

let tuple Nel.(x :: xs) =
  (* KLUDGE: Non tailtrecursive, sorry, but hey CPS. *)
  let rec aux = function
    | List.[ x ] -> x
    | List.(x :: xs) -> pair x (aux xs)
    | _ ->
      (* NOTE: Never reached because of the Nonempty nature of the
         input. *)
      assert false
  in
  aux (x :: xs)
;;

let rec infer
  =
  (* KLUDGE: the term "infer" is maybe extrem but since we unify types
     for lists... *)
  function
  | Repr.Null -> null
  | Repr.Bool _ -> bool
  | Repr.Int _ -> int
  | Repr.Float _ -> float
  | Repr.String _ -> string
  | Repr.List xs -> list (from_list_aux infer xs)
  | Repr.Record [ ("constr", String k); ("value", v) ]
  | Repr.Record [ ("value", v); ("constr", String k) ] -> branch k (infer v)
  | Repr.Record [ ("first", a); ("second", b) ]
  | Repr.Record [ ("second", b); ("first", a) ] -> Pair (infer a, infer b)
  | Repr.Record fields -> record (List.map (fun (k, v) -> k, infer v) fields)
;;

let rec to_string
  =
  (* NOTE: To string is mostly for inspection (and test unit
     purpose). *)
  function
  | Any -> "any"
  | Null -> "null"
  | Bool -> "bool"
  | Int -> "int"
  | Float -> "float"
  | String -> "string"
  | Or l ->
    let l = l |> List.map to_string |> String.concat " | " in
    "(" ^ l ^ ")"
  | Pair (a, b) -> "(" ^ to_string a ^ ", " ^ to_string b ^ ")"
  | List a -> "[" ^ to_string a ^ "]"
  | Branch (constr, kind) ->
    "#" ^ Misc.strim constr ^ "<" ^ to_string kind ^ ">"
  | Record r ->
    "{"
    ^ (r
       |> List.map (fun (k, v) -> {|"|} ^ k ^ {|": |} ^ to_string v)
       |> String.concat ", ")
    ^ "}"
;;