`###autodef`s in an `;;;###if`-gated autoload file are emitted as live autoloads when the cookie is false
- Dominant language
- Emacs Lisp
- Stars
- 22.7k
- Forks
- 3.1k
- Avg merge
- 10h 46m
- Merged PRs (30d)
- 4
Description
### Describe your issue
`doom-loaddefs--scan-file` (`lisp/cli/loaddefs.el`) skips the `;;;###autoload` cookies of a file whose `;;;###if` cookie is nil, but hands `doom-loaddefs--scan-autodefs` only the module-level `enabled?` flag, not the cookie verdict:
```elisp
(unless skip?
(quiet! (autoload-generate-file-autoloads file target-buffer)))
(when module?
(doom-loaddefs--scan-autodefs file target-buffer module enabled?))
```
So every `;;;###autodef` in an `###if`-gated file of an active module is written as a live `autoload` into that file instead of the disabled-feature stub. Calling it loads the whole gated file, which redefines whatever else that file `defun`s. The pre-rewrite core passed `(and (doom-module-p ...) (doom-file-cookie-p file "if" t))` (see 3a0f1aa3e).
**Symptom.** `:checkers spell` without `+flyspell`. `+flyspell.el` is gated by `;;;###if (modulep! +flyspell)` and autodefs `set-flyspell-predicate!`. The generated profile init still contains:
```elisp
(autoload 'set-flyspell-predicate! "~/.emacs.d/sources/doom+/modules/checkers/spell/autoload/+flyspell.el" ...)
```
`:lang markdown` calls `set-flyspell-predicate!` as soon as `markdown-mode` loads (an org src block fontified as markdown is enough). That loads `+flyspell.el`, whose `defun`s overwrite all four spell-fu aliases: `+spell/add-word`, `+spell/remove-word`, `+spell/next-error`, `+spell/previous-error`. `+spell/add-word` then fails because its interactive spec needs ispell's dictionary parameters, which spell-fu never initialises:
```
Debugger entered--Lisp error: (error "No data for dictionary \"nil\" in ‘ispell-local-dictionary-alist’ or ‘ispell-dictionary-alist’")
error("No data for dictionary \"%s\" in `ispell-local-dictionary-alist' or `ispell-dictionary-alist'" nil)
ispell-get-decoded-string(1)
flyspell-get-casechars()
flyspell-get-word()
(car (flyspell-get-word))
(progn (require 'flyspell) (car (flyspell-get-word)))
(list (progn (require 'flyspell) (car (flyspell-get-word))) (cond ((equal current-prefix-arg '(16)) 'session) ((equal current-prefix-arg '(4)) 'buffer)))
call-interactively(+spell/add-word)
```
**Proposed fix.** For module files `(not skip?)` is exactly `(and enabled? cookie-ok)`:
```diff
(when module?
- (doom-loaddefs--scan-autodefs file target-buffer module enabled?))
+ (doom-loaddefs--scan-autodefs file target-buffer module (not skip?)))
```
**Workaround** (verified in a fresh daemon: after `(require 'markdown-mode)`, `+spell/add-word` is still `spell-fu-word-add` and `+flyspell.el` is not in `load-history`):
```elisp
(defun set-flyspell-predicate! (&rest _) nil)
```
### Steps to reproduce
1. Enable `:checkers spell` (no `+flyspell`) and `:lang markdown`; `doom sync`.
2. Grep `.local/etc/@/init..el` for `set-flyspell-predicate!`: it is an `autoload` into `+flyspell.el`, not a stub.
3. In Emacs, `(symbol-function '+spell/add-word)` → `spell-fu-word-add`.
4. `(require 'markdown-mode)`.
5. `(symbol-function '+spell/add-word)` → the flyspell `defun`.
6. On a misspelt word in a `text-mode` buffer with `spell-fu-mode`, `M-x +spell/add-word` → backtrace above.
### System information
`doom info`: https://gist.github.com/dustinfarris/66cb6953a4f0841959498fa67c707387
### Disclosures
- [x] This issue was written with/by AI.
Drafted end to end by Claude Fable 5.1 in Claude Code, working in my Emacs session; the diagnosis, init excerpt, backtrace and workaround verification were all produced on my machine. I reviewed it before posting.
Contributor guide
Research direction
Start in lisp/cli/loaddefs.el with doom-loaddefs--scan-file and doom-loaddefs--scan-autodefs, then reproduce with :checkers spell without +flyspell and :lang markdown followed by doom sync. Done means the generated init file emits a disabled-feature stub rather than a live autoload for +flyspell.el, and loading markdown does not redefine the spell-fu commands.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- emacs-lisp
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100