aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorF. Jason Park2025-10-26 06:51:59 -0700
committerF. Jason Park2025-10-27 19:29:44 -0700
commit44cdb65cf3d98f9d6706419a5cf866ff2df79019 (patch)
treec64eb6bbfcae08be4e7bc3c33658643099a39c40
parent2fa768dd4b37319a45761e7c0f02347f32fe8cc6 (diff)
downloademacs-44cdb65cf3d98f9d6706419a5cf866ff2df79019.tar.gz
emacs-44cdb65cf3d98f9d6706419a5cf866ff2df79019.zip
Fix regression involving erc-channel-user accessors
* etc/ERC-NEWS: New section for ERC 5.6.2 and new entry mentioning slight change in `erc-channel-user' accessor behavior. * lisp/erc/erc-common.el (erc-channel-user): Change type for status slot to natnum from integer. * lisp/erc/erc.el (erc-channel-user-signal-if-status-unknown): New variable. (erc--define-channel-user-status-compat-getter): Only use fallback in Non-ERC buffers because "obviously" a status flag is unusable if the server doesn't advertise it or, rather, advertises nonsupport via its absence. This regression was introduced in ERC 5.6. (erc-channel-begin-receiving-names): Sharp-quote function name. (erc--get-prefix-flag): Mention in doc that a return value of nil can also mean the status flag is not supported by the server. * test/lisp/erc/erc-tests.el (erc--parsed-prefix): Show that it returns nil in a non-ERC buffer. (erc-tests--make-combinations) (erc-tests--with-channel-user-status-accessors): New functions. (erc-channel-user/status-accessors/solo/default) (erc-channel-user/status-accessors/solo/ov) (erc-channel-user/status-accessors/multi/default) (erc-channel-user/status-accessors/multi/ov): New tests. (Bug#67220)
-rw-r--r--etc/ERC-NEWS14
-rw-r--r--lisp/erc/erc-common.el2
-rw-r--r--lisp/erc/erc.el46
-rw-r--r--test/lisp/erc/erc-tests.el158
4 files changed, 203 insertions, 17 deletions
diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 513ed8f706d..932b7a58aa7 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -12,6 +12,20 @@ extensible IRC (Internet Relay Chat) client distributed with
12GNU Emacs since Emacs version 22.1. 12GNU Emacs since Emacs version 22.1.
13 13
14 14
15* Changes in ERC 5.6.2
16
17** Changes in the library API.
18
19*** Accessors like 'erc-channel-user-voice' may ignore assignments.
20ERC now silently ignores attempts to enable certain status flags on
21'erc-channel-user' objects if the connection's "PREFIX" parameter omits
22them. In the future, ERC will likely signal an error if such an attempt
23is made. Users can preview this potentially disruptive behavior by
24setting the new variable 'erc-channel-user-signal-if-status-unknown' to
25t. This change stems from a bug fix for a regression affecting ERC 5.6
26and 5.6.1 in which these accessors mishandled unsupported flags.
27
28
15* Changes in ERC 5.6.1 29* Changes in ERC 5.6.1
16 30
17** Option 'erc-truncate-padding-size' controls truncation frequency. 31** Option 'erc-truncate-padding-size' controls truncation frequency.
diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el
index d293e6ba878..e383e92c7ff 100644
--- a/lisp/erc/erc-common.el
+++ b/lisp/erc/erc-common.el
@@ -100,7 +100,7 @@ ERC only refolds `string', never `substxt'."))
100 :named) 100 :named)
101 "Object containing channel-specific data for a single user." 101 "Object containing channel-specific data for a single user."
102 ;; voice halfop op admin owner 102 ;; voice halfop op admin owner
103 (status 0 :type integer) 103 (status 0 :type natnum)
104 ;; Last message time (in the form of the return value of 104 ;; Last message time (in the form of the return value of
105 ;; (current-time) 105 ;; (current-time)
106 ;; 106 ;;
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index e26ee8728a3..271c5d5fcf8 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -684,6 +684,11 @@ Also remove members from the server table if this was their only buffer."
684 (funcall original-function nick user)))))) 684 (funcall original-function nick user))))))
685 (erc-remove-channel-users))) 685 (erc-remove-channel-users)))
686 686
687(defvar erc-channel-user-signal-if-status-unknown nil
688 "If non-nil ERC signals before setting an unadvertized status prefix.
689But only in ERC buffers. Otherwise, if nil, accessors like
690`erc-channel-user-halfop' ignore such attempts and return nil.")
691
687(defmacro erc--define-channel-user-status-compat-getter (name c d) 692(defmacro erc--define-channel-user-status-compat-getter (name c d)
688 "Define accessor with gv getter for historical `erc-channel-user' slot NAME. 693 "Define accessor with gv getter for historical `erc-channel-user' slot NAME.
689Expect NAME to be a string, C to be its traditionally associated letter, 694Expect NAME to be a string, C to be its traditionally associated letter,
@@ -691,19 +696,30 @@ and D to be its fallback power-of-2 integer for non-ERC buffers. Unlike
691pre-ERC-5.6 accessors, do not bother generating a compiler macro for 696pre-ERC-5.6 accessors, do not bother generating a compiler macro for
692inlining calls to these adapters." 697inlining calls to these adapters."
693 `(defun ,(intern (concat "erc-channel-user-" name)) (u) 698 `(defun ,(intern (concat "erc-channel-user-" name)) (u)
694 ,(format "Get equivalent of pre-5.6 `%s' slot for `erc-channel-user'." 699 ,(concat
695 name) 700 "Get equivalent of pre-5.6 `" name "' slot for `erc-channel-user'."
696 (declare (gv-setter (lambda (v) 701 "\nUse a fallback value in non-ERC buffers. Treat an unadvertised"
697 (macroexp-let2 nil v v 702 "\nstatus according to `erc-channel-user-signal-if-status-unknown'.")
698 (,'\`(let ((val (erc-channel-user-status ,',u)) 703 (declare (gv-setter
699 (n (or (erc--get-prefix-flag ,c) ,d))) 704 (lambda (v)
700 (setf (erc-channel-user-status ,',u) 705 (macroexp-let2 nil v v
701 (if ,',v 706 (,'\`(let* ((val (erc-channel-user-status ,',u))
702 (logior val n) 707 (p (erc--parsed-prefix))
703 (logand val (lognot n)))) 708 (n (if p (or (erc--get-prefix-flag ,c p) 0) ,d))
704 ,',v)))))) 709 (nop (and p ,',v (zerop n))) ; unsupportedp
705 (let ((n (or (erc--get-prefix-flag ,c) ,d))) 710 (rv (and (not nop) ,',v)))
706 (= n (logand n (erc-channel-user-status u)))))) 711 (when (and nop
712 erc-channel-user-signal-if-status-unknown)
713 (error "Unsupported status prefix: %c" ,c))
714 (unless nop
715 (setf (erc-channel-user-status ,',u)
716 (if ,',v
717 (logior val n)
718 (logand val (lognot n)))))
719 rv))))))
720 (let* ((p (erc--parsed-prefix))
721 (n (if p (erc--get-prefix-flag ,c p) ,d)))
722 (and n (= n (logand n (erc-channel-user-status u)))))))
707 723
708(erc--define-channel-user-status-compat-getter "voice" ?v 1) 724(erc--define-channel-user-status-compat-getter "voice" ?v 1)
709(erc--define-channel-user-status-compat-getter "halfop" ?h 2) 725(erc--define-channel-user-status-compat-getter "halfop" ?h 2)
@@ -7090,7 +7106,7 @@ Used when a channel names list is about to be received. Should
7090be called with the current buffer set to the channel buffer. 7106be called with the current buffer set to the channel buffer.
7091 7107
7092See also `erc-channel-end-receiving-names'." 7108See also `erc-channel-end-receiving-names'."
7093 (setq erc-channel-new-member-names (make-hash-table :test 'equal))) 7109 (setq erc-channel-new-member-names (make-hash-table :test #'equal)))
7094 7110
7095(defun erc-channel-end-receiving-names () 7111(defun erc-channel-end-receiving-names ()
7096 "Internal function. 7112 "Internal function.
@@ -7142,7 +7158,7 @@ stand-in from the fallback value \"(qaohv)~&@%+\"."
7142 :alist (nreverse alist))))) 7158 :alist (nreverse alist)))))
7143 7159
7144(defun erc--get-prefix-flag (char &optional parsed-prefix from-prefix-p) 7160(defun erc--get-prefix-flag (char &optional parsed-prefix from-prefix-p)
7145 "Return numeric rank for CHAR or nil if unknown. 7161 "Return numeric rank for CHAR or nil if unknown or unsupported.
7146For example, given letters \"qaohv\" return 1 for ?v, 2 for ?h, 7162For example, given letters \"qaohv\" return 1 for ?v, 2 for ?h,
7147and 4 for ?o, etc. If given, expect PARSED-PREFIX to be a 7163and 4 for ?o, etc. If given, expect PARSED-PREFIX to be a
7148`erc--parsed-prefix' object. With FROM-PREFIX-P, expect CHAR to 7164`erc--parsed-prefix' object. With FROM-PREFIX-P, expect CHAR to
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 1b486c68584..7ad2d268fa3 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -688,6 +688,9 @@
688 (should-not (erc--parse-nuh "abc\nde!fg@xy"))) 688 (should-not (erc--parse-nuh "abc\nde!fg@xy")))
689 689
690(ert-deftest erc--parsed-prefix () 690(ert-deftest erc--parsed-prefix ()
691 ;; Effectively a no-op in a non-ERC buffer.
692 (should-not (erc--parsed-prefix))
693
691 (erc-tests-common-make-server-buf (buffer-name)) 694 (erc-tests-common-make-server-buf (buffer-name))
692 695
693 ;; Uses fallback values when no PREFIX parameter yet received, thus 696 ;; Uses fallback values when no PREFIX parameter yet received, thus
@@ -817,6 +820,156 @@
817 (should-not (erc--cusr-status-p cusr ?v)) 820 (should-not (erc--cusr-status-p cusr ?v))
818 (should-not (erc--cusr-status-p cusr ?q))))) 821 (should-not (erc--cusr-status-p cusr ?q)))))
819 822
823(defun erc-tests--make-combinations (flags)
824 "Return a list of all combinations in FLAGS, preserving order."
825 (let* ((flags (apply #'vector flags))
826 (n (length flags))
827 (max (1- (ash 1 n)))
828 (mask 0)
829 (out ()))
830 (while (<= (cl-incf mask) max)
831 (let ((i 0)
832 (subset ()))
833 (while (< i n)
834 (unless (zerop (logand mask (ash 1 i)))
835 (push (aref flags i) subset))
836 (cl-incf i))
837 (when (cdr subset)
838 (push (nreverse subset) out))))
839 out))
840
841(defun erc-tests--with-channel-user-status-accessors (test)
842 (erc-tests-common-make-server-buf)
843
844 (let* ((u (make-erc-channel-user))
845 (get (lambda (letter)
846 (pcase letter
847 (?v (erc-channel-user-voice u))
848 (?h (erc-channel-user-halfop u))
849 (?o (erc-channel-user-op u))
850 (?a (erc-channel-user-admin u))
851 (?q (erc-channel-user-owner u)))))
852
853 (set (lambda (letter val)
854 (pcase letter
855 (?v (setf (erc-channel-user-voice u) val))
856 (?h (setf (erc-channel-user-halfop u) val))
857 (?o (setf (erc-channel-user-op u) val))
858 (?a (setf (erc-channel-user-admin u) val))
859 (?q (setf (erc-channel-user-owner u) val)))))
860
861 (assert-null
862 (lambda (&rest letters)
863 (dolist (letter letters)
864 (ert-info ((format "Assert null: %c" letter))
865 (should-not (funcall get letter))))))
866
867 (assert-set
868 (lambda (letter &optional nop)
869 (ert-info ((format "Assert: %c%s" letter (if nop " (no-op)" "")))
870 (should-not (funcall get letter))
871 (if (and nop erc-channel-user-signal-if-status-unknown)
872 (should-error (funcall set letter t))
873 ;; If the flag is unsupported, always return nil,
874 ;; and don't set anything, otherwise, return t.
875 (let ((rv (funcall set letter t)))
876 (ert-info ((format "Set: %S" rv))
877 (should (xor rv nop)))))
878 (let ((rv (funcall get letter)))
879 (ert-info ((format "Get: %S" rv))
880 (should (xor rv nop)))))))
881
882 (assert-solo
883 (lambda (letter &optional nop)
884 (setf (erc-channel-user-status u) 0) ; clear
885 (funcall assert-set letter nop)
886 (apply assert-null (seq-difference '(?v ?h ?o ?a ?q)
887 (list letter)))))
888
889 (assert-multi
890 (lambda (&rest supported)
891 ;; Set all defined flags from smallest to largest rank.
892 (dolist (flags (erc-tests--make-combinations '(?v ?h ?o ?a ?q)))
893 (setf (erc-channel-user-status u) 0)
894 (ert-info ((let ((print-integers-as-characters t))
895 (format "Multi %S" (list :flags flags
896 :supported supported))))
897 (let ((seen-supported ())
898 (seen-unsupported ()))
899 (dolist (a flags)
900 (let ((supportedp (memq a supported)))
901 (push a (if supportedp seen-supported seen-unsupported))
902 (funcall assert-set a (not supportedp))
903 ;; Addition of new flag has not corrupted others.
904 (dolist (aa seen-supported)
905 (ert-info ((format "Seen supported: %s %c" u aa))
906 (should (funcall get aa))))
907 (dolist (aa `(,@seen-unsupported ,@(cdr (memq a flags))))
908 (should-not (funcall get aa))))))
909 ;; Unset in reverse, although not doing so is valid.
910 (setq flags (nreverse flags))
911 (let ((seen ()))
912 (while-let ((b (pop flags)))
913 (ert-info ((format "Unsetting: %S %c" u b))
914 (should-not (funcall set b nil))
915 (dolist (bb (push b seen))
916 (ert-info ((format "Seen unset: %c" bb))
917 (should-not (funcall get bb))))
918 (dolist (bb flags)
919 (ert-info ((format "Unseen set: %c" bb))
920 (if (memq bb supported)
921 (should (funcall get bb))
922 (should-not (funcall get bb)))))))))))))
923
924 ;; Run the same test twice, with compat flag nil and non-nil.
925 (let ((erc-channel-user-signal-if-status-unknown nil))
926 (funcall test assert-null assert-set assert-solo assert-multi))
927
928 (ert-info ("With `erc-channel-user-signal-if-status-unknown'")
929 (setf (erc-channel-user-status u) 0) ; clear
930 (let ((erc-channel-user-signal-if-status-unknown t))
931 (funcall test assert-null assert-set assert-solo assert-multi)))
932
933 (erc-tests-common-kill-buffers)))
934
935(ert-deftest erc-channel-user/status-accessors/solo/default ()
936 (erc-tests--with-channel-user-status-accessors
937 (lambda (assert-null _assert-set assert-solo _assert-multi)
938
939 (ert-info ("Baseline")
940 (funcall assert-null ?v ?h ?o ?a ?q))
941
942 (ert-info ("+v") (funcall assert-solo ?v))
943 (ert-info ("+h") (funcall assert-solo ?h))
944 (ert-info ("+o") (funcall assert-solo ?o))
945 (ert-info ("+a") (funcall assert-solo ?a))
946 (ert-info ("+q") (funcall assert-solo ?q)))))
947
948(ert-deftest erc-channel-user/status-accessors/solo/ov ()
949 (erc-tests--with-channel-user-status-accessors
950 (lambda (assert-null _assert-set assert-solo _assert-multi)
951 (erc-tests-common-simulate-line ":irc.gnu.org 005 tester PREFIX=(ov)@+")
952
953 (ert-info ("Baseline")
954 (funcall assert-null ?v ?h ?o ?a ?q))
955
956 (ert-info ("+v") (funcall assert-solo ?v))
957 (ert-info ("+h (unknown)") (funcall assert-solo ?h 'nop))
958 (ert-info ("+o") (funcall assert-solo ?o))
959 (ert-info ("+a (unknown)") (funcall assert-solo ?a 'nop))
960 (ert-info ("+q (unknown)") (funcall assert-solo ?q 'nop)))))
961
962(ert-deftest erc-channel-user/status-accessors/multi/default ()
963 (erc-tests--with-channel-user-status-accessors
964 (lambda (_assert-null _assert-set _assert-solo assert-multi)
965 (funcall assert-multi ?v ?h ?o ?a ?q))))
966
967(ert-deftest erc-channel-user/status-accessors/multi/ov ()
968 (erc-tests--with-channel-user-status-accessors
969 (lambda (_assert-null _assert-set _assert-solo assert-multi)
970 (erc-tests-common-simulate-line ":irc.gnu.org 005 tester PREFIX=(ov)@+")
971 (funcall assert-multi ?v ?o))))
972
820;; This exists as a reference to assert legacy behavior in order to 973;; This exists as a reference to assert legacy behavior in order to
821;; preserve and incorporate it as a fallback in the 5.6+ replacement. 974;; preserve and incorporate it as a fallback in the 5.6+ replacement.
822(ert-deftest erc-parse-modes () 975(ert-deftest erc-parse-modes ()
@@ -3489,8 +3642,11 @@
3489 (when noninteractive 3642 (when noninteractive
3490 (erc-tests-common-kill-buffers))) 3643 (erc-tests-common-kill-buffers)))
3491 3644
3645;; For legacy accessors, like `erc-channel-user-halfop', this test only
3646;; demonstrates compat-oriented behavior in a non-ERC buffer. See
3647;; `erc-tests--with-channel-user-status-accessors' based tests for
3648;; behavior in ERC buffers, both fallback and ISUPPORT-defined.
3492(ert-deftest erc-channel-user () 3649(ert-deftest erc-channel-user ()
3493 ;; Traditional and alternate constructor swapped for compatibility.
3494 (should (= 0 (erc-channel-user-status (erc-channel-user--make)))) 3650 (should (= 0 (erc-channel-user-status (erc-channel-user--make))))
3495 (should-not (erc-channel-user-last-message-time (erc-channel-user--make))) 3651 (should-not (erc-channel-user-last-message-time (erc-channel-user--make)))
3496 3652