Subject: Re: bug#38000: 27.0.50; [PATCH] WIP on using gnus info accessor macros

From: Eric Abrahamsen <[email-masked]>
Date: Fri, 01 Nov 2019 15:52:44 -0700

Eric Abrahamsen <[email-masked]> writes:

> Lars Ingebrigtsen <[email-masked]> writes:
>
>> Eric Abrahamsen <[email-masked]> writes:
>>
>>> Huh, I didn't realize that list-based structs didn't put a tag in the
>>> `car' position unless you pass the :named keyword.
>>>
>>> We could switch to list-based structs without breaking out-of-tree code,
>>> but I don't see much point: you can't use them for generic function
>>> dispatch, even when they're :named, nor do you get a NAME-p predicate
>>> for free. That doesn't leave much point.
>>
>> It means that you don't have to write the accessor macros by hand.  :-)
>
> But you already did! :)
>
> Okay, let's use them anyway. And I'd like to move the "extend" and
> "vacuum" procedures to the reading and writing of the newsrc.eld file,
> so the rest of the code can forget about it. I'll leave the setter
> macros in place, and continue to accept (and ignore) an EXTEND argument,
> so other people's code continues to work.

Okay, here's what the patch looks like now.

1. gnus-info is defined with defstruct, and the constructor arglist
looks like: (group rank read &optional marks method params), meaning
that if you have an info list that may or may not be fully extended, you
can run it through the constructor and all the missing elements will be
set to their default (nil).

2. This is done when reading the newsrc.eld file (and creating new
groups), and all the other uses of the 'extend argument have been
removed.`

3. The bit that chops off nils from the end of infos has been moved from
`gnus-update-marks' to `gnus-gnus-to-quick-newsrc-format', so it only
happens right at the last minute, when saving.

4. I've changed `gnus-set-info' to update each element of an info
individually, rather than just replacing the whole thing. This looks
stupid, but preserves object reference, meaning we no longer need to mess
with the `gnus-newsrc-alist' in `gnus-group-set-info', which was causing
trouble for people. It's going to be a bit slower, but I don't think this is
super performance-crucial.

I think that's it... This still needs some testing, but I'm optimistic.

>From 56f4dacf5c1062b156df46778644e65378a9b4a2 Mon Sep 17 00:00:00 2001
From: Eric Abrahamsen <[email-masked]>
Date: Wed, 17 Jul 2019 18:49:27 -0700
Subject: [PATCH] WIP on using gnus info accessors

---
 lisp/gnus/gnus-agent.el |  36 ++++++------
 lisp/gnus/gnus-group.el |  80 +++++++++++++-------------
 lisp/gnus/gnus-int.el   |   2 +-
 lisp/gnus/gnus-sieve.el |   2 +-
 lisp/gnus/gnus-start.el |  79 ++++++++++++++------------
 lisp/gnus/gnus-sum.el   |  49 +++++++---------
 lisp/gnus/gnus.el       | 122 ++++++++++++++++++++++++----------------
 lisp/gnus/nndiary.el    |   5 +-
 lisp/gnus/nndraft.el    |  18 +++---
 lisp/gnus/nnimap.el     |  23 ++++----
 lisp/gnus/nnir.el       |   8 +--
 lisp/gnus/nnmaildir.el  |  10 ++--
 lisp/gnus/nnmairix.el   |  38 ++++++-------
 lisp/gnus/nnml.el       |   4 +-
 lisp/gnus/nnvirtual.el  |  17 +++---
 15 files changed, 252 insertions(+), 241 deletions(-)

