Subject: bug#67795: [PATCH] Handle local-variable major-mode remaps specifying non-existent mode

From: Stefan Monnier <[email-masked]>
Date: Tue, 12 Dec 2023 11:02:43 -0500

> Thanks, but if we want to integrate major-mode-remap-alist better, I'd
> rather we did that in some lower-level place, so that we wouldn't need
> to sprinkle these alist-get calls all over Emacs.
>
> Also, if we do this, there will be no way for specifying a particular
> mode in file-local variables.  Do we really want that?
>
> Stefan, WDYT?

I agree that we should try to keep it in "one place", but I don't think
it can be done right now without some code reorganization :-(

I can't wrap my head around what `hack-local-variables--find-variables`
is supposed to do, but for the other part of the change, maybe the patch
below is a good start?

        Stefan
diff --git a/lisp/files.el b/lisp/files.el
index f87e7807301..1935e4dbb4b 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -3428,12 +3451,9 @@ set-auto-mode
     (if modes
 	(catch 'nop
 	  (dolist (mode (nreverse modes))
-	    (if (not (functionp mode))
-		(message "Ignoring unknown mode `%s'" mode)
-	      (setq done t)
-	      (or (set-auto-mode-0 mode keep-mode-if-same)
-		  ;; continuing would call minor modes again, toggling them off
-		  (throw 'nop nil))))))
+	    (or (setq done (set-auto-mode-0 mode keep-mode-if-same))
+		;; continuing would call minor modes again, toggling them off
+		(throw 'nop nil)))))
     ;; Check for auto-mode-alist entry in dir-locals.
     (unless done
       (with-demoted-errors "Directory-local variables error: %s"
@@ -3445,10 +3465,7 @@ set-auto-mode
     (and (not done)
 	 (setq mode (hack-local-variables t (not try-locals)))
 	 (not (memq mode modes))	; already tried and failed
-	 (if (not (functionp mode))
-	     (message "Ignoring unknown mode `%s'" mode)
-	   (setq done t)
-	   (set-auto-mode-0 mode keep-mode-if-same)))
+	 (setq done (set-auto-mode-0 mode keep-mode-if-same)))
     ;; If we didn't, look for an interpreter specified in the first line.
     ;; As a special case, allow for things like "#!/bin/env perl", which
     ;; finds the interpreter anywhere in $PATH.
@@ -3490,7 +3507,7 @@ set-auto-mode
                               (error
                                "Problem in magic-mode-alist with element %s"
                                re))))))))
-	  (set-auto-mode-0 done keep-mode-if-same)))
+	  (setq done (set-auto-mode-0 done keep-mode-if-same))))
     ;; Next compare the filename against the entries in auto-mode-alist.
     (unless done
       (setq done (set-auto-mode--apply-alist auto-mode-alist
@@ -3515,7 +3532,7 @@ set-auto-mode
                                             (error
                                              "Problem with magic-fallback-mode-alist element: %s"
                                              re))))))))
-	  (set-auto-mode-0 done keep-mode-if-same)))
+	  (setq done (set-auto-mode-0 done keep-mode-if-same))))
     (unless done
       (set-buffer-major-mode (current-buffer)))))
 
@@ -3539,17 +3556,22 @@ set-auto-mode-0
 If optional arg KEEP-MODE-IF-SAME is non-nil, MODE is chased of
 any aliases and compared to current major mode.  If they are the
 same, do nothing and return nil."
-  (unless (and keep-mode-if-same
-	       (or (eq (indirect-function mode)
+  (let ((modefun (alist-get mode major-mode-remap-alist mode)))
+    (unless (and keep-mode-if-same
+		 (or (eq (indirect-function mode)
 		       (indirect-function major-mode))
 		   (and set-auto-mode--last
 		        (eq mode (car set-auto-mode--last))
 		        (eq major-mode (cdr set-auto-mode--last)))))
-    (when mode
-      (funcall (alist-get mode major-mode-remap-alist mode))
-      (unless (eq mode major-mode)
-        (setq set-auto-mode--last (cons mode major-mode)))
-      mode)))
+      (when mode
+        (if (not (functionp modefun))
+            (progn
+              (message "Ignoring unknown mode `%s'" mode)
+              nil)
+          (funcall modefun)
+          (unless (eq mode major-mode)
+            (setq set-auto-mode--last (cons mode major-mode)))
+          mode)))))
 
 (defvar file-auto-mode-skip "^\\(#!\\|'\\\\\"\\)"
   "Regexp of lines to skip when looking for file-local settings.