--- /dev/null
+#+TITLE: On Buglet-Bindings
+#+SUBTITLE: Highlighting a pervasive Emacs Lisp bug
+#+DESCRIPTION: A post by Eshel Yaron highlighting a pervasive Emacs Lisp bug
+#+KEYWORDS: emacs,lisp
+#+DATE: 2024-07-15
+
+@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
+
+This post is about a class of bugs that you run into left and right in
+Emacs Lisp code, a sneaky pitfall that developers keep falling into,
+n00bs and greybeards alike: unsafe let-bindings of special variables.
+
+Say you're writing some code that lets you (or other users) input some
+text interactively. Say you're using the minibuffer for reading that
+input. Say you're going the extra mile to provide text completion.
+In short, say you're using ~completing-read~:
+
+#+begin_src emacs-lisp
+ (completing-read "Frobnicate: " '("Foo" "Bar" "Spam"))
+#+end_src
+
+So far so good, but when you try it out you realize that completion is
+case-sensitive by default, so type =s TAB= doesn't complete to =Spam=
+and you have to type =S TAB= with an uppercase =S= instead. That's a
+nuance, so you venture to make completion case-insensitive. You could
+just configure you're Emacs with ~completion-ignore-case~ set to ~t~,
+but that affects all kinds of completion, and you only want "this"
+completion to be case-insensitive. That's when you fall head first
+into the /buglet-binding/ pitfall, and do this:
+
+#+begin_src emacs-lisp
+ ;; BAD!
+ (let ((completion-ignore-case t))
+ (completing-read "Frobnicate: " '("Foo" "Bar" "Spam")))
+#+end_src
+
+You've let-bound ~completion-ignore-case~ around the call to
+~completing-read~. You do this because that's what you see others do.
+It makes sense. But it's wrong. The others you mimic are staring at
+you from the bottom of the pitfall, and you've jumped in to join them.
+
+It is wrong because this binding affects all recursive minibuffers,
+while you only wanted to influence the completion you're coding up.
+Reading input from the minibuffer puts Emacs in a recursive edit.
+Many things can happen before the minibuffer is terminated---you can
+switch away to another buffer and do some editing, get a coffee, and
+even enter more, recursive, minibuffers. And your let-binding takes
+effect over all of that.
+
+To see this nasty effect, execute the above code, and while in the
+minibuffer type =C-h f= to invoke ~describe-function~. Now, in the
+new minibuffer, try to complete some function name. For example, type
+=info=, all lower case, followed by =?= to show the completions
+list---you get completion candidates that start with capitalized
+=Info=, because your let-binding of ~completion-ignore-case~ is still
+in effect. Ouch!
+
+Instead, the correct way to implement case-insensitive minibuffer
+completion, and more generally, to change some minibuffer settings
+without affecting recursive minibuffers, is to use buffer-local
+bindings. For example:
+
+#+begin_src emacs-lisp
+ ;; OK.
+ (minibuffer-with-setup-hook
+ (lambda () (setq-local completion-ignore-case t))
+ (completing-read "Frobnicate: " '("Foo" "Bar" "Spam")))
+#+end_src
+
+[ Actually, since Emacs version 29, ~completion-ignore-case~ in
+ particular gets a special treatment that requires some additional
+ work to handle correctly. This is fine for other variables. ]
+
+While let-bindings are local to a certain piece of code, that piece
+may become boundless when recursive edits or other forms of arbitrary
+code execution are involved. On the other hand, buffer-local variable
+values only affect the one minibuffer in which you set them.
+Recursive minibuffers are different buffers, so they have their own
+buffer-local values and thus maintain their usual behavior.
+
+A /bug-let/, or a /buglet-binding/, is a coding error in which a
+let-binding has unintended influences on some code in the scope of
+that let-binding, like in the example we saw earlier. Let-binding
+special variables (those defined with ~defvar~, ~defcustom~, etc.)
+around functions that read input in the minibuffer can often yield
+buglet-bindings. Variables that are often buglet-bound include
+~enable-recursive-minibuffers~, ~completion-extra-properties~,
+~completion-ignore-case~, ~minibuffer-allow-text-properties~,
+~completion-ignored-extensions~ and ~minibuffer-completing-file-name~.
+But it's not all minibuffers, buglet-bindings are a more general class
+of bugs that can occur even when no minibuffers are in play. When you
+let-bind special variables, you need to ensure that everything in the
+binding's scope cooperates with it correctly. That's easy to do for
+your own special variables that only your code uses: if all other code
+is indifferent to the value of your variable, then all you need to
+check is that your code is not called recursively, or that it's
+prepared for being called recursively with the let-binding in effect.
+For special variables defined elsewhere, keep the scope of
+let-bindings as small as possible and check that all functions in
+scope are known and that the let-binding doesn't change their behavior
+in unindented ways. If you're invoking a user-supplied function in
+the let-binding scope, document that clearly so users can prepare
+their code accordingly.
+
+Other than that, let-bind away, and stay safe!