diff options
| author | Mattias EngdegÄrd | 2023-05-19 12:32:28 +0200 |
|---|---|---|
| committer | Mattias EngdegÄrd | 2023-05-19 15:50:42 +0200 |
| commit | bd6bba4780dcfdec97ab5e6469f7777c4b2a1b0d (patch) | |
| tree | 68165f5e80829f975c9e740fd2ca1e06fcc40d2a | |
| parent | 156973639cc57dec47705f76f63c2ef3dc00a61d (diff) | |
| download | emacs-bd6bba4780dcfdec97ab5e6469f7777c4b2a1b0d.tar.gz emacs-bd6bba4780dcfdec97ab5e6469f7777c4b2a1b0d.zip | |
Improved copy-tree documentation and test (bug#63509)
* etc/NEWS: Move entry since it's an incompatible change.
* lisp/emacs-lisp/shortdoc.el (vector): Make the example relevant.
* lisp/subr.el (copy-tree): Rename second argument,
since 'vector-like' is a term with a specific meaning in Emacs
but not the one intended here.
* doc/lispref/lists.texi (Building Lists): Rename second argument,
and make it clear that the input must be acyclic.
* doc/lispref/records.texi (Record Functions):
Be more precise: `copy-sequence` is used to copy records,
`copy-tree` copies trees made of records etc.
* test/lisp/subr-tests.el (subr--copy-tree): Extend and strengthen the
test considerably, using the print-circle trick to detect structure
sharing precisely.
| -rw-r--r-- | doc/lispref/lists.texi | 8 | ||||
| -rw-r--r-- | doc/lispref/records.texi | 5 | ||||
| -rw-r--r-- | etc/NEWS | 6 | ||||
| -rw-r--r-- | lisp/emacs-lisp/shortdoc.el | 2 | ||||
| -rw-r--r-- | lisp/subr.el | 21 | ||||
| -rw-r--r-- | test/lisp/subr-tests.el | 77 |
6 files changed, 72 insertions, 47 deletions
diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi index 16ed0358974..6a00f2887e7 100644 --- a/doc/lispref/lists.texi +++ b/doc/lispref/lists.texi | |||
| @@ -696,7 +696,7 @@ not a list, the sequence's elements do not become elements of the | |||
| 696 | resulting list. Instead, the sequence becomes the final @sc{cdr}, like | 696 | resulting list. Instead, the sequence becomes the final @sc{cdr}, like |
| 697 | any other non-list final argument. | 697 | any other non-list final argument. |
| 698 | 698 | ||
| 699 | @defun copy-tree tree &optional vector-like-p | 699 | @defun copy-tree tree &optional vectors-and-records |
| 700 | This function returns a copy of the tree @var{tree}. If @var{tree} is a | 700 | This function returns a copy of the tree @var{tree}. If @var{tree} is a |
| 701 | cons cell, this makes a new cons cell with the same @sc{car} and | 701 | cons cell, this makes a new cons cell with the same @sc{car} and |
| 702 | @sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the | 702 | @sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the |
| @@ -704,9 +704,9 @@ same way. | |||
| 704 | 704 | ||
| 705 | Normally, when @var{tree} is anything other than a cons cell, | 705 | Normally, when @var{tree} is anything other than a cons cell, |
| 706 | @code{copy-tree} simply returns @var{tree}. However, if | 706 | @code{copy-tree} simply returns @var{tree}. However, if |
| 707 | @var{vector-like-p} is non-@code{nil}, it copies vectors and records | 707 | @var{vectors-and-records} is non-@code{nil}, it copies vectors and records |
| 708 | too (and operates recursively on their elements). This function | 708 | too (and operates recursively on their elements). The @var{tree} |
| 709 | cannot cope with circular lists. | 709 | argument must not contain cycles. |
| 710 | @end defun | 710 | @end defun |
| 711 | 711 | ||
| 712 | @defun flatten-tree tree | 712 | @defun flatten-tree tree |
diff --git a/doc/lispref/records.texi b/doc/lispref/records.texi index ebc4569c388..287ad869297 100644 --- a/doc/lispref/records.texi +++ b/doc/lispref/records.texi | |||
| @@ -81,8 +81,9 @@ This function returns a new record with type @var{type} and | |||
| 81 | @end example | 81 | @end example |
| 82 | @end defun | 82 | @end defun |
| 83 | 83 | ||
| 84 | To copy records, use @code{copy-tree} with its optional second argument | 84 | To copy trees consisting of records, vectors and conses (lists), use |
| 85 | non-@code{nil}. @xref{Building Lists, copy-tree}. | 85 | @code{copy-tree} with its optional second argument non-@code{nil}. |
| 86 | @xref{Building Lists, copy-tree}. | ||
| 86 | 87 | ||
| 87 | @node Backward Compatibility | 88 | @node Backward Compatibility |
| 88 | @section Backward Compatibility | 89 | @section Backward Compatibility |
| @@ -388,6 +388,9 @@ These hooks were named incorrectly, and so they never actually ran | |||
| 388 | when unloading the correspending feature. Instead, you should use | 388 | when unloading the correspending feature. Instead, you should use |
| 389 | hooks named after the feature name, like 'esh-mode-unload-hook'. | 389 | hooks named after the feature name, like 'esh-mode-unload-hook'. |
| 390 | 390 | ||
| 391 | +++ | ||
| 392 | ** 'copy-tree' now copies records when its optional 2nd argument is non-nil. | ||
| 393 | |||
| 391 | 394 | ||
| 392 | * Lisp Changes in Emacs 30.1 | 395 | * Lisp Changes in Emacs 30.1 |
| 393 | 396 | ||
| @@ -585,9 +588,6 @@ Since circular alias chains now cannot occur, 'function-alias-p', | |||
| 585 | 'indirect-function' and 'indirect-variable' will never signal an error. | 588 | 'indirect-function' and 'indirect-variable' will never signal an error. |
| 586 | Their 'noerror' arguments have no effect and are therefore obsolete. | 589 | Their 'noerror' arguments have no effect and are therefore obsolete. |
| 587 | 590 | ||
| 588 | +++ | ||
| 589 | ** 'copy-tree' now copies records when its optional 2nd argument is non-nil. | ||
| 590 | |||
| 591 | 591 | ||
| 592 | * Changes in Emacs 30.1 on Non-Free Operating Systems | 592 | * Changes in Emacs 30.1 on Non-Free Operating Systems |
| 593 | 593 | ||
diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 6580e0e4e0c..1e8ab4ad46d 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el | |||
| @@ -834,7 +834,7 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'), | |||
| 834 | :eval (seq-subseq [1 2 3 4 5] 1 3) | 834 | :eval (seq-subseq [1 2 3 4 5] 1 3) |
| 835 | :eval (seq-subseq [1 2 3 4 5] 1)) | 835 | :eval (seq-subseq [1 2 3 4 5] 1)) |
| 836 | (copy-tree | 836 | (copy-tree |
| 837 | :eval (copy-tree [1 2 3 4])) | 837 | :eval (copy-tree [1 (2 3) [4 5]] t)) |
| 838 | "Mapping Over Vectors" | 838 | "Mapping Over Vectors" |
| 839 | (mapcar | 839 | (mapcar |
| 840 | :eval (mapcar #'identity [1 2 3])) | 840 | :eval (mapcar #'identity [1 2 3])) |
diff --git a/lisp/subr.el b/lisp/subr.el index 83735933963..5a641965659 100644 --- a/lisp/subr.el +++ b/lisp/subr.el | |||
| @@ -824,26 +824,31 @@ of course, also replace TO with a slightly larger value | |||
| 824 | next (+ from (* n inc))))) | 824 | next (+ from (* n inc))))) |
| 825 | (nreverse seq)))) | 825 | (nreverse seq)))) |
| 826 | 826 | ||
| 827 | (defun copy-tree (tree &optional vector-like-p) | 827 | (defun copy-tree (tree &optional vectors-and-records) |
| 828 | "Make a copy of TREE. | 828 | "Make a copy of TREE. |
| 829 | If TREE is a cons cell, this recursively copies both its car and its cdr. | 829 | If TREE is a cons cell, this recursively copies both its car and its cdr. |
| 830 | Contrast to `copy-sequence', which copies only along the cdrs. With second | 830 | Contrast to `copy-sequence', which copies only along the cdrs. |
| 831 | argument VECTOR-LIKE-P, this copies vectors and records as well as conses." | 831 | With the second argument VECTORS-AND-RECORDS non-nil, this |
| 832 | traverses and copies vectors and records as well as conses." | ||
| 832 | (declare (side-effect-free error-free)) | 833 | (declare (side-effect-free error-free)) |
| 833 | (if (consp tree) | 834 | (if (consp tree) |
| 834 | (let (result) | 835 | (let (result) |
| 835 | (while (consp tree) | 836 | (while (consp tree) |
| 836 | (let ((newcar (car tree))) | 837 | (let ((newcar (car tree))) |
| 837 | (if (or (consp (car tree)) (and vector-like-p (or (vectorp (car tree)) (recordp (car tree))))) | 838 | (if (or (consp (car tree)) |
| 838 | (setq newcar (copy-tree (car tree) vector-like-p))) | 839 | (and vectors-and-records |
| 840 | (or (vectorp (car tree)) (recordp (car tree))))) | ||
| 841 | (setq newcar (copy-tree (car tree) vectors-and-records))) | ||
| 839 | (push newcar result)) | 842 | (push newcar result)) |
| 840 | (setq tree (cdr tree))) | 843 | (setq tree (cdr tree))) |
| 841 | (nconc (nreverse result) | 844 | (nconc (nreverse result) |
| 842 | (if (and vector-like-p (or (vectorp tree) (recordp tree))) (copy-tree tree vector-like-p) tree))) | 845 | (if (and vectors-and-records (or (vectorp tree) (recordp tree))) |
| 843 | (if (and vector-like-p (or (vectorp tree) (recordp tree))) | 846 | (copy-tree tree vectors-and-records) |
| 847 | tree))) | ||
| 848 | (if (and vectors-and-records (or (vectorp tree) (recordp tree))) | ||
| 844 | (let ((i (length (setq tree (copy-sequence tree))))) | 849 | (let ((i (length (setq tree (copy-sequence tree))))) |
| 845 | (while (>= (setq i (1- i)) 0) | 850 | (while (>= (setq i (1- i)) 0) |
| 846 | (aset tree i (copy-tree (aref tree i) vector-like-p))) | 851 | (aset tree i (copy-tree (aref tree i) vectors-and-records))) |
| 847 | tree) | 852 | tree) |
| 848 | tree))) | 853 | tree))) |
| 849 | 854 | ||
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el index 4ebb68556be..1c220b1da18 100644 --- a/test/lisp/subr-tests.el +++ b/test/lisp/subr-tests.el | |||
| @@ -1207,35 +1207,54 @@ final or penultimate step during initialization.")) | |||
| 1207 | (should (eq a a-dedup)))) | 1207 | (should (eq a a-dedup)))) |
| 1208 | 1208 | ||
| 1209 | (ert-deftest subr--copy-tree () | 1209 | (ert-deftest subr--copy-tree () |
| 1210 | (should (eq (copy-tree nil) nil)) | 1210 | ;; Check that values other than conses, vectors and records are |
| 1211 | (let* ((a (list (list "a") "b" (list "c") "g")) | 1211 | ;; neither copied nor traversed. |
| 1212 | (copy1 (copy-tree a)) | 1212 | (let ((s (propertize "abc" 'prop (list 11 12))) |
| 1213 | (copy2 (copy-tree a t))) | 1213 | (h (make-hash-table :test #'equal))) |
| 1214 | (should (equal a copy1)) | 1214 | (puthash (list 1 2) (list 3 4) h) |
| 1215 | (should (equal a copy2)) | 1215 | (dolist (x (list nil 'a "abc" s h)) |
| 1216 | (should-not (eq a copy1)) | 1216 | (should (eq (copy-tree x) x)) |
| 1217 | (should-not (eq a copy2))) | 1217 | (should (eq (copy-tree x t) x)))) |
| 1218 | (let* ((a (list (list "a") "b" (list "c" (record 'foo "d")) (list ["e" "f"]) "g")) | 1218 | |
| 1219 | (copy1 (copy-tree a)) | 1219 | ;; Use the printer to detect common parts of Lisp values. |
| 1220 | (copy2 (copy-tree a t))) | 1220 | (let ((print-circle t)) |
| 1221 | (should (equal a copy1)) | 1221 | (cl-labels ((prn3 (x y z) (prin1-to-string (list x y z))) |
| 1222 | (should (equal a copy2)) | 1222 | (cat3 (x y z) (concat "(" x " " y " " z ")"))) |
| 1223 | (should-not (eq a copy1)) | 1223 | (let ((x '(a (b ((c) . d) e) (f)))) |
| 1224 | (should-not (eq a copy2))) | 1224 | (should (equal (prn3 x (copy-tree x) (copy-tree x t)) |
| 1225 | (let* ((a (record 'foo "a" (record 'bar "b"))) | 1225 | (cat3 "(a (b ((c) . d) e) (f))" |
| 1226 | (copy1 (copy-tree a)) | 1226 | "(a (b ((c) . d) e) (f))" |
| 1227 | (copy2 (copy-tree a t))) | 1227 | "(a (b ((c) . d) e) (f))")))) |
| 1228 | (should (equal a copy1)) | 1228 | (let ((x '(a [b (c d)] #s(e (f [g]))))) |
| 1229 | (should (equal a copy2)) | 1229 | (should (equal (prn3 x (copy-tree x) (copy-tree x t)) |
| 1230 | (should (eq a copy1)) | 1230 | (cat3 "(a #1=[b (c d)] #2=#s(e (f [g])))" |
| 1231 | (should-not (eq a copy2))) | 1231 | "(a #1# #2#)" |
| 1232 | (let* ((a ["a" "b" ["c" ["d"]]]) | 1232 | "(a [b (c d)] #s(e (f [g])))")))) |
| 1233 | (copy1 (copy-tree a)) | 1233 | (let ((x [a (b #s(c d))])) |
| 1234 | (copy2 (copy-tree a t))) | 1234 | (should (equal (prn3 x (copy-tree x) (copy-tree x t)) |
| 1235 | (should (equal a copy1)) | 1235 | (cat3 "#1=[a (b #s(c d))]" |
| 1236 | (should (equal a copy2)) | 1236 | "#1#" |
| 1237 | (should (eq a copy1)) | 1237 | "[a (b #s(c d))]")))) |
| 1238 | (should-not (eq a copy2)))) | 1238 | (let ((x #s(a (b [c d])))) |
| 1239 | (should (equal (prn3 x (copy-tree x) (copy-tree x t)) | ||
| 1240 | (cat3 "#1=#s(a (b [c d]))" | ||
| 1241 | "#1#" | ||
| 1242 | "#s(a (b [c d]))")))) | ||
| 1243 | ;; Check cdr recursion. | ||
| 1244 | (let ((x '(a b . [(c . #s(d))]))) | ||
| 1245 | (should (equal (prn3 x (copy-tree x) (copy-tree x t)) | ||
| 1246 | (cat3 "(a b . #1=[(c . #s(d))])" | ||
| 1247 | "(a b . #1#)" | ||
| 1248 | "(a b . [(c . #s(d))])")))) | ||
| 1249 | ;; Check that we can copy DAGs (the result is a tree). | ||
| 1250 | (let ((x (list '(a b) nil [c d] nil #s(e f) nil))) | ||
| 1251 | (setf (nth 1 x) (nth 0 x)) | ||
| 1252 | (setf (nth 3 x) (nth 2 x)) | ||
| 1253 | (setf (nth 5 x) (nth 4 x)) | ||
| 1254 | (should (equal (prn3 x (copy-tree x) (copy-tree x t)) | ||
| 1255 | (cat3 "(#1=(a b) #1# #2=[c d] #2# #3=#s(e f) #3#)" | ||
| 1256 | "((a b) (a b) #2# #2# #3# #3#)" | ||
| 1257 | "((a b) (a b) [c d] [c d] #s(e f) #s(e f))"))))))) | ||
| 1239 | 1258 | ||
| 1240 | (provide 'subr-tests) | 1259 | (provide 'subr-tests) |
| 1241 | ;;; subr-tests.el ends here | 1260 | ;;; subr-tests.el ends here |