diff --git a/lisp/gnus/gnus-agent.el b/lisp/gnus/gnus-agent.el
index 1f25255278..a58a35df98 100644
--- a/lisp/gnus/gnus-agent.el
+++ b/lisp/gnus/gnus-agent.el
@@ -1212,22 +1212,21 @@ gnus-agent-synchronize-group-flags
 	      (marks (nth 2 action)))
 	  (dolist (mark marks)
 	    (cond ((eq mark 'read)
-		   (gnus-info-set-read
-		    info
-		    (funcall (if (eq what 'add)
-				 'gnus-range-add
-			       'gnus-remove-from-range)
-			     (gnus-info-read info)
-			     range))
+		   (setf (gnus-info-read info)
+			 (funcall (if (eq what 'add)
+				      'gnus-range-add
+				    'gnus-remove-from-range)
+				  (gnus-info-read info)
+				  range))
 		   (gnus-get-unread-articles-in-group
 		    info
 		    (gnus-active (gnus-info-group info))))
 		  ((memq mark '(tick))
 		   (let ((info-marks (assoc mark (gnus-info-marks info))))
 		     (unless info-marks
-                       (gnus-info-set-marks
-                        info (cons (setq info-marks (list mark))
-                                   (gnus-info-marks info))))
+		       (setf (gnus-info-marks info)
+			     (cons (setq info-marks (list mark))
+				   (gnus-info-marks info))))
                      (setcdr info-marks
                              (funcall (if (eq what 'add)
                                           'gnus-range-add
@@ -1303,12 +1302,11 @@ gnus-agent-possibly-alter-active
           ;; file.
 
           (let ((read (gnus-info-read info)))
-            (gnus-info-set-read
-             info
-             (gnus-range-add
-              read
-              (list (cons (1+ agent-max)
-                          (1- active-min))))))
+	    (setf (gnus-info-read info)
+		  (gnus-range-add
+		   read
+		   (list (cons (1+ agent-max)
+                               (1- active-min))))))
 
           ;; Lie about the agent's local range for this group to
           ;; disable the set read each time this server is opened.
@@ -2535,11 +2533,11 @@ gnus-agent-fetch-group-1
                           (when (cdr marked-arts)
                             (setq marks
 				  (delq marked-arts (gnus-info-marks info)))
-                            (gnus-info-set-marks info marks)))))
+			    (setf (gnus-info-marks info) marks)))))
                     (let ((read (gnus-info-read
 				 (or info (setq info (gnus-get-info group))))))
-                      (gnus-info-set-read
-		       info (gnus-add-to-range read unfetched-articles)))
+		      (setf (gnus-info-read info)
+			    (gnus-add-to-range read unfetched-articles)))
 
                     (gnus-group-update-group group t)
                     (sit-for 0)
diff --git a/lisp/gnus/gnus-group.el b/lisp/gnus/gnus-group.el
index 8f5f5d66e4..0d3649120d 100644
--- a/lisp/gnus/gnus-group.el
+++ b/lisp/gnus/gnus-group.el
@@ -1423,7 +1423,7 @@ gnus-group-update-group-line
 	   (not (gnus-ephemeral-group-p group))
 	   (gnus-dribble-enter
 	    (concat "(gnus-group-set-info '"
-		    (gnus-prin1-to-string (nth 1 entry))
+		    (gnus-prin1-to-string (gnus-get-info group))
 		    ")")
 	    (concat "^(gnus-group-set-info '(\"" (regexp-quote group) "\"")))
       (setq gnus-group-indentation (gnus-group-group-indentation))
@@ -1440,10 +1440,10 @@ gnus-group-insert-group-line-info
     (if entry
 	(progn
 	  ;; (Un)subscribed group.
-	  (setq info (nth 1 entry))
+	  (setq info (gnus-get-info group))
 	  (gnus-group-insert-group-line
 	   group (gnus-info-level info) (gnus-info-marks info)
-	   (or (car entry) t) (gnus-info-method info)))
+	   (or (gnus-group-unread group) t) (gnus-info-method info)))
       ;; This group is dead.
       (gnus-group-insert-group-line
        group
@@ -1457,7 +1457,7 @@ gnus-group-insert-group-line-info
        (gnus-method-simplify (gnus-find-method-for-group group))))))
 
 (defun gnus-number-of-unseen-articles-in-group (group)
-  (let* ((info (nth 1 (gnus-group-entry group)))
+  (let* ((info (gnus-get-info group))
 	 (marked (gnus-info-marks info))
 	 (seen (cdr (assq 'seen marked)))
 	 (active (gnus-active group)))
@@ -1595,15 +1595,15 @@ gnus-group-update-eval-form
   "Eval `car' of each element of LIST, and return the first that return t.
 Some value are bound so the form can use them."
   (when list
-    (let* ((entry (gnus-group-entry group))
+    (let* ((unread (gnus-group-unread group))
            (active (gnus-active group))
-           (info (nth 1 entry))
+           (info (gnus-get-info group))
            (method (inline (gnus-server-get-method
 			    group (gnus-info-method info))))
            (marked (gnus-info-marks info))
 	   (env
 	    (list
-	     (cons 'unread (if (numberp (car entry)) (car entry) 0))
+	     (cons 'unread (if (numberp unread) unread 0))
 	     (cons 'total (if active (1+ (- (cdr active) (car active))) 0))
 	     (cons 'mailp (apply
 			   'append
@@ -1676,7 +1676,7 @@ gnus-group-update-group
 			 (not (gnus-ephemeral-group-p group)))
 		(gnus-dribble-enter
 		 (concat "(gnus-group-set-info '"
-			 (gnus-prin1-to-string (nth 1 entry))
+			 (gnus-prin1-to-string (gnus-get-info group))
 			 ")")
 		 (concat "^(gnus-group-set-info '(\""
 			 (regexp-quote group) "\"")))))
@@ -2752,7 +2752,9 @@ gnus-group-make-group
       (error "Group %s already exists" nname))
     ;; Subscribe to the new group.
     (gnus-group-change-level
-     (setq info (list t nname gnus-level-default-subscribed nil nil meth))
+     (setq info (list t (make-gnus-info
+			 nname
+			 gnus-level-default-subscribed nil nil meth)))
      gnus-level-default-subscribed gnus-level-killed
      (gnus-group-group-name) t)
     ;; Make it active.
@@ -2958,9 +2960,9 @@ gnus-group-edit-group-done
 			(if (or (not method)
 				(gnus-server-equal
 				 gnus-select-method method))
-			    (gnus-group-real-name (car info))
+			    (gnus-group-real-name group)
 			  (gnus-group-prefixed-name
-			   (gnus-group-real-name (car info)) method))
+			   (gnus-group-real-name group) method))
 		      nil)))
     (when (and new-group
 	       (not (equal new-group group)))
@@ -2973,11 +2975,11 @@ gnus-group-edit-group-done
       (setq info (copy-tree info))
       (setcar info new-group)
       (unless (gnus-server-equal method "native")
-	(unless (nthcdr 3 info)
+	(unless (gnus-info-marks info)
 	  (nconc info (list nil nil)))
-	(unless (nthcdr 4 info)
+	(unless (gnus-info-method info)
 	  (nconc info (list nil)))
-	(gnus-info-set-method info method))
+	(setf (gnus-info-method info) method))
       (gnus-group-set-info info))
     (gnus-group-update-group (or new-group group))
     (gnus-group-position-point)))
@@ -3531,17 +3533,17 @@ gnus-info-clear-data
     (gnus-undo-register
       `(progn
 	 (gnus-request-set-mark ,group ',action)
-	 (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
-	 (gnus-info-set-read ',info ',(gnus-info-read info))
+	 (setf (gnus-info-marks ',info) ',(gnus-info-marks info) t)
+	 (setf (gnus-info-read ',info) ',(gnus-info-read info))
 	 (when (gnus-group-jump-to-group ,group)
 	   (gnus-get-unread-articles-in-group ',info ',(gnus-active group) t)
 	   (gnus-group-update-group-line))))
     (setq action (mapcar (lambda (el) (list (nth 0 el) 'del (nth 2 el)))
 			 action))
     (gnus-request-set-mark group action)
-    (gnus-info-set-read info nil)
+    (setf (gnus-info-read info) nil)
     (when (gnus-info-marks info)
-      (gnus-info-set-marks info nil))))
+      (setf (gnus-info-marks info) nil))))
 
 ;; Group catching up.
 
@@ -3903,7 +3905,7 @@ gnus-group-yank-group
     (while (>= (cl-decf arg) 0)
       (when (not (setq info (pop gnus-list-of-killed-groups)))
 	(error "No more newsgroups to yank"))
-      (push (setq group (nth 1 info)) out)
+      (push (setq group (gnus-info-group info)) out)
       ;; Find which newsgroup to insert this one before - search
       ;; backward until something suitable is found.  If there are no
       ;; other newsgroups in this buffer, just make this newsgroup the
@@ -4413,6 +4415,7 @@ gnus-group-browse-foreign-server
   (gnus-browse-foreign-server method))
 
 (defun gnus-group-set-info (info &optional method-only-group part)
+  "Update status for group represented by INFO."
   (when (or info part)
     (let* ((entry (gnus-group-entry
 		   (or method-only-group (gnus-info-group info))))
@@ -4459,20 +4462,12 @@ gnus-group-set-info
       ;; can do the update.
       (if entry
 	  (progn
-	    (setcar (nthcdr 1 entry) info)
+	    (gnus-set-info (nthcdr 1 entry) info)
 	    (when (and (not (eq (car entry) t))
 		       (gnus-active (gnus-info-group info)))
-	      (setcar entry (length
-			     (gnus-list-of-unread-articles (car info)))))
-	    ;; The above `setcar' will only affect the hashtable, not
-	    ;; the alist: update the alist separately, but only if
-	    ;; it's been initialized.
-	    (when gnus-newsrc-alist
-	      (push info (cdr (setq gnus-newsrc-alist
-				    (remove (assoc-string
-					     (gnus-info-group info)
-					     gnus-newsrc-alist)
-					    gnus-newsrc-alist))))))
+	      (setf (gnus-group-unread (gnus-info-group info))
+		    (length
+		     (gnus-list-of-unread-articles (car info))))))
 	(error "No such group: %s" (gnus-info-group info))))))
 
 ;; Ad-hoc function for inserting data from a different newsrc.eld
@@ -4497,20 +4492,23 @@ gnus-add-marked-articles
   (let ((info (or info (gnus-get-info group)))
 	marked m)
     (or (not info)
-	(and (not (setq marked (nthcdr 3 info)))
+	(and (not (setq marked (gnus-info-marks info)))
 	     (or (null articles)
-		 (setcdr (nthcdr 2 info)
-			 (list (list (cons type (gnus-compress-sequence
-						 articles t)))))))
-	(and (not (setq m (assq type (car marked))))
+		 ;; Group had no marks at all, set unconditionally.
+		 (setf (gnus-info-marks info)
+		       (list (cons type (gnus-compress-sequence
+					 articles t))))))
+	(and (not (setq m (assq type marked)))
 	     (or (null articles)
-		 (setcar marked
-			 (cons (cons type (gnus-compress-sequence articles t) )
-			       (car marked)))))
+		 ;; Group had no marks of TYPE.
+		 (setf (gnus-info-marks info)
+		       (cons (cons type (gnus-compress-sequence articles t))
+			     marked))))
 	(if force
+	    ;; Replace (or remove) all marks of TYPE.
 	    (if (null articles)
-		(setcar (nthcdr 3 info)
-			(assq-delete-all type (car marked)))
+		(setf (gnus-info-marks info)
+		      (assq-delete-all type marked))
 	      (setcdr m (gnus-compress-sequence articles t)))
 	  (setcdr m (gnus-compress-sequence
 		     (sort (nconc (gnus-uncompress-range (cdr m))
diff --git a/lisp/gnus/gnus-int.el b/lisp/gnus/gnus-int.el
index 0abbfe6720..a21bc25840 100644
--- a/lisp/gnus/gnus-int.el
+++ b/lisp/gnus/gnus-int.el
@@ -727,7 +727,7 @@ gnus-request-marks
 	       (let* ((range (if (= min 2) 1 (cons 1 (1- min))))
 		      (read (gnus-info-read info))
 		      (new-read (gnus-range-add read (list range))))
-		 (gnus-info-set-read info new-read)))
+		 (setf (gnus-info-read info) new-read)))
 	     info)))))
 
 (defun gnus-request-expire-articles (articles group &optional force)
diff --git a/lisp/gnus/gnus-sieve.el b/lisp/gnus/gnus-sieve.el
index fc0bf3098b..5edd6f5f7a 100644
--- a/lisp/gnus/gnus-sieve.el
+++ b/lisp/gnus/gnus-sieve.el
@@ -128,7 +128,7 @@ gnus-sieve-article-add-rule
 	  (info (gnus-get-info gnus-newsgroup-name)))
       (if (null rule)
 	  (error "Could not guess rule for article")
-	(gnus-info-set-params info (cons rule (gnus-info-params info)))
+	(push rule (gnus-info-params info))
 	(message "Added rule in group %s for article: %s" gnus-newsgroup-name
 		 rule)))))
 
diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el
index e142c438ee..70e63188c9 100644
--- a/lisp/gnus/gnus-start.el
+++ b/lisp/gnus/gnus-start.el
@@ -1295,7 +1295,7 @@ gnus-group-change-level
        (t
 	(when (and (>= level gnus-level-zombie)
 		   entry)
-	  (remhash (car (nth 1 entry)) gnus-newsrc-hashtb)
+	  (remhash group gnus-newsrc-hashtb)
 	  (setq gnus-group-list (remove group gnus-group-list))
 	  (setq gnus-newsrc-alist (delq (assoc group gnus-newsrc-alist)
 					gnus-newsrc-alist)))))
@@ -1323,7 +1323,7 @@ gnus-group-change-level
 	    ;; It was alive, and it is going to stay alive, so we
 	    ;; just change the level and don't change any pointers or
 	    ;; hash table entries.
-	    (setcar (cdadr entry) level)
+	    (setf (gnus-info-level (nth 1 entry)) level)
 	  (if (listp entry)
 	      (setq info (cdr entry)
 		    num (car entry))
@@ -1577,7 +1577,7 @@ gnus-get-unread-articles-in-group
       ;; Set the number of unread articles.
       (when (and info
 		 (gnus-group-entry (gnus-info-group info)))
-	(setcar (gnus-group-entry (gnus-info-group info)) num))
+	(setf (gnus-group-unread (gnus-info-group info)) num))
       num)))
 
 ;; Go though `gnus-newsrc-alist' and compare with `gnus-active-hashtb'
@@ -1603,7 +1603,7 @@ gnus-get-unread-articles
 	 (gnus-agent-article-local-times 0)
 	 (archive-method (gnus-server-to-method "archive"))
 	 infos info group active method cmethod
-	 method-type method-group-list entry)
+	 method-type method-group-list)
     (gnus-message 6 "Checking new news...")
 
     (while newsrc
@@ -1650,8 +1650,8 @@ gnus-get-unread-articles
 	;; It leads `(gnus-group-unread group)' to return t.  See also
 	;; `gnus-group-prepare-flat'.
 	(unless active
-	  (when (setq entry (gnus-group-entry group))
-	    (setcar entry t)))))
+	  (when (gnus-group-entry group)
+	    (setf (gnus-group-unread group) t)))))
 
     ;; Sort the methods based so that the primary and secondary
     ;; methods come first.  This is done for legacy reasons to try to
