3 ;;; Class finalization implementation
5 ;;; (c) 2009 Straylight/Edgeware
8 ;;;----- Licensing notice ---------------------------------------------------
10 ;;; This file is part of the Sensible Object Design, an object system for C.
12 ;;; SOD is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 2 of the License, or
15 ;;; (at your option) any later version.
17 ;;; SOD is distributed in the hope that it will be useful,
18 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with SOD; if not, write to the Free Software Foundation,
24 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ;;;--------------------------------------------------------------------------
29 ;;; Class precedence lists.
31 ;; Just for fun, we implement a wide selection of precedence list algorithms.
32 ;; C3 seems to be clearly the best, with fewer sharp edges for the unwary.
34 ;; The extended precedence graph (EPG) is constructed by adding edges to the
35 ;; superclass graph. If A and B are classes, then write A < B if A is a
36 ;; (maybe indirect) subclass of B. For every two classes A and B, and for
37 ;; every /maximal/ subclass of both A and B (i.e., every C for which C < A
38 ;; and C < B, but there does not exist D such that D < A, D < B and C < D):
39 ;; if A precedes B in C's direct superclass list, then draw an edge A -> B,
40 ;; otherwise draw the edge B -> A.
42 ;; A linearization respects the EPG if, whenever A precedes B in the
43 ;; linearization, there is a path from A to B. The EPG can be cyclic; in
44 ;; that case, we don't care which order the classes in the cycle are
47 ;; See Barrett, Cassels, Haahr, Moon, Playford, Withington, `A Monotonic
48 ;; Superclass Linearization for Dylan' for more detail.
49 ;; http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
53 (export 'merge-class-lists)
54 (defun merge-class-lists (lists pick)
55 "Merge the LISTS of classes, using PICK to break ties.
57 This is a convenience wrapper around the main `merge-lists' function.
58 Given that class linearizations (almost?) always specify a custom
59 tiebreaker function, this isn't a keyword argument."
60 (merge-lists lists :pick pick))
62 ;;; Tiebreaker functions.
64 (defun clos-tiebreaker (candidates so-far)
65 "The CLOS linearization tiebreaker function.
67 Intended for use with `merge-lists'. Returns the member of CANDIDATES
68 which has a direct subclass furthest to the right in the list SO-FAR.
70 This must disambiguate. The SO-FAR list cannot be empty, since the class
71 under construction precedes all of the others. If two classes share a
72 direct subclass then that subclass's direct superclasses list must order
73 them relative to each other."
75 (dolist (class so-far)
76 (dolist (candidate candidates)
77 (when (member candidate (sod-class-direct-superclasses class))
78 (return-from clos-tiebreaker candidate))))
79 (error "SOD INTERNAL ERROR: Failed to break tie in CLOS"))
81 (defun c3-tiebreaker (candidates cpls)
82 "The C3 linearization tiebreaker function.
84 Intended for use with `merge-lists'. Returns the member of CANDIDATES
85 which appears in the earliest element of CPLS, which should be the list of
86 the class precedence lists of the direct superclasses of the class in
87 question, in the order specified in the class declaration.
89 The only class in the class precedence list which does not appear in one
90 of these lists is the new class itself, which must precede all of the
93 This must disambiguate, since if two classes are in the same class
94 precedence list, then one must appear in it before the other, which
95 provides an ordering between them. (In this situation we return the one
96 that matches earliest anyway, which would still give the right answer.)
98 Note that this will merge the CPLs of superclasses /as they are/, not
99 necessarily as C3 would have computed them. This ensures monotonicity
100 assuming that the superclass CPLs are already monotonic. If they aren't,
101 you're going to lose anyway."
104 (dolist (candidate candidates)
105 (when (member candidate cpl)
106 (return-from c3-tiebreaker candidate))))
107 (error "SOD INTERNAL ERROR: Failed to break tie in C3"))
109 ;;; Linearization functions.
112 (defun clos-cpl (class)
113 "Compute the class precedence list of CLASS using CLOS linearization rules.
115 We merge the direct-superclass lists of all of CLASS's superclasses,
116 disambiguating using `clos-tiebreaker'.
118 The CLOS linearization preserves local class ordering, but is not
119 monotonic, and does not respect the extended precedence graph. CLOS
120 linearization will succeed whenever Dylan or C3 linearization succeeds;
121 the converse is not true."
123 (labels ((superclasses (class)
124 (let ((direct-supers (sod-class-direct-superclasses class)))
125 (remove-duplicates (cons class
126 (mappend #'superclasses
129 (mapcar (lambda (class)
130 (cons class (sod-class-direct-superclasses class)))
131 (superclasses class))
135 (defun dylan-cpl (class)
136 "Compute the class precedence list of CLASS using Dylan linearization
139 We merge the direct-superclass list of CLASS with the full class
140 precedence lists of its direct superclasses, disambiguating using
141 `clos-tiebreaker'. (Inductively, these lists will be consistent with the
142 CPLs of indirect superclasses, since those CPLs' orderings are reflected
143 in the CPLs of the direct superclasses.)
145 The Dylan linearization preserves local class ordering and is monotonic,
146 but does not respect the extended precedence graph.
148 Note that this will merge the CPLs of superclasses /as they are/, not
149 necessarily as Dylan would have computed them. This ensures monotonicity
150 assuming that the superclass CPLs are already monotonic. If they aren't,
151 you're going to lose anyway."
153 (let ((direct-supers (sod-class-direct-superclasses class)))
155 (cons (cons class direct-supers)
156 (mapcar #'sod-class-precedence-list direct-supers))
160 (defun c3-cpl (class)
161 "Compute the class precedence list of CLASS using C3 linearization rules.
163 We merge the direct-superclass list of CLASS with the full class
164 precedence lists of its direct superclasses, disambiguating using
167 The C3 linearization preserves local class ordering, is monotonic, and
168 respects the extended precedence graph. It is the linearization used in
169 Python, Perl 6 and other languages. It is the recommended linearization
172 (let* ((direct-supers (sod-class-direct-superclasses class))
173 (cpls (mapcar #'sod-class-precedence-list direct-supers)))
174 (merge-class-lists (cons (cons class direct-supers) cpls)
175 (lambda (candidates so-far)
176 (declare (ignore so-far))
177 (c3-tiebreaker candidates cpls)))))
179 (export 'flavors-cpl)
180 (defun flavors-cpl (class)
181 "Compute the class precedence list of CLASS using Flavors linearization
184 We do a depth-first traversal of the superclass graph, ignoring duplicates
185 of classes we've already visited. Interestingly, this has the property of
186 being able to tolerate cyclic superclass graphs, though defining cyclic
187 graphs is syntactically impossible in SOD.
189 This linearization has few other redeeming features, however. In
190 particular, the top class tends not to be at the end of the CPL, despite
191 it being unequivocally less specific than any other class."
194 (labels ((walk (class)
195 (unless (member class done)
197 (dolist (super (sod-class-direct-superclasses class))
203 (defun python-cpl (class)
204 "Compute the class precedence list of CLASS using the documented Python 2.2
207 We do a depth-first traversal of the superclass graph, retaining only the
208 last occurrence of each class visited.
210 This linearization has few redeeming features. It was never actually
211 implemented; the true Python 2.2 linearization seems closer to (but
212 different from) L*LOOPS."
215 (labels ((walk (class)
217 (dolist (super (sod-class-direct-superclasses class))
220 (delete-duplicates (nreverse done)))))
222 (export 'l*loops-cpl)
223 (defun l*loops-cpl (class)
224 "Compute the class precedence list of CLASS using L*LOOPS linearization
227 We merge the class precedence lists of the direct superclasses of CLASS,
228 disambiguating by choosing the earliest candidate which appears in a
229 depth-first walk of the superclass graph.
231 The L*LOOPS rules are monotonic and respect the extended precedence
232 graph. However (unlike Dylan and CLOS) they don't respect local
233 precedence order i.e., the direct-superclasses list orderings."
235 (let ((dfs (flavors-cpl class)))
237 (merge-class-lists (mapcar #'sod-class-precedence-list
238 (sod-class-direct-superclasses class))
239 (lambda (candidates so-far)
240 (declare (ignore so-far))
242 (when (member class candidates)
243 (return class))))))))
245 ;;; Default function.
247 (defmethod compute-cpl ((class sod-class))
248 (handler-case (c3-cpl class)
249 (inconsistent-merge-error ()
250 (error "Failed to compute class precedence list for `~A'"
251 (sod-class-name class)))))
253 ;;;--------------------------------------------------------------------------
256 (defmethod compute-chains ((class sod-class))
257 (with-default-error-location (class)
258 (with-slots (chain-link class-precedence-list) class
259 (let* ((head (if chain-link
260 (sod-class-chain-head chain-link)
262 (chain (cons class (and chain-link
263 (sod-class-chain chain-link))))
264 (state (make-inheritance-path-reporter-state class))
265 (table (make-hash-table)))
267 ;; Check the chains. We work through each superclass, maintaining a
268 ;; hash table keyed by class. If we encounter a class C which links
269 ;; to L, then we store C as L's value; if L already has a value then
270 ;; we've found an error. By the end of all of this, the classes
271 ;; which don't have an entry are the chain tails.
272 (dolist (super class-precedence-list)
273 (let* ((link (sod-class-chain-link super))
274 (found (and link (gethash link table))))
275 (cond ((not found) (setf (gethash link table) super))
277 (cerror* "Conflicting chains in class `~A': ~
278 (`~A' and `~A' both link to `~A')"
279 class super found link)
280 (report-inheritance-path state super)
281 (report-inheritance-path state found)))))
286 (mapcar #'sod-class-chain
287 (remove-if (lambda (super)
288 (gethash super table))
289 (cdr class-precedence-list)))))))))
291 ;;;--------------------------------------------------------------------------
294 (defmethod guess-metaclass ((class sod-class))
295 "Default metaclass-guessing function for classes.
297 Return the most specific metaclass of any of the CLASS's direct
300 ;; During bootstrapping, our superclasses might not have their own
301 ;; metaclasses resolved yet. If we find this, then throw `bootstrapping'
302 ;; so that `shared-initialize' on `sod-class' can catch it (or as a shot
303 ;; across the bows of anyone else who calls us).
304 (finalization-error (:bad-metaclass)
305 (select-minimal-class-property (sod-class-direct-superclasses class)
307 (if (slot-boundp super 'metaclass)
308 (slot-value super 'metaclass)
309 (throw 'bootstrapping nil)))
310 #'sod-subclass-p class "metaclass")))
312 ;;;--------------------------------------------------------------------------
315 (defmethod check-sod-class ((class sod-class))
316 (with-default-error-location (class)
318 ;; Check the names of things are valid.
319 (flet ((check-list (list what namefunc)
321 (let ((name (funcall namefunc item)))
322 (unless (valid-name-p name)
323 (cerror*-with-location item
324 "Invalid ~A name `~A' ~
326 what name class))))))
327 (unless (valid-name-p (sod-class-name class))
328 (cerror* "Invalid class name `~A'" class))
329 (unless (valid-name-p (sod-class-nickname class))
330 (cerror* "Invalid class nickname `~A' for class `~A'"
331 (sod-class-nickname class) class))
332 (check-list (sod-class-messages class) "message" #'sod-message-name)
333 (check-list (sod-class-slots class) "slot" #'sod-slot-name))
335 ;; Check that the class doesn't define conflicting things.
336 (labels ((check-list (list keyfunc complain)
337 (let ((seen (make-hash-table :test #'equal)))
339 (let* ((key (funcall keyfunc item))
340 (found (gethash key seen)))
341 (if found (funcall complain item found)
342 (setf (gethash key seen) item))))))
343 (simple-previous (previous)
344 (info-with-location previous "Previous definition was here"))
345 (simple-complain (what namefunc)
346 (lambda (item previous)
347 (cerror*-with-location item
348 "Duplicate ~A `~A' in class `~A'"
349 what (funcall namefunc item) class)
350 (simple-previous previous))))
352 ;; Make sure direct slots have distinct names.
353 (check-list (sod-class-slots class) #'sod-slot-name
354 (simple-complain "slot name" #'sod-slot-name))
356 ;; Make sure there's at most one initializer for each slot.
357 (flet ((check-initializer-list (list kind)
358 (check-list list #'sod-initializer-slot
359 (lambda (initializer previous)
361 (sod-initializer-slot initializer)))
362 (cerror*-with-location initializer
368 (simple-previous previous))))))
369 (check-initializer-list (sod-class-instance-initializers class)
371 (check-initializer-list (sod-class-class-initializers class)
374 ;; Make sure messages have distinct names.
375 (check-list (sod-class-messages class) #'sod-message-name
376 (simple-complain "message name" #'sod-message-name))
378 ;; Make sure methods are sufficiently distinct.
379 (check-list (sod-class-methods class) #'sod-method-function-name
380 (lambda (method previous)
381 (cerror*-with-location method
382 "Duplicate ~A direct method ~
385 (sod-method-description method)
386 (sod-method-message method)
388 (simple-previous previous)))
390 ;; Make sure superclasses have distinct nicknames.
391 (let ((state (make-inheritance-path-reporter-state class)))
392 (check-list (sod-class-precedence-list class) #'sod-class-nickname
393 (lambda (super previous)
394 (cerror*-with-location class
395 "Duplicate nickname `~A' ~
396 in superclasses of `~A': ~
397 used by `~A' and `~A'"
398 (sod-class-nickname super)
399 class super previous)
400 (report-inheritance-path state super)
401 (report-inheritance-path state previous)))))
403 ;; Check that the CHAIN-TO class is actually a proper superclass. (This
404 ;; eliminates hairy things like a class being its own link.)
405 (let ((link (sod-class-chain-link class)))
406 (unless (or (not link)
407 (member link (cdr (sod-class-precedence-list class))))
408 (cerror* "In `~A~, chain-to class `~A' is not a proper superclass"
411 ;; Check that the initargs declare compatible types. Duplicate entries,
412 ;; even within a class, are harmless, but at most one initarg in any
413 ;; class should declare a default value.
414 (let ((seen (make-hash-table :test #'equal))
415 (state (make-inheritance-path-reporter-state class)))
416 (dolist (super (sod-class-precedence-list class))
417 (dolist (initarg (reverse (sod-class-initargs super)))
418 (let* ((initarg-name (sod-initarg-name initarg))
419 (initarg-type (sod-initarg-type initarg))
420 (initarg-default (sod-initarg-default initarg))
421 (found (gethash initarg-name seen))
422 (found-type (and found (sod-initarg-type found)))
423 (found-default (and found (sod-initarg-default found)))
424 (found-class (and found (sod-initarg-class found)))
425 (found-location (and found (file-location found))))
426 (with-default-error-location (initarg)
428 (setf (gethash initarg-name seen) initarg))
429 ((not (c-type-equal-p initarg-type found-type))
430 (cerror* "Inititalization argument `~A' defined ~
431 with incompatible types: ~
432 ~A in class `~A', but ~A in class `~A'"
433 initarg-name initarg-type super
434 found-type found-class found-location)
435 (report-inheritance-path state super))
436 ((and initarg-default found-default
437 (eql super found-class))
438 (cerror* "Initialization argument `~A' redefined ~
441 (info-with-location found-location
442 "Previous definition is here"))
444 (setf (gethash initarg-name seen) initarg))))))))
446 ;; Check for circularity in the superclass graph. Since the superclasses
447 ;; should already be acyclic, it suffices to check that our class is not
448 ;; a superclass of any of its own direct superclasses.
449 (let ((circle (find-if (lambda (super)
450 (sod-subclass-p super class))
451 (sod-class-direct-superclasses class))))
453 (cerror* "`~A' is already a superclass of `~A'" class circle)
454 (report-inheritance-path (make-inheritance-path-reporter-state class)
457 ;; Check that the class has a unique root superclass.
458 (find-root-superclass class)
460 ;; Check that the metaclass is a subclass of each direct superclass's
462 (finalization-error (:bad-metaclass)
463 (let ((meta (sod-class-metaclass class)))
464 (dolist (super (sod-class-direct-superclasses class))
465 (let ((supermeta (sod-class-metaclass super)))
466 (unless (sod-subclass-p meta supermeta)
467 (cerror* "Metaclass `~A' of `~A' isn't a subclass of `~A'"
468 meta class supermeta)
469 (info-with-location super
470 "Direct superclass `~A' defined here ~
472 super supermeta))))))))
474 ;;;--------------------------------------------------------------------------
477 (defmethod finalize-sod-class :around ((class sod-class))
478 "Common functionality for `finalize-sod-class'.
480 * If an attempt to finalize the CLASS has been made before, then we
481 don't try again. Similarly, attempts to finalize a class recursively
484 * A condition handler is established to keep track of whether any errors
485 are signalled during finalization. The CLASS is only marked as
486 successfully finalized if no (unhandled) errors are encountered."
487 (with-default-error-location (class)
488 (ecase (sod-class-state class)
491 ;; If this fails, leave the class marked as a loss.
492 (setf (slot-value class 'state) :broken)
494 ;; Invoke the finalization method proper. If it signals any
495 ;; continuable errors, take note of them so that we can report failure
498 ;; Catch: we get called recursively to clean up superclasses and
499 ;; metaclasses, but there should only be one such handler, so don't
500 ;; add another. (In turn, this means that other methods mustn't
501 ;; actually trap their significant errors.)
502 (let ((have-handler-p (boundp '*finalization-errors*))
503 (*finalization-errors* nil)
504 (*finalization-error-token* nil))
505 (catch '%finalization-failed
506 (if have-handler-p (call-next-method)
507 (handler-bind ((error (lambda (cond)
508 (declare (ignore cond))
509 (pushnew *finalization-error-token*
510 *finalization-errors*
514 (when *finalization-errors* (finalization-failed))
515 (setf (slot-value class 'state) :finalized)
518 ;; If the class is broken, we're not going to be able to fix it now.
522 ;; If we already finalized it, there's no point doing it again.
526 (defmethod finalize-sod-class ((class sod-class))
528 ;; CLONE-AND-HACK WARNING: Note that `bootstrap-classes' has a (very brief)
529 ;; clone of the CPL and chain establishment code. If the interface changes
530 ;; then `bootstrap-classes' will need to be changed too.
532 ;; Set up the metaclass if it's not been set already. This is delayed
533 ;; to give bootstrapping a chance to set up metaclass and superclass
535 (default-slot (class 'metaclass) (guess-metaclass class))
537 ;; Finalize all of the superclasses. There's some special pleading here to
538 ;; make bootstrapping work: we don't try to finalize the metaclass if we're
539 ;; a root class (no direct superclasses -- because in that case the
540 ;; metaclass will have to be a subclass of us!), or if it's equal to us.
541 ;; This is enough to tie the knot at the top of the class graph. If we
542 ;; can't manage this then we're doomed.
543 (flet ((try-finalizing (what other-class)
544 (unless (finalize-sod-class other-class)
545 (cerror* "Class `~A' has broken ~A `~A'" class what other-class)
546 (info-with-location other-class
547 "Class `~A' defined here" other-class)
548 (finalization-failed))))
549 (let ((supers (sod-class-direct-superclasses class))
550 (meta (sod-class-metaclass class)))
551 (dolist (super supers)
552 (try-finalizing "direct superclass" super))
553 (unless (or (null supers) (eq class meta))
554 (try-finalizing "metaclass" meta))))
556 ;; Stash the class's type.
557 (setf (slot-value class '%type)
558 (make-class-type (sod-class-name class)))
560 ;; Clobber the lists of items if they've not been set.
561 (dolist (slot '(slots instance-initializers class-initializers
563 (unless (slot-boundp class slot)
564 (setf (slot-value class slot) nil)))
566 ;; If the CPL hasn't been done yet, compute it. If we can't manage this
567 ;; then there's no hope at all.
568 (unless (slot-boundp class 'class-precedence-list)
570 (setf (slot-value class 'class-precedence-list) (compute-cpl class))
571 (continue () :report "Continue"
572 (finalization-failed))))
574 ;; Check that the class is fairly sane.
575 (check-sod-class class)
577 ;; Determine the class's layout.
578 (setf (values (slot-value class 'chain-head)
579 (slot-value class 'chain)
580 (slot-value class 'chains))
581 (compute-chains class)))
583 ;;;----- That's all, folks --------------------------------------------------