Subject: bug#30008: 27.0.50; Subdirectory vs major mode in .dir-locals.el

From: [email-masked] (Kévin Le Gouguec)
Date: Sat, 06 Jan 2018 18:46:45 +0100

Hi,

While fiddling with directory-local variables, I think I stumbled on a
change of behavior between Emacs 25 and 26.

Given the following .dir-locals.el file:

    ((c-mode
      . ((my-list . (1 2 3))))
     ("subdir"
      . ((c-mode
          . ((my-list . (1 2 3 4 5 6)))))))

And the following directory structure:

    .
    ├── .dir-locals.el
    └── subdir
        └── foo.c

my-list gets different values depending on whether I open subdir/foo.c
with Emacs 25 or 26.  With 25, my-list equals (1 2 3 4 5 6); with 26,
my-list equals (1 2 3).

If I transpose the first (c-mode …) sexp with the ("subdir" …) one:

    (("subdir"
      . ((c-mode
          . ((my-list . (1 2 3 4 5 6))))))
     (c-mode
      . ((my-list . (1 2 3)))))

Then opening subdir/foo.c in Emacs 25 yields (1 2 3), while in 26 I
get (1 2 3 4 5 6).

I attached some scripts to reproduce the issue[1].  I skimmed
etc/NEWS.26 and (emacs)Directory Variables, but I did not find
anything suggesting that this change was deliberate, or which
behavior is "officially intended".

I haven't looked at the code yet, so I do not know if this change lies
more toward "obviously incorrect refactoring accident", "deliberate
and logical if you think about it", or something else.

AFAICT, there are two questions to answer:

- Which behavior should be kept?
- Should it be documented?

(Assuming what I observe is indeed due to an undocumented change;
maybe nothing changed and my setup is to blame, or the change *is*
documented and I missed the memo)

Since I can work around this change, I don't feel strongly about
either behavior.  I might find the old one more intuitive ("look for
definitions that match the major mode and the subfolder, apply the
last one"), but the new one also makes sense ("apply the first
definition that matches").

As for documentation, perhaps "multiple definitions in a single
.dir-locals.el file" is something of a weird edge case.  In that case,
maybe it's not worth setting it in stone, so that the code can keep
some wiggle room.

tl;dr: close this as soon as you're done reading I guess 😛

[1]: Sample output:

    $ ./reproduce.sh 
    ((c-mode
      . ((my-list . (1 2 3))))
     ("subdir"
      . ((c-mode
          . ((my-list . (1 2 3 4 5 6)))))))
    Opening subdir/foo.c with…
    25.3.50.1 (c3ff6712ad24fcf45874dc0665a8606e9b2208a4): (1 2 3 4 5 6)
    26.0.90 (aa66da220cdb6aaab5b347093fd40f0e1580913b): (1 2 3)
    27.0.50 (8e13d3ab1a31c3f5aee19ee7a92ddaed5fc3eb2d): (1 2 3)

    (("subdir"
      . ((c-mode
          . ((my-list . (1 2 3 4 5 6))))))
     (c-mode
      . ((my-list . (1 2 3)))))
    Opening subdir/foo.c with…
    25.3.50.1 (c3ff6712ad24fcf45874dc0665a8606e9b2208a4): (1 2 3)
    26.0.90 (aa66da220cdb6aaab5b347093fd40f0e1580913b): (1 2 3 4 5 6)
    27.0.50 (8e13d3ab1a31c3f5aee19ee7a92ddaed5fc3eb2d): (1 2 3 4 5 6)

reproduce.sh assumes that

- the attached Elisp files are in the same folder:

    .
    ├── reproduce.sh
    ├── subdir-after.el
    ├── subdir-before.el
    └── test.el

- emacs-{25,26,master} resolve to something executable; in my case:

    ~/bin
    ├── emacs-25 -> ~/Downloads/sources/emacs-25/src/emacs
    ├── emacs-26 -> ~/Downloads/sources/emacs-26/src/emacs
    └── emacs-master -> ~/Downloads/sources/emacs/src/emacs

In GNU Emacs 27.0.50 (build 6, i686-pc-linux-gnu, GTK+ Version 3.14.5)
 of 2017-12-14 built on nc10-laptop
Repository revision: 8e13d3ab1a31c3f5aee19ee7a92ddaed5fc3eb2d
Windowing system distributor 'The X.Org Foundation', version 11.0.11604000
System Description: BunsenLabs GNU/Linux 8.9 (Hydrogen)

Configured using:
 'configure --with-xwidgets --with-x-toolkit=gtk3'

Configured features:
XPM JPEG TIFF GIF PNG RSVG IMAGEMAGICK SOUND DBUS GSETTINGS NOTIFY
GNUTLS LIBXML2 FREETYPE XFT ZLIB TOOLKIT_SCROLL_BARS GTK3 X11 XWIDGETS
JSON LCMS2

Important settings:
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Text

Minor modes in effect:
  scroll-lock-mode: t
  flyspell-mode: t
  shell-dirtrack-mode: t
  show-paren-mode: t
  icomplete-mode: t
  global-page-break-lines-mode: t
  page-break-lines-mode: t
  electric-pair-mode: t
  delete-selection-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  column-number-mode: t
  line-number-mode: t
  auto-fill-function: do-auto-fill
  visual-line-mode: t
  transient-mark-mode: t

Load-path shadows:
/home/snip/.emacs.d/elpa/seq-20151121.1017/seq hides /home/snip/Downloads/sources/emacs/lisp/emacs-lisp/seq

Features:
(shadow sort mail-extr emacsbug sendmail gnus-async qp gnus-ml nndraft
nnmh nnfolder utf-7 epa-file network-stream nsm starttls gnus-agent
gnus-srvr gnus-score score-mode nnvirtual gnus-msg gnus-art mm-uu
mml2015 mm-view mml-smime smime dig mailcap nntp gnus-cache gnus-sum
gnus-group gnus-undo gnus-start gnus-cloud nnimap nnmail mail-source tls
gnutls utf7 netrc nnoo parse-time gnus-spec gnus-int gnus-range message
rmc puny format-spec rfc822 mml mml-sec epa derived epg mm-decode
mm-bodies mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader
gnus-win gnus nnheader gnus-util rmail rmail-loaddefs rfc2047 rfc2045
ietf-drums mail-utils mm-util mail-prsvr dired-aux wid-edit descr-text
markdown-mode rx color thingatpt noutline outline easy-mmode scroll-lock
flyspell face-remap iso-transl info-look tabify imenu man shell
pcomplete sh-script smie misearch multi-isearch ispell executable dired
dired-loaddefs delight advice eighters-theme quail cl-extra help-mode rg
s ibuf-ext ibuffer ibuffer-loaddefs grep compile comint ansi-color ring
edmacro kmacro disp-table paren icomplete page-break-lines elec-pair
delsel cus-start cus-load finder-inf info package easymenu epg-config
url-handlers url-parse auth-source cl-seq eieio eieio-core cl-macs
eieio-loaddefs password-cache url-vars seq byte-opt gv bytecomp
byte-compile cconv cl-loaddefs cl-lib time-date mule-util tooltip eldoc
electric uniquify ediff-hook vc-hooks lisp-float-type mwheel term/x-win
x-win term/common-win x-dnd tool-bar dnd fontset image regexp-opt fringe
tabulated-list replace newcomment text-mode elisp-mode lisp-mode
prog-mode register page menu-bar rfn-eshadow isearch timer select
scroll-bar mouse jit-lock font-lock syntax facemenu font-core
term/tty-colors frame cl-generic cham georgian utf-8-lang misc-lang
vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932
hebrew greek romanian slovak czech european ethiopic indian cyrillic
chinese composite charscript charprop case-table epa-hook jka-cmpr-hook
help simple abbrev obarray minibuffer cl-preloaded nadvice loaddefs
button faces cus-face macroexp files text-properties overlay sha1 md5
base64 format env code-pages mule custom widget hashtable-print-readable
backquote dbusbind inotify lcms2 dynamic-setting system-font-setting
font-render-setting xwidget-internal move-toolbar gtk x-toolkit x
multi-tty make-network-process emacs)

Memory information:
((conses 8 360934 34337)
 (symbols 24 50464 2)
 (miscs 20 785 1669)
 (strings 16 157140 4077)
 (string-bytes 1 4109553)
 (vectors 12 47141)
 (vector-slots 4 2021200 80958)
 (floats 8 394 582)
 (intervals 28 1976 261)
 (buffers 536 34)
 (heap 1024 60853 2288))