@@ -1824,17 +1824,17 @@ gnus-make-hashtable-from-newsrc-alist
 		    gnus-newsrc-alist
 		  (cons (list "dummy.group" 0 nil) alist))))
     (while alist
-      (setq info (car alist))
+      (setq info (apply #'make-gnus-info (car alist)))
       ;; Make the same select-methods identical Lisp objects.
       (when (setq method (gnus-info-method info))
 	(if (setq rest (member method methods))
-	    (gnus-info-set-method info (car rest))
+	    (setf (gnus-info-method info) (car rest))
 	  (push method methods)))
       ;; Check for encoded group names and decode them.
-      (when (string-match-p "[^[:ascii:]]" (setq gname (car info)))
+      (when (string-match-p "[^[:ascii:]]" (setq gname (gnus-info-group info)))
 	(let ((decoded (gnus-group-decoded-name gname)))
 	 (setf gname decoded
-	       (car info) decoded)))
+	       (gnus-info-group info) decoded)))
       ;; Check for duplicates.
       (if (gethash gname gnus-newsrc-hashtb)
 	  ;; Remove this entry from the alist.
@@ -1879,9 +1879,9 @@ gnus-parse-active
 
 (defun gnus-make-articles-unread (group articles)
   "Mark ARTICLES in GROUP as unread."
-  (let* ((info (nth 1 (or (gnus-group-entry group)
-			  (gnus-group-entry
-			   (gnus-group-real-name group)))))
+  (let* ((info (or (gnus-get-info group)
+		   (gnus-get-info
+		    (gnus-group-real-name group))))
 	 (ranges (gnus-info-read info))
 	 news article)
     (while articles
@@ -1890,8 +1890,8 @@ gnus-make-articles-unread
 	(push article news)))
     (when news
       ;; Enter this list into the group info.
-      (gnus-info-set-read
-       info (gnus-remove-from-range (gnus-info-read info) (nreverse news)))
+      (setf (gnus-info-read info)
+	    (gnus-remove-from-range (gnus-info-read info) (nreverse news)))
 
       ;; Set the number of unread articles in gnus-newsrc-hashtb.
       (gnus-get-unread-articles-in-group info (gnus-active group))
@@ -1901,9 +1901,8 @@ gnus-make-articles-unread
 
 (defun gnus-make-ascending-articles-unread (group articles)
   "Mark ascending ARTICLES in GROUP as unread."
-  (let* ((entry (or (gnus-group-entry group)
-                    (gnus-group-entry (gnus-group-real-name group))))
-         (info (nth 1 entry))
+  (let* ((info (or (gnus-get-info group)
+                   (gnus-get-info (gnus-group-real-name group))))
 	 (ranges (gnus-info-read info))
          (r ranges)
 	 modified)
@@ -1911,7 +1910,7 @@ gnus-make-ascending-articles-unread
     (while articles
       (let ((article (pop articles))) ; get the next article to remove from ranges
         (while (let ((range (car ranges))) ; note the current range
-                 (if (atom range)       ; single value range
+                 (if (atom range)	   ; single value range
                      (cond ((not range)
                             ;; the articles extend past the end of the ranges
                             ;; OK - I'm done
@@ -1958,7 +1957,7 @@ gnus-make-ascending-articles-unread
       (when (eq modified 'remove-null)
         (setq r (delq nil r)))
       ;; Enter this list into the group info.
-      (gnus-info-set-read info r)
+      (setf (gnus-info-read info) r)
 
       ;; Set the number of unread articles in gnus-newsrc-hashtb.
       (gnus-get-unread-articles-in-group info (gnus-active group))
@@ -2362,12 +2361,11 @@ gnus-convert-old-ticks
 	(setq dormant (cdr (assq 'dormant marks))
 	      ticked (cdr (assq 'tick marks)))
 	(when (or dormant ticked)
-	  (gnus-info-set-read
-	   info
-	   (gnus-add-to-range
-	    (gnus-info-read info)
-	    (nconc (gnus-uncompress-range dormant)
-		   (gnus-uncompress-range ticked)))))))))
+	  (setf (gnus-info-read info)
+		(gnus-add-to-range
+		 (gnus-info-read info)
+		 (nconc (gnus-uncompress-range dormant)
+			(gnus-uncompress-range ticked)))))))))
 
 (defun gnus-load (file)
   "Load FILE, but in such a way that read errors can be reported."
@@ -2438,9 +2436,9 @@ gnus-read-old-newsrc-el-file
       (while (setq group (pop newsrc))
 	(if (setq info (gnus-get-info (car group)))
 	    (progn
-	      (gnus-info-set-read info (cddr group))
-	      (gnus-info-set-level
-	       info (if (nth 1 group) gnus-level-default-subscribed
+	      (setf (gnus-info-read info) (cddr group))
+	      (setf (gnus-info-level info)
+		    (if (nth 1 group) gnus-level-default-subscribed
 		      gnus-level-default-unsubscribed))
 	      (push info gnus-newsrc-alist))
 	  (push (setq info
@@ -2451,10 +2449,10 @@ gnus-read-old-newsrc-el-file
 		gnus-newsrc-alist))
 	;; Copy marks into info.
 	(when (setq m (assoc (car group) marked))
-	  (unless (nthcdr 3 info)
+	  (unless (gnus-info-marks info)
 	    (nconc info (list nil)))
-	  (gnus-info-set-marks
-	   info (list (cons 'tick (gnus-compress-sequence
+	  (setf (gnus-info-marks info)
+		(list (cons 'tick (gnus-compress-sequence
 				   (sort (cdr m) '<) t))))))
       (setq newsrc killed)
       (while newsrc
@@ -2609,7 +2607,7 @@ gnus-newsrc-to-gnus-format
 		;; There is an entry for this file in
 		;; `gnus-newsrc-hashtb'.
 		(progn
-		  (gnus-info-set-read info (nreverse reads))
+		  (setf (gnus-info-read info) (nreverse reads))
 		  ;; We update the level very gently.  In fact, we
 		  ;; only change it if there's been a status change
 		  ;; from subscribed to unsubscribed, or vice versa.
@@ -2621,7 +2619,7 @@ gnus-newsrc-to-gnus-format
 				       (1+ gnus-level-default-unsubscribed))))
 			((and (> level gnus-level-subscribed) subscribed)
 			 (setq level gnus-level-default-subscribed)))
-		  (gnus-info-set-level info level))
+		  (setf (gnus-info-level info) level))
 	      ;; This is a new group.
 	      (setq info (list group
 			       (if subscribed
@@ -2645,7 +2643,7 @@ gnus-newsrc-to-gnus-format
 	    (prev gnus-newsrc-alist)
 	    entry mentry)
 	(while rc
-	  (or (null (nth 4 (car rc)))	; It's a native group.
+	  (or (null (gnus-info-method (car rc))) ; It's a native group.
 	      (assoc (caar rc) newsrc)	; It's already in the alist.
 	      (if (setq entry (assoc (caar prev) newsrc))
 		  (setcdr (setq mentry (memq entry newsrc))
@@ -2903,7 +2901,14 @@ gnus-gnus-to-quick-newsrc-format
 	   ;; `gnus-read-newsrc-el-file' into a conversion routine.
 	   (gnus-newsrc-alist
 	    (mapcar (lambda (info)
-		      (cons (encode-coding-string (car info) 'utf-8-emacs)
+		      (let ((i 5))
+			;; Don't write unnecessary nils to disk.
+			(while (and (> i 2)
+				    (not (nth i info)))
+			  (when (nthcdr (cl-decf i) info)
+			    (setcdr (nthcdr i info) nil))))
+		      (cons (encode-coding-string
+			     (gnus-info-group info) 'utf-8-emacs)
 			    (cdr info)))
 		    gnus-newsrc-alist))
 	   (gnus-topic-alist
@@ -2952,7 +2957,7 @@ gnus-gnus-to-newsrc-format
 	(insert gnus-newsrc-options))
       ;; Write subscribed and unsubscribed.
       (dolist (g-name groups)
-	(setq info (nth 1 (gnus-group-entry g-name)))
+	(setq info (gnus-get-info g-name))
 	;; Maybe don't write foreign groups to .newsrc.
 	(when (or (null (setq method (gnus-info-method info)))
 		  (equal method "native")
diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el
index f21bc7584e..75bc9acb66 100644
--- a/lisp/gnus/gnus-sum.el
+++ b/lisp/gnus/gnus-sum.el
@@ -6172,18 +6172,7 @@ gnus-update-marks
 	(gnus-request-set-mark gnus-newsgroup-name delta-marks))
 
       ;; Enter these new marks into the info of the group.
-      (if (nthcdr 3 info)
-	  (setcar (nthcdr 3 info) newmarked)
-	;; Add the marks lists to the end of the info.
-	(when newmarked
-	  (setcdr (nthcdr 2 info) (list newmarked))))
-
-      ;; Cut off the end of the info if there's nothing else there.
-      (let ((i 5))
-	(while (and (> i 2)
-		    (not (nth i info)))
-	  (when (nthcdr (cl-decf i) info)
-	    (setcdr (nthcdr i info) nil)))))))
+      (setf (gnus-info-marks info) newmarked))))
 
 (defun gnus-set-mode-line (where)
   "Set the mode line of the article or summary buffers.
@@ -6278,7 +6267,7 @@ gnus-create-xref-hashtb
 (defun gnus-mark-xrefs-as-read (from-newsgroup headers unreads)
   "Look through all the headers and mark the Xrefs as read."
   (let ((virtual (gnus-virtual-group-p from-newsgroup))
-	info xref-hashtb method nth4)
+	info xref-hashtb method tmp-method)
     (with-current-buffer gnus-group-buffer
       (when (setq xref-hashtb
 		  (gnus-create-xref-hashtb from-newsgroup headers unreads))
@@ -6288,17 +6277,18 @@ gnus-mark-xrefs-as-read
 	     ;; Dead groups are not updated.
 	     (and (prog1
 		      (setq info (gnus-get-info group))
-		    (when (stringp (setq nth4 (gnus-info-method info)))
-		      (setq nth4 (gnus-server-to-method nth4))))
+		    (when (stringp (setq tmp-method (gnus-info-method info)))
+		      (setq tmp-method (gnus-server-to-method tmp-method))))
 		  ;; Only do the xrefs if the group has the same
 		  ;; select method as the group we have just read.
 		  (or (gnus-methods-equal-p
-		       nth4 (gnus-find-method-for-group from-newsgroup))
+		       tmp-method (gnus-find-method-for-group from-newsgroup))
 		      virtual
-		      (equal nth4 (setq method (gnus-find-method-for-group
-						from-newsgroup)))
-		      (and (equal (car nth4) (car method))
-			   (equal (nth 1 nth4) (nth 1 method))))
+		      (equal tmp-method
+			     (setq method (gnus-find-method-for-group
+					   from-newsgroup)))
+		      (and (equal (car tmp-method) (car method))
+			   (equal (nth 1 tmp-method) (nth 1 method))))
 		  gnus-use-cross-reference
 		  (or (not (eq gnus-use-cross-reference t))
 		      virtual
@@ -6310,7 +6300,7 @@ gnus-mark-xrefs-as-read
 
 (defun gnus-compute-read-articles (group articles)
   (let* ((entry (gnus-group-entry group))
-	 (info (nth 1 entry))
+	 (info (gnus-get-info entry))
 	 (active (gnus-active group))
 	 ninfo)
     (when entry
@@ -6347,7 +6337,7 @@ gnus-group-make-articles-read
   "Update the info of GROUP to say that ARTICLES are read."
   (let* ((num 0)
 	 (entry (gnus-group-entry group))
-	 (info (nth 1 entry))
+	 (info (gnus-get-info entry))
 	 (active (gnus-active group))
 	 (set-marks
 	  (gnus-method-option-p
@@ -6367,7 +6357,7 @@ gnus-group-make-articles-read
 	(gnus-undo-register
 	  `(progn
 	     (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
-	     (gnus-info-set-read ',info ',(gnus-info-read info))
+	     (setf (gnus-info-read ',info) ',(gnus-info-read info))
 	     (gnus-get-unread-articles-in-group ',info (gnus-active ,group))
 	     (when ,set-marks
 	       (gnus-request-set-mark
@@ -6375,7 +6365,7 @@ gnus-group-make-articles-read
 	     (gnus-group-jump-to-group ,group)
 	     (gnus-group-update-group ,group t))))
       ;; Add the read articles to the range.
-      (gnus-info-set-read info range)
+      (setf (gnus-info-read info) range)
       (when set-marks
 	(gnus-request-set-mark group (list (list range 'add '(read)))))
       ;; Then we have to re-compute how many unread
@@ -6395,7 +6385,7 @@ gnus-group-make-articles-read
 	    (setq range (cdr range)))
 	  (setq num (- (cdr active) num))))
 	;; Update the number of unread articles.
-	(setcar entry num)
+	(setf (gnus-group-unread group) num)
 	;; Update the group buffer.
 	(unless (gnus-ephemeral-group-p group)
 	  (gnus-group-update-group group t))))))
@@ -10283,8 +10273,8 @@ gnus-summary-move-article
 	    (when (and (not (memq article gnus-newsgroup-unreads))
 		       (cdr art-group))
 	      (push 'read to-marks)
-	      (gnus-info-set-read
-	       info (gnus-add-to-range (gnus-info-read info)
+	      (setf (gnus-info-read info)
+		    (gnus-add-to-range (gnus-info-read info)
 				       (list (cdr art-group)))))
 
 	    ;; See whether the article is to be put in the cache.
@@ -12894,15 +12884,16 @@ gnus-update-read-articles
 	    (set-buffer gnus-group-buffer)
 	    (gnus-undo-register
 	      `(progn
+		 ;;FIXME: A better way of ensuring info completness?
 		 (gnus-info-set-marks ',info ',(gnus-info-marks info) t)
-		 (gnus-info-set-read ',info ',(gnus-info-read info))
+		 (setf (gnus-info-read ',info) ',(gnus-info-read info))
 		 (gnus-group-jump-to-group ,group)
 		 (gnus-get-unread-articles-in-group ',info
 						    (gnus-active ,group))
 		 (gnus-group-update-group ,group t)
 		 ,setmarkundo))))
 	;; Enter this list into the group info.
-	(gnus-info-set-read info read)
+	(setf (gnus-info-read info) read)
 	;; Set the number of unread articles in gnus-newsrc-hashtb.
 	(gnus-get-unread-articles-in-group info (gnus-active group))
 	t))))
diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el
index 0673ac15f6..392a4e8812 100644
--- a/lisp/gnus/gnus.el
+++ b/lisp/gnus/gnus.el
@@ -2815,20 +2815,37 @@ gnus-set-active
   "Set GROUP's active info."
   `(puthash ,group ,active gnus-active-hashtb))
 
-;; Info access macros.
-
-(defmacro gnus-info-group (info)
-  `(nth 0 ,info))
-(defmacro gnus-info-rank (info)
-  `(nth 1 ,info))
-(defmacro gnus-info-read (info)
-  `(nth 2 ,info))
-(defmacro gnus-info-marks (info)
-  `(nth 3 ,info))
-(defmacro gnus-info-method (info)
-  `(nth 4 ,info))
-(defmacro gnus-info-params (info)
-  `(nth 5 ,info))
+;; Info definition.
+
+(cl-defstruct (gnus-info
+	       (:copier nil)
+	       (:constructor make-gnus-info
+			     (group rank read &optional marks method params))
+	       (:type list))
+  "A structure holding information about a Gnus group."
+  (group ""
+	 :type string
+	 :documentation "The group name of this info.")
+  (rank `,gnus-level-default-subscribed
+	:type (or integer cons)
+	:documentation "The rank of this group.  Either a single
+        integer indicating the level of subscribedness, or a cons
+        of (LEVEL . SCORE).")
+  (read nil
+	:type list
+	:documentation "A range of read articles in this group.")
+  (marks nil
+	 :type list
+	 :documentation "An alist of marks (as symbols) mapped to
+	 ranges of articles that have those marks.")
+  (method nil
+	  :type list
+	  :documentation "This group's server method.  Typically
+	  a list of (TYPE ADDRESS), where TYPE is a backend
+	  symbol and ADDRESS is a string adress.")
+  (params nil
+	  :type list
+	  :documentation "An alist of group parameters."))
 
 (defmacro gnus-info-level (info)
   `(let ((rank (gnus-info-rank ,info)))
@@ -2839,49 +2856,55 @@ gnus-info-score
   `(let ((rank (gnus-info-rank ,info)))
      (or (and (consp rank) (cdr rank)) 0)))
 
+;; These setters are mostly here for backwards compatibility.
+
 (defmacro gnus-info-set-group (info group)
-  `(setcar ,info ,group))
+  `(setf (gnus-info-group ,info) ,group))
+
 (defmacro gnus-info-set-rank (info rank)
-  `(setcar (nthcdr 1 ,info) ,rank))
+  `(setf (gnus-info-rank ,info) ,rank))
+
 (defmacro gnus-info-set-read (info read)
-  `(setcar (nthcdr 2 ,info) ,read))
-(defmacro gnus-info-set-marks (info marks &optional extend)
-  (if extend
-      `(gnus-info-set-entry ,info ,marks 3)
-    `(setcar (nthcdr 3 ,info) ,marks)))
-(defmacro gnus-info-set-method (info method &optional extend)
-  (if extend
-      `(gnus-info-set-entry ,info ,method 4)
-    `(setcar (nthcdr 4 ,info) ,method)))
-(defmacro gnus-info-set-params (info params &optional extend)
-  (if extend
-      `(gnus-info-set-entry ,info ,params 5)
-    `(setcar (nthcdr 5 ,info) ,params)))
-
-(defun gnus-info-set-entry (info entry number)
-  ;; Extend the info until we have enough elements.
-  (while (<= (length info) number)
-    (nconc info (list nil)))
-  ;; Set the entry.
-  (setcar (nthcdr number info) entry))
+  `(setf (gnus-info-read ,info) ,read))
+
+(defmacro gnus-info-set-marks (info marks &optional _extend)
+  `(setf (gnus-info-marks ,info) ,marks))
+
+(defmacro gnus-info-set-method (info method &optional _extend)
+  `(setf (gnus-info-method ,info) ,method))
+
+(defmacro gnus-info-set-params (info params &optional _extend)
+  `(setf (gnus-info-params ,info) ,params))
 
 (defmacro gnus-info-set-level (info level)
-  `(let ((rank (cdr ,info)))
-     (if (consp (car rank))
-	 (setcar (car rank) ,level)
-       (setcar rank ,level))))
+  `(let ((rank (gnus-info-rank ,info)))
+     (if (consp rank)
+	 (setcar (gnus-info-rank ,info) ,level)
+       (setf (gnus-info-rank ,info) ,level))))
+
 (defmacro gnus-info-set-score (info score)
-  `(let ((rank (cdr ,info)))
-     (if (consp (car rank))
-	 (setcdr (car rank) ,score)
-       (setcar rank (cons (car rank) ,score)))))
+  `(let ((rank (gnus-info-rank ,info)))
+     (if (consp rank)
+	 (setcdr (gnus-info-rank ,info) ,score)
+       (setf (gnus-info-rank ,info) (cons rank ,score)))))
 
 (defmacro gnus-get-info (group)
   `(nth 1 (gethash ,group gnus-newsrc-hashtb)))
 
 (defun gnus-set-info (group info)
-  (setcdr (gethash group gnus-newsrc-hashtb)
-	  (list info)))
+  (let ((entry (cdr (gethash group gnus-newsrc-hashtb))))
+    ;; Set individual elements of the info so as not to break object
+    ;; reference.
+    (setf (gnus-info-rank entry)
+	  (gnus-info-rank info))
+    (setf (gnus-info-read entry)
+	  (gnus-info-read info))
+    (setf (gnus-info-marks entry)
+	  (gnus-info-marks info))
+    (setf (gnus-info-method entry)
+	  (gnus-info-method info))
+    (setf (gnus-info-params entry)
+	  (gnus-info-params info))))
 
 
 ;;;
@@ -3689,7 +3712,7 @@ gnus-group-set-parameter
 	    (setq new-params (append new-params (list (car old-params)))))
 	  (setq old-params (cdr old-params)))
 	(if (listp group)
-	    (gnus-info-set-params info new-params t)
+	    (setf (gnus-info-params info) new-params)
 	  (gnus-group-set-info new-params (gnus-info-group info) 'params))))))
 
 (defun gnus-group-remove-parameter (group name)
@@ -3704,14 +3727,15 @@ gnus-group-remove-parameter
 	  (setq params (delq name params))
 	  (while (assq name params)
 	    (gnus-alist-pull name params))
-	  (gnus-info-set-params info params))))))
+	  (setf (gnus-info-params info) params))))))
 
 (defun gnus-group-add-score (group &optional score)
   "Add SCORE to the GROUP score.
 If SCORE is nil, add 1 to the score of GROUP."
   (let ((info (gnus-get-info group)))
     (when info
-      (gnus-info-set-score info (+ (gnus-info-score info) (or score 1))))))
+      (setf (gnus-info-score info)
+	    (+ (gnus-info-score info) (or score 1))))))
 
 (defun gnus-short-group-name (group &optional levels)
   "Collapse GROUP name LEVELS.
diff --git a/lisp/gnus/nndiary.el b/lisp/gnus/nndiary.el
index f79d8f1707..88bb5651e2 100644
--- a/lisp/gnus/nndiary.el
+++ b/lisp/gnus/nndiary.el
@@ -795,8 +795,9 @@ nndiary-request-update-info
 	  (kill-buffer buf))
 	(setq unread (sort unread '<))
 	(and unread
-	     (gnus-info-set-read info (gnus-update-read-articles
-				       (gnus-info-group info) unread t)))
+	     (setf (gnus-info-read info)
+		   (gnus-update-read-articles
+		    (gnus-info-group info) unread t)))
 	))
     (run-hook-with-args 'nndiary-request-update-info-functions
 			(gnus-info-group info))
diff --git a/lisp/gnus/nndraft.el b/lisp/gnus/nndraft.el
index bc475ee295..cc9923164a 100644
--- a/lisp/gnus/nndraft.el
+++ b/lisp/gnus/nndraft.el
@@ -147,17 +147,17 @@ nndraft-request-restore-buffer
 
 (deffoo nndraft-request-update-info (group info &optional server)
   (nndraft-possibly-change-group group)
-  (gnus-info-set-read
-   info
-   (gnus-update-read-articles (gnus-group-prefixed-name group '(nndraft ""))
-			      (nndraft-articles) t))
-  (let ((marks (nth 3 info)))
+  (setf (gnus-info-read info)
+	(gnus-update-read-articles
+	 (gnus-group-prefixed-name group '(nndraft ""))
+	 (nndraft-articles) t))
+  (let ((marks (gnus-info-marks info)))
     (when marks
       ;; Nix out all marks except the `unsend'-able article marks.
-      (setcar (nthcdr 3 info)
-	      (if (assq 'unsend marks)
-		  (list (assq 'unsend marks))
-		nil))))
+      (setf (gnus-info-marks info)
+	    (if (assq 'unsend marks)
+		(list (assq 'unsend marks))
+	      nil))))
   t)
 
 (defun nndraft-generate-headers ()
diff --git a/lisp/gnus/nnimap.el b/lisp/gnus/nnimap.el
index 856ac75cd6..98be27fc74 100644
--- a/lisp/gnus/nnimap.el
+++ b/lisp/gnus/nnimap.el
@@ -1620,7 +1620,7 @@ nnimap-update-info
 		       read)))
 	      (when (or (not (listp permanent-flags))
 			(memq '%Seen permanent-flags))
-		(gnus-info-set-read info read))
+		(setf (gnus-info-read info) read))
 	      ;; Update the marks.
 	      (setq marks (gnus-info-marks info))
 	      (dolist (type (cdr nnimap-mark-alist))
@@ -1663,7 +1663,7 @@ nnimap-update-info
 		(if old-unexists
 		    (setcdr old-unexists unexists)
 		  (push (cons 'unexist unexists) marks)))
-	      (gnus-info-set-marks info marks t))))
+	      (setf (gnus-info-marks info) marks))))
 	;; Tell Gnus whether there are any \Recent messages in any of
 	;; the groups.
 	(let ((recent (cdr (assoc '%Recent flags))))
@@ -1680,14 +1680,13 @@ nnimap-update-info
 
 (defun nnimap-update-qresync-info (info existing vanished flags)
   ;; Add all the vanished articles to the list of read articles.
-  (gnus-info-set-read
-   info
-   (gnus-add-to-range
-    (gnus-add-to-range
-     (gnus-range-add (gnus-info-read info)
-		     vanished)
-     (cdr (assq '%Flagged flags)))
-    (cdr (assq '%Seen flags))))
+  (setf (gnus-info-read info)
+	(gnus-add-to-range
+	 (gnus-add-to-range
+	  (gnus-range-add (gnus-info-read info)
+			  vanished)
+	  (cdr (assq '%Flagged flags)))
+	 (cdr (assq '%Seen flags))))
   (let ((marks (gnus-info-marks info)))
     (dolist (type (cdr nnimap-mark-alist))
       (let ((ticks (assoc (car type) marks))
@@ -1707,7 +1706,7 @@ nnimap-update-qresync-info
 			(gnus-sorted-complement existing new-marks))))
 	  (when ticks
 	    (push (cons (car type) ticks) marks))
-	  (gnus-info-set-marks info marks t))))
+	  (setf (gnus-info-marks info) marks))))
     ;; Add vanished to the list of unexisting articles.
     (when vanished
       (let* ((old-unexists (assq 'unexist marks))
@@ -1715,7 +1714,7 @@ nnimap-update-qresync-info
 	(if old-unexists
 	    (setcdr old-unexists unexists)
 	  (push (cons 'unexist unexists) marks)))
-      (gnus-info-set-marks info marks t))))
+      (setf (gnus-info-marks info) marks))))
 
 (defun nnimap-imap-ranges-to-gnus-ranges (irange)
   (if (zerop (length irange))
diff --git a/lisp/gnus/nnir.el b/lisp/gnus/nnir.el
index 7cb2d1615a..45fbadf23c 100644
--- a/lisp/gnus/nnir.el
+++ b/lisp/gnus/nnir.el
@@ -874,8 +874,8 @@ nnir-request-set-mark
 (deffoo nnir-request-update-info (group info &optional server)
   (nnir-possibly-change-group group server)
   ;; clear out all existing marks.
-  (gnus-info-set-marks info nil)
-  (gnus-info-set-read info nil)
+  (setf (gnus-info-marks info) nil)
+  (setf (gnus-info-read info) nil)
   (let ((group (gnus-group-guess-full-name-from-command-method group))
 	(articles-by-group
 	 (nnir-categorize
@@ -889,8 +889,8 @@ nnir-request-update-info
 	     (group-info (gnus-get-info (car group-articles)))
 	     (marks (gnus-info-marks group-info))
 	     (read (gnus-info-read group-info)))
-	(gnus-info-set-read
-	 info
+	(setf
+	 (gnus-info-read info)
 	 (gnus-add-to-range
 	  (gnus-info-read info)
 	  (delq nil
diff --git a/lisp/gnus/nnmaildir.el b/lisp/gnus/nnmaildir.el
index 0133fc6ce2..fdb487d076 100644
--- a/lisp/gnus/nnmaildir.el
+++ b/lisp/gnus/nnmaildir.el
@@ -1000,8 +1000,8 @@ nnmaildir-request-update-info
 	    pgname (nnmaildir--pgname nnmaildir--cur-server gname)
 	    flist (nnmaildir--grp-flist group))
       (when (zerop (nnmaildir--grp-count group))
-	(gnus-info-set-read info nil)
-	(gnus-info-set-marks info nil 'extend)
+	(setf (gnus-info-read info) nil
+	      (gnus-info-marks info) nil)
 	(throw 'return info))
       (setq old-marks (cons 'read (gnus-info-read info))
 	    old-marks (cons old-marks (gnus-info-marks info))
@@ -1083,9 +1083,9 @@ nnmaildir-request-update-info
 	    (setq ranges (gnus-add-to-range ranges (sort article-list '<)))))
 	(if (eq mark 'read) (setq read ranges)
 	  (if ranges (setq marks (cons (cons mark ranges) marks)))))
-      (gnus-info-set-read info (gnus-range-add read missing))
-      (gnus-info-set-marks info marks 'extend)
-      (setf (nnmaildir--grp-mmth group) new-mmth)
+      (setf (gnus-info-read info) (gnus-range-add read missing)
+	    (gnus-info-marks info) marks
+	    (nnmaildir--grp-mmth group) new-mmth)
       info)))
 
 (defun nnmaildir-request-group (gname &optional server fast _info)
diff --git a/lisp/gnus/nnmairix.el b/lisp/gnus/nnmairix.el
index 6c5502ac3d..722cb95306 100644
--- a/lisp/gnus/nnmairix.el
+++ b/lisp/gnus/nnmairix.el
@@ -711,29 +711,27 @@ nnmairix-request-marks
 	    (nnimap-request-update-info-internal folder folderinfo nnmairix-backend-server)
 	  (nnmairix-call-backend "request-update-info" folder folderinfo nnmairix-backend-server))
 	;; set range of read articles
-	(gnus-info-set-read
-	 info
-	 (if docorr
-	     (nnmairix-map-range
-	      `(lambda (x) (+ x ,(cadr corr)))
-	      (gnus-info-read folderinfo))
-	   (gnus-info-read folderinfo)))
+	(setf (gnus-info-read info)
+	      (if docorr
+		  (nnmairix-map-range
+		   `(lambda (x) (+ x ,(cadr corr)))
+		   (gnus-info-read folderinfo))
+		(gnus-info-read folderinfo)))
 	;; set other marks
-	(gnus-info-set-marks
-	 info
-	 (if docorr
-	     (mapcar (lambda (cur)
-			 (cons
-			  (car cur)
-			  (nnmairix-map-range
-			   `(lambda (x) (+ x ,(cadr corr)))
-			   (list (cadr cur)))))
-		     (gnus-info-marks folderinfo))
-	   (gnus-info-marks folderinfo))))
+	(setf (gnus-info-marks info)
+	      (if docorr
+		  (mapcar (lambda (cur)
+			    (cons
+			     (car cur)
+			     (nnmairix-map-range
+			      `(lambda (x) (+ x ,(cadr corr)))
+			      (list (cadr cur)))))
+			  (gnus-info-marks folderinfo))
+		(gnus-info-marks folderinfo))))
       (when (eq readmarks 'unread)
-	(gnus-info-set-read info nil))
+	(setf (gnus-info-read info) nil))
       (when (eq readmarks 'read)
-	(gnus-info-set-read info (gnus-active qualgroup))))
+	(setf (gnus-info-read info) (gnus-active qualgroup))))
   t)
 
 (nnoo-define-skeleton nnmairix)
diff --git a/lisp/gnus/nnml.el b/lisp/gnus/nnml.el
index 302589bd6d..8d8d5ae83a 100644
--- a/lisp/gnus/nnml.el
+++ b/lisp/gnus/nnml.el
@@ -1067,7 +1067,7 @@ nnml-request-compact-group
 		  (when (gnus-member-of-range old-number read)
 		    (setq read (gnus-remove-from-range read (list old-number)))
 		    (setq read (gnus-add-to-range read (list new-number))))
-		  (gnus-info-set-read info read))
+		  (setf (gnus-info-read info) read))
 		;; 2 b/ marked articles:
 		(let ((oldmarks (gnus-info-marks info))
 		      mark newmarks)
@@ -1080,7 +1080,7 @@ nnml-request-compact-group
 		      (setcdr mark (gnus-add-to-range (cdr mark)
 						      (list new-number))))
 		    (push mark newmarks))
-		  (gnus-info-set-marks info newmarks))
+		  (setf (gnus-info-marks info) newmarks))
 		;; 3/ Update the NOV entry for this article:
 		(unless nnml-nov-is-evil
 		  (with-current-buffer (nnml-open-nov group)
diff --git a/lisp/gnus/nnvirtual.el b/lisp/gnus/nnvirtual.el
index 25f3413fcd..0a4d33cfab 100644
--- a/lisp/gnus/nnvirtual.el
+++ b/lisp/gnus/nnvirtual.el
@@ -307,11 +307,9 @@ nnvirtual-request-update-info
     ;; Install the precomputed lists atomically, so the virtual group
     ;; is not left in a half-way state in case of C-g.
     (gnus-atomic-progn
-      (setcar (cddr info) nnvirtual-mapping-reads)
-      (if (nthcdr 3 info)
-	  (setcar (nthcdr 3 info) nnvirtual-mapping-marks)
-	(when nnvirtual-mapping-marks
-	  (setcdr (nthcdr 2 info) (list nnvirtual-mapping-marks))))
+      (setf (gnus-info-read info) nnvirtual-mapping-reads)
+      (when nnvirtual-mapping-marks
+	(setf (gnus-info-marks info) nnvirtual-mapping-marks))
       (setq nnvirtual-info-installed t))
     t))
 
@@ -463,11 +461,10 @@ nnvirtual-update-read-and-marked
 	(dolist (group nnvirtual-component-groups)
 	  (when (and (setq info (gnus-get-info group))
 		     (gnus-info-marks info))
-	    (gnus-info-set-marks
-	     info
-	     (if (assq 'score (gnus-info-marks info))
-		 (list (assq 'score (gnus-info-marks info)))
-	       nil))))
+	    (setf (gnus-info-marks info)
+		  (if (assq 'score (gnus-info-marks info))
+		      (list (assq 'score (gnus-info-marks info)))
+		    nil))))
 
 	;; Ok, currently type-marks is an assq list with keys of a mark type,
 	;; with data of an assq list with keys of component group names
-- 
2.23.0