forked from froggey/Mezzano
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtype-check.lisp
267 lines (243 loc) · 12.5 KB
/
type-check.lisp
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
;;;; A pass to insert type checks around THE forms.
(in-package :mezzano.compiler)
(defgeneric insert-type-checks-1 (form value-context))
(defun insert-type-checks (form target)
(declare (ignore target))
(insert-type-checks-1 form :single))
(defmethod insert-type-checks-1 ((form lexical-variable) value-context)
form)
(defmethod insert-type-checks-1 ((form lambda-information) value-context)
(let ((*current-lambda* form))
(dolist (arg (lambda-information-optional-args form))
(setf (second arg) (insert-type-checks-1 (second arg) :single)))
(dolist (arg (lambda-information-key-args form))
(setf (second arg) (insert-type-checks-1 (second arg) :single)))
(setf (lambda-information-body form)
(insert-type-checks-1 (lambda-information-body form) :tail)))
form)
(defmethod insert-type-checks-1 ((form ast-call) value-context)
(setf (arguments form)
(loop
for arg in (arguments form)
collect (insert-type-checks-1 arg :single)))
form)
(defmethod insert-type-checks-1 ((form ast-block) value-context)
(setf (block-information-return-mode (info form)) value-context)
(setf (body form) (insert-type-checks-1 (body form) value-context))
form)
(defmethod insert-type-checks-1 ((form ast-function) value-context)
form)
(defmethod insert-type-checks-1 ((form ast-go) value-context)
(setf (info form) (insert-type-checks-1 (info form) :single))
form)
(defmethod insert-type-checks-1 ((form ast-if) value-context)
(setf (test form) (insert-type-checks-1 (test form) :single)
(if-then form) (insert-type-checks-1 (if-then form) value-context)
(if-else form) (insert-type-checks-1 (if-else form) value-context))
form)
(defmethod insert-type-checks-1 ((form ast-let) value-context)
(setf (bindings form) (loop
for (var initform) in (bindings form)
collect (list var (insert-type-checks-1 initform :single))))
(setf (body form) (insert-type-checks-1 (body form) value-context))
form)
(defmethod insert-type-checks-1 ((form ast-multiple-value-bind) value-context)
(setf (value-form form) (insert-type-checks-1 (value-form form) :multiple)
(body form) (insert-type-checks-1 (body form) value-context))
form)
(defmethod insert-type-checks-1 ((form ast-multiple-value-call) value-context)
(setf (function-form form) (insert-type-checks-1 (function-form form) :single)
(value-form form) (insert-type-checks-1 (value-form form) :multiple))
form)
(defmethod insert-type-checks-1 ((form ast-multiple-value-prog1) value-context)
(setf (value-form form) (insert-type-checks-1 (value-form form) value-context)
(body form) (insert-type-checks-1 (body form) :effect))
form)
(defmethod insert-type-checks-1 ((form ast-progn) value-context)
(setf (forms form) (loop
for (subform . rest) on (forms form)
collect (insert-type-checks-1 subform
(if rest
:effect
value-context))))
form)
(defmethod insert-type-checks-1 ((form ast-quote) value-context)
form)
(defmethod insert-type-checks-1 ((form ast-return-from) value-context)
(setf (value form) (insert-type-checks-1 (value form)
(block-information-return-mode (ast-target form)))
(info form) (insert-type-checks-1 (info form) :single))
form)
(defmethod insert-type-checks-1 ((form ast-setq) value-context)
(setf (value form) (insert-type-checks-1 (value form) value-context))
form)
(defmethod insert-type-checks-1 ((form ast-tagbody) value-context)
(setf (statements form)
(loop
for (go-tag statement) in (statements form)
collect (list go-tag (insert-type-checks-1 statement :effect))))
form)
(defun parse-values-type (type)
(cond ((not (typep type '(cons (eql values) t)))
;; Simple non-values type.
(values '() (list type) 't 't))
((not (or (find '&optional (rest type))
(find '&rest (rest type))
(find '&allow-other-keys (rest type))))
;; Simple values type.
(values '() (rest type) 't 't))
(t
;; Complicated values type.
(let ((required-types '())
(optional-types '())
(rest-type 'null)
(allow-other-keys nil)
(current (rest type)))
;; Collecting required types.
(loop
(when (or (endp current)
(member (first current) '(&optional &rest &allow-other-keys)))
(return))
(push (pop current) required-types))
(when (eql (first current) '&optional)
(pop current)
;; Collecting optional types.
(loop
(when (eql (first current) '&optional)
(error "Invalid VALUES type ~S. &OPTIONAL specified multiple times." type))
(when (or (endp current)
(member (first current) '(&rest &allow-other-keys)))
(return))
(push (pop current) optional-types)))
(when (eql (first current) '&rest)
(pop current)
(when (endp current)
(error "Invalid VALUES type ~S. &REST not followed by typespec."
type))
(when (eql (first current) '&allow-other-keys)
(error "Invalid VALUES type ~S. &REST not followed by typespec, saw &ALLOW-OTHER-KEYS."
type))
(setf rest-type (pop current)))
(when (eql (first current) '&allow-other-keys)
(pop current)
(setf allow-other-keys t))
(when (not (endp current))
(error "Invalid VALUES type ~S. Junk at end." type))
(values (reverse required-types)
(reverse optional-types)
rest-type
allow-other-keys)))))
(defun simplify-complicated-function-type (type &optional environment)
"Reduce complicated function types like (FUNCTION (T T) BOOLEAN) to just FUNCTION.
Descends into inner AND/OR/NOT types."
(labels ((frob (type)
(let ((expanded (sys.int::typeexpand type environment)))
;; Try to keep the original type intact as much as possible.
(cond ((not (consp expanded))
(values type nil))
((eql (first expanded) 'function)
(values 'function t))
((member (first expanded) '(and or not))
(let* ((did-change nil)
(new (loop
for ty in (rest expanded)
collect (multiple-value-bind (new-ty differentp)
(frob ty)
(when differentp
(setf did-change t))
new-ty))))
(if did-change
(values (list* (first expanded) new) t)
(values type nil))))
(t
(values type nil))))))
(values (frob type))))
(defmethod insert-type-checks-1 ((form ast-the) value-context)
;; There are a few possible cases:
;; 1) Not compiling at safety 3. Don't do anything.
;; 2) A type of (VALUES). Don't do anything.
;; 3) Single/effect value context with a single-value type X or (VALUES X).
;; Bind the value to a variable and test it.
;; 4) Single/effect value context with a multiple-value type or
;; multiple/tail value context. Save values, inspect them.
;; TODO: What does &ALLOW-OTHER-KEYS mean?
(multiple-value-bind (required-typespecs optional-typespecs rest-typespec)
(parse-values-type (ast-the-type form))
(setf (value form)
(cond ((or (not (eql (optimize-quality form 'safety) 3))
;; (VALUES T...)
(and (endp required-typespecs)
(every (lambda (x) (eql x 't)) optional-typespecs)
(eql rest-typespec 't)))
(insert-type-checks-1 (value form) value-context))
((and (endp required-typespecs)
(eql (length optional-typespecs) 1)
(eql rest-typespec 't)
(or (member value-context '(:effect :single))
;; Lexical variable obviously only generates one value.
(typep (unwrap-the (value form)) 'lexical-variable)))
;; Single value case.
(ast `(let ((val ,(insert-type-checks-1 (value form) :single)))
(progn
(if (source-fragment (typep val ',(simplify-complicated-function-type (first optional-typespecs))))
'nil
(progn
(call sys.int::raise-type-error val ',(ast-the-type form))
(call sys.int::%%unreachable)))
val))
form))
(t
;; Complicated case, many types.
;; This preserves all values passed through the THE.
;; TODO: Make this more efficient. Save values a la M-V-P1
;; then inspect the saved values.
(let ((req-values (loop
for ty in required-typespecs
collect (gensym)))
(opt-values (loop
for ty in optional-typespecs
collect (gensym)))
(rest-value (gensym)))
(insert-type-checks-1
(ast `(multiple-value-call
(source-fragment
(lambda (,@req-values &optional ,@opt-values &rest ,rest-value sys.int::&count n-values)
(declare (dynamic-extent ,rest-value)
(sys.int::lambda-name (the ,(ast-the-type form))))
,@(loop
for ty in required-typespecs
for var in req-values
collect `(the ,ty ,var))
,@(loop
for ty in optional-typespecs
for var in opt-values
collect `(the ,ty ,var))
(the ,rest-typespec ,rest-value)
;; Avoid consing...
(let* ((opt-list (list* ,@opt-values ,rest-value))
(req-list (list* ,@req-values opt-list)))
(declare (dynamic-extent opt-list req-list))
;; Slice the list down to the values actually returned.
,@(when opt-values
;; Only needed when there are optional values (I think?)
`((when (< n-values ,(+ (length req-values) (length opt-values)))
(let* ((scratch (cons nil req-list))
(last (nthcdr n-values scratch)))
(declare (dynamic-extent scratch))
(setf (cdr last) nil
req-list (cdr scratch))))))
(values-list req-list))))
,(value form))
form)
value-context))))))
form)
(defmethod insert-type-checks-1 ((form ast-unwind-protect) value-context)
(setf (protected-form form) (insert-type-checks-1 (protected-form form) value-context))
(setf (cleanup-function form) (insert-type-checks-1 (cleanup-function form) :effect))
form)
(defmethod insert-type-checks-1 ((form ast-jump-table) value-context)
(setf (value form) (insert-type-checks-1 (value form) :single))
(setf (targets form) (loop
for target in (targets form)
collect (insert-type-checks-1 target value-context)))
form)