Package: emacs;
Reported by: Julian Gilbey <julian-gnu <at> d-and-j.net>
Date: Wed, 25 Nov 2020 13:10:01 UTC
Severity: normal
Tags: moreinfo, wontfix
Found in version 27.1
Done: Lars Ingebrigtsen <larsi <at> gnus.org>
Bug is archived. No further changes may be made.
View this message in rfc822 format
From: Stefan Monnier <monnier <at> iro.umontreal.ca> To: Julian Gilbey <julian-gnu <at> d-and-j.net> Cc: 44864 <at> debbugs.gnu.org Subject: bug#44864: 27.1; advice.el: ad-with-originals deprecated, but no advice on replacement Date: Thu, 26 Nov 2020 12:54:51 -0500
> That sounds eminently sensible. Scouring the rest of Dave's file, I > found the following comment at the beginning of the function you've > just quoted from: Hmm... seems relevant indeed. > (defun multi-install-mode (mode &optional chunk-fn base) > "Add MODE to the multiple major modes supported by the current buffer. > CHUNK-FN, if non-nil, is a function to select the mode of a chunk, > added to the list `multi-chunk-fns'. BASE non-nil means that this > is the base mode." > (unless (memq mode multi-indirect-buffers-alist) ; be idempotent > ;; This is part of a grim hack for lossage in AUCTeX, which > ;; bogusly advises `hack-one-local-variable'. This loses, due to > ;; the way advice works, when we run `multi-hack-local-variables' > ;; below -- there ought to be a way round this, probably with CL's > ;; flet. Any subsequent use of it then fails because advice has > ;; captured the now-unbound variable `late-hack'... Thus ensure > ;; we've loaded the mode in advance to get any autoloads sorted > ;; out. Do it generally in case other modes have similar > ;; problems. [The AUCTeX stuff is in support of an undocumented > ;; feature which is unnecessary and, anyway, wouldn't need advice > ;; to implement. Unfortunately the maintainer seems not to > ;; understand the local variables mechanism and wouldn't remove > ;; this. To invoke minor modes, you should just use `mode:' in > ;; `local variables'.] I'm not sure I understand the details of the problem described, but I think I see the general idea. Indeed when multi-mode.el does: (let ((late-hack (symbol-function 'hack-one-local-variable))) (fset 'hack-one-local-variable (lambda (var val) (unless (eq var 'mode) (funcall late-hack var val)))) (unwind-protect (hack-local-variables) (fset 'hack-one-local-variable late-hack)))) if the code run within `hack-local-variables` ends up looking at the definition of `hack-one-local-variable` and saving it somewhere for later use (e.g. if `defadvice` is used at this moment, maybe because AUCTeX was autoloaded during the course of running `hack-local-variables`), then we could have a problem. To some extent the final `fset` above should hide the problem, but it probably just ends up putting advice in an inconsistent state or worse (depending on the version of `advice`). I think with the new version of `advice.el` (introduced when `nadvice.el` was introduced, to make them interact tolerably) the problem should be minor: the final `fset` should not put `advice.el` in an inconsistent state but should simply undo the `defadvice` that happened during `hack-local-variables` (which is still undesirable, arguably, but should be mostly harmless as long as you don't rely on the feature implemented by that advice). > So I'm guessing that's what he's referring to. Indeed. You might like to try the patch below (guaranteed 100% untested). Do you think it would be worthwhile to add it to GNU ELPA? Stefan diff --git a/multi-mode.el b/multi-mode.el index cece376..06291cf 100644 --- a/multi-mode.el +++ b/multi-mode.el @@ -1,6 +1,6 @@ -;;; multi-mode.el --- support for multiple major modes +;;; multi-mode.el --- support for multiple major modes -*- lexical-binding: t; -*- -;; Copyright (C) 2003, 2004, 2007, 2009 Free Software Foundation, Inc. +;; Copyright (C) 2003-2020 Free Software Foundation, Inc. ;; Author: Dave Love <fx <at> gnu.org> ;; Keywords: languages, extensions, files @@ -164,27 +164,26 @@ Buffer local.") "Original value of `imenu-create-index-function' for the buffer's mode.") (make-variable-buffer-local 'multi-late-index-function) +(define-obsolete-variable-alias 'multi-alist 'multi-mode-alist nil) (defvar multi-mode-alist nil "Alist of elements (MODE . FUNCTION) specifying a buffer's multiple modes. MODE is a major mode and FUNCTION is a function used as an element of `multi-chunk-fns' or nil. Use nil if MODE is detected by another element of the alist.") -(if (fboundp 'define-obsolete-variable-alias) - (define-obsolete-variable-alias 'multi-alist 'multi-mode-alist) - (make-obsolete-variable 'multi-alist 'multi-mode-alist)) - ;; See the commentary below. (defun multi-hack-local-variables () "Like `hack-local-variables', but ignore `mode' items." - (let ((late-hack (symbol-function 'hack-one-local-variable))) - (fset 'hack-one-local-variable - (lambda (var val) - (unless (eq var 'mode) - (funcall late-hack var val)))) + (let ((f (lambda (orig-fun var val) + (unless (eq var 'mode) + (funcall orig-fun var val))))) (unwind-protect - (hack-local-variables) - (fset 'hack-one-local-variable late-hack)))) + (progn + (advice-add 'hack-one-local-variable :around f) + (hack-local-variables)) + (advice-remove 'hack-one-local-variable f)))) + +(defvar multi-mode) (defun multi-install-mode (mode &optional chunk-fn base) "Add MODE to the multiple major modes supported by the current buffer. @@ -235,21 +234,7 @@ is the base mode." (funcall mode)) ;; Now we can make it local: (set (make-local-variable 'multi-mode) t) - ;; Use file's local variables section to set variables in - ;; this buffer. (Don't just copy local variables from the - ;; base buffer because it may have set things locally that - ;; we don't want in the other modes.) We need to prevent - ;; `mode' being processed and re-setting the major mode. - ;; It all goes badly wrong if `hack-one-local-variable' is - ;; advised. The appropriate mechanism to get round this - ;; appears to be `ad-with-originals', but we don't want to - ;; pull in the advice package unnecessarily. `flet'-like - ;; mechanisms lose with advice because `fset' acts on the - ;; advice anyway. - (if (featurep 'advice) - (ad-with-originals (hack-one-local-variable) - (multi-hack-local-variables)) - (multi-hack-local-variables)) + (multi-hack-local-variables) ;; Indentation should first narrow to the chunk. Modes ;; should normally just bind `indent-line-function' to ;; handle indentation. @@ -290,9 +275,9 @@ is the base mode." ;; Kill the base buffer along with the indirect one; careful not ;; to infloop. (add-hook 'kill-buffer-hook - '(lambda () - (setq kill-buffer-hook nil) - (kill-buffer (buffer-base-buffer))) + (lambda () + (setq kill-buffer-hook nil) + (kill-buffer (buffer-base-buffer))) t t) ;; This should probably be at the front of the hook list, so ;; that other hook functions get run in the (perhaps) @@ -306,7 +291,7 @@ is the base mode." (setq buffer-file-coding-system coding) ;; For benefit of things like VC (setq buffer-file-name file) - (vc-find-file-hook)) + (vc-refresh-state)) ;; Propagate updated values of the relevant buffer-local ;; variables to the indirect buffers. (dolist (x alist) @@ -373,8 +358,7 @@ Fontifies chunk-by-chunk within the region from START for up to Works piece-wise in all the chunks with the same major mode. Assigned to `imenu-create-index-function'." (let ((selected-mode major-mode) - imenu-alist ; accumulator - last mode) + imenu-alist) ; accumulator (multi-map-over-chunks (point-min) (point-max) (lambda () @@ -454,8 +438,7 @@ Destructively modifies `multi-mode-list' to avoid consing in Return a list (MODE START END), the value returned by the function in the list for which START is closest to POS (and before it); i.e. the innermost mode is selected. POS defaults to point." - (let ((fns multi-chunk-fns) - (start (point-min)) + (let ((start (point-min)) (mode (with-current-buffer (multi-base-buffer) major-mode)) (end (point-max)) @@ -500,28 +483,6 @@ mode is selected. POS defaults to point." (fundamental-mode) (error "`multi-mode-alist' not defined for multi-mode"))) -;; In 21.3, Flyspell breaks things, apparently by getting an error in -;; post-command-hook and thus clobbering it. In development code it -;; doesn't do that, but does check indirect buffers it shouldn't. I'm -;; not sure exactly how this happens, but checking flyspell-mode in -;; the hook functions cures this. For the moment, we'll hack this up. -;; (Let's not bring advice into it...) -(eval-after-load "flyspell" - '(progn - (defalias 'flyspell-post-command-hook - `(lambda () - ,(concat (documentation 'flyspell-post-command-hook) - "\n\n[Wrapped by multi-mode.]") - (if flyspell-mode - (funcall ,(symbol-function 'flyspell-post-command-hook))))) - - (defalias 'flyspell-pre-command-hook - `(lambda () - (concat (documentation 'flyspell-pre-command-hook) - "\n\n[Wrapped by multi-mode.]") - (if 'flyspell-mode - (funcall ,(symbol-function 'flyspell-pre-command-hook))))))) - ;; This is useful in composite modes to determine whether a putative ;; major mode is safe to invoke. (defun multi-mode-major-mode-p (value)
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.