changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/obj/meta/sealed.lisp

changeset 698: 96958d3eb5b0
parent: 10faf95f90dd
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 ;;; obj/meta/sealed.lisp --- Sealed Meta-objects
2 
3 ;; see https://github.com/marcoheisig/sealable-metaobjects
4 
5 ;;; Code:
6 (in-package :obj/meta/sealed)
7 
8 (defun starts-with (item)
9  (lambda (sequence)
10  (typecase sequence
11  (list (eql (first sequence) item))
12  (sequence (eql (elt sequence 0) item))
13  (otherwise nil))))
14 
15 (defun type-specifier-and (&rest type-specifiers)
16  (let ((relevant (remove t type-specifiers)))
17  (cond ((null relevant) t)
18  ((null (cdr relevant)) (first relevant))
19  (t `(and ,@relevant)))))
20 
21 (defun type-specifier-or (&rest type-specifiers)
22  (let ((relevant (remove nil type-specifiers)))
23  (cond ((null relevant) nil)
24  ((null (cdr relevant)) (first relevant))
25  (t `(or ,@relevant)))))
26 
27 (defun type-specifier-not (type-specifier)
28  (cond ((eql type-specifier t) nil)
29  ((eql type-specifier nil) t)
30  (t `(not ,type-specifier))))
31 
32 (defgeneric ensure-specializer (specializer-designator)
33  (:method ((class class))
34  class)
35  (:method ((symbol symbol))
36  (or (find-class symbol nil)
37  (call-next-method)))
38  (:method ((cons cons))
39  (if (typep cons '(cons (eql eql) (cons t null)))
40  (intern-eql-specializer (second cons))
41  (call-next-method)))
42  (:method ((object t))
43  (error "~@<~S is not a specializer, or a type designator that ~
44  can be converted to a specializer.~:@>"
45  object)))
46 
47 (defgeneric specializer-type (specializer)
48  (:method ((class class))
49  (class-name class))
50  (:method ((eql-specializer eql-specializer))
51  `(eql ,(eql-specializer-object eql-specializer))))
52 
53 (defgeneric specializer-prototype (specializer &optional excluded-specializers)
54  (:documentation
55  "Returns an object that is of the type indicated by SPECIALIZER, but not
56 of any of the types indicated the optionally supplied
57 EXCLUDED-SPECIALIZERS. Returns a secondary value of T if such an object
58 could be determined, and NIL if no such object was found.
59 
60 Examples:
61  (specializer-prototype
62  (find-class 'double-float))
63  => 5.0d0, T
64 
65  (specializer-prototype
66  (find-class 'double-float)
67  (list (intern-eql-specializer 5.0d0)))
68  => 6.0d0, T
69 
70  (specializer-prototype
71  (find-class 'real)
72  (list (find-class 'rational) (find-class 'float)))
73  => NIL, NIL
74 "))
75 
76 (defgeneric specializer-direct-superspecializers (specializer)
77  (:method ((class class))
78  (class-direct-superclasses class))
79  (:method ((eql-specializer eql-specializer))
80  (list
81  (class-of
82  (eql-specializer-object eql-specializer)))))
83 
84 (defgeneric specializer-intersectionp (specializer-1 specializer-2)
85  (:method ((class-1 class) (class-2 class))
86  (multiple-value-bind (disjointp success)
87  (subtypep `(and ,class-1 ,class-2) nil)
88  (assert success)
89  (not disjointp)))
90  (:method ((class class) (eql-specializer eql-specializer))
91  (typep (eql-specializer-object eql-specializer) class))
92  (:method ((eql-specializer eql-specializer) (class class))
93  (typep (eql-specializer-object eql-specializer) class))
94  (:method ((eql-specializer-1 eql-specializer) (eql-specializer-2 eql-specializer))
95  (eql (eql-specializer-object eql-specializer-1)
96  (eql-specializer-object eql-specializer-2))))
97 
98 (defgeneric specializer-subsetp (specializer-1 specializer-2)
99  (:method ((class-1 class) (class-2 class))
100  (values (subtypep class-1 class-2)))
101  (:method ((class class) (eql-specializer eql-specializer))
102  (subtypep class (specializer-type eql-specializer)))
103  (:method ((eql-specializer eql-specializer) (class class))
104  (typep (eql-specializer-object eql-specializer) class))
105  (:method ((eql-specializer-1 eql-specializer) (eql-specializer-2 eql-specializer))
106  (eql (eql-specializer-object eql-specializer-1)
107  (eql-specializer-object eql-specializer-2))))
108 
109 ;;; Working with domains.
110 
111 (defgeneric ensure-domain (domain-designator))
112 
113 (defgeneric method-domain (method))
114 
115 (defgeneric domain-specializers (domain))
116 
117 (defgeneric domain-arity (domain))
118 
119 (defgeneric domain-equal (domain-1 domain-2))
120 
121 (defgeneric domain-intersectionp (domain-1 domain-2))
122 
123 (defgeneric domain-subsetp (domain-1 domain-2))
124 
125 ;;; Checking for sealability.
126 
127 (defgeneric metaobject-sealable-p (metaobject)
128  (:method ((class class)) (eql class (find-class t)))
129  (:method ((generic-function generic-function)) nil)
130  (:method ((method method)) nil)
131  (:method ((built-in-class built-in-class)) t)
132  (:method ((structure-class structure-class)) t)
133  #+sbcl (:method ((system-class sb-pcl:system-class)) t))
134 
135 (defgeneric class-sealable-p (class)
136  (:method ((class class))
137  (metaobject-sealable-p class)))
138 
139 (defgeneric generic-function-sealable-p (generic-function)
140  (:method ((generic-function generic-function))
141  (metaobject-sealable-p generic-function)))
142 
143 (defgeneric method-sealable-p (method)
144  (:method ((method method))
145  (metaobject-sealable-p method)))
146 
147 (defgeneric specializer-sealable-p (specializer)
148  (:method ((class class))
149  (class-sealable-p class))
150  (:method ((eql-specializer eql-specializer))
151  (class-sealable-p
152  (class-of
153  (eql-specializer-object eql-specializer)))))
154 
155 ;;; Checking for sealed-ness.
156 
157 (defgeneric metaobject-sealed-p (metaobject)
158  (:method ((class class)) (eql class (find-class t)))
159  (:method ((generic-function generic-function)) nil)
160  (:method ((method method)) nil)
161  (:method ((built-in-class built-in-class)) t)
162  (:method ((structure-class structure-class)) t)
163  #+sbcl (:method ((system-class sb-pcl:system-class)) t))
164 
165 (defgeneric class-sealed-p (class)
166  (:method ((class class))
167  (metaobject-sealed-p class)))
168 
169 (defgeneric generic-function-sealed-p (generic-function)
170  (:method ((generic-function generic-function))
171  (metaobject-sealed-p generic-function)))
172 
173 (defgeneric method-sealed-p (method)
174  (:method ((method method))
175  (metaobject-sealed-p method)))
176 
177 (defgeneric specializer-sealed-p (specializer)
178  (:method ((class class))
179  (class-sealed-p class))
180  (:method ((eql-specializer eql-specializer))
181  (specializer-sealed-p
182  (class-of
183  (eql-specializer-object eql-specializer)))))
184 
185 ;;; Sealing of metaobjects.
186 
187 (defgeneric seal-metaobject (metaobject)
188  ;; Invoke primary methods on SEAL-METAOBJECT at most once.
189  (:method :around ((metaobject t))
190  (unless (metaobject-sealed-p metaobject)
191  (call-next-method)))
192  ;; Signal an error if the default primary method is reached.
193  (:method ((metaobject t))
194  (error "Cannot seal the metaobject ~S." metaobject))
195  (:method :before ((class class))
196  ;; Class sealing implies finalization.
197  (unless (class-finalized-p class)
198  (finalize-inheritance class))
199  ;; A sealed class must have sealed superclasses.
200  (loop for class in (rest (class-precedence-list class))
201  until (member class *standard-metaobjects*)
202  do (seal-class class))))
203 
204 (defgeneric seal-class (class)
205  ;; Invoke primary methods on SEAL-CLASS at most once.
206  (:method :around ((class class))
207  (unless (class-sealed-p class)
208  (call-next-method)))
209  (:method ((symbol symbol))
210  (seal-metaobject (find-class symbol)))
211  (:method ((class class))
212  (seal-metaobject class)))
213 
214 (defgeneric seal-generic-function (generic-function)
215  ;; Invoke primary methods on SEAL-GENERIC-FUNCTION at most once.
216  (:method :around ((generic-function generic-function))
217  (unless (generic-function-sealed-p generic-function)
218  (call-next-method)))
219  (:method ((generic-function generic-function))
220  (seal-metaobject generic-function)))
221 
222 (defgeneric seal-method (method)
223  ;; Invoke primary methods on SEAL-METHOD at most once.
224  (:method :around ((method method))
225  (unless (method-sealed-p method)
226  (call-next-method)))
227  (:method ((method method))
228  (seal-metaobject method)))
229 
230 (defgeneric seal-domain (generic-function domain))
231 
232 (defgeneric seal-specializer (specializer)
233  (:method ((class class))
234  (seal-class class))
235  (:method ((eql-specializer eql-specializer))
236  (seal-class
237  (class-of
238  (eql-specializer-object eql-specializer)))))
239 
240 ;;; Method properties
241 
242 (defgeneric method-properties (method)
243  (:method ((method method))
244  '()))
245 
246 (defgeneric validate-method-property (method method-property)
247  (:method ((method method) (method-property t))
248  nil))
249 
250 ;;; Miscellaneous
251 
252 (defgeneric sealed-domains (generic-function)
253  (:method ((generic-function generic-function))
254  '()))
255 
256 (defgeneric compute-static-call-signatures (generic-function domain))
257 
258 (defgeneric externalizable-object-p (object)
259  ;; Built-in objects are usually externalizable.
260  (:method ((object t))
261  (typep (class-of object) 'built-in-class))
262  ;; Functions are not externalizable by definition.
263  (:method ((function function))
264  nil)
265  ;; Structure objects may be externalizable even without an appropriate
266  ;; method on MAKE-LOAD-FORM.
267  (:method ((structure-object structure-object))
268  ;; TODO: Returning T here is a bit bold. Actually we'd have to check
269  ;; whether each slot of the structure has a value that is
270  ;; externalizable.
271  t)
272  ;; Standard objects are only externalizable if they have an appropriate
273  ;; method on MAKE-LOAD-FORM.
274  (:method ((standard-object standard-object))
275  (and (make-load-form standard-object) t)))
276 
277 (defclass domain ()
278  ((%specializers
279  :initform (required-argument :specializers)
280  :initarg :specializers
281  :reader domain-specializers)
282  (%arity
283  :initform (required-argument :arity)
284  :initarg :arity
285  :reader domain-arity)))
286 
287 (defmethod print-object ((domain domain) stream)
288  (print-unreadable-object (domain stream :type t)
289  (format stream "~{~S~^ ~}"
290  (mapcar #'specializer-type (domain-specializers domain)))))
291 
292 (defun make-domain (specializers &aux (arity (list-length specializers)))
293  (dolist (specializer specializers)
294  (check-type specializer specializer))
295  (make-instance 'domain
296  :specializers specializers
297  :arity arity))
298 
299 (defmethod ensure-domain ((domain domain))
300  domain)
301 
302 (defmethod ensure-domain ((sequence sequence))
303  (make-domain
304  (map 'list #'ensure-specializer sequence)))
305 
306 (defmethod method-domain ((method method))
307  (make-domain (method-specializers method)))
308 
309 (defmethod domain-equal
310  ((domain-1 domain)
311  (domain-2 domain))
312  (and (= (domain-arity domain-1)
313  (domain-arity domain-2))
314  (every #'eq
315  (domain-specializers domain-1)
316  (domain-specializers domain-2))))
317 
318 (defmethod domain-intersectionp
319  ((domain-1 domain)
320  (domain-2 domain))
321  (assert (= (domain-arity domain-1)
322  (domain-arity domain-2)))
323  (every #'specializer-intersectionp
324  (domain-specializers domain-1)
325  (domain-specializers domain-2)))
326 
327 (defmethod domain-subsetp
328  ((domain-1 domain)
329  (domain-2 domain))
330  (assert (= (domain-arity domain-1)
331  (domain-arity domain-2)))
332  (every #'specializer-subsetp
333  (domain-specializers domain-1)
334  (domain-specializers domain-2)))
335 
336 (defclass sealable-metaobject-mixin ()
337  ((%sealed-p :initform nil :reader metaobject-sealed-p)))
338 
339 (defmethod metaobject-sealable-p ((metaobject sealable-metaobject-mixin))
340  t)
341 
342 (defmethod seal-metaobject ((metaobject sealable-metaobject-mixin))
343  (setf (slot-value metaobject '%sealed-p) t))
344 
345 ;;; It is an error to change the class of a sealed metaobject.
346 (defmethod change-class :around
347  ((metaobject sealable-metaobject-mixin) new-class &key &allow-other-keys)
348  (declare (ignore new-class))
349  (if (metaobject-sealed-p metaobject)
350  (error "Attempt to change the class of the sealed metaobject ~S."
351  metaobject)
352  (call-next-method)))
353 
354 ;;; It is an error to change any object's class to a sealed metaobject.
355 (defmethod update-instance-for-different-class :around
356  (previous (current sealable-metaobject-mixin) &key &allow-other-keys)
357  (error "Attempt to change the class of ~S to the sealable metaobject ~S."
358  previous (class-of current)))
359 
360 ;;; Attempts to reinitialize a sealed metaobject are silently ignored.
361 (defmethod reinitialize-instance :around
362  ((metaobject sealable-metaobject-mixin) &key &allow-other-keys)
363  (if (metaobject-sealed-p metaobject)
364  metaobject
365  (call-next-method)))
366 
367 ;;; It is an error to change the class of an instance of a sealable
368 ;;; metaobject.
369 
370 (defclass sealable-metaobject-instance (t)
371  ())
372 
373 (defmethod change-class :around
374  ((instance sealable-metaobject-instance) new-class &key &allow-other-keys)
375  (declare (ignore new-class))
376  (error "Attempt to change the class of the sealable metaobject instance ~S."
377  instance))
378 
379 (defmethod shared-initialize
380  ((instance sealable-metaobject-mixin)
381  (slot-names (eql t))
382  &rest initargs
383  &key direct-superclasses)
384  (unless (every #'class-sealable-p direct-superclasses)
385  (error "~@<The superclasses of a sealable metaobject must be sealable. ~
386  The superclass ~S violates this restriction.~:@>"
387  (find-if-not #'class-sealable-p direct-superclasses)))
388  (apply #'call-next-method instance slot-names
389  :direct-superclasses
390  (adjoin (find-class 'sealable-metaobject-instance) direct-superclasses)
391  initargs))
392 
393 (defclass sealable-class (sealable-metaobject-mixin class)
394  ())
395 
396 ;;; There is no portable way to add options to a method. So instead, we
397 ;;; allow programmers to declare METHOD-PROPERTIES.
398 ;;;
399 ;;; Example:
400 ;;;
401 ;;; (defmethod foo (x y)
402 ;;; (declare (method-properties inline))
403 ;;; (+ x y))
404 
405 (declaim (declaration method-properties))
406 
407 (defclass potentially-sealable-method (sealable-metaobject-mixin method)
408  ((%method-properties
409  :initarg .method-properties.
410  :accessor method-properties
411  :initform '())))
412 
413 (defmethod shared-initialize :after
414  ((psm potentially-sealable-method)
415  slot-names &key ((.method-properties. method-properties) '()) &allow-other-keys)
416  (declare (ignore slot-names))
417  (dolist (method-property method-properties)
418  (unless (validate-method-property psm method-property)
419  (error "~@<~S is not a valid method property for the method ~S.~@:>"
420  method-property psm))))
421 
422 ;;; Track all properties that have been declared in the body of the method
423 ;;; lambda, and make them accessible as METHOD-PROPERTIES of that method.
424 (defmethod make-method-lambda :around
425  ((gf generic-function)
426  (psm potentially-sealable-method)
427  lambda
428  environment)
429  (declare (ignore environment))
430  (multiple-value-bind (method-lambda initargs)
431  (call-next-method)
432  (values
433  method-lambda
434  (list* '.method-properties.
435  (let* ((declare-forms (remove-if-not (starts-with 'declare) lambda))
436  (declarations (apply #'append (mapcar #'rest declare-forms))))
437  (reduce #'union (remove-if-not (starts-with 'method-properties) declarations)
438  :key #'rest
439  :initial-value '()))
440  initargs))))
441 
442 (defmethod metaobject-sealable-p ((psm potentially-sealable-method))
443  (every #'specializer-sealed-p (method-specializers psm)))
444 
445 (defmethod seal-metaobject :before ((psm potentially-sealable-method))
446  (mapcar #'seal-specializer (method-specializers psm)))
447 
448 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
449 ;;;
450 ;;; Derived Classes
451 
452 (defclass potentially-sealable-standard-method
453  (standard-method potentially-sealable-method)
454  ())
455 
456 (defclass sealable-generic-function (sealable-metaobject-mixin generic-function)
457  ((%sealed-domains
458  :initform '()
459  :type list
460  :reader sealed-domains
461  :writer (setf %sealed-domains)))
462  (:default-initargs
463  :method-class (find-class 'potentially-sealable-method))
464  (:metaclass funcallable-standard-class))
465 
466 ;;; Check that the supplied domain is sane.
467 (defmethod seal-domain
468  ((sgf sealable-generic-function)
469  (domain t))
470  (seal-domain sgf (ensure-domain domain)))
471 
472 (defmethod seal-domain :around
473  ((sgf sealable-generic-function)
474  (domain domain))
475  ;; Ensure that we don't seal any domain more than once.
476  (unless (find domain (sealed-domains sgf) :test #'domain-equal)
477  (call-next-method sgf domain)))
478 
479 ;;; Ensure that the generic function is sealed, and that the newly sealed
480 ;;; domain is disjoint from other domains.
481 (defmethod seal-domain :before
482  ((sgf sealable-generic-function)
483  (domain domain))
484  ;; Ensure that the length of the domain matches the number of mandatory
485  ;; arguments of the generic function.
486  (unless (= (domain-arity domain)
487  (length (generic-function-argument-precedence-order sgf)))
488  (error "~@<Cannot seal the domain ~S with arity ~R ~
489  of the generic function ~S with arity ~R.~@:>"
490  (mapcar #'specializer-type (domain-specializers domain))
491  (domain-arity domain)
492  (generic-function-name sgf)
493  (length (generic-function-argument-precedence-order sgf))))
494  ;; Attempt to seal the supplied generic function.
495  (seal-generic-function sgf)
496  ;; Ensure that the domain does not intersect any existing sealed domains.
497  (dolist (existing-domain (sealed-domains sgf))
498  (when (domain-intersectionp domain existing-domain)
499  (error "~@<Cannot seal the domain ~S of the generic function ~S, ~
500  because it intersects with the existing domain ~S.~@:>"
501  (mapcar #'specializer-type domain)
502  sgf
503  (mapcar #'specializer-type existing-domain)))))
504 
505 ;;; Add a new sealed domain.
506 (defmethod seal-domain
507  ((sgf sealable-generic-function)
508  (domain domain))
509  (dolist (method (generic-function-methods sgf))
510  (when (domain-intersectionp (method-domain method) domain)
511  (unless (domain-subsetp (method-domain method) domain)
512  (error "~@<The method ~S with specializers ~S is only partially ~
513  within the sealed domain ~S.~:@>"
514  method
515  (mapcar #'specializer-type (method-specializers method))
516  (mapcar #'specializer-type (domain-specializers domain))))
517  (seal-method method)))
518  (setf (%sealed-domains sgf)
519  (cons domain (sealed-domains sgf))))
520 
521 ;;; Skip the call to add-method if the list of specializers is equal to
522 ;;; that of an existing, sealed method.
523 (defmethod add-method :around
524  ((sgf sealable-generic-function)
525  (psm potentially-sealable-method))
526  (dolist (method (generic-function-methods sgf))
527  (when (and (method-sealed-p method)
528  (equal (method-specializers psm)
529  (method-specializers method)))
530  (return-from add-method psm)))
531  (call-next-method))
532 
533 ;;; Ensure that the method to be added is disjoint from all sealed domains.
534 (defmethod add-method :before
535  ((sgf sealable-generic-function)
536  (psm potentially-sealable-method))
537  (dolist (domain (sealed-domains sgf))
538  (when (domain-intersectionp domain (method-domain psm))
539  (error "~@<Cannot add the method ~S with specializers ~S to ~
540  the sealed generic function ~S, because it intersects ~
541  with the existing sealed domain ~S.~:@>"
542  psm (method-specializers psm)
543  sgf (mapcar #'specializer-type (domain-specializers domain))))))
544 
545 ;;; Ensure that the method to be removed is disjoint from all sealed domains.
546 (defmethod remove-method :before
547  ((sgf sealable-generic-function)
548  (psm potentially-sealable-method))
549  (dolist (domain (sealed-domains sgf))
550  (when (domain-intersectionp domain (method-domain psm))
551  (error "~@<Cannot remove the method ~S with specializers ~S from ~
552  the sealed generic function ~S, because it intersects ~
553  with the existing sealed domain ~S.~:@>"
554  psm (method-specializers psm)
555  sgf (mapcar #'specializer-type (domain-specializers domain))))))
556 
557 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
558 ;;;
559 ;;; Derived Classes
560 
561 (defclass sealable-standard-generic-function
562  (standard-generic-function sealable-generic-function)
563  ()
564  (:default-initargs
565  :method-class (find-class 'potentially-sealable-standard-method))
566  (:metaclass funcallable-standard-class))
567 
568 ;;; Finding a suitable prototype for eql specializers is easy.
569 (defmethod specializer-prototype ((eql-specializer eql-specializer)
570  &optional excluded-specializers)
571  (if (member eql-specializer excluded-specializers)
572  (values nil nil)
573  (values (eql-specializer-object eql-specializer) t)))
574 
575 (defun eql-specializer-p (object)
576  (typep object 'eql-specializer))
577 
578 (defmethod specializer-prototype ((class class) &optional excluded-specializers)
579  (let* ((excluded-non-eql-specializers (remove-if #'eql-specializer-p excluded-specializers))
580  (excluded-eql-specializers (remove-if-not #'eql-specializer-p excluded-specializers))
581  (excluded-objects (mapcar #'eql-specializer-object excluded-eql-specializers))
582  (excluded-types (mapcar #'specializer-type excluded-non-eql-specializers)))
583  (map-class-prototypes
584  (lambda (prototype)
585  ;; The prototype must not be a member of the excluded objects.
586  (when (not (member prototype excluded-objects))
587  ;; The prototype must not be of one of the excluded types.
588  (when (notany
589  (lambda (excluded-type)
590  (typep prototype excluded-type))
591  excluded-types)
592  (return-from specializer-prototype (values prototype t)))))
593  class)
594  (values nil nil)))
595 
596 ;;; The difficult part is to find suitable prototypes for specializers that
597 ;;; are classes. Ideally, we want several prototypes for each class, such
598 ;;; that we can avoid collisions with excluded specializers. Our technique
599 ;;; is to find prototypes from two sources - the value returned by the MOP
600 ;;; function CLASS-PROTOTYPE, and manually curated lists of prototypes for
601 ;;; each class, which we store in the hash table *CLASS-PROTOTYPES*.
602 
603 (defvar *class-prototypes* (make-hash-table :test #'eq))
604 
605 (defun map-class-prototypes (function class)
606  (let ((visited-classes (make-hash-table :test #'eq)))
607  (labels ((visit-class (class)
608  (unless (gethash class visited-classes)
609  (setf (gethash class visited-classes) t)
610  (loop for prototype in (gethash class *class-prototypes* '()) do
611  (funcall function prototype))
612  (mapc #'visit-class (class-direct-subclasses class))
613  ;; CLASS-PROTOTYPE is difficult to handle...
614  (when (class-finalized-p class)
615  (let ((prototype (class-prototype class)))
616  ;; Surprisingly, some implementations don't always
617  ;; return a CLASS-PROTOTYPE that is an instance of the
618  ;; given class. So we only scan the prototype if it is
619  ;; actually valid.
620  (when (typep prototype class)
621  (funcall function prototype)))))))
622  (visit-class class))))
623 
624 (defun register-class-prototype (prototype)
625  (pushnew prototype (gethash (class-of prototype) *class-prototypes* '())
626  :test #'equalp))
627 
628 ;; Register list prototypes.
629 (register-class-prototype '(.prototype.))
630 (register-class-prototype nil)
631 
632 (defparameter *array-element-types*
633  (remove-duplicates
634  (mapcar #'upgraded-array-element-type
635  (append '(short-float single-float double-float long-float base-char character t)
636  '((complex short-float)
637  (complex single-float)
638  (complex double-float)
639  (complex long-float))
640  (loop for bits from 1 to 64
641  collect `(unsigned-byte ,bits)
642  collect `(signed-byte ,bits))))
643  :test #'equal))
644 
645 (defun array-initial-element (element-type)
646  (cond ((subtypep element-type 'number)
647  (coerce 0 element-type))
648  ((subtypep element-type 'character)
649  (coerce #\0 element-type))
650  (t t)))
651 
652 ;; Register vector and array prototypes.
653 (loop for adjustable in '(nil t) do
654  (loop for fill-pointer in '(nil t) do
655  (loop for dimensions in '(() (2) (2 2)) do
656  (loop for element-type in *array-element-types* do
657  (let ((storage-vector
658  (make-array (reduce #'* dimensions)
659  :element-type element-type
660  :initial-element (array-initial-element element-type))))
661  (register-class-prototype
662  (make-array dimensions
663  :adjustable adjustable
664  :fill-pointer (and (= 1 (length dimensions)) fill-pointer)
665  :element-type element-type
666  :displaced-to storage-vector))
667  (register-class-prototype
668  (make-array dimensions
669  :adjustable adjustable
670  :fill-pointer (and (= 1 (length dimensions)) fill-pointer)
671  :element-type element-type
672  :initial-element (array-initial-element element-type))))))))
673 
674 ;; Register integer and rational prototypes.
675 (loop for integer in '(19 1337 1338 91676) do
676  (register-class-prototype (+ integer))
677  (register-class-prototype (- integer)))
678 (loop for bits = 1 then (* bits 2) until (>= bits 512)
679  for value = (expt 2 bits) do
680  (loop for value in (list (1+ value) value (1- value)) do
681  (register-class-prototype value)
682  (register-class-prototype (- value))
683  (register-class-prototype (/ value 17))))
684 
685 ;; Register float and complex float prototypes.
686 (register-class-prototype pi)
687 (register-class-prototype (- pi))
688 (register-class-prototype (exp 1S0))
689 (register-class-prototype (exp 1F0))
690 (register-class-prototype (exp 1D0))
691 (register-class-prototype (exp 1L0))
692 (mapcar #'register-class-prototype
693  (list most-positive-short-float
694  most-positive-single-float
695  most-positive-double-float
696  most-positive-long-float
697  most-negative-short-float
698  most-negative-single-float
699  most-negative-double-float
700  most-positive-long-float
701  short-float-epsilon
702  single-float-epsilon
703  double-float-epsilon
704  long-float-epsilon
705  short-float-negative-epsilon
706  single-float-negative-epsilon
707  double-float-negative-epsilon
708  long-float-negative-epsilon))
709 (loop for base in '(-0.7L0 -0.1L0 -0.0L0 +0.0L0 +0.1L0 +0.7L0) do
710  (loop for fp-type in '(short-float single-float double-float long-float) do
711  (loop for exponent in '(1 2 3 5 7 23 99) do
712  (let ((float (scale-float (coerce base fp-type) exponent)))
713  (register-class-prototype float)
714  (register-class-prototype (complex (float 0 float) float))))))
715 
716 ;; Register character prototypes.
717 (loop for char across "The quick brown fox jumps over the lazy dog." do
718  (register-class-prototype (char-downcase char))
719  (register-class-prototype (char-upcase char)))
720 (loop for char across "0123456789!$\"'(),_-./:;?+<=>#%&*@[\\]{\|}`^~" do
721  (register-class-prototype char))
722 (loop for char in '(#\backspace #\tab #\newline #\linefeed #\page #\return #\space #\rubout) do
723  (register-class-prototype char))
724 
725 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
726 ;;;
727 ;;; Reasoning About Specializer Specificity
728 
729 (defclass snode ()
730  (;; The specializer of an snode.
731  (%specializer :initarg :specializer :accessor snode-specializer)
732  ;; A (possibly empty) list of snodes for each child class or eql specializer.
733  (%children :initform '() :accessor snode-children)
734  ;; A list of snodes with one entry for each parent class.
735  (%parents :initform '() :accessor snode-parents)
736  ;; Whether the snode has already been visited.
737  (%visitedp :initform nil :accessor snode-visitedp)
738  ;; Whether the snode corresponds to a specializer of an existing method
739  ;; or the domain.
740  (%relevantp :initform nil :accessor snode-relevantp)))
741 
742 (defun snode-type (snode)
743  (type-specifier-and
744  (specializer-type (snode-specializer snode))
745  (type-specifier-not
746  (apply #'type-specifier-or
747  (loop for subspecializer in (snode-children snode)
748  collect
749  (specializer-type
750  (snode-specializer subspecializer)))))))
751 
752 (defun snode-prototype (snode)
753  (specializer-prototype
754  (snode-specializer snode)
755  (mapcar #'snode-specializer (snode-children snode))))
756 
757 (defvar *snode-table*)
758 
759 (defun specializer-snode (specializer)
760  (multiple-value-bind (snode present-p)
761  (gethash specializer *snode-table*)
762  (if present-p
763  snode
764  (let ((snode (make-instance 'snode :specializer specializer)))
765  (setf (gethash specializer *snode-table*) snode)
766  snode))))
767 
768 (defun snode-add-edge (super-snode sub-snode)
769  (pushnew super-snode (snode-parents sub-snode))
770  (pushnew sub-snode (snode-children super-snode))
771  (values))
772 
773 (defun type-prototype-pairs (specializers domain)
774  (let* ((*snode-table* (make-hash-table))
775  (specializer-snodes (mapcar #'specializer-snode specializers))
776  (domain-snode (specializer-snode domain)))
777  ;; Initialize domain and specializer snodes.
778  (dolist (snode specializer-snodes)
779  (setf (snode-relevantp snode) t))
780  (setf (snode-relevantp domain-snode) t)
781  ;; Now connect all snodes.
782  (labels ((visit (current relevant)
783  (unless (snode-visitedp current)
784  (setf (snode-visitedp current) t)
785  (unless (eql current domain)
786  (dolist (specializer
787  (specializer-direct-superspecializers
788  (snode-specializer current)))
789  (let ((super (specializer-snode specializer)))
790  (cond ((snode-relevantp super)
791  (snode-add-edge super relevant)
792  (visit super super))
793  (t
794  (visit super relevant)))))))))
795  (mapc #'visit specializer-snodes specializer-snodes))
796  ;; Finally, build all pairs.
797  (let ((pairs '()))
798  (loop for snode being the hash-values of *snode-table* do
799  (when (snode-relevantp snode)
800  (multiple-value-bind (prototype prototype-p)
801  (snode-prototype snode)
802  (when prototype-p
803  (push (list (snode-type snode) prototype)
804  pairs)))))
805  pairs)))
806 
807 ;;; In this file, we compute the static call signatures of a given, sealed
808 ;;; generic function. A static call signature consists of a list of types,
809 ;;; and a list of prototypes. The list of types is guaranteed to be
810 ;;; non-overlapping with the types of any other call signature. The list
811 ;;; of prototypes is chosen such that the list of applicable methods of
812 ;;; these prototypes is representative for all arguments of the types of
813 ;;; the call signature.
814 
815 (defclass static-call-signature ()
816  ((%types
817  :initarg :types
818  :reader static-call-signature-types)
819  (%prototypes
820  :initarg :prototypes
821  :reader static-call-signature-prototypes)))
822 
823 (defmethod print-object ((scs static-call-signature) stream)
824  (print-unreadable-object (scs stream :type t :identity t)
825  (format stream "~S ~S"
826  (static-call-signature-types scs)
827  (static-call-signature-prototypes scs))))
828 
829 (defmethod make-load-form
830  ((static-call-signature static-call-signature) &optional environment)
831  (make-load-form-saving-slots
832  static-call-signature
833  :slot-names '(%types %prototypes)
834  :environment environment))
835 
836 (defmethod externalizable-object-p
837  ((static-call-signature static-call-signature))
838  (and
839  (every #'externalizable-object-p
840  (static-call-signature-types static-call-signature))
841  (every #'externalizable-object-p
842  (static-call-signature-prototypes static-call-signature))))
843 
844 (defmethod compute-static-call-signatures
845  ((sgf sealable-generic-function)
846  (domain domain))
847  (let* ((sealed-methods
848  (remove-if-not
849  (lambda (method)
850  (domain-intersectionp (method-domain method) domain))
851  (generic-function-methods sgf)))
852  (list-of-specializers
853  (mapcar #'method-specializers sealed-methods))
854  (static-call-signatures '()))
855  (unless (null list-of-specializers)
856  (map-types-and-prototypes
857  (lambda (types prototypes)
858  (push (make-instance 'static-call-signature
859  :types types
860  :prototypes prototypes)
861  static-call-signatures))
862  ;; Transpose the list of specializers so that we operate on each
863  ;; argument instead of on each method.
864  (apply #'mapcar #'list list-of-specializers)
865  domain))
866  static-call-signatures))
867 
868 (defun map-types-and-prototypes (fn specializers-list domain)
869  (assert (= (length specializers-list)
870  (domain-arity domain)))
871  (labels ((rec (sl specializers types prototypes)
872  (if (null sl)
873  (funcall fn (reverse types) (reverse prototypes))
874  (loop for (type prototype)
875  in (type-prototype-pairs
876  (first sl)
877  (first specializers))
878  do (rec (rest sl)
879  (rest specializers)
880  (cons type types)
881  (cons prototype prototypes))))))
882  (rec specializers-list (domain-specializers domain) '() '())))