Skip to content

Commit c62bcbe

Browse files
committed
Add type inference for several number functions
1 parent daba082 commit c62bcbe

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

src/cl-functions.lisp

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,162 @@
171171
(maybe-constant-form-value type env :default t))
172172

173173
(_ t)))
174+
175+
(defstruct (numeric-op (:conc-name %numeric-op-))
176+
closed-under-fixnum-p
177+
closed-under-integers-p
178+
closed-under-rationals-p
179+
closed-under-float-p
180+
result-necessarily-float-p
181+
result-necessarily-integer-p
182+
result-necessarily-real-p)
183+
184+
(macrolet ((def (slot-name)
185+
`(defun ,(symbolicate 'numeric-op '- slot-name) (op)
186+
(multiple-value-bind (value existsp)
187+
(gethash op *numeric-op-table*)
188+
(if existsp
189+
(,(symbolicate '%numeric-op- slot-name) value)
190+
nil)))))
191+
(def closed-under-fixnum-p)
192+
(def closed-under-integers-p)
193+
(def closed-under-rationals-p)
194+
(def closed-under-float-p)
195+
(def result-necessarily-float-p)
196+
(def result-necessarily-integer-p)
197+
(def result-necessarily-real-p))
198+
199+
(defparameter *numeric-op-table*
200+
(alist-hash-table
201+
(nconc
202+
(list (cons '+ (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t))
203+
(cons '- (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t))
204+
(cons '* (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t))
205+
(cons '/ (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p nil :closed-under-rationals-p t :closed-under-float-p t))
206+
207+
(cons '1+ (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t))
208+
(cons '1- (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t)))
209+
210+
#+sbcl
211+
(list (cons 'max (make-numeric-op :closed-under-fixnum-p t :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t :result-necessarily-real-p t))
212+
(cons 'min (make-numeric-op :closed-under-fixnum-p t :closed-under-integers-p t :closed-under-rationals-p t :closed-under-float-p t :result-necessarily-real-p t)))
213+
214+
;; Implementations are free to decide whether to apply contagions or not: http://clhs.lisp.se/Body/f_max_m.htm
215+
#-sbcl
216+
(list (cons 'max (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p nil :closed-under-rationals-p nil :closed-under-float-p nil :result-necessarily-real-p t))
217+
(cons 'min (make-numeric-op :closed-under-fixnum-p nil :closed-under-integers-p nil :closed-under-rationals-p nil :closed-under-float-p nil :result-necessarily-real-p t)))
218+
219+
(list (cons 'floor (make-numeric-op :closed-under-fixnum-p t :closed-under-integers-p t :closed-under-rationals-p t :result-necessarily-integer-p t))
220+
(cons 'ceiling (make-numeric-op :closed-under-fixnum-p t :closed-under-integers-p t :closed-under-rationals-p t :result-necessarily-integer-p t))
221+
(cons 'truncate (make-numeric-op :closed-under-fixnum-p t :closed-under-integers-p t :closed-under-rationals-p t :result-necessarily-integer-p t))
222+
(cons 'round (make-numeric-op :closed-under-fixnum-p t :closed-under-integers-p t :closed-under-rationals-p t :result-necessarily-integer-p t))
223+
224+
(cons 'ffloor (make-numeric-op :closed-under-float-p t :result-necessarily-float-p t))
225+
(cons 'fceiling (make-numeric-op :closed-under-float-p t :result-necessarily-float-p t))
226+
(cons 'ftruncate (make-numeric-op :closed-under-float-p t :result-necessarily-float-p t))
227+
(cons 'fround (make-numeric-op :closed-under-float-p t :result-necessarily-float-p t)))
228+
229+
#+sbcl
230+
(list (cons 'sin (make-numeric-op :result-necessarily-float-p t))
231+
(cons 'cos (make-numeric-op :result-necessarily-float-p t))
232+
(cons 'tan (make-numeric-op :result-necessarily-float-p t)))
233+
#-sbcl
234+
(list (cons 'sin (make-numeric-op))
235+
(cons 'cos (make-numeric-op))
236+
(cons 'tan (make-numeric-op))))))
237+
238+
(defun numeric-result-type (op arg-types env)
239+
240+
(flet ((some-subtypep (type)
241+
(some (lambda (arg-type)
242+
(subtypep arg-type type env))
243+
arg-types))
244+
(all-subtypep (type)
245+
(every (lambda (arg-type)
246+
(subtypep arg-type type env))
247+
arg-types)))
248+
249+
(let* ((realp (numeric-op-result-necessarily-real-p op))
250+
(complex-possible-p (not realp)))
251+
252+
(when (numeric-op-closed-under-fixnum-p op)
253+
(when (all-subtypep 'fixnum)
254+
(return-from numeric-result-type 'fixnum))
255+
(when (and complex-possible-p
256+
(all-subtypep '(complex fixnum)))
257+
(return-from numeric-result-type '(complex fixnum))))
258+
259+
(when (numeric-op-closed-under-integers-p op)
260+
(when (all-subtypep 'integer)
261+
(return-from numeric-result-type 'integer))
262+
(when (and complex-possible-p
263+
(all-subtypep '(complex integer)))
264+
(return-from numeric-result-type '(complex integer))))
265+
266+
(when (numeric-op-closed-under-rationals-p op)
267+
(when (all-subtypep 'rational)
268+
(return-from numeric-result-type 'rational))
269+
(when (and complex-possible-p
270+
(all-subtypep '(complex rational)))
271+
(return-from numeric-result-type '(complex rational))))
272+
273+
(when (numeric-op-closed-under-float-p op)
274+
(when (all-subtypep 'single-float)
275+
(return-from numeric-result-type 'single-float))
276+
(when (all-subtypep 'double-float)
277+
(return-from numeric-result-type 'double-float))
278+
279+
(when complex-possible-p
280+
(when (all-subtypep '(complex single-float))
281+
(return-from numeric-result-type '(complex single-float)))
282+
(when (all-subtypep '(complex double-float))
283+
(return-from numeric-result-type '(complex double-float)))))
284+
285+
(when (numeric-op-result-necessarily-integer-p op)
286+
(return-from numeric-result-type 'integer))
287+
288+
(when (numeric-op-result-necessarily-float-p op)
289+
(when (some-subtypep 'double-float)
290+
(return-from numeric-result-type 'double-float))
291+
;; What happens if all the results are rational?
292+
(return-from numeric-result-type 'single-float))
293+
294+
(return-from numeric-result-type 'number))))
295+
296+
(defun every-eql-type-p (types env)
297+
(let (values)
298+
(every (lambda (type)
299+
(optima:match type
300+
((list 'eql value)
301+
(push value values))
302+
(_
303+
(return-from every-eql-type-p
304+
(values nil nil)))))
305+
types)
306+
(values t (nreverse values))))
307+
308+
(defun numeric-op-form-type (op args env)
309+
(let ((arg-types (mapcar (lambda (arg)
310+
(introspect-environment:typexpand
311+
(form-type arg env)
312+
env))
313+
args)))
314+
(multiple-value-bind (all-eql-p values)
315+
(every-eql-type-p arg-types env)
316+
(cond (all-eql-p
317+
(or (ignore-errors `(eql ,(apply op values)))
318+
`number))
319+
(t
320+
(numeric-result-type op arg-types env))))))
321+
322+
(macrolet ((def (&rest ops)
323+
`(progn
324+
,@(mapcar (lambda (op)
325+
`(defmethod custom-form-type ((op (eql ',op)) args env)
326+
(numeric-op-form-type op args env)))
327+
ops))))
328+
(def + - / * 1+ 1- max min
329+
floor ceiling truncate round
330+
ffloor fceiling fruncate fround
331+
332+
sin cos tan atan))

0 commit comments

Comments
 (0)