This repository has been archived by the owner on Jan 2, 2018. It is now read-only.
/
core.clj
615 lines (533 loc) · 20.5 KB
/
core.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
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
(ns typed.core
(:refer-clojure :exclude [defrecord type])
(:import (clojure.lang IPersistentList IPersistentVector Symbol Cons Seqable IPersistentCollection
ISeq ASeq ILookup Var Namespace PersistentVector APersistentVector
IFn IPersistentStack Associative IPersistentSet IPersistentMap IMapEntry
Keyword Atom PersistentList IMeta PersistentArrayMap Compiler Named
IRef AReference ARef IDeref IReference APersistentSet PersistentHashSet Sorted
LazySeq APersistentMap))
(:require [analyze.core :refer [ast] :as analyze]
[clojure.set :as set]
[clojure.reflect :as reflect]
[clojure.string :as str]
[clojure.repl :refer [pst]]
[clojure.pprint :refer [pprint]]
[trammel.core :as contracts]
[clojure.math.combinatorics :as comb]
[cljs
[compiler]
[analyzer :as cljs]]
[clojure.tools.trace :refer [trace-vars untrace-vars
trace-ns untrace-ns]]))
; constraint shorthands, other handy functions
(load "utils")
;Note: defrecord is now trammel's defconstrainedrecord
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Special functions
(defn print-env
"Print the current type environment, and debug-string"
[debug-string] nil)
(defn print-filterset
"Print the filter set attached to form, and debug-string"
[debug-string frm]
frm)
(declare Method->Function unparse-type unparse-filter)
(defn method-type
"Given a method symbol, print the Typed Clojure types assigned to it"
[mname]
(let [ms (->> (reflect/type-reflect (Class/forName (namespace mname)))
:members
(filter #(and (instance? clojure.reflect.Method %)
(= (str (:name %)) (name mname))))
set)
_ (assert (seq ms) (str "Method " mname " not found"))]
(prn "Method name:" mname)
(doseq [m ms]
(prn (unparse-type (Method->Function m))))))
(defn inst-poly
[inst-of types-syn]
inst-of)
(defn inst-poly-ctor [inst-of types-syn]
inst-of)
(defmacro inst
"Instantiate a polymorphic type with a number of types"
[inst-of & types]
`(inst-poly ~inst-of '~types))
(defmacro inst-ctor
"Instantiate a call to a constructor with a number of types.
First argument must be an immediate call to a constructor."
[inst-of & types]
`(inst-poly-ctor ~inst-of '~types))
(defn fn>-ann [fn-of param-types-syn]
fn-of)
(defn pfn>-ann [fn-of polys param-types-syn]
fn-of)
(defn loop>-ann [loop-of bnding-types]
loop-of)
(defn- parse-fn>
"(fn> name? :- type? [[param :- type]* & [param :- type *]?] exprs*)
(fn> name? (:- type? [[param :- type]* & [param :- type *]?] exprs*)+)"
[is-poly forms]
(let [name (when (symbol? (first forms))
(first forms))
forms (if name (rest forms) forms)
poly (when is-poly
(first forms))
forms (if poly (rest forms) forms)
methods (if ((some-fn vector? keyword?) (first forms))
(list forms)
forms)
;(fn> name? (:- type? [[param :- type]* & [param :- type *]?] exprs*)+)"
; (HMap {:dom (Seqable TypeSyntax)
; :rng (U nil TypeSyntax)
; :body Any})
parsed-methods (doall
(for [method methods]
(let [[ret has-ret?] (when (not (vector? (first method)))
(assert (= :- (first method))
"Return type for fn> must be prefixed by :-")
[(second method) true])
method (if ret
(nnext method)
method)
body (rest method)
arg-anns (first method)
[required-params _ [rest-param]] (split-with #(not= '& %) arg-anns)]
(assert (sequential? required-params)
"Must provide a sequence of typed parameters to fn>")
(assert (not rest-param) "fn> doesn't support rest parameters yet")
{:dom-syntax (doall (map (comp second next) required-params))
:dom-lhs (doall (map first required-params))
:rng-syntax ret
:has-rng? has-ret?
:body body})))]
{:poly poly
:fn `(fn ~@(concat
(when name
[name])
(for [{:keys [body dom-lhs]} parsed-methods]
(apply list (vec dom-lhs) body))))
:parsed-methods parsed-methods}))
(defmacro pfn>
"Define a polymorphic typed anonymous function.
(pfn> name? [binder+] :- type? [[param :- type]* & [param :- type *]?] exprs*)
(pfn> name? [binder+] (:- type? [[param :- type]* & [param :- type *]?] exprs*)+)"
[& forms]
(let [{:keys [poly fn parsed-methods]} (parse-fn> true forms)]
`(pfn>-ann ~fn '~poly '~parsed-methods)))
(defmacro fn>
"Define a typed anonymous function.
(fn> name? :- type? [[param :- type]* & [param :- type *]?] exprs*)
(fn> name? (:- type? [[param :- type]* & [param :- type *]?] exprs*)+)"
[& forms]
(let [{:keys [fn parsed-methods]} (parse-fn> false forms)]
`(fn>-ann ~fn '~parsed-methods)))
(defmacro loop>
"Define a typed loop"
[bndings* & forms]
(let [bnds (partition 2 bndings*)
; [[lhs :- bnd-ann] rhs]
lhs (map ffirst bnds)
rhs (map second bnds)
bnd-anns (map #(-> % first next second) bnds)]
`(loop>-ann (loop ~(vec (mapcat vector lhs rhs))
~@forms)
'~bnd-anns)))
(defmacro declare-datatypes
"Declare datatypes, similar to declare but on the type level."
[& syms]
`(tc-ignore
(doseq [sym# '~syms]
(assert (not (or (some #(= \. %) (str sym#))
(namespace sym#)))
(str "Cannot declare qualified datatype: " sym#))
(let [qsym# (symbol (str (munge (name (ns-name *ns*))) \. (name sym#)))]
(declare-datatype* qsym#)))))
(defmacro declare-protocols
"Declare protocols, similar to declare but on the type level."
[& syms]
`(tc-ignore
(doseq [sym# '~syms]
(let [qsym# (if (namespace sym#)
sym#
(symbol (str (name (ns-name *ns*))) (name sym#)))]
(declare-protocol* qsym#)))))
(defmacro declare-alias-kind
"Declare a kind for an alias, similar to declare but on the kind level."
[sym ty]
`(tc-ignore
(do (ensure-clojure)
(let [sym# '~sym
qsym# (if (namespace sym#)
sym#
(symbol (name (ns-name *ns*)) (name sym#)))
ty# (parse-type '~ty)]
(assert (not (namespace sym#)) (str "Cannot declare qualified name " sym#))
(declare ~sym)
(declare-names ~sym)
(declare-alias-kind* qsym# ty#)))))
(defmacro declare-names
"Declare names, similar to declare but on the type level."
[& syms]
`(tc-ignore
(doseq [sym# '~syms]
(let [qsym# (if (namespace sym#)
sym#
(symbol (name (ns-name *ns*)) (name sym#)))]
(declare-name* qsym#)))))
(defmacro def-alias
"Define a type alias"
[sym type]
`(tc-ignore
(do (ensure-clojure)
(let [sym# (if (namespace '~sym)
'~sym
(symbol (name (ns-name *ns*)) (name '~sym)))
ty# (parse-type '~type)]
(add-type-name sym# ty#)
(declare ~sym)
(when-let [tfn# (@DECLARED-KIND-ENV sym#)]
(assert (subtype? ty# tfn#) (error-msg "Declared kind " (unparse-type tfn#)
" does not match actual kind " (unparse-type ty#))))
[sym# (unparse-type ty#)]))))
(defn into-array>* [javat cljt coll]
(into-array (resolve javat) coll))
(defmacro into-array>
"Make a Java array with Java class javat and Typed Clojure type
cljt. Resulting array will be of type javat, but elements of coll must be under
cljt. cljt should be a subtype of javat (the same or more specific)."
[javat cljt coll]
`(into-array>* '~javat '~cljt ~coll))
(defn ann-form* [form ty]
form)
(defn ann-form* [form ty]
form)
(defmacro ann-form [form ty]
`(ann-form* ~form '~ty))
(defn unsafe-ann-form* [form ty]
form)
(defmacro unsafe-ann-form [form ty]
`(unsafe-ann-form* ~form '~ty))
(defn tc-ignore-forms* [r]
r)
(defmacro tc-ignore
"Ignore forms in body during type checking"
[& body]
`(tc-ignore-forms* (do
~@body)))
(defmacro non-nil-return
"Override the return type of method msym to be non-nil.
Takes a set of relevant arities,
represented by the number of parameters it takes (rest parameter counts as one),
or :all which overrides all arities.
eg. (non-nil-return java.lang.Class/getDeclaredMethod :all)"
[msym arities]
`(tc-ignore
(add-nonnilable-method-return '~msym '~arities)))
(defmacro nilable-param
"Overrides which parameters in a method may accept
nilable values. If the parameter is a parameterised type or
an Array, this also declares the parameterised types and the Array type as nilable.
mmap is a map mapping arity parameter number to a set of parameter
positions (integers). If the map contains the key :all then this overrides
other entries. The key can also be :all, which declares all parameters nilable."
[msym mmap]
`(tc-ignore
(add-method-nilable-param '~msym '~mmap)))
(declare abstract-many instantiate-many)
(load "type_rep")
(load "type_ops")
(load "filter_rep")
(load "filter_ops")
(load "path_rep")
(load "object_rep")
; must be after type/object/filter definitions
(load "fold")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Annotations
(declare TCResult?)
(defrecord PropEnv [l props]
"A lexical environment l, props is a list of known propositions"
[(every? (every-pred symbol? (complement namespace)) (keys l))
(every? Type? (vals l))
(every? Filter? props)])
(declare ^:dynamic *lexical-env*)
(defn print-env
([] (print-env *lexical-env*))
([e]
{:pre [(PropEnv? e)]}
;; DO NOT REMOVE
(prn {:env (into {} (for [[k v] (:l e)]
[k (unparse-type v)]))
:props (map unparse-filter (:props e))})))
(defonce VAR-ANNOTATIONS (atom {}))
(def ^:dynamic *lexical-env* (->PropEnv {} []))
(defmacro with-lexical-env [env & body]
`(binding [*lexical-env* ~env]
~@body))
(set-validator! VAR-ANNOTATIONS #(and (every? (every-pred symbol? namespace) (keys %))
(every? Type? (vals %))))
(set-validator! #'*lexical-env* PropEnv?)
(defmacro ann [varsym typesyn]
`(tc-ignore
(do (ensure-clojure)
(let [t# (parse-type '~typesyn)
s# (if (namespace '~varsym)
'~varsym
(symbol (-> *ns* ns-name str) (str '~varsym)))]
(do (add-var-type s# t#)
[s# (unparse-type t#)])))))
(declare parse-type alter-class*)
(defn parse-field [[n _ t]]
[n (parse-type t)])
(defn gen-datatype* [provided-name fields variances args ancests]
`(do (ensure-clojure)
(let [provided-name-str# (str '~provided-name)
;_# (prn "provided-name-str" provided-name-str#)
munged-ns-str# (if (some #(= \. %) provided-name-str#)
(apply str (butlast (apply concat (butlast (partition-by #(= \. %) provided-name-str#)))))
(str (munge (-> *ns* ns-name))))
;_# (prn "munged-ns-str" munged-ns-str#)
demunged-ns-str# (str (clojure.repl/demunge munged-ns-str#))
;_# (prn "demunged-ns-str" demunged-ns-str#)
local-name# (if (some #(= \. %) provided-name-str#)
(symbol (apply str (last (partition-by #(= \. %) (str provided-name-str#)))))
provided-name-str#)
;_# (prn "local-name" local-name#)
s# (symbol (str munged-ns-str# \. local-name#))
fs# (apply array-map (apply concat (with-frees (mapv make-F '~args)
(mapv parse-field '~fields))))
as# (set (with-frees (mapv make-F '~args)
(mapv parse-type '~ancests)))
_# (add-datatype-ancestors s# as#)
pos-ctor-name# (symbol demunged-ns-str# (str "->" local-name#))
args# '~args
vs# '~variances
dt# (if args#
(Poly* args# (repeat (count args#) no-bounds)
(->DataType s# vs# (map make-F args#) fs#)
args#)
(->DataType s# nil nil fs#))
pos-ctor# (if args#
(Poly* args# (repeat (count args#) no-bounds)
(make-FnIntersection
(make-Function (vec (vals fs#)) (->DataType s# vs# (map make-F args#) fs#)))
args#)
(make-FnIntersection
(make-Function (vec (vals fs#)) dt#)))]
(do
(when vs#
(let [f# (mapv make-F (repeatedly (count vs#) gensym))]
(alter-class* s# (RClass* (map :name f#) vs# f# s# {}))))
(add-datatype s# dt#)
(add-var-type pos-ctor-name# pos-ctor#)
[[s# (unparse-type dt#)]
[pos-ctor-name# (unparse-type pos-ctor#)]]))))
(defmacro ann-datatype [dname fields & {ancests :unchecked-ancestors rplc :replace}]
(assert (not rplc) "Replace NYI")
(assert (symbol? dname)
(str "Must provide name symbol: " dname))
`(tc-ignore
~(gen-datatype* dname fields nil nil ancests)))
(defmacro ann-pdatatype [dname vbnd fields & {ancests :unchecked-ancestors rplc :replace}]
(assert (not rplc) "Replace NYI")
(assert (symbol? dname)
(str "Must provide local symbol: " dname))
`(tc-ignore
~(gen-datatype* dname fields (map second vbnd) (map first vbnd) ancests)))
(defn gen-protocol* [local-varsym variances args mths]
`(do (ensure-clojure)
(let [local-vsym# '~local-varsym
s# (symbol (-> *ns* ns-name str) (str local-vsym#))
on-class# (symbol (str (munge (namespace s#)) \. local-vsym#))
; add a Name so the methods can be parsed
_# (declare-protocol* s#)
args# '~args
fs# (when args#
(map make-F args#))
ms# (into {} (for [[knq# v#] '~mths]
(do
(assert (not (namespace knq#))
"Protocol method should be unqualified")
[knq# (with-frees fs# (parse-type v#))])))
t# (if fs#
(Poly* (map :name fs#) (repeat (count fs#) no-bounds)
(->Protocol s# '~variances fs# on-class# ms#)
(map :name fs#))
(->Protocol s# nil nil on-class# ms#))]
(do
(add-protocol s# t#)
(doseq [[kuq# mt#] ms#]
;qualify method names when adding methods as vars
(let [kq# (symbol (-> *ns* ns-name str) (str kuq#))]
(add-var-type kq# mt#)))
[s# (unparse-type t#)]))))
(defmacro ann-protocol [local-varsym & {mths :methods}]
(assert (not (or (namespace local-varsym)
(some #{\.} (str local-varsym))))
(str "Must provide local var name for protocol: " local-varsym))
`(tc-ignore
~(gen-protocol* local-varsym nil nil mths)))
(defmacro ann-pprotocol [local-varsym vbnd & {mths :methods}]
(assert (not (or (namespace local-varsym)
(some #{\.} (str local-varsym))))
(str "Must provide local var name for protocol: " local-varsym))
`(tc-ignore
~(gen-protocol* local-varsym (mapv second vbnd) (mapv first vbnd) mths)))
(defmacro override-constructor [ctorsym typesyn]
`(tc-ignore
(do (ensure-clojure)
(let [t# (parse-type '~typesyn)
s# '~ctorsym]
(do (add-constructor-override s# t#)
[s# (unparse-type t#)])))))
(defmacro override-method [methodsym typesyn]
`(tc-ignore
(do (ensure-clojure)
(let [t# (parse-type '~typesyn)
s# (if (namespace '~methodsym)
'~methodsym
(throw (Exception. "Method name must be a qualified symbol")))]
(do (add-method-override s# t#)
[s# (unparse-type t#)])))))
(defn add-var-type [sym type]
(swap! VAR-ANNOTATIONS #(assoc % sym type))
nil)
(defn lookup-local [sym]
(-> *lexical-env* :l sym))
(defn var->symbol [var]
{:pre [(var? var)]
:post [(symbol? %)
(namespace %)]}
(symbol (str (ns-name (.ns ^Var var)))
(str (.sym ^Var var))))
(defn symbol->Class [sym]
{:pre [(symbol? sym)]
:post [(class? %)]}
(case sym
byte Byte/TYPE
short Short/TYPE
int Integer/TYPE
long Long/TYPE
float Float/TYPE
double Double/TYPE
boolean Boolean/TYPE
char Character/TYPE
(Class/forName (str sym))))
(defn Class->symbol [cls]
{:pre [(class? cls)]
:post [(symbol? %)]}
(symbol (.getName cls)))
(defn lookup-Var [nsym]
(assert (contains? @VAR-ANNOTATIONS nsym)
(str (when *current-env*
(str (:line *current-env*) ": "))
"Untyped var reference: " nsym))
(@VAR-ANNOTATIONS nsym))
(defn merge-locals [env new]
(-> env
(update-in [:l] #(merge % new))))
(defmacro with-locals [locals & body]
`(binding [*lexical-env* (merge-locals *lexical-env* ~locals)]
~@body))
(declare ^:dynamic *current-env*)
(defn type-of [sym]
{:pre [(symbol? sym)]
:post [(or (Type? %)
(TCResult? %))]}
(cond
(not (namespace sym)) (if-let [t (lookup-local sym)]
t
(throw (Exception. (str (when *current-env*
(str (:line *current-env*) ": "))
"Reference to untyped binding: " sym))))
:else (lookup-Var sym)))
(derive ::clojurescript ::default)
(derive ::clojure ::default)
(def TYPED-IMPL (atom ::clojure))
(set-validator! TYPED-IMPL #(isa? % ::default))
(defn ensure-clojure []
(reset! TYPED-IMPL ::clojure))
(defn ensure-clojurescript []
(reset! TYPED-IMPL ::clojurescript))
(defn checking-clojure? []
(= ::clojure @TYPED-IMPL))
(defn checking-clojurescript? []
(= ::clojurescript @TYPED-IMPL))
(load "dvar_env")
(load "datatype_ancestor_env")
(load "datatype_env")
(load "protocol_env")
(load "method_override_env")
(load "ctor_override_env")
(load "method_return_nilables")
(load "method_param_nilables")
(load "declared_kind_env")
(load "name_env")
(load "rclass_env")
(load "parse")
(load "unparse")
(load "frees")
(load "promote_demote")
(load "cs_gen")
(load "subst_dots")
(load "infer")
(load "tvar_rep")
(load "subst")
(load "trans")
(load "inst")
(load "subtype")
(load "alter")
(load "ann")
(load "check")
(load "check_cljs")
(defmacro cf-cljs
"Type check a Clojurescript form and return its type"
([form]
`(do (ensure-clojurescript)
(tc-ignore
(-> (ana-cljs {:locals {} :context :expr :ns {:name '~'cljs.user}} '~form) check-cljs expr-type unparse-TCResult))))
([form expected]
`(do (ensure-clojure)
(tc-ignore
(-> (ana-cljs {:locals {} :context :expr :ns {:name '~'cljs.user}}
'(typed.core/ann-form-cljs ~form ~expected))
(#(check-cljs % (ret (parse-type '~expected)))) expr-type unparse-TCResult)))))
(defmacro cf
"Type check a Clojure form and return its type"
([form]
`(do (ensure-clojure)
(tc-ignore
(-> (ast ~form) check expr-type unparse-TCResult))))
([form expected]
`(do (ensure-clojure)
(tc-ignore
(-> (ast (ann-form-cljs ~form ~expected)) (#(check % (ret (parse-type '~expected)))) expr-type unparse-TCResult)))))
(defn check-ns
"Type check a namespace. If not provided default to current namespace"
([] (check-ns (ns-name *ns*)))
([nsym]
(require nsym)
(ensure-clojure)
(with-open [pbr (analyze/pb-reader-for-ns nsym)]
(let [[_ns-decl_ & asts] (analyze/analyze-ns pbr (analyze/uri-for-ns nsym) nsym)]
(doseq [ast asts]
(check ast))))))
(defn trepl []
(clojure.main/repl
:eval (fn [f]
(let [t (do (ensure-clojure)
(-> (analyze/analyze-form f)
check expr-type unparse-TCResult))]
(prn t)
(eval f)))))
(comment
(check-ns 'typed.test.example)
; very slow because of update-composite
(check-ns 'typed.test.rbt)
(check-ns 'typed.test.macro)
(check-ns 'typed.test.conduit)
(check-ns 'typed.test.deftype)
(check-ns 'typed.test.core-logic)
(check-ns 'typed.test.ckanren)
)