Subject: Unexpected behavior with read-file-name and functional REQUIRE-MATCH argument
From: Joseph Turner <[email-masked]>
To: Emacs Bugs Mailing List <[email-masked]>
Date: Tue, 30 Jan 2024 01:00:47 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

Hello!

With #66187 resolved (<https://yhetil.org/emacs-bugs/[email-masked]/>),
I reexamined #66114 (<https://yhetil.org/emacs-bugs/[email-masked]/>),
only to discover that filename completion with a functional
REQUIRE-MATCH argument doesn't work as expected.

To reproduce:

emacs -Q on the tip of the emacs-29 branch (after commit 77f5d4d523a), run...

(let ((default-directory "~/"))
  (read-directory-name "Clone into new or empty directory: " nil nil
                       (lambda (dir) (or (not (file-exists-p dir))
                                    (directory-empty-p dir)))))

...then type "/tmp/" (the whole minibuffer now reads "~//tmp") then RET.

Expected: Completion does not exit, instead saying "[No match]".

Actual: Completion exits, returning "/tmp/".

If I delete the leading "~/" before typing "/tmp/", I get the expected result.

The issue appears to be inside completion--complete-and-exit:

((functionp minibuffer-completion-confirm)
    (if (funcall minibuffer-completion-confirm
                 (buffer-substring beg end))  ; Here, buffer-substring returns "~//tmp/"
        (funcall exit-function)
      (unless completion-fail-discreetly
	(ding)
	(completion--message "No match"))))

Since (file-exists-p "~//tmp/") returns nil, the whole predicate returns
t and the minibuffer completes "/tmp/".

In completion--complete-and-exit, should (buffer-substring beg end)
return only "/tmp/"?

Or maybe (file-exists-p "~//tmp/") should return t?

Thank you!!

Joseph