1

I have the following code in my emacs init files, and it is causing an infinite recursion, which eventually bottoms out and throws an error.

(defun robs-org-mode-hook ()
  (org-indent-mode)
  ...)

(use-package org
  :init
  ;; ...
  :hook (org-mode . #'robs-org-mode-hook)

The stack if I run M-x toggle-debug-on-error:

org-mode()
run-hooks(change-major-mode-after-body-hook text-mode-hook outline-mode-hook org-mode-hook)
apply(run-hooks change-major-mode-after-body-hook (text-mode-hook outline-mode-hook org-mode-hook))
run-mode-hooks(org-mode-hook)
org-mode()
run-hooks(change-major-mode-after-body-hook text-mode-hook outline-mode-hook org-mode-hook)
apply(run-hooks change-major-mode-after-body-hook (text-mode-hook outline-mode-hook org-mode-hook))
run-mode-hooks(org-mode-hook)
org-mode()
run-hooks(change-major-mode-after-body-hook text-mode-hook outline-mode-hook org-mode-hook)
apply(run-hooks change-major-mode-after-body-hook (text-mode-hook outline-mode-hook org-mode-hook))
run-mode-hooks(org-mode-hook)
org-mode()
run-hooks(change-major-mode-after-body-hook text-mode-hook outline-mode-hook org-mode-hook)
apply(run-hooks change-major-mode-after-body-hook (text-mode-hook outline-mode-hook org-mode-hook))
run-mode-hooks(org-mode-hook)

Is there something wrong with that code, or is it a bug in emacs/org?

M-x emacs-version => "GNU Emacs 30.0.50 (build 1, x86_64-apple-darwin18.7.0, NS appkit-1671.60 Version 10.14.6 (Build 18G9323)) of 2023-06-26"

M-x org-version => Org mode version 9.6.6 (release_9.6.6 @ /Applications/Emacs.app/Contents/Resources/lisp/org/)

Rob N
  • 547
  • 2
  • 12
  • It's probably a bug in your setup. What is the value of `org-mode-hook` and `change-major-mode-after-body-hook`? Does this happen with `emacs -q -l /path/to/minimal/init.el` with a minimal init that just has the `defun` and the `use-package` above? – NickD Jun 30 '23 at 19:32
  • 1
    Mmm. Weird. If I change `#'robs-org-mode-hook` to simply `robs-org-mode-hook`, then it all works fine. But with that `#'` (which I copied from online examples), then the value of `org-mode-hook` contains `org-mode`. Without it, then the value of `org-mode-hook` contains the `robs...` function, as expected. And this is with minimal startup init code, as you suggested. – Rob N Jun 30 '23 at 20:31
  • OK - that's probably worth opening an issue against `use-package`. The examples at the [use-package repo home page](https://github.com/jwiegley/use-package#hooks) use just the symbol for the function, so the `use-package` macro must do something weird with the quoting, but it should probably protect against this kind of misbehavior. – NickD Jun 30 '23 at 21:06

1 Answers1

0

[The OP has already answered his question in a comment above, but I dug a little to understand what was going on.]

As the OP points out in a comment:

(use-package org
    :hook (org-mode . foo))

works fine.

You can use macroexpand to see what the macro expands to:

(pp (macroexpand '(use-package org :hook (org-mode . foo)))) ===>
(progn
  (defvar use-package--warning8
    #'(lambda (keyword err)
    (let
        ((msg
          (format "%s/%s: %s" 'org keyword
              (error-message-string err))))
      (display-warning 'use-package msg :error))))
  (condition-case-unless-debug err
      (progn
    (unless (fboundp 'foo) (autoload #'foo "org" nil t))
    (add-hook 'org-mode-hook #'foo))
    (error (funcall use-package--warning8 :catch err))))

As you can see, the function foo is added to org-mode-hook and everything is fine. But if you try the original form the OP used:

(pp (macroexpand '(use-package org :hook (org-mode . #'foo))))
(progn
  (defvar use-package--warning9
    #'(lambda (keyword err)
    (let
        ((msg
          (format "%s/%s: %s" 'org keyword
              (error-message-string err))))
      (display-warning 'use-package msg :error))))
  (condition-case-unless-debug err
      (progn
    (unless (fboundp 'org-mode) (autoload #'org-mode "org" nil t))
    (add-hook 'org-mode-hook #'org-mode)
    (add-hook 'function-hook #'org-mode)
    (add-hook 'foo-hook #'org-mode))
    (error (funcall use-package--warning9 :catch err))))

you can see three hooks are modified and they are modified incorrectly.

Part of the explanation is that #'foo is shorthand for (function foo) and if you try to macroexpand (use-package org :hook (org-mode . (function foo))) you get the same expansion as for #'foo.

The problem is that the expression (a . (b c)) is equal to the list (a b c), so when use-package sees (org-mode . (function foo)), it sees the same thing as if you had said (org-mode function foo) and interprets that as needing to set multiple hooks - see the second paragraph of section Hooks. Notice that there is no such thing as function-hook or foo-hook, but add-hook creates the variables if they don't exist already (do C-h f add-hook and read the doc string). They don't do any harm here because nobody uses them. But setting org-mode-hook to org-mode is indeed fatal.

The remaining question is: why are the three hooks (one real, two manufactured by use-package) set to #'org-mode, rather than to nil say? I don't know the answer to that; I would have to go through the guts of the macro and given that I don't use use-package, I will leave it at that.

As I suggested in a comment, it is worth opening an issue on the use-package Github site. You might also let the people who provided the online examples that you copied, know that they should fix their code.


EDIT: In a comment to this answer it is stated that "...if you pass only one argument (or a list of symbols) [as the value of the :hook keyword], use-package assumes those are the modes you want to activate your current 'use-package package'...". Although the description is not quite clear, I take it to mean that if you don't specify a function to add to the list of hooks (as in this case), it will take the name of the package you are setting up (org in this case), tack a -mode afterwards (org-mode in this case) and use that as the function. If that is indeed the case (and after some rudimentary checking, it seems that that is indeed the case), that would explain the above results, but it seems a somewhat bizarre interpretation by use-package of what the user wants to do.

It is also stated in that comment that the use-package Github page documents the above behavior, but I failed to find any reference to it. Caveat emptor.

NickD
  • 27,023
  • 3
  • 23
  • 42