From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Leo Vivier Newsgroups: gmane.emacs.bugs Subject: bug#44579: Unintended behaviour with =?UTF-8?Q?defcustom=E2=80=99s_?= =?UTF-8?Q?=E2=80=98choice=E2=80=99?= widgets and ":inline t" & Wrong documentation on "(elisp) Splicing into Lists" Date: Wed, 11 Nov 2020 16:28:46 +0100 Message-ID: <87lff7yksx.fsf@hidden> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="29792"; mail-complaints-to="usenet@ciao.gmane.io" To: 44579@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Wed Nov 11 16:31:39 2020 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kcs5y-0007cL-PH for geb-bug-gnu-emacs@m.gmane-mx.org; Wed, 11 Nov 2020 16:31:39 +0100 Original-Received: from localhost ([::1]:45262 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kcs5x-0005oG-MI for geb-bug-gnu-emacs@m.gmane-mx.org; Wed, 11 Nov 2020 10:31:37 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:52392) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kcs5S-0005Is-7C for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 10:31:07 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:59295) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kcs5Q-0007zR-3q for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 10:31:05 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1kcs5P-00014F-W1 for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 10:31:04 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Leo Vivier Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Wed, 11 Nov 2020 15:31:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 44579 X-GNU-PR-Package: emacs X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.16051086344030 (code B ref -1); Wed, 11 Nov 2020 15:31:03 +0000 Original-Received: (at submit) by debbugs.gnu.org; 11 Nov 2020 15:30:34 +0000 Original-Received: from localhost ([127.0.0.1]:42604 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kcs4v-00012q-41 for submit@debbugs.gnu.org; Wed, 11 Nov 2020 10:30:33 -0500 Original-Received: from lists.gnu.org ([209.51.188.17]:57146) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kcs3d-0000zB-PL for submit@debbugs.gnu.org; Wed, 11 Nov 2020 10:29:16 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:51824) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kcs3d-0003NW-2B for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 10:29:13 -0500 Original-Received: from aibo.runbox.com ([91.220.196.211]:58812) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kcs3Z-0007Eu-HL for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 10:29:12 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zaeph.net; s=selector2; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From; bh=r36Q48L4evo/akdZSO0TbgcgjuS8yHRY1lGAYsfx2nE=; b=KiWdw3XHKobUObFmqeQRglZQS kD0ePerrdBc1weOoT0wJzAiat92NyfI2p2KfKQ9aXUP73XKyqy3Z0TbG0zYSVRvTbPgEcOt1hfaPO bzlCWvnduyh3+oUeNXEYeRZepWDb9kmKwniUkGn1OcJgyKb2aUFSO4DpklC+90MyeNh6Sbeu+nabH eeEcJWb4OyRG2VpSoWpr1Mmtf4H40HjDvP5c3ZBfs7+R4qoakLWYeQ9Y5dOQy7xvDC4Tj6+k8g8YU uwFQN+6X456LHptl6+5FyZ5PtBLjxGq6ekDac/3xryS32WVLXUbtXjh48FH1eJK/BZ2ABP2cL9b/Z ea0Oh7TJA==; Original-Received: from [10.9.9.73] (helo=submission02.runbox) by mailtransmit03.runbox with esmtp (Exim 4.86_2) (envelope-from ) id 1kcs3V-0005db-07 for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 16:29:05 +0100 Original-Received: by submission02.runbox with esmtpsa [Authenticated alias (984850)] (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) id 1kcs3D-0003iN-5o for bug-gnu-emacs@gnu.org; Wed, 11 Nov 2020 16:28:47 +0100 Received-SPF: pass client-ip=91.220.196.211; envelope-from=zaeph@zaeph.net; helo=aibo.runbox.com X-detected-operating-system: by eggs.gnu.org: First seen = 2020/11/11 10:29:05 X-ACL-Warn: Detected OS = ??? X-Spam_score_int: -27 X-Spam_score: -2.8 X-Spam_bar: -- X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Wed, 11 Nov 2020 10:30:30 -0500 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:193111 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Hi there, There seems to be a problem in `defcustom' forms with the way the `choice' widget handles `:inline t'. I=E2=80=99ve written an .el file to walk you through it: I=E2=80=99ve docum= ented the bug, the explanation, and a tentative solution. I recommended downloading the attachment and eval=E2=80=99ing the forms as = they appear. Make sure that you use C-M-x with the `defcustom' forms, lest their STANDARD not be reset. HTH, --=20 Leo Vivier Freelance Software Engineer Website: www.leovivier.com | Blog: www.zaeph.net --=-=-= Content-Type: text/emacs-lisp; charset=utf-8 Content-Disposition: inline; filename=defcustom-inline-debug.el Content-Transfer-Encoding: quoted-printable ;;; DESCRIPTION ;; Incriminated section of the manual: (info "(elisp) Splicing into Lists") ;; > When the element-type is a =E2=80=98choice=E2=80=99, you use =E2=80= =98:inline=E2=80=99 not in the ;; > =E2=80=98choice=E2=80=99 itself, but in (some of) the alternatives of = the =E2=80=98choice=E2=80=99. For ;; > example, to match a list which must start with a file name, followed ;; > either by the symbol =E2=80=98t=E2=80=99 or two strings, use this cust= omization type: ;; > ;; > (list file ;; > (choice (const t) ;; > (list :inline t string string))) ;; > ;; > If the user chooses the first alternative in the choice, then the ;; > overall list has two elements and the second element is =E2=80=98t=E2= =80=99. If the ;; > user chooses the second alternative, then the overall list has three ;; > elements and the second and third must be strings. ;; The first option in =E2=80=98choice=E2=80=99 works. (defcustom zp/testing '("foo" t) "Testing variable." :type '(list file (choice (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;; The second option in =E2=80=98choice=E2=80=99 doesn=E2=80=99t work. (defcustom zp/testing '("foo" "bar" "baz") "Testing variable." :type '(list file (choice (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is *not* recognised. ;; Proof that =E2=80=98choice=E2=80=99 implies a list in this context by en= closing the result ;; of the =E2=80=98choice=E2=80=99 in a list in STANDARD. (defcustom zp/testing '("foo" ("bar" "baz")) ; <--- "Testing variable." :type '(list file (choice (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;;; DEBUGGING ;; Adding another ":inline t" for `choice' allows us to make progress. (defcustom zp/testing '("foo" "bar" "baz") "Testing variable." :type '(list file (choice :inline t ; <--- (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;; =E2=80=A6But this makes the first option not recognised t as a legitimat= e value. (defcustom zp/testing '("foo" t) "Testing variable." :type '(list file (choice :inline t ; <--- (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised, but t is not recognised as a valid value. ;; However, manually selecting t in the "Value menu" produces a form which = you ;; can apply, producing exactly the same form as STANDARD. Closing and ;; reopening the customise window after applying the value produces the same ;; invalid value, as expected. ;; Enclosing t in a list resolves the issue. (defcustom zp/testing '("foo" "bar" "baz") "Testing variable." :type '(list file (choice :inline t (list :inline t ; <--- (const t)) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised, but at what cost? ;; Also, it is slightly misleading to present the first choice as a "List" = in ;; the menu, considering that the list is exploded into its single element. ;; =E2=80=A6Let=E2=80=99s dive deeper, shall we? ;;; EXPLANATION (for the courageous) ;; The issue can be more easily grokked if we consider a close alternative = to ;; the =E2=80=98choice=E2=80=99 widget: =E2=80=98set=E2=80=99. Instead of = the XOR presented in the previous ;; choices, we=E2=80=99re going to turn it into an OR and enclose it into a= list. (defcustom zp/testing '("foo" (t "bar" "baz")) ; <--- "Testing variable." :type '(list file (set ;No ":inline t" to keep the the list as= is (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;; As we can see, the =E2=80=98set=E2=80=99 widget always implies a list, w= hich is why all the ;; options are enclosed in a list. ;; Now, let=E2=80=99s explode that list by adding ":inline t" to the =E2=80= =98set=E2=80=99 widget: (defcustom zp/testing '("foo" t "bar" "baz") ; <--- "Testing variable." :type '(list file (set :inline t ; <--- (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;; If we focus on the second option in =E2=80=98set=E2=80=99, we can see th= at =E2=80=98set=E2=80=99 receives ;; the constant t alongside two strings which are the elements of an explod= ed ;; list. Therefore, =E2=80=98set=E2=80=99 is handed three elements which i= t packages into ;; a list. This list is then exploded via ":inline t". ;; (Please note that my usage of "handed over" is used to describe what is ;; *seemingly* happening, which is not how the code actually implements it,= as ;; we=E2=80=99ll see later.) ;; ;; The difference between the =E2=80=98set=E2=80=99 widget and the =E2=80= =98choice=E2=80=99 widget is that the ;; later does not always imply a list. ;; ;; Let=E2=80=99s return to the previous non-functioning examples with =E2= =80=98choice=E2=80=99 and ;; ":inline t". ;; Scenario 1: 2nd option for =E2=80=98choice=E2=80=99 (defcustom zp/testing '("foo" "bar" "baz") ; <--- "Testing variable." :type '(list file (choice :inline t (const t) (list :inline t ; <--- MATCH string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;; Here, we have a very similar scenario to the one which was just described ;; with =E2=80=98set=E2=80=99: =E2=80=98choice=E2=80=99 is handed an explod= ed list which it packages into ;; a list. This list is then exploded via ":inline t". ;; Scenario 2: 1st option for =E2=80=98choice=E2=80=99 *without* a wrapping= list (defcustom zp/testing '("foo" t) "Testing variable." :type '(list file (choice :inline t (const t) ; <--- MATCH? (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised, but t is not recognised as a valid value. ;; In this scenario, =E2=80=98choice=E2=80=99 receives a single element, t,= and is instructed ;; to explode it via ":inline t". This results in an existential panic ;; (=C2=AB Sacrebleu, ceci n=E2=80=99est pas une liste ?! =C2=BB) which ult= imately causes ;; =E2=80=98customize=E2=80=99 to not parse the form properly. ;; Scenario 3: 1st option for =E2=80=98choice=E2=80=99 *with* a wrapping li= st (defcustom zp/testing '("foo" t) "Testing variable." :type '(list file (choice :inline t (list :inline t ;Wrapping list (const t)) ; <--- MATCH (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised, but at what cost? ;; If we pause here for a moment, this is the reason why we need to revise = the ;; "handed over" model I described above. If =E2=80=98choice=E2=80=99 and = =E2=80=98set=E2=80=99 exploded ;; their elements in the order I detailed, elements shouldn=E2=80=99t be di= scriminated ;; upon their provenance, i.e., whether they were atomic elements or if they ;; came from an exploded list. ;; Let=E2=80=99s essentialise the form a little bit to clarify my point: ;; (All the examples below produce well-formed results for =E2=80=98customi= ze=E2=80=99.) (defcustom zp/testing t ;Atomic element "Testing variable." :type '(choice (list :inline t (const t)) (const t))) ; <--- MATCH (customize-variable 'zp/testing) ;; Based on the order of the options, if the 1st option had been exploded ;; before considering STANDARD, it should have matched. Instead, it=E2=80= =99s the 2nd ;; option which matches. (defcustom zp/testing '(t) ;List with an atomic element "Testing variable." :type '(choice (list :inline t ; <--- MATCH (const t)) (const t))) (customize-variable 'zp/testing) ;; When STANDARD is a list, the 1st option matches in spite of the exploded ;; list, which goes against what is described in the manual. ;; My interpretation is that =E2=80=98choice=E2=80=99 was not designed with= ":inline t" in ;; mind, or at least not as =E2=80=98set=E2=80=99 was. I haven=E2=80=99t i= nvestigated how the parsing ;; is done internally, but I assume that =E2=80=98choice=E2=80=99 fails to = understand ":inline ;; t" in composite widgets. ;; Just to be clear, ":inline t" implies that we want the elements in the ;; composite widget to be merged inside the super-element, i.e., the one wh= ich ;; *includes* the composite widget with ":inline t". ;; Let=E2=80=99s refer back to the examples with =E2=80=98set=E2=80=99 to i= llustrate this: (defcustom zp/testing '("foo" (t "bar" "baz")) ; <--- "Testing variable." :type '(list file (set ;No ":inline t" to keep the the list as is (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. (defcustom zp/testing '("foo" t "bar" "baz") ; <--- "Testing variable." :type '(list file (set :inline t ;Now with ":inline t" (const t) (list :inline t string string)))) (customize-variable 'zp/testing) ;; =3D> The form is recognised. ;; Now, the fundamental problem with =E2=80=98choice=E2=80=99---and mind th= e quotes, this ;; isn=E2=80=99t Philosophy 101---is that it is a composite widget which mu= st ;; *sometimes* return non-composite widgets: ;; ;; 1. (choice symbol symbol) ;; always returns a non-composite widget ;; ;; 2. (choice (list symbol) (list symbol)) ;; always returns a composite widget ;; ;; 3. (choice (list symbol) symbol) ;; *sometimes* returns a non-composite widget ;; *sometimes* returns a composite widget ;; ;; Because of this, the current implementation of =E2=80=98choice=E2=80=99 = always tries to ;; return a single well-formed widget. When we use ":inline t" on a compos= ite ;; widget, we explicity tell that widget that we don=E2=80=99t care about i= ts opinion ;; and we explode it. If it only had one element, Mazel-fucking-tov, we=E2= =80=99ve ;; got a well-formed non-composite widget! But if it had more than one ;; element, all we=E2=80=99re left with is a a group of elements which aren= =E2=80=99t in ;; a list, but which would have to be coerced into one for =E2=80=98choice= =E2=80=99 to ;; consider them as a well-formed option. ;; ;; So, since all widgets are created equals, rather than discriminating upon ;; well-formedness---society abhors originals, after all---we coerce this ;; rebelious group into a list to set the world aright. ;; ;; But there is a way, and I=E2=80=99ll explain it in the next section. ;;; SUGGESTED FIX ;; =E2=80=98choice=E2=80=99 needs to be made aware of the ":inline t" in it= s options. ;; ;; Since =E2=80=98choice=E2=80=99 is intended to receed into the background= once the ;; appropriate option has been pattern-matched, it doesn=E2=80=99t make sen= se to have ;; it carry the ":inline t". Instead, it should respect the ":inline t" th= at ;; is present in its option when said option is matched. ;; Let=E2=80=99s walk through an example to clarify: (defcustom zp/testing t ;Atomic element "Testing variable." :type '(choice (list :inline t ;Option 1 (const t)) (const t))) ;Option 2 (customize-variable 'zp/testing) ;; (For those of you following at home, this was the first essentialised ;; example. If you didn=E2=80=99t read the previous section and you=E2=80= =99re feeling left ;; out, that=E2=80=99s what you get for skipping my lecture, you ingrate.) ;; With the *current implementation*, Option 2 is matched. Option 1 is ;; exploded as instructed, but =E2=80=98choice=E2=80=99 coerces it into a l= ist in order to ;; return it as a well-formed widget. ;; ;; Instead, when =E2=80=98choice=E2=80=99 pattern-matches an option which h= as ":inline t" in ;; it (notwithstanding those which might be nested if the option happens to= be ;; a composite widget), it should *distribute* that ":inline t" to the ;; =E2=80=98choice=E2=80=99 widget itself so as to return the rebellious gr= oup of elements ;; (that=E2=80=99s another reference you might have missed from the previou= s section; ;; sucks to be you, eh?). ;; ;; So, in short: when =E2=80=98choice=E2=80=99 has an option with ":inline = t", explode the ;; option for pattern-matching, and if it matches, distribute the ":inline = t" ;; to the =E2=80=98choice=E2=80=99 widget. ;; ;; *** ;; ;; Now, I know what you=E2=80=99re thinking: why spend so long documenting = a problem ;; rather than *actually* trying to fix it? Well, firstly, congratulations, ;; you=E2=80=99ve described academia, but I also happen to be an English ma= jor, and ;; I=E2=80=99ll jump on every occasion I get to offset my technical-illiter= ateness ;; with my actual-literateness. Secondly I couldn=E2=80=99t believe the doc= of Emacs ;; to be any less than perfect: claiming otherwise would have been hubris, = and ;; I had no idea what pandemonium awaited me if I dared to refactor the ;; scriptures. ;; ;; Thank you for listening to my TED Talk.^A^K ;; Remember that the real treasure is the friends we made along the way.^A^K ;; ;; =E2=80=A6Thank you. --=-=-=--