]> git.eshelyaron.com Git - emacs.git/commitdiff
Improved copy-tree documentation and test (bug#63509)
authorMattias Engdegård <mattiase@acm.org>
Fri, 19 May 2023 10:32:28 +0000 (12:32 +0200)
committerMattias Engdegård <mattiase@acm.org>
Fri, 19 May 2023 13:50:42 +0000 (15:50 +0200)
* 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.

doc/lispref/lists.texi
doc/lispref/records.texi
etc/NEWS
lisp/emacs-lisp/shortdoc.el
lisp/subr.el
test/lisp/subr-tests.el

index 16ed0358974af0bc264b26b9d8cd54072b07ff1b..6a00f2887e7f7bf3219a101a11e5105913a75d92 100644 (file)
@@ -696,7 +696,7 @@ not a list, the sequence's elements do not become elements of the
 resulting list.  Instead, the sequence becomes the final @sc{cdr}, like
 any other non-list final argument.
 
-@defun copy-tree tree &optional vector-like-p
+@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
@@ -704,9 +704,9 @@ same way.
 
 Normally, when @var{tree} is anything other than a cons cell,
 @code{copy-tree} simply returns @var{tree}.  However, if
-@var{vector-like-p} is non-@code{nil}, it copies vectors and records
-too (and operates recursively on their elements).  This function
-cannot cope with circular lists.
+@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
 
 @defun flatten-tree tree
index ebc4569c388ea45fcf8fd8fee66b2b377e39b7d1..287ad86929764e946cea5d2874f575b50c7c70af 100644 (file)
@@ -81,8 +81,9 @@ This function returns a new record with type @var{type} and
 @end example
 @end defun
 
-To copy records, use @code{copy-tree} with its optional second argument
-non-@code{nil}.  @xref{Building Lists, copy-tree}.
+To copy trees consisting of records, vectors and conses (lists), use
+@code{copy-tree} with its optional second argument non-@code{nil}.
+@xref{Building Lists, copy-tree}.
 
 @node Backward Compatibility
 @section Backward Compatibility
index f1fb70c5fc60f6d74bf7ee147d9256d9d35321e2..04ef976a8d175f1fbe3d9202b7f0546b0387af20 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -388,6 +388,9 @@ These hooks were named incorrectly, and so they never actually ran
 when unloading the correspending feature.  Instead, you should use
 hooks named after the feature name, like 'esh-mode-unload-hook'.
 
++++
+** 'copy-tree' now copies records when its optional 2nd argument is non-nil.
+
 \f
 * Lisp Changes in Emacs 30.1
 
@@ -585,9 +588,6 @@ Since circular alias chains now cannot occur, 'function-alias-p',
 'indirect-function' and 'indirect-variable' will never signal an error.
 Their 'noerror' arguments have no effect and are therefore obsolete.
 
-+++
-** 'copy-tree' now copies records when its optional 2nd argument is non-nil.
-
 \f
 * Changes in Emacs 30.1 on Non-Free Operating Systems
 
index 6580e0e4e0c68d56b38dfe1c62a3799923750560..1e8ab4ad46d96024b0913fd1f380f6976cba76c1 100644 (file)
@@ -834,7 +834,7 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'),
    :eval (seq-subseq [1 2 3 4 5] 1 3)
    :eval (seq-subseq [1 2 3 4 5] 1))
   (copy-tree
-   :eval (copy-tree [1 2 3 4]))
+   :eval (copy-tree [1 (2 3) [4 5]] t))
   "Mapping Over Vectors"
   (mapcar
    :eval (mapcar #'identity [1 2 3]))
index 83735933963108dfbd7895e8c4c9f01581d8a720..5a64196565979d73ce0021437410016ff2790518 100644 (file)
@@ -824,26 +824,31 @@ of course, also replace TO with a slightly larger value
                 next (+ from (* n inc)))))
       (nreverse seq))))
 
-(defun copy-tree (tree &optional vector-like-p)
+(defun copy-tree (tree &optional vectors-and-records)
   "Make a copy of TREE.
 If TREE is a cons cell, this recursively copies both its car and its cdr.
-Contrast to `copy-sequence', which copies only along the cdrs.  With second
-argument VECTOR-LIKE-P, this copies vectors and records as well as conses."
+Contrast to `copy-sequence', which copies only along the cdrs.
+With the second argument VECTORS-AND-RECORDS non-nil, this
+traverses and copies vectors and records as well as conses."
   (declare (side-effect-free error-free))
   (if (consp tree)
       (let (result)
        (while (consp tree)
          (let ((newcar (car tree)))
-           (if (or (consp (car tree)) (and vector-like-p (or (vectorp (car tree)) (recordp (car tree)))))
-               (setq newcar (copy-tree (car tree) vector-like-p)))
+           (if (or (consp (car tree))
+                    (and vectors-and-records
+                         (or (vectorp (car tree)) (recordp (car tree)))))
+               (setq newcar (copy-tree (car tree) vectors-and-records)))
            (push newcar result))
          (setq tree (cdr tree)))
        (nconc (nreverse result)
-               (if (and vector-like-p (or (vectorp tree) (recordp tree))) (copy-tree tree vector-like-p) tree)))
-    (if (and vector-like-p (or (vectorp tree) (recordp tree)))
+               (if (and vectors-and-records (or (vectorp tree) (recordp tree)))
+                   (copy-tree tree vectors-and-records)
+                 tree)))
+    (if (and vectors-and-records (or (vectorp tree) (recordp tree)))
        (let ((i (length (setq tree (copy-sequence tree)))))
          (while (>= (setq i (1- i)) 0)
-           (aset tree i (copy-tree (aref tree i) vector-like-p)))
+           (aset tree i (copy-tree (aref tree i) vectors-and-records)))
          tree)
       tree)))
 
index 4ebb68556beef0c611f64827203ea04fac0bfa1f..1c220b1da1879bb11494152547e0ed29e980d502 100644 (file)
@@ -1207,35 +1207,54 @@ final or penultimate step during initialization."))
     (should (eq a a-dedup))))
 
 (ert-deftest subr--copy-tree ()
-  (should (eq (copy-tree nil) nil))
-  (let* ((a (list (list "a") "b" (list "c") "g"))
-         (copy1 (copy-tree a))
-         (copy2 (copy-tree a t)))
-    (should (equal a copy1))
-    (should (equal a copy2))
-    (should-not (eq a copy1))
-    (should-not (eq a copy2)))
-  (let* ((a (list (list "a") "b" (list "c" (record 'foo "d")) (list ["e" "f"]) "g"))
-         (copy1 (copy-tree a))
-         (copy2 (copy-tree a t)))
-    (should (equal a copy1))
-    (should (equal a copy2))
-    (should-not (eq a copy1))
-    (should-not (eq a copy2)))
-  (let* ((a (record 'foo "a" (record 'bar "b")))
-         (copy1 (copy-tree a))
-         (copy2 (copy-tree a t)))
-    (should (equal a copy1))
-    (should (equal a copy2))
-    (should (eq a copy1))
-    (should-not (eq a copy2)))
-  (let* ((a ["a" "b" ["c" ["d"]]])
-         (copy1 (copy-tree a))
-         (copy2 (copy-tree a t)))
-    (should (equal a copy1))
-    (should (equal a copy2))
-    (should (eq a copy1))
-    (should-not (eq a copy2))))
+  ;; Check that values other than conses, vectors and records are
+  ;; neither copied nor traversed.
+  (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 (copy-tree x) x))
+      (should (eq (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 (copy-tree x) (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)] #s(e (f [g])))))
+        (should (equal (prn3 x (copy-tree x) (copy-tree x t))
+                       (cat3 "(a #1=[b (c d)] #2=#s(e (f [g])))"
+                             "(a #1# #2#)"
+                             "(a [b (c d)] #s(e (f [g])))"))))
+      (let ((x [a (b #s(c d))]))
+        (should (equal (prn3 x (copy-tree x) (copy-tree x t))
+                       (cat3 "#1=[a (b #s(c d))]"
+                             "#1#"
+                             "[a (b #s(c d))]"))))
+      (let ((x #s(a (b [c d]))))
+        (should (equal (prn3 x (copy-tree x) (copy-tree x t))
+                       (cat3 "#1=#s(a (b [c d]))"
+                             "#1#"
+                             "#s(a (b [c d]))"))))
+      ;; Check cdr recursion.
+      (let ((x '(a b . [(c . #s(d))])))
+        (should (equal (prn3 x (copy-tree x) (copy-tree x t))
+                       (cat3 "(a b . #1=[(c . #s(d))])"
+                             "(a b . #1#)"
+                             "(a b . [(c . #s(d))])"))))
+      ;; Check that we can copy DAGs (the result is a tree).
+      (let ((x (list '(a b) nil [c d] nil #s(e 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 (copy-tree x) (copy-tree x t))
+                       (cat3 "(#1=(a b) #1# #2=[c d] #2# #3=#s(e f) #3#)"
+                             "((a b) (a b) #2# #2# #3# #3#)"
+                             "((a b) (a b) [c d] [c d] #s(e f) #s(e f))")))))))
 
 (provide 'subr-tests)
 ;;; subr-tests.el ends here