/
assoc_utils.clj
420 lines (381 loc) · 17.3 KB
/
assoc_utils.clj
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
;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors.
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.
; support for assoc/merge/conj
(ns typed.clj.checker.assoc-utils
(:require [typed.cljc.checker.type-rep :as r]
[typed.cljc.checker.type-ctors :as c]
[typed.cljc.checker.indirect-ops :as ind]
[typed.cljc.checker.utils :as u]
[typed.cljc.checker.free-ops :as free-ops]
[clojure.core.typed.errors :as err]
[typed.cljc.checker.tvar-bnds :as bnds]
[clojure.set :as set]
[clojure.core.typed.current-impl :as impl])
(:import (typed.cljc.checker.type_rep HeterogeneousMap Value Intersection F RClass DataType HSequential)
(clojure.lang IPersistentMap IPersistentVector)))
;supporting assoc functionality
(declare assoc-type-pairs)
(defprotocol AssocableType
(-assoc-pair [left kv]))
(extend-protocol AssocableType
Intersection
(-assoc-pair
[old-i assoc-entry]
; attempt to simplify the intersection before recursing. Parsing an
; intersection type does not simplify it.
(let [new-i (apply c/In (:types old-i))]
(if (r/Intersection? new-i)
(apply c/In (keep #(assoc-type-pairs % assoc-entry) (:types new-i)))
(assoc-type-pairs new-i assoc-entry))))
; use the upper bound if bounds below (Map t/Any t/Any)
F
(-assoc-pair
[{:keys [name] :as f} assoc-entry]
(let [bnd (free-ops/free-with-name-bnds name)
_ (when-not bnd
(err/int-error (str "No bounds for type variable: " name bnds/*current-tvar-bnds*)))]
(when (ind/subtype? (:upper-bound bnd)
(impl/impl-case
:clojure (c/RClass-of IPersistentMap [r/-any r/-any])
:cljs (c/-name 'typed.clojure/Map r/-any r/-any)))
(r/AssocType-maker f [(mapv r/ret-t assoc-entry)] nil))))
Value
(-assoc-pair
[v [kt vt]]
(when (ind/subtype? v r/-nil)
(let [rkt (-> kt :t c/fully-resolve-type)]
(if (c/keyword-value? rkt)
(c/-complete-hmap {rkt (:t vt)})
(impl/impl-case
:clojure (c/RClass-of IPersistentMap [rkt (:t vt)])
:cljs (c/-name 'typed.clojure/Map rkt (:t vt)))))))
RClass
(-assoc-pair
[rc [kt vt]]
(let [_ (impl/assert-clojure)
rkt (-> kt :t c/fully-resolve-type)]
(cond
(= (:the-class rc) 'clojure.lang.IPersistentMap)
(c/RClass-of IPersistentMap [(c/Un (:t kt) (nth (:poly? rc) 0))
(c/Un (:t vt) (nth (:poly? rc) 1))])
(and (= (:the-class rc) 'clojure.lang.IPersistentVector)
(r/Value? rkt))
(let [kt ^Value rkt]
(when (integer? (.val kt))
(c/RClass-of IPersistentVector [(c/Un (:t vt) (nth (:poly? rc) 0))])))
(and (= (:the-class rc) 'clojure.lang.IPersistentVector)
(ind/subtype? rkt (r/Name-maker 'clojure.core.typed/Int)))
(c/RClass-of IPersistentVector [(c/Un (:t vt) (nth (:poly? rc) 0))]))))
HeterogeneousMap
(-assoc-pair
[hmap [kt vt]]
(let [rkt (-> kt :t c/fully-resolve-type)]
(if (c/keyword-value? rkt)
(c/make-HMap
:mandatory (-> (:types hmap)
(assoc rkt (:t vt)))
:optional (:optional hmap)
:absent-keys (-> (:absent-keys hmap)
(disj rkt))
:complete? (c/complete-hmap? hmap))
; devolve the map
;; todo: probably some machinery I can reuse here?
(let [ks (apply c/Un (cons rkt (mapcat keys [(:types hmap) (:optional hmap)])))
vs (apply c/Un (cons (:t vt) (mapcat vals [(:types hmap) (:optional hmap)])))]
(impl/impl-case
:clojure (c/RClass-of IPersistentMap [ks vs])
:cljs (c/-name 'typed.clojure/Map ks vs))))))
HSequential
(-assoc-pair
[v [kt vt]]
(when (r/HeterogeneousVector? v)
(let [rkt (-> kt :t c/fully-resolve-type)]
(when (r/Value? rkt)
(let [kt rkt
k (:val kt)]
(when (and (integer? k) (<= k (count (:types v))))
(r/-hvec (assoc (:types v) k (:t vt))
:filters (assoc (:fs v) k (:fl vt))
:objects (assoc (:objects v) k (:o vt)))))))))
DataType
(-assoc-pair
[dt [kt vt]]
(let [rkt (-> kt :t c/fully-resolve-type)]
(when (and (r/Record? dt) (c/keyword-value? rkt))
(let [kt rkt
field-type (when (c/keyword-value? kt)
(get (:fields dt) (symbol (name (:val kt)))))]
(when (and field-type (ind/subtype? (:t vt) field-type))
dt))))))
(defn assoc-type-pairs [t & pairs]
{:pre [(r/Type? t)
(every? (fn [[k v :as kv]]
(and (= 2 (count kv))
(r/TCResult? k)
(r/TCResult? v)))
pairs)]
:post [((some-fn nil? r/Type?) %)]}
(c/reduce-type-transform -assoc-pair t pairs
:when #(satisfies? AssocableType %)))
(defn assoc-pairs-noret [t & pairs]
{:pre [(r/Type? t)
(every? (fn [[k v :as kv]]
(and (= 2 (count kv))
(r/Type? k)
(r/Type? v)))
pairs)]
:post [((some-fn nil? r/Type?) %)]}
(apply assoc-type-pairs t (map (fn [[k v]] [(r/ret k) (r/ret v)]) pairs)))
; dissoc support functions
(defn- -dissoc-key [t k]
{:pre [(r/Type? t)
(r/TCResult? k)]
:post [((some-fn nil? r/Type?) %)]}
(c/union-or-nil
(for [rtype (c/resolved-type-vector k)]
(cond
(ind/subtype? t r/-nil)
t
(and (r/HeterogeneousMap? t) (c/keyword-value? rtype))
(c/make-HMap
:mandatory
(dissoc (:types t) rtype)
:optional
(dissoc (:optional t) rtype)
:absent-keys
(conj (:absent-keys t) rtype)
:complete? (c/complete-hmap? t))
(ind/subtype? t (impl/impl-case
:clojure (c/RClass-of IPersistentMap [r/-any r/-any])
:cljs (c/-name 'typed.clojure/Map r/-any r/-any)))
t))))
(defn dissoc-keys [t ks]
{:post [((some-fn nil? r/Type?) %)]}
(c/reduce-type-transform -dissoc-key t ks))
; merge support functions
(defn- merge-hmaps
"Merges two HMaps into one, right into left.
Preserves all key information where possible, missing keys in a right hand incomplete
map will erase type information for those keys in the left.
This strategy allows a merge of HMaps to always stay an HMap, without having to drop
down to an IPersistentMap.
For example:
(merge '{:a 4 :b 6} '{:b 5}) -> '{:a t/Any :b 5}"
[left right]
{:pre [(r/HeterogeneousMap? left)
(r/HeterogeneousMap? right)]}
;; want to know how often complete HMap's help with merging.
(u/trace-when (c/complete-hmap? right)
"Merge: complete used on the right")
(c/make-HMap
:mandatory
(let [m (:types left)
; optional keys on the right may or may not overwrite mandatory
; entries, so we union the common mandatory and optional val types together.
;
; eg. (merge (HMap :mandatory {:a Number}) (HMap :optional {:a t/Sym}))
; => (HMap :mandatory {:a (t/U Number t/Sym)})
m (merge-with c/Un
m
(select-keys (:optional right) (keys (:types left))))
;_ (prn "after first mandatory pass" m)
; combine left+right mandatory entries.
; If right is partial, we can only update the entries common to both
; and give any entries type Any.
;
; eg. (merge (HMap :mandatory {:a Number}) (HMap :mandatory {:b Number}))
; ;=> (HMap :mandatory {:a t/Any :b Number})
;
; If right is complete, it's safe to merge both mandatory maps.
; right-most wins on duplicates.
m (merge m
(cond
(c/partial-hmap? right)
(merge (:types right)
(zipmap (set/difference
(set (keys (:types left)))
(set (keys (:types right)))
(set (keys (:optional right)))
(:absent-keys right))
(repeat r/-any)))
:else
(:types right)))]
;(prn "after final mandatory pass" m)
m)
:optional
(let [o (:optional left)
;_ (prn "before first optional pass" o)
; dissoc keys that end up in the mandatory map
o (apply dissoc o
(concat (keys (:types right))
; entries mandatory on the left and optional
; on the right are always in the mandatory map
(set/intersection
(set (keys (:optional right)))
(set (keys (:types left))))))
;_ (prn "after first optional pass" o)
; now we merge any new :optional entries
o (merge-with c/Un
o
; if the left is partial then we only add optional entries
; common to both maps.
; if left is complete, we are safe to merge both maps.
;
; (merge (HMap :optional {:a Number})
; (HMap :optional {:b Number}))
; => (HMap)
;
; (merge (HMap :mandatory {:a '5})
; (HMap :optional {:a '10}))
; => (HMap :mandatory {:a (t/U '5 '10)})
;
; (merge (HMap :optional {:a Number})
; (HMap :optional {:a t/Sym}))
; => (HMap :optional {:a (t/U Number t/Sym)})
;
; (merge (HMap :optional {:a Number})
; (HMap :optional {:b Number} :complete? true))
; => (HMap :optional {:a Number :b Number})
;
; (merge (HMap :optional {:a Number} :complete? true)
; (HMap :optional {:b Number}))
; => (HMap :optional {:a Number :b Number})
;
; (merge (HMap :optional {:a Number} :complete? true)
; (HMap :optional {:b Number} :complete? true))
; => (HMap :optional {:a Number :b Number})
(select-keys (:optional right)
(set/difference
(set (keys (:optional right)))
;remove keys that will be mandatory in the result
(set (keys (:types left)))
(if (c/partial-hmap? left)
; remove keys that give no new information.
; If left is partial, we remove optional
; keys in right that are not mentioned in left.
(set/difference
(set (keys (:optional right)))
(set (keys (:types left)))
(set (keys (:optional left)))
(:absent-keys left))
#{}))))]
;(prn "after final optional pass" o)
o)
:absent-keys
(cond
; (merge (HMap :absent-keys [:a :b :c]) (HMap :optional {:a Foo} :mandatory {:b Bar} :absent-keys [:c]))
; => (HMap :absent-keys [:c] :optional {:a Foo} :mandatory {:b Bar})
; (merge (HMap :absent-keys [:a :b :c]) (HMap :optional {:a Foo} :mandatory {:b Bar}))
; => (HMap :absent-keys [] :optional {:a Foo} :mandatory {:b Bar})
(and (c/partial-hmap? left)
(c/partial-hmap? right))
(set/intersection
(set/difference (:absent-keys left)
(set (keys (:optional right)))
(set (keys (:types right))))
(:absent-keys right))
; (merge (HMap :absent-keys [:a :b :c])
; (HMap :optional {:a Foo} :mandatory {:b Bar} :complete? true))
; => (HMap :absent-keys [:c] :optional {:a Foo} :mandatory {:b Bar})
(and (c/partial-hmap? left)
(c/complete-hmap? right))
(set/difference (:absent-keys left)
(set (keys (:optional right)))
(set (keys (:types right))))
; (merge (HMap :complete? true)
; (HMap :absent-keys [:c] :optional {:a Foo} :mandatory {:b Bar}))
; => (HMap :absent-keys [:c] :optional {:a Foo} :mandatory {:b Bar})
(and (c/complete-hmap? left)
(c/partial-hmap? right))
(:absent-keys right)
; (merge (HMap :absent-keys [:a :b :c] :complete? true)
; (HMap :optional {:a Foo} :mandatory {:b Bar} :absent-keys [:c] :complete? true))
; => (HMap :optional {:a Foo} :mandatory {:b Bar} :complete? true)
(and (c/complete-hmap? left)
(c/complete-hmap? right))
#{}
:else (throw (Exception. "should never get here")))
:complete?
(and (c/complete-hmap? left)
(c/complete-hmap? right))))
(defn- merge-pair
[left right]
{:pre [(r/Type? left)
(r/TCResult? right)]
:post [((some-fn nil? r/Type?) %)]}
(let [left-map (ind/subtype? left (impl/impl-case
:clojure (c/RClass-of IPersistentMap [r/-any r/-any])
:cljs (c/-name 'typed.clojure/Map r/-any r/-any)))
right-map (ind/subtype? (r/ret-t right) (impl/impl-case
:clojure (c/RClass-of IPersistentMap [r/-any r/-any])
:cljs (c/-name 'typed.clojure/Map r/-any r/-any)))]
(cond
; preserve the rhand alias when possible
(and (ind/subtype? left r/-nil) right-map)
(r/ret-t right)
:else
(c/union-or-nil
(for [rtype (c/resolved-type-vector (r/ret-t right))]
(cond
(and (or left-map (ind/subtype? left r/-nil))
(ind/subtype? rtype r/-nil))
left
(and (ind/subtype? left r/-nil)
(ind/subtype? rtype (impl/impl-case
:clojure (c/RClass-of IPersistentMap [r/-any r/-any])
:cljs (c/-name 'typed.clojure/Map r/-any r/-any))))
rtype
(and (r/HeterogeneousMap? left) (r/HeterogeneousMap? rtype))
(merge-hmaps left rtype)
(and (not (ind/subtype? left (impl/impl-case
:clojure (c/RClass-of IPersistentVector [r/-any])
:cljs (c/Protocol-of 'cljs.core/IVector [r/-any]))))
(satisfies? AssocableType left)
(r/HeterogeneousMap? rtype))
(do
;TODO
(assert (empty? (:optional rtype)))
(apply assoc-type-pairs left (map (fn [[k t]]
[(r/ret k) (r/ret t)])
(:types rtype))))))))))
(defn merge-types [left & r-tcresults]
{:pre [(r/Type? left)
(every? r/TCResult? r-tcresults)]
:post [((some-fn nil? r/Type?) %)]}
(c/reduce-type-transform merge-pair left r-tcresults))
; conj helper
(defn- conj-pair [left right]
{:pre [(r/Type? left)
(r/TCResult? right)]
:post [((some-fn nil? r/TCResult?) right)]}
(cond
(r/HeterogeneousVector? left)
(assoc-type-pairs left [(r/ret (r/-val (count (:types left))))
right])
(ind/subtype? left r/-nil)
(r/-hvec [(:t right)]
:filters [(:fl right)]
:objects [(:o right)])
; other rules need to unwrap the rhs
:else
(c/union-or-nil
(for [rtype (c/resolved-type-vector right)]
(cond
(and (r/HeterogeneousMap? left)
(r/HeterogeneousVector? rtype))
(if (= (count (:types rtype)) 2)
(assoc-type-pairs left (map r/ret (:types rtype)))
(err/int-error "Need vector of length 2 to conj to map"))
(and (r/HeterogeneousMap? left)
(ind/subtype? rtype r/-nil))
left)))))
(defn conj-types [left & rtypes]
{:pre [(r/Type? left)
(every? r/TCResult? rtypes)]
:post [((some-fn nil? r/Type?) %)]}
(c/reduce-type-transform conj-pair left rtypes))