mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-15 10:30:25 -08:00
(cl--class-allparents): Fix bug#78989
Give more control over ordering when linearizing the parent graph and avoid pathological misbehavior (such as placing `t` in the middle of the linearization instead of the end) when we can't "do it right". * lisp/subr.el (merge-ordered-lists): Degrade more gracefully in case of inconsistent hierarchies and don't do it silently. * lisp/emacs-lisp/cl-preloaded.el (cl--class-allparents): Use the local ordering to break ties, as in the C3 algorithm.
This commit is contained in:
parent
c8b6e90b4e
commit
7f1cae9637
2 changed files with 50 additions and 28 deletions
|
|
@ -285,8 +285,14 @@
|
|||
|
||||
(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)
|
||||
|
|
|
|||
68
lisp/subr.el
68
lisp/subr.el
|
|
@ -2860,38 +2860,54 @@ By default we choose the head of the first list."
|
|||
;; [C3](https://en.wikipedia.org/wiki/C3_linearization)
|
||||
(let ((result '()))
|
||||
(setq lists (remq nil lists)) ;Don't mutate the original `lists' argument.
|
||||
(while (cdr (setq lists (delq nil lists)))
|
||||
;; Try to find the next element of the result. This
|
||||
;; is achieved by considering the first element of each
|
||||
;; (non-empty) input list and accepting a candidate if it is
|
||||
;; consistent with the rests of the input lists.
|
||||
(let* ((next nil)
|
||||
(tail lists))
|
||||
(while tail
|
||||
(let ((candidate (caar tail))
|
||||
(other-lists lists))
|
||||
;; Ensure CANDIDATE is not in any position but the first
|
||||
;; in any of the element lists of LISTS.
|
||||
(while other-lists
|
||||
(if (not (memql candidate (cdr (car other-lists))))
|
||||
(setq other-lists (cdr other-lists))
|
||||
(setq candidate nil)
|
||||
(setq other-lists nil)))
|
||||
(if (not candidate)
|
||||
(setq tail (cdr tail))
|
||||
(setq next candidate)
|
||||
(setq tail nil))))
|
||||
(while (cdr lists)
|
||||
;; Try to find the next element of the result. This is achieved
|
||||
;; by considering the first element of each input list and accepting
|
||||
;; a candidate if it is consistent with the rest of the input lists.
|
||||
(let* ((find-next
|
||||
(lambda (lists)
|
||||
(let ((next nil)
|
||||
(tail lists))
|
||||
(while tail
|
||||
(let ((candidate (caar tail))
|
||||
(other-lists lists))
|
||||
;; Ensure CANDIDATE is not in any position but the first
|
||||
;; in any of the element lists of LISTS.
|
||||
(while other-lists
|
||||
(if (not (memql candidate (cdr (car other-lists))))
|
||||
(setq other-lists (cdr other-lists))
|
||||
(setq candidate nil)
|
||||
(setq other-lists nil)))
|
||||
(if (not candidate)
|
||||
(setq tail (cdr tail))
|
||||
(setq next candidate)
|
||||
(setq tail nil))))
|
||||
next)))
|
||||
(next (funcall find-next lists)))
|
||||
(unless next ;; The graph is inconsistent.
|
||||
(setq next (funcall (or error-function #'caar) lists))
|
||||
(unless (assoc next lists #'eql)
|
||||
(error "Invalid candidate returned by error-function: %S" next)))
|
||||
(let ((tail lists))
|
||||
;; Try and reduce the "remaining-list" such that its `caar`
|
||||
;; participates in the inconsistency (is part of an actual cycle).
|
||||
(while (and (cdr tail) (null (funcall find-next (cdr tail))))
|
||||
(setq tail (cdr tail)))
|
||||
(setq next (funcall (or error-function
|
||||
(lambda (remaining-lists)
|
||||
(message "Inconsistent hierarchy: %S"
|
||||
remaining-lists)
|
||||
(caar remaining-lists)))
|
||||
tail))
|
||||
(unless (assoc next lists #'eql)
|
||||
(error "Invalid candidate returned by error-function: %S" next))
|
||||
;; Break the cycle, while keeping other dependencies.
|
||||
(dolist (list lists) (setcdr list (remq next (cdr list))))))
|
||||
;; The graph is consistent so far, add NEXT to result and
|
||||
;; merge input lists, dropping NEXT from their heads where
|
||||
;; applicable.
|
||||
(push next result)
|
||||
(setq lists
|
||||
(mapcar (lambda (l) (if (eql (car l) next) (cdr l) l))
|
||||
lists))))
|
||||
(delq nil
|
||||
(mapcar (lambda (l) (if (eql (car l) next) (cdr l) l))
|
||||
lists)))))
|
||||
(if (null result) (car lists) ;; Common case.
|
||||
(append (nreverse result) (car lists)))))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue