Subject: [PATCH] Add support for using setf with seq-subseq

From: Okamsn <[email-masked]>
Date: Sun, 04 Feb 2024 18:33:14 +0000

Hello,

I've added the maintainers e-mail address to the discussion and have 
added "[PATCH]" to the subject to make it more clear that there is a 
patch file attached.

I tested a version that uses a `seq-map-while`, but it only made a 
difference when the sequence of values to copy to the existing sequence 
was longer than 100 elements, which I would guess is a less common use case.

Thank you,
Earl

From 0a5fac443cdcbeb9312d7ee68bafdd22e0905828 Mon Sep 17 00:00:00 2001
From: Earl Hyatt <[email-masked]>
Date: Sun, 28 Jan 2024 22:48:13 -0500
Subject: [PATCH] Add setf support for seq-subseq

* lisp/emacs-lisp/seq.el (seq-subseq): Add a generic version of
calling setf on seq-subseq and add a specialized version for when the
modified sequence is a list.
* test/lisp/emacs-lisp/seq-tests.el (test-setf-seq-subseq)
(test-setf-seq-subseq-combinations): Add tests for the feature.
---
 lisp/emacs-lisp/seq.el            | 43 +++++++++++++++++
 test/lisp/emacs-lisp/seq-tests.el | 76 +++++++++++++++++++++++++++++++
 2 files changed, 119 insertions(+)

diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 4c6553972c2..fd971806d87 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -193,6 +193,49 @@ seq-subseq
         (copy-sequence sequence))))
    (t (error "Unsupported sequence: %s" sequence))))
 
+(cl-defgeneric (setf seq-subseq) (store sequence start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (idx (if (< start 0)
+                  (+ start len)
+                start))
+         (end (cond
+               ((null end) len)
+               ((< end 0)
+                (+ end len))
+               (t (min len end)))))
+    (when (< idx end)
+      (seq-do (lambda (v)
+                (when (< idx end)
+                  (setf (seq-elt sequence idx) v
+                        idx (1+ idx))))
+              store)))
+  store)
+
+(cl-defmethod (setf seq-subseq) (store (sequence list) start &optional end)
+  "Modify the elements of SEQUENCE from START to END to be those of STORE.
+
+SEQUENCE is neither lengthened nor shortened."
+  (let* ((len (seq-length sequence))
+         (idx (if (< start 0)
+                  (+ start len)
+                start))
+         (end (cond
+               ((null end) len)
+               ((< end 0)  (+ end len))
+               (t          (min len end)))))
+    (when (< idx end)
+      (seq-do (let ((replaced (nthcdr idx sequence)))
+                (lambda (v)
+                  (when (< idx end)
+                    (setf (car replaced) v
+                          replaced (cdr replaced)
+                          idx (1+ idx)))))
+              store)))
+  store)
+
 
 (cl-defgeneric seq-map (function sequence)
   "Return the result of applying FUNCTION to each element of SEQUENCE."
diff --git a/test/lisp/emacs-lisp/seq-tests.el b/test/lisp/emacs-lisp/seq-tests.el
index c06ceb00bdb..6b8789688d3 100644
--- a/test/lisp/emacs-lisp/seq-tests.el
+++ b/test/lisp/emacs-lisp/seq-tests.el
@@ -312,6 +312,82 @@ test-seq-subseq
                   (:success
                    (should (equal (seq-subseq list start end) res))))))))))))
 
+(cl-defmacro test-setf-seq-subseq-combinations (&key result range init-vals
+                                                     sub-vals)
+  "Produce substitutions tests for `seq-subseq' using `setf'.
+
+- INIT-VALS is a list holding the initial elements.
+- RESULT is what the final value should be after substitution.
+- SUB-VALS is a list holding the elements to be substituted in.
+- RANGE is a list of the `start' and `end' arguments of `seq-subseq'."
+  (let ((tests))
+    (dolist (type1 '(list vector string))
+      (dolist (type2 '(list vector string))
+        (push  `(should (equal (,type1 ,@result)
+                               (let ((seq (,type1 ,@init-vals)))
+                                 (setf (seq-subseq seq ,@range)
+                                       (,type2 ,@sub-vals))
+                                 seq)))
+               tests)))
+    `(progn ,@tests)))
+
+(ert-deftest test-setf-seq-subseq ()
+  "Test using `seq-subseq' with `setf'.
+Any combination of sequences should work."
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11 12))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1)
+   :sub-vals (10 11 12)
+   :range (0 100)
+   :result (10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 100)
+   :result (0 1 12 13 14))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 3)
+   :result (0 1 12 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4)
+   :sub-vals (12 13 14 15)
+   :range (2 2)
+   :result (0 1 2 3 4))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-2)
+   :result (0 1 2 10 11))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -3)
+   :result (0 1 2 3 10 11 12 7 8 9))
+
+  (test-setf-seq-subseq-combinations
+   :init-vals (0 1 2 3 4 5 6 7 8 9)
+   :sub-vals (10 11 12 13 14)
+   :range (-6 -10)
+   :result (0 1 2 3 4 5 6 7 8 9)))
+
 (ert-deftest test-seq-concatenate ()
   (with-test-sequences (seq '(2 4 6))
     (should (equal (seq-concatenate 'string seq [8]) (string 2 4 6 8)))
-- 
2.34.1