Subject: Re: bug#73880: Master: emacs-lisp-mode: Tab completion for a function position fails in a `let' form.

From: Alan Mackenzie <[email-masked]>
Date: Sat, 26 Oct 2024 14:35:10 +0000

Hello, Dmitry.

On Sat, Oct 26, 2024 at 04:50:15 +0300, Dmitry Gutov wrote:
> Hi Alan!

> Sorry about the late response.

No problem!

> On 20/10/2024 13:54, Alan Mackenzie wrote:
> > Hello, Dmitry and Emacs.

> > On Sat, Oct 19, 2024 at 13:09:00 +0000, Alan Mackenzie wrote:
> >> Hello, Emacs.

> >> In a recent master version, for example this commit:

> >> commit 5340fdaade1f8fe7af08293619cca89ae0796fcf (HEAD -> master, origin/master, origin/HEAD)
> >> Author: Alan Mackenzie <[email-masked]>
> >> Date:   Wed Oct 16 13:17:26 2024 +0000

> >>      CC Mode: Fix dodgy lisp `let' form.

> >> , start emacs -Q, followed by entering the following incomplete form:

> >> (defun foo ()
> >>    (let (
> >>          )
> >>      (match-b

> >> With point after match-b, type M-TAB.  This should complete to
> >> match-beginning or show that function as a completion option.  Instead
> >> it signals the error "No match".  This is a bug.

> >> It would seem the completion function elisp-completion-at-point thinks
> >> it is completing a variable symbol rather than a function symbol.

> > This is indeed the case.  The following patch fixes this by checking
> > that the symbol being completed begins within the binding list.  It also
> > adds a test into elisp-mode-tests.el.

> The test looks good, but for the fix itself maybe we could have 
> something shorter.

I wasn't happy with the length of my patch, either.  But ....

> Could you try the below one-liner? It seems like the problem is the
> (forward-symbol -1) skipping over the empty parens.

Yes.

I haven't tried it out your patch yet, but surely it will fail when there
are comments in the `let' form.  For that matter, forward-symbol doesn't
handle comments, either.

> This satisfies the test, at least.

OK.

I've just looked at another form which doesn't work optimally, namely:

    `(let (match-b

..  Here a M-TAB ought to signal "No match", but instead offers the two
functions.  If the backquote is removed, it works as it should.

elisp-completion-at-point seems to be an approximate function which works
most of the time.  To make it work all the time would involve
incorporating a substantial bit of the byte-compiler into it.  So I don't
know if it's worth doing that.

But the `(let\*? form is so common (~469 occurrences in Emacs) it might
be worth fixing.

> BTW, I had to move the corresponding piece of code to a separate 
> function to debug it. Too bad edebug doesn't know how to jump into the 
> 'guard' clauses.

Yes, I had that problem, too.  Looking at the edebug spec for pcase, it
seems that guard is meant to be handled, and probably is at the top
level.  But inside an `and' or `or' clause, it isn't.

The documentation (on elisp page "Specification List") says that edebug
specs are capable of handling "recursive processing of forms" amongst
other things, so I think it should be possible, though not necessarily
easy.  Are you going to raise a bug report for this or should I?  ;-)

> diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
> index 2f931daedc7..eab9b2d8ede 100644
> --- a/lisp/progmodes/elisp-mode.el
> +++ b/lisp/progmodes/elisp-mode.el
> @@ -789,7 +789,7 @@ elisp-completion-at-point
>                                         (goto-char (1- beg))
>                                         (when (eq parent ?\()
>                                           (up-list -1))
> -                                      (forward-symbol -1)
> +                                      (skip-syntax-backward " w_")
>                                         (or
>                                          (looking-at
>                                           "\\_<\\(let\\*?\\|bind\\*\\)\\_>")

-- 
Alan Mackenzie (Nuremberg, Germany).