Subject: bug#78737: sit-for behavior changes when byte-compiled

From: Daniel Colascione <[email-masked]>
Date: Wed, 11 Jun 2025 07:49:23 -0700

Pip Cet <[email-masked]> writes:

> "Daniel Colascione" <[email-masked]> writes:
>
>> Pip Cet <[email-masked]> writes:
>>
>>> "Daniel Colascione" <[email-masked]> writes:
>>>
>>>> On June 10, 2025 10:40:39 AM PDT, Pip Cet <[email-masked]> wrote:
>>>>>"Stefan Monnier" <[email-masked]> writes:
>>>>>
>>>>>>> BTW: the problem isn't just with transient. It also manifests with
>>>>>>> read-extended-command! It's a nasty race that, AFAICT, has been with us
>>>>>>> since the 90s. I think defining read_char to translate quits to quit_char
>>>>>>> solves the problem.
>>>>>>
>>>>>> I like your way of thinking.  I'm not completely sure it will solve
>>>>>> world hunger, and it may come with regressions, but it's worth a try.
>>>>>
>>>>>I must be missing something: read_char translates quits to quit_char if
>>>>>called with inhibit-quit bound to t, and never returns with quit-flag
>>>>>set to t in that case.
>>>>
>>>> You shouldn't have to call it with inhibit-quit for it do that. It should just happen all the time.
>>>
>>> But we don't usually want read-event to eat quits and report them by
>>> returning quit_char.  The old behavior gave me a choice.
>>
>> You still have a choice.  You can signal quit if you get a quit event.
>
> Making
>
> (while t (read-event))
>
> infloop without being able to quit is a bad idea.  We shouldn't do it.

So we should "fix" read-key-sequence so that it, too, quits?

>>> (while t (read-event))
>>>
>>> on your branch appears to hang the Emacs session unquittably (even
>>> SIGUSR2 doesn't seem to work).  It should permit quits, because that
>>> code says nothing about wanting quits to be reported.
>>
>> By calling read-event, you're asking Emacs read an event.  C-g is an
>> event.
>
> It's not an ordinary event.

The problem is that C-g is ordinary event on some code paths and
not others.

> It's an event that should be handled
> specially, which is what we do now and should continue doing.

So if I stuff C-g into unread-command-events and then call (read-event),
I should get a quit?  It's even more confusing to say that C-g is a
special event _unless_ it's been unread (perhaps transparently by
something somewhere), in which case it's actually a regular event.

> There's a
> way around that, but the vast majority of callers don't need to worry
> about it: (read-event) should read an ordinary event and return it,
> without breaking quit.
>
>> Lisp isn't saying
>> read-event-unless-it's-C-g-in-which-case-exit-non-locally.  Lisp says
>> read-event.
>
> That's implicit as for any other Lisp command that isn't specifically
> documented not to quit.  (As, for example, read-key-sequence is).

Yes, and one or the other function should change, along with its
documentation.  What _reason_ is there to quit from one and not
the other?

>>> Anyway, here are the minor changes to keyboard.c to avoid the original
>>> problem (the third change is somewhat independent and avoids quitting in
>>> kbd_buffer_get_event):
>>
>> This change papers over the problem of ambiguous C-g representation
>
> It doesn't paper over anything.  It merely quits before dequeueing
> events when throw-on-input is in effect, without any major changes.
>
>> without trying to fix it or address the even worse problem of the quits
>> going to the event loop and not being looked up as commands.
>
> Are you saying quits should *generally* be treated as commands rather
> than by the event loop?  Because my opinion is precisely the opposite:
> quits should always be handled by C code, never by keyboard-quit,
> because that's what they are used for: to break out of misbehaving Lisp
> code, with minimal involvement of Lisp machinery which may be
> misbehaving.

_Quits_ are intended to break out of Lisp code, but C-g doesn't always
mean quit.  Transient, for example, uses C-g to dismiss its menus.
execute-extended-command does too, as does completion.  Like it or not,
C-g is not merely a signal to Lisp to stop what it's doing, but also a
legitimate event to which people bind regular commands, and it's a bug
if we don't respect these bindings because in a certain circumstance
invisible to users we decided that the C-g is really a
stop-the-Lisp-world-now pseudo-error and not the user telling us he
wants to run a command.

> We'd need new, incompatible versions of almost all callers, because
> those do want to quit when we hit C-g.  And, again, we're taking away
> the choice "act on C-g as usual", replacing it by a new "identify
> (somehow) quit events and (somehow) quit when you see one".

I haven't seen anything actually malfunction due to having (read-event)
return C-g as an event instead of quitting, and some code actually
expects it.

Some code actually _works_ better.  Consider, for example,
calc-get-user-defn.

(defun calc-get-user-defn ()
  (interactive)
  (calc-wrapper
   (message "Get definition of command: z-")
   (let* ((key (read-char))
	  (def (or (assq key (calc-user-key-map))
		   (assq (upcase key) (calc-user-key-map))
		   (assq (downcase key) (calc-user-key-map))
		   (error "No command defined for that key")))
  ...)

If read-char returns \?C-g, the code above produces a _better_ message,
"No command defined for that key" (which is the truth) where it used to
just say "Quit".  Even hairy code like kbd-macro-query works fine when
read-char, read-event, and so on report C-g as an event.

If you can find something that actually breaks when we do that, we can
do something else, but so far, I haven't seen reporting C-g break
anything and have seen it make things a bit better.