Subject: Re: bug#25152: 25.1; Customize: errors for `restricted-sexp' in `repeat'

From: Mauro Aranda <[email-masked]>
Date: Sat, 5 Sep 2020 08:57:06 -0300

Drew Adams <[email-masked]> writes:

> emacs -Q
>
> (defcustom bar
>   `(ignore)
>   "..."
>   :type
>   '(repeat (restricted-sexp :match-alternatives (functionp)))
>   :group 'emacs)
>
> M-x customize-option bar
>
> 1. Click the INS button, to insert a new element in the list.
> 2. At the prompt "Lisp expression: ", hit `C-g'.

You shouldn't be prompted, because the widget library is not ready to
take user input at this stage (the stage being the creation of the
widget).

The prompt comes from the function :value-to-external of the sexp
widget, which is called in the process of creating the widget.  The
:value-to-external function calls read with the value of the widget,
like this: (read value)

It does that assuming value is a string.  It does not want user input
(by reading it from the minibuffer), it just wants to take the widget
value and return it in the "external" format.  The function
:value-to-internal is the one that should print the value of the widget
to a string.  But it only does that if the value matches the widget, as
with the function :match, which in the defcustom posted in the recipe is
widget-restricted-sexp-match.

So, why doesn't the value of the widget satisfy
widget-restricted-sexp-match, at the widget creation stage?  That's
because :match-alternatives is '(functionp), and the default value of
the widget is nil.  Because of that, widget-restricted-sexp-match
returns nil, and we store the value intact (i.e., not in the "internal"
format), and we end up evaluating (read nil), resulting in the prompt
and losing badly.

But, the Elisp manual says:
‘:value DEFAULT’
     Provide a default value.

     If ‘nil’ is not a valid value for the alternative, then it is
     essential to specify a valid default with ‘:value’.

So the defcustom provided is lacking the essential :value DEFAULT thing.
If we compare the behavior with the following defcustom:
(defcustom bar
  `(ignore)
  "..."
  :type
  '(repeat (restricted-sexp :match-alternatives (functionp)
                            :value ignore))
  :group 'emacs)

And in the customization buffer we click the INS button, then there's no
prompt: creation of the widget happens with no problem.

Another defcustom, that makes nil a valid value:
(defcustom bar
  `(ignore)
  "..."
  :type
  '(repeat (restricted-sexp :match-alternatives (functionp null)))
  :group 'emacs)

Again, click the INS button: no problem.

To sum it up, the prompt is an unfortunate mistake, and maybe we could
protect against that, but I think the real problem comes from the
defcustom, which fails to provide a valid default value.