branch: externals/compat commit ac9d0a6e7866d17d5f71ae9c4868c6e787b9314e Author: Joseph Turner <jos...@breatheoutbreathe.in> Commit: Daniel Mendler <m...@daniel-mendler.de>
Add copy-tree --- NEWS.org | 2 ++ compat-30.el | 31 ++++++++++++++++++++++++ compat-tests.el | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ compat.texi | 14 +++++++++++ 4 files changed, 122 insertions(+) diff --git a/NEWS.org b/NEWS.org index 99dab032eb..d938a193e5 100644 --- a/NEWS.org +++ b/NEWS.org @@ -7,6 +7,8 @@ - compat-28: Improve =make-separator-line= visuals on graphic displays. - compat-28: Add =native-comp-available-p=, which always returns nil. - compat-29: Add variable =lisp-directory=. +- compat-30: Replace ~copy-tree~ with version from Emacs 30, support + copying records with non-nil optional second argument. * Release of "Compat" Version 29.1.4.1 diff --git a/compat-30.el b/compat-30.el index fabb48b28e..879a743b72 100644 --- a/compat-30.el +++ b/compat-30.el @@ -28,5 +28,36 @@ ;; TODO Update to 30.1 as soon as the Emacs emacs-30 branch version bumped (compat-version "30.0.50") +;;;; Defined in subr.el + +(compat-defun copy-tree (tree &optional vectors-and-records) ;; <compat-tests:copy-tree> + "Handle copying records when optional arg is non-nil." + :extended t + (declare (side-effect-free error-free)) + ;; `recordp' was not added until Emacs 26 + (let ((compat--recordp (if (fboundp #'recordp) + #'recordp + #'always))) + (if (consp tree) + (let (result) + (while (consp tree) + (let ((newcar (car tree))) + (if (or (consp (car tree)) + (and vectors-and-records + (or (vectorp (car tree)) (funcall compat--recordp (car tree))))) + (setq newcar (compat--copy-tree (car tree) vectors-and-records))) + (push newcar result)) + (setq tree (cdr tree))) + (nconc (nreverse result) + (if (and vectors-and-records (or (vectorp tree) (funcall compat--recordp tree))) + (compat--copy-tree tree vectors-and-records) + tree))) + (if (and vectors-and-records (or (vectorp tree) (funcall compat--recordp tree))) + (let ((i (length (setq tree (copy-sequence tree))))) + (while (>= (setq i (1- i)) 0) + (aset tree i (compat--copy-tree (aref tree i) vectors-and-records))) + tree) + tree)))) + (provide 'compat-30) ;;; compat-30.el ends here diff --git a/compat-tests.el b/compat-tests.el index 060c9caf0c..1fa5de33a0 100644 --- a/compat-tests.el +++ b/compat-tests.el @@ -2992,5 +2992,80 @@ (with-temp-buffer (should-equal (take 3 (widget-create 'key)) '(key :value "")))) +(ert-deftest compat-copy-tree () + ;; Adapted from Emacs /test/lisp/subr-tests.el + ;; Check that values other than conses, vectors and records are + ;; neither copied nor traversed. + (cl-defstruct compat-test--a foo) + (cl-defstruct compat-test--c foo) + (cl-defstruct compat-test--d) + (cl-defstruct compat-test--e foo) + (let ((s (propertize "abc" 'prop (list 11 12))) + (h (make-hash-table :test #'equal))) + (puthash (list 1 2) (list 3 4) h) + (dolist (x (list nil 'a "abc" s h)) + (should (eq (compat-call copy-tree x) x)) + (should (eq (compat-call copy-tree x t) x)))) + + ;; Use the printer to detect common parts of Lisp values. + (let ((print-circle t)) + (cl-labels ((prn3 (x y z) (prin1-to-string (list x y z))) + (cat3 (x y z) (concat "(" x " " y " " z ")"))) + (let ((x '(a (b ((c) . d) e) (f)))) + (should (equal (prn3 x (compat-call copy-tree x) (compat-call copy-tree x t)) + (cat3 "(a (b ((c) . d) e) (f))" + "(a (b ((c) . d) e) (f))" + "(a (b ((c) . d) e) (f))")))) + (let ((x `(a [b (c d)] ,(make-compat-test--e :foo '(f [g]))))) + (should (equal (prn3 x (compat-call copy-tree x) (compat-call copy-tree x t)) + (if (< emacs-major-version 26) + (cat3 "(a #1=[b (c d)] #2=[cl-struct-compat-test--e (f [g])])" + "(a #1# #2#)" + "(a [b (c d)] [cl-struct-compat-test--e (f [g])])") + (cat3 "(a #1=[b (c d)] #2=#s(compat-test--e (f [g])))" + "(a #1# #2#)" + "(a [b (c d)] #s(compat-test--e (f [g])))"))))) + (let ((x (vector 'a `(b ,(make-compat-test--c :foo 'd))))) + (should (equal (prn3 x (compat-call copy-tree x) (compat-call copy-tree x t)) + (if (< emacs-major-version 26) + (cat3 "#1=[a (b [cl-struct-compat-test--c d])]" + "#1#" + "[a (b [cl-struct-compat-test--c d])]") + (cat3 "#1=[a (b #s(compat-test--c d))]" + "#1#" + "[a (b #s(compat-test--c d))]"))))) + (let ((x (make-compat-test--a :foo '(b [c d])))) + (should (equal (prn3 x (compat-call copy-tree x) (compat-call copy-tree x t)) + (if (< emacs-major-version 26) + (cat3 "#1=[cl-struct-compat-test--a (b [c d])]" + "#1#" + "[cl-struct-compat-test--a (b [c d])]") + (cat3 "#1=#s(compat-test--a (b [c d]))" + "#1#" + "#s(compat-test--a (b [c d]))"))))) + ;; Check cdr recursion. + (let ((x `(a b . ,(vector `(c . ,(make-compat-test--d)))))) + (should (equal (prn3 x (compat-call copy-tree x) (compat-call copy-tree x t)) + (if (< emacs-major-version 26) + (cat3 "(a b . #1=[(c . [cl-struct-compat-test--d])])" + "(a b . #1#)" + "(a b . [(c . [cl-struct-compat-test--d])])") + (cat3 "(a b . #1=[(c . #s(compat-test--d))])" + "(a b . #1#)" + "(a b . [(c . #s(compat-test--d))])"))))) + ;; Check that we can copy DAGs (the result is a tree). + (let ((x (list '(a b) nil [c d] nil (make-compat-test--e :foo 'f) nil))) + (setf (nth 1 x) (nth 0 x)) + (setf (nth 3 x) (nth 2 x)) + (setf (nth 5 x) (nth 4 x)) + (should (equal (prn3 x (compat-call copy-tree x) (compat-call copy-tree x t)) + (if (< emacs-major-version 26) + (cat3 "(#1=(a b) #1# #2=[c d] #2# #3=[cl-struct-compat-test--e f] #3#)" + "((a b) (a b) #2# #2# #3# #3#)" + "((a b) (a b) [c d] [c d] [cl-struct-compat-test--e f] [cl-struct-compat-test--e f])") + (cat3 "(#1=(a b) #1# #2=[c d] #2# #3=#s(compat-test--e f) #3#)" + "((a b) (a b) #2# #2# #3# #3#)" + "((a b) (a b) [c d] [c d] #s(compat-test--e f) #s(compat-test--e f))")))))))) + (provide 'compat-tests) ;;; compat-tests.el ends here diff --git a/compat.texi b/compat.texi index 9eefdaa377..5a7388fb1b 100644 --- a/compat.texi +++ b/compat.texi @@ -3333,6 +3333,20 @@ care. These functions must be called explicitly via @code{compat-call}, since their calling convention or behavior was extended in Emacs 30.1: +@c copied from lispref/lists.texi +@defun copy-tree tree &optional vectors-and-records +This function returns a copy of the tree @var{tree}. If @var{tree} is a +cons cell, this makes a new cons cell with the same @sc{car} and +@sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the +same way. + +Normally, when @var{tree} is anything other than a cons cell, +@code{copy-tree} simply returns @var{tree}. However, if +@var{vectors-and-records} is non-@code{nil}, it copies vectors and records +too (and operates recursively on their elements). The @var{tree} +argument must not contain cycles. +@end defun + @subsection Missing Definitions Compat does not provide support for the following Lisp features implemented in 30.1: