Subject: bug#78989: 31.0.50; classes and methods inheritance (defclass) seq-contains-p

From: Stefan Monnier <[email-masked]>
Date: Fri, 11 Jul 2025 17:27:55 -0400

>> FWIW, `cl--class-allparents` does not promise a general topological sort.
> It doesn't?

No, you can have classes A and B with (linearized) parents:

    (A .. C .. D .. t)

    (B .. D .. C .. t)

in which case inheriting from both A and B is simply not supported
because we can't create a merged linearization of the parents which
is monotonic (i.e. obeys the existing linearization in both parents).

> Then the cl-generic code won't work...

AFAIK it's good enough for most languages, so I hope it should be good
enough for us.

>> 1 -> (merge-ordered-lists ((widgetlist widobj baselist state sortable
>> eieio-default-superclass record atom t) (loadable loadfile loader
>> eieio-persistent state interface title widobj abstitle
>> eieio-default-superclass record atom t) (interfacelist widgetlist
>> interface title widobj baselist state sortable abstitle
>> eieio-default-superclass record atom t)))
>> 1 <- merge-ordered-lists: (loadable loadfile loader eieio-persistent
>> interfacelist widgetlist widobj baselist state interface title widobj
>> baselist state sortable abstitle eieio-default-superclass record atom
>> t)
>>
>> where we see that `baselist` (and several others as well) appears twice in the output.
>
> which is the behavior of merge-ordered-lists when given a non-fatal (or
> no) error-function.

Yup: In one of the lists we have (.. widobj .. state ..) while in
another we have (.. state .. widobj ..), so this is one of those cases
that are not supported.

I do see some problems in our code:

- We should emit some warning when faced with such an
  inconsistent hierarchy.
  [ Note: the hierarchy is not globally inconsistent, but it becomes
    inconsistent because linearization is performed
    piecemeal/locally/modularly, so by the time we're faced with an
    inconsistency we'd have to go back and choose different
    linearizations for some of the existing classes.  ]
- We should try harder to degrade gracefully in the face of inconsistent
  hierarchies, e.g. avoiding multiple occurrences of the same parent or
  with `t` in the middle, ...
- We should give more control to the programmer to impose a particular
  choice of linearization.  To mimic what CLOS does, we could use the
  patch below.

With that patch Pierre could replace

    (defclass loadable (loadfile interface)
      (d(irectory :initarg directory)))

with

    (defclass loadable (loadfile interface widobj state)
      (d(irectory :initarg directory)))

to force the linearization to place `widobj` before `state`.
In turn it would require changing

    (defclass interface (widobj title) ())

to

    (defclass interface (title widobj) ())
or
    (defclass interface (title) ())

because `widobj` is a parent of `title`.
Similarly Pierre would have to change

    (defclass father (widgetlist loadable interfacelist) ())

to

    (defclass father (interfacelist widgetlist loadable) ())
or
    (defclass father (loadable interfacelist) ())

since `widgetlist` is a parent of `interfacelist`.

        Stefan

diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el
index 263a9b85225..71b4a863b30 100644
--- a/lisp/emacs-lisp/cl-preloaded.el
+++ b/lisp/emacs-lisp/cl-preloaded.el
@@ -285,8 +285,14 @@ cl--copy-slot-descriptor
 
 (defun cl--class-allparents (class)
   (cons (cl--class-name class)
-        (merge-ordered-lists (mapcar #'cl--class-allparents
-                                     (cl--class-parents class)))))
+        (let* ((parents (cl--class-parents class))
+               (aps (mapcar #'cl--class-allparents parents)))
+          (if (null (cdr aps)) ;; Single-inheritance fast-path.
+              (car aps)
+            (merge-ordered-lists
+             ;; Add the list of immediate parents, to control which
+             ;; linearization is chosen.  doi:10.1145/236337.236343
+             (nconc aps (list (mapcar #'cl--class-name parents))))))))
 
 (cl-defstruct (built-in-class
                (:include cl--class)