all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#62419: 28.2; Elisp let-bound buffer-local variable and kill-local-variable
@ 2023-03-24 13:37 Matthew Malcomson
  2023-03-25 11:49 ` Eli Zaretskii
  2023-03-26 14:01 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 11+ messages in thread
From: Matthew Malcomson @ 2023-03-24 13:37 UTC (permalink / raw)
  To: 62419

[-- Attachment #1: Type: text/plain, Size: 3040 bytes --]

Hello,

I’m inlining some elisp which has behaviour I find unintuitive.
To view the bug I would run each top-level form with C-x C-e in turn in an elisp buffer.
This behaviour may be expected — the manual mentions something related — but I believe this is an unintended edge-case.
N.b. the use of `auto-fill-function’ is just for a variable which turns buffer-local when set, not as anything related to this particular symbol.
FWIW I believe this behaviour to be the root cause of https://github.com/joaotavora/yasnippet/issues/919 (which was closed due to not being able to reproduce it).

—————
;; Ensure that `auto-fill-function' has a buffer-local version and a global
;; version.
(setq auto-fill-function 'local-symbol)
(describe-variable 'auto-fill-function)
;; `auto-fill-function' is let-bound in the buffer scope
(let ((auto-fill-function 'temp-symbol))
  ;; Now there is no buffer-local variable for `auto-fill-function', but the
  ;; `let' unwrapping info is still there.
  (kill-local-variable 'auto-fill-function)
  ;; Since the check in the emacs source is
  ;; a) Is there a buffer-local variable.
  ;; b) Is there a let-binding shadowing the current variable.
  ;; Then this `setq' sets the *global* variable.
  (setq auto-fill-function 'other-symbol))
;; Exiting the `let' has special handling to avoid resetting a local variable
;; when the local variable was `let' bound, which means that overall the `setq'
;; set the global variable and the `let' has been lost.
(describe-variable 'auto-fill-function)
—————


I think the final state after having done the above should be:
1) Global value has not changed.
2) Local value has not changed.

While the observed state after the above is:
1) Global value has been set to `other-symbol'.
2) Local value has been removed.

- The `setq` inside the `let` sets the *global* value rather than creating a buffer-local variable.
- The `let` on the buffer-local version of the variable is lost.

The manual for `make-variable-buffer-local` — in `(elisp) Creating Buffer-Local` — does mention that if a variable is in a `let` binding then a `setq` does not create a buffer-local version.
That said, I’m guessing the intention of this behaviour is so a `let` binding on a global variable is modified rather than bypassed by a `setq`.
I’d suggest that is not relevant to the above code since the use of `kill-local-variable` means the `let` is effectively lost already (e.g. it does not get “reset” on an unwind).

I’m attaching the source code hack that I’ve started using to work around this.
I’m not proposing this as a change, just including it for extra context about the cause.
I *believe* that the form of the C code around here looks like the special case of a forwarded variable not having a buffer-local value but having a buffer-local `let` binding could easily have been an oversight rather than intention.
(N.b. for this hack I guessed the meanings of the `let` enum values).


[-- Attachment #2: emacs-patch.diff --]
[-- Type: application/octet-stream, Size: 1872 bytes --]

diff --git a/src/data.c b/src/data.c
index 57205d8..af427ea 100644
--- a/src/data.c
+++ b/src/data.c
@@ -1607,7 +1607,7 @@ set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
 	    if (idx > 0 && bindflag == SET_INTERNAL_SET
 	        && !PER_BUFFER_VALUE_P (buf, idx))
 	      {
-		if (let_shadows_buffer_binding_p (sym))
+		if (let_shadows_buffer_binding_p_and_is_global (sym))
 		  set_default_internal (symbol, newval, bindflag);
 		else
 		  SET_PER_BUFFER_VALUE_P (buf, idx, 1);
diff --git a/src/eval.c b/src/eval.c
index d002e81..9200d71 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -3491,6 +3491,25 @@ let_shadows_buffer_binding_p (struct Lisp_Symbol *symbol)
   return 0;
 }
 
+bool
+let_shadows_buffer_binding_p_and_is_global (struct Lisp_Symbol *symbol)
+{
+  union specbinding *p;
+  Lisp_Object buf = Fcurrent_buffer ();
+
+  for (p = specpdl_ptr; p > specpdl; )
+    if ((--p)->kind == SPECPDL_LET_DEFAULT)
+      {
+	struct Lisp_Symbol *let_bound_symbol = XSYMBOL (specpdl_symbol (p));
+	eassert (let_bound_symbol->u.s.redirect != SYMBOL_VARALIAS);
+	if (symbol == let_bound_symbol
+	    && EQ (specpdl_where (p), buf))
+	  return 1;
+      }
+
+  return 0;
+}
+
 static void
 do_specbind (struct Lisp_Symbol *sym, union specbinding *bind,
              Lisp_Object value, enum Set_Internal_Bind bindflag)
diff --git a/src/lisp.h b/src/lisp.h
index 0cad97c..0a296cd 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4221,6 +4221,7 @@ extern void mark_specpdl (union specbinding *first, union specbinding *ptr);
 extern void get_backtrace (Lisp_Object array);
 Lisp_Object backtrace_top_function (void);
 extern bool let_shadows_buffer_binding_p (struct Lisp_Symbol *symbol);
+extern bool let_shadows_buffer_binding_p_and_is_global (struct Lisp_Symbol *symbol);
 
 /* Defined in unexmacosx.c.  */
 #if defined DARWIN_OS && defined HAVE_UNEXEC

^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2023-09-12  0:00 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-03-24 13:37 bug#62419: 28.2; Elisp let-bound buffer-local variable and kill-local-variable Matthew Malcomson
2023-03-25 11:49 ` Eli Zaretskii
2023-03-25 16:19   ` Matthew Malcomson
2023-03-26 14:01 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-03-26 14:34   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-03-26 15:01   ` Matthew Malcomson
2023-03-26 15:16     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-03-26 15:30     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-03-29 10:56       ` Matthew Malcomson
2023-04-02 21:55         ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-09-12  0:00           ` Stefan Kangas

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.