src/method-{proto,impl}.lisp: Abstract out the receiver type.
[sod] / src / class-finalize-impl.lisp
index 4696a0a..895b3c9 100644 (file)
    direct subclass then that subclass's direct superclasses list must order
    them relative to each other."
 
    direct subclass then that subclass's direct superclasses list must order
    them relative to each other."
 
-  (dolist (class so-far)
+  (dolist (class (reverse so-far))
     (dolist (candidate candidates)
       (when (member candidate (sod-class-direct-superclasses class))
        (return-from clos-tiebreaker candidate))))
     (dolist (candidate candidates)
       (when (member candidate (sod-class-direct-superclasses class))
        (return-from clos-tiebreaker candidate))))
 ;;;--------------------------------------------------------------------------
 ;;; Sanity checking.
 
 ;;;--------------------------------------------------------------------------
 ;;; Sanity checking.
 
+(defmethod check-class-initializer ((slot effective-slot) (class sod-class))
+  (finalization-error (:missing-class-initializer)
+    (unless (find-class-initializer slot class)
+      (let ((dslot (effective-slot-direct-slot slot)))
+       (cerror* "Missing initializer for class slot `~A', ~
+                 defined by meta-superclass `~A' of `~A'"
+                dslot (sod-slot-class dslot) class)))))
+
+(defmethod check-class-initializer
+    ((slot sod-class-effective-slot) (class sod-class))
+  ;; The programmer shouldn't set an explicit initializer here.
+  (finalization-error (:invalid-class-initializer)
+    (let ((init (find-class-initializer slot class))
+         (dslot (effective-slot-direct-slot slot)))
+      (when init
+       (cerror* "Initializers not permitted for class slot `~A', ~
+                 defined by meta-superclass `~A' of `~A'"
+                dslot (sod-slot-class dslot) class)
+       (info-with-location init "Offending initializer defined here")))))
+
 (defmethod check-sod-class ((class sod-class))
 (defmethod check-sod-class ((class sod-class))
-  (with-default-error-location (class)
 
 
-    ;; Check the names of things are valid.
-    (flet ((check-list (list what namefunc)
-            (dolist (item list)
-              (let ((name (funcall namefunc item)))
-                (unless (valid-name-p name)
-                  (cerror*-with-location item
-                                         "Invalid ~A name `~A' ~
-                                          in class `~A'"
-                                         what name class))))))
-      (unless (valid-name-p (sod-class-name class))
-       (cerror* "Invalid class name `~A'" class))
-      (unless (valid-name-p (sod-class-nickname class))
-       (cerror* "Invalid class nickname `~A' for class `~A'"
-                (sod-class-nickname class) class))
-      (check-list (sod-class-messages class) "message" #'sod-message-name)
-      (check-list (sod-class-slots class) "slot" #'sod-slot-name))
-
-    ;; Check that the class doesn't define conflicting things.
-    (labels ((check-list (list keyfunc complain)
-              (let ((seen (make-hash-table :test #'equal)))
-                (dolist (item list)
-                  (let* ((key (funcall keyfunc item))
-                         (found (gethash key seen)))
-                    (if found (funcall complain item found)
-                        (setf (gethash key seen) item))))))
-            (simple-previous (previous)
-              (info-with-location previous "Previous definition was here"))
-            (simple-complain (what namefunc)
-              (lambda (item previous)
+  ;; Check the names of things are valid.
+  (flet ((check-list (list what namefunc)
+          (dolist (item list)
+            (let ((name (funcall namefunc item)))
+              (unless (valid-name-p name)
                 (cerror*-with-location item
                 (cerror*-with-location item
-                                       "Duplicate ~A `~A' in class `~A'"
-                                       what (funcall namefunc item) class)
-                (simple-previous previous))))
-
-      ;; Make sure direct slots have distinct names.
-      (check-list (sod-class-slots class) #'sod-slot-name
-                 (simple-complain "slot name" #'sod-slot-name))
-
-      ;; Make sure there's at most one initializer for each slot.
-      (flet ((check-initializer-list (list kind)
-              (check-list list #'sod-initializer-slot
-                          (lambda (initializer previous)
-                            (let ((slot
-                                   (sod-initializer-slot initializer)))
-                              (cerror*-with-location initializer
-                                                     "Duplicate ~
-                                                      initializer for ~
-                                                      ~A slot `~A' ~
-                                                      in class `~A'"
-                                                     kind slot class)
-                              (simple-previous previous))))))
-       (check-initializer-list (sod-class-instance-initializers class)
-                               "instance")
-       (check-initializer-list (sod-class-class-initializers class)
-                               "class"))
-
-      ;; Make sure messages have distinct names.
-      (check-list (sod-class-messages class) #'sod-message-name
-                 (simple-complain "message name" #'sod-message-name))
-
-      ;; Make sure methods are sufficiently distinct.
-      (check-list (sod-class-methods class) #'sod-method-function-name
-                 (lambda (method previous)
-                   (cerror*-with-location method
-                                          "Duplicate ~A direct method ~
-                                           for message `~A' ~
-                                           in classs `~A'"
-                                          (sod-method-description method)
-                                          (sod-method-message method)
-                                          class)
-                   (simple-previous previous)))
-
-      ;; Make sure superclasses have distinct nicknames.
-      (let ((state (make-inheritance-path-reporter-state class)))
-       (check-list (sod-class-precedence-list class) #'sod-class-nickname
-                   (lambda (super previous)
-                     (cerror*-with-location class
-                                            "Duplicate nickname `~A' ~
-                                             in superclasses of `~A': ~
-                                             used by `~A' and `~A'"
-                                            (sod-class-nickname super)
-                                            class super previous)
-                     (report-inheritance-path state super)
-                     (report-inheritance-path state previous)))))
-
-    ;; Check that the CHAIN-TO class is actually a proper superclass.  (This
-    ;; eliminates hairy things like a class being its own link.)
-    (let ((link (sod-class-chain-link class)))
-      (unless (or (not link)
-                 (member link (cdr (sod-class-precedence-list class))))
-       (cerror* "In `~A~, chain-to class `~A' is not a proper superclass"
-                class link)))
-
-    ;; Check that the initargs declare compatible types.  Duplicate entries,
-    ;; even within a class, are harmless, but at most one initarg in any
-    ;; class should declare a default value.
-    (let ((seen (make-hash-table :test #'equal))
-         (state (make-inheritance-path-reporter-state class)))
-      (dolist (super (sod-class-precedence-list class))
-       (dolist (initarg (reverse (sod-class-initargs super)))
-         (let* ((initarg-name (sod-initarg-name initarg))
-                (initarg-type (sod-initarg-type initarg))
-                (initarg-default (sod-initarg-default initarg))
-                (found (gethash initarg-name seen))
-                (found-type (and found (sod-initarg-type found)))
-                (found-default (and found (sod-initarg-default found)))
-                (found-class (and found (sod-initarg-class found)))
-                (found-location (and found (file-location found))))
-           (with-default-error-location (initarg)
-             (cond ((not found)
-                    (setf (gethash initarg-name seen) initarg))
-                   ((not (c-type-equal-p initarg-type found-type))
-                    (cerror* "Inititalization argument `~A' defined ~
-                              with incompatible types: ~
-                              ~A in class `~A', but ~A in class `~A'"
-                             initarg-name initarg-type super
-                             found-type found-class found-location)
-                    (report-inheritance-path state super))
-                   ((and initarg-default found-default
-                         (eql super found-class))
-                    (cerror* "Initialization argument `~A' redefined ~
-                              with default value"
-                             initarg-name)
-                    (info-with-location found-location
-                                        "Previous definition is here"))
-                   (initarg-default
-                    (setf (gethash initarg-name seen) initarg))))))))
-
-    ;; Check for circularity in the superclass graph.  Since the superclasses
-    ;; should already be acyclic, it suffices to check that our class is not
-    ;; a superclass of any of its own direct superclasses.
-    (let ((circle (find-if (lambda (super)
-                            (sod-subclass-p super class))
-                          (sod-class-direct-superclasses class))))
-      (when circle
-       (cerror* "`~A' is already a superclass of `~A'" class circle)
-       (report-inheritance-path (make-inheritance-path-reporter-state class)
-                                circle)))
-
-    ;; Check that the class has a unique root superclass.
-    (find-root-superclass class)
-
-    ;; Check that the metaclass is a subclass of each direct superclass's
-    ;; metaclass.
-    (finalization-error (:bad-metaclass)
-      (let ((meta (sod-class-metaclass class)))
-       (dolist (super (sod-class-direct-superclasses class))
-         (let ((supermeta (sod-class-metaclass super)))
-           (unless (sod-subclass-p meta supermeta)
-             (cerror* "Metaclass `~A' of `~A' isn't a subclass of `~A'"
-                      meta class supermeta)
-             (info-with-location super
-                                 "Direct superclass `~A' defined here ~
-                                  has metaclass `~A'"
-                                 super supermeta))))))))
+                                       "Invalid ~A name `~A' in class `~A'"
+                                       what name class))))))
+    (unless (valid-name-p (sod-class-name class))
+      (cerror* "Invalid class name `~A'" class))
+    (unless (valid-name-p (sod-class-nickname class))
+      (cerror* "Invalid class nickname `~A' for class `~A'"
+              (sod-class-nickname class) class))
+    (check-list (sod-class-messages class) "message" #'sod-message-name)
+    (check-list (sod-class-slots class) "slot" #'sod-slot-name))
+
+  ;; Check that the class doesn't define conflicting things.
+  (labels ((simple-previous (previous)
+            (info-with-location previous "Previous definition was here"))
+          (simple-complain (what namefunc)
+            (lambda (item previous)
+              (cerror*-with-location item
+                                     "Duplicate ~A `~A' in class `~A'"
+                                     what (funcall namefunc item) class)
+              (simple-previous previous))))
+
+    ;; Make sure direct slots have distinct names.
+    (find-duplicates (simple-complain "slot name" #'sod-slot-name)
+                    (sod-class-slots class)
+                    :key #'sod-slot-name
+                    :test #'equal)
+
+    ;; Make sure there's at most one initializer for each slot.
+    (flet ((check-initializer-list (list kind)
+            (find-duplicates (lambda (initializer previous)
+                               (let ((slot
+                                      (sod-initializer-slot initializer)))
+                                 (cerror*-with-location initializer
+                                                        "Duplicate ~
+                                                         initializer ~
+                                                         for ~A slot `~A' ~
+                                                         in class `~A'"
+                                                        kind slot class)
+                                 (simple-previous previous)))
+                             list
+                             :key #'sod-initializer-slot)))
+      (check-initializer-list (sod-class-instance-initializers class)
+                             "instance")
+      (check-initializer-list (sod-class-class-initializers class)
+                             "class"))
+
+    ;; Make sure messages have distinct names.
+    (find-duplicates (simple-complain "message name" #'sod-message-name)
+                    (sod-class-messages class)
+                    :key #'sod-message-name
+                    :test #'equal)
+
+    ;; Make sure methods are sufficiently distinct.
+    (find-duplicates (lambda (method previous)
+                      (cerror*-with-location method
+                                             "Duplicate ~A direct method ~
+                                              for message `~A' ~
+                                              in classs `~A'"
+                                             (sod-method-description method)
+                                             (sod-method-message method)
+                                             class)
+                      (simple-previous previous))
+                    (sod-class-methods class)
+                    :key #'sod-method-function-name
+                    :test #'equal)
+
+    ;; Make sure superclasses have distinct nicknames.
+    (let ((state (make-inheritance-path-reporter-state class)))
+      (find-duplicates (lambda (super previous)
+                        (cerror*-with-location class
+                                               "Duplicate nickname `~A' ~
+                                                in superclasses of `~A': ~
+                                                used by `~A' and `~A'"
+                                               (sod-class-nickname super)
+                                               class super previous)
+                        (report-inheritance-path state super)
+                        (report-inheritance-path state previous))
+                      (sod-class-precedence-list class)
+                      :key #'sod-class-nickname :test #'equal)))
+
+  ;; Check that the CHAIN-TO class is actually a proper superclass.  (This
+  ;; eliminates hairy things like a class being its own link.)
+  (let ((link (sod-class-chain-link class)))
+    (unless (or (not link)
+               (member link (cdr (sod-class-precedence-list class))))
+      (cerror* "In `~A', chain-to class `~A' is not a proper superclass"
+              class link)))
+
+  ;; Check that the initargs declare compatible types.  Duplicate entries,
+  ;; even within a class, are harmless, but at most one initarg in any
+  ;; class should declare a default value.
+  (let ((seen (make-hash-table :test #'equal))
+       (state (make-inheritance-path-reporter-state class)))
+    (dolist (super (sod-class-precedence-list class))
+      (dolist (initarg (reverse (sod-class-initargs super)))
+       (let* ((initarg-name (sod-initarg-name initarg))
+              (initarg-type (sod-initarg-type initarg))
+              (initarg-default (sod-initarg-default initarg))
+              (found (gethash initarg-name seen))
+              (found-type (and found (sod-initarg-type found)))
+              (found-default (and found (sod-initarg-default found)))
+              (found-class (and found (sod-initarg-class found)))
+              (found-location (and found (file-location found))))
+         (with-default-error-location (initarg)
+           (cond ((not found)
+                  (setf (gethash initarg-name seen) initarg))
+                 ((not (c-type-equal-p initarg-type found-type))
+                  (cerror* "Inititalization argument `~A' defined ~
+                            with incompatible types: ~
+                            ~A in class `~A', but ~A in class `~A'"
+                           initarg-name initarg-type super
+                           found-type found-class found-location)
+                  (report-inheritance-path state super))
+                 ((and initarg-default found-default
+                       (eql super found-class))
+                  (cerror* "Initialization argument `~A' redefined ~
+                            with default value"
+                           initarg-name)
+                  (info-with-location found-location
+                                      "Previous definition is here"))
+                 (initarg-default
+                  (setf (gethash initarg-name seen) initarg))))))))
+
+  ;; Check for circularity in the superclass graph.  Since the superclasses
+  ;; should already be acyclic, it suffices to check that our class is not
+  ;; a superclass of any of its own direct superclasses.
+  (let ((circle (find-if (lambda (super)
+                          (sod-subclass-p super class))
+                        (sod-class-direct-superclasses class))))
+    (when circle
+      (cerror* "`~A' is already a superclass of `~A'" class circle)
+      (report-inheritance-path (make-inheritance-path-reporter-state class)
+                              circle)))
+
+  ;; Check that the class has a unique root superclass.
+  (find-root-superclass class)
+
+  ;; Check that the metaclass is a subclass of each direct superclass's
+  ;; metaclass.
+  (finalization-error (:bad-metaclass)
+    (let ((meta (sod-class-metaclass class)))
+      (dolist (super (sod-class-direct-superclasses class))
+       (let ((supermeta (sod-class-metaclass super)))
+         (unless (sod-subclass-p meta supermeta)
+           (cerror* "Metaclass `~A' of `~A' isn't a subclass of `~A'"
+                    meta class supermeta)
+           (info-with-location super
+                               "Direct superclass `~A' defined here ~
+                                has metaclass `~A'"
+                               super supermeta))))))
+
+  ;; Check that all of the messages we can be sent have coherent collections
+  ;; of applicable methods.  This can go wrong, for example, if we inherit
+  ;; methods with differently typed keyword arguments.
+  (finalization-error (:mismatched-applicable-methods)
+    (dolist (super (sod-class-precedence-list class))
+      (dolist (message (sod-class-messages super))
+       (let ((methods (sod-message-applicable-methods message class)))
+         (sod-message-check-methods message class methods)))))
+
+  ;; Check that an initializer is available for every slot in the class's
+  ;; metaclass.  Skip this and trust the caller if the metaclass isn't
+  ;; finalized yet: in that case, we must be bootstrapping, and we must hope
+  ;; that the caller knows what they're doing.
+  (let* ((meta (sod-class-metaclass class))
+        (ilayout (and (eq (sod-class-state meta) :finalized)
+                      (sod-class-ilayout meta))))
+    (dolist (ichain (and ilayout (ilayout-ichains ilayout)))
+      (dolist (item (cdr (ichain-body ichain)))
+       (when (typep item 'islots)
+         (dolist (slot (islots-slots item))
+           (check-class-initializer slot class)))))))
 
 ;;;--------------------------------------------------------------------------
 ;;; Finalization.
 
 ;;;--------------------------------------------------------------------------
 ;;; Finalization.