~pkal/compat#9: 
Invalid function: if-let when loading subr-x on Emacs 24.3

On Emacs 24.3.1/CentOS, when I try load subr-x, this error appears:

compat--require: Invalid function: if-let

Reproduction steps from emacs -q:

;; -*- lexical-binding: t; -*-

(require 'package)

(package-initialize)

;; For unknown reasons I can't reach the official elpa.gnu.org on CentOS 7 container
(setq package-archives '(("gnu" . "http://1.15.88.122/gnu/")))

(package-refresh-contents)

(unless (package-installed-p 'nadvice) (package-install 'nadvice))
(unless (package-installed-p 'compat) (package-install 'compat))

(require 'nadvice)

(require 'compat)

(require 'subr-x)

Backtrace:

Debugger entered--Lisp error: (invalid-function if-let)
  if-let()
  funcall(if-let)
  (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--)))
  (while --dolist-tail-- (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--))))
  (let ((--dolist-tail-- (cdr entry))) (while --dolist-tail-- (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--)))))
  (let ((load-file-name nil)) (let ((--dolist-tail-- (cdr entry))) (while --dolist-tail-- (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--))))))
  (let ((entry (assq feature after-load-alist))) (let ((load-file-name nil)) (let ((--dolist-tail-- (cdr entry))) (while --dolist-tail-- (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--)))))))
  (if (eq feature (quote subr-x)) (let ((entry (assq feature after-load-alist))) (let ((load-file-name nil)) (let ((--dolist-tail-- (cdr entry))) (while --dolist-tail-- (let ((form ...)) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--))))))) (apply oldfun feature args))

Looks like it's related to this:

(compat-advise require (feature &rest args)
  "Allow for Emacs 24.x to require the inexistent FEATURE subr-x."
  ;; As the compatibility advise around `require` is more a hack than
  ;; of of actual value, the highlighting is suppressed.
  :no-highlight t
  (if (eq feature 'subr-x)
      (let ((entry (assq feature after-load-alist)))
        (let ((load-file-name nil))
          (dolist (form (cdr entry))
            (funcall (eval form t)))))
    (apply oldfun feature args)))

Inspecting after-load-alist:

(subr-x
 (if load-file-name
     (let ((fun (make-symbol "eval-after-load-helper")))
       (fset fun `(lambda (file)
                    (if (not (equal file ',load-file-name))
                        nil
                      (remove-hook 'after-load-functions ',fun)
                      ,'(funcall '(closure (t) nil
                                   (funcall
                                    '(closure (t) nil
                                      (defalias 'if-let #'compat--if-let))))))))
       (add-hook 'after-load-functions fun))
   (funcall '(closure (t) nil
              (funcall
               '(closure (t) nil
                 (defalias 'if-let #'compat--if-let)))))))
(funcall '(closure (t) nil (funcall '(closure (t) nil (defalias 'if-let #'compat--if-let)))))
=> if-let

(eval form t)
=>if-let

(funcall 'if-let)
=> Error!

It seems to be fixed if I change (funcall (eval form t)) to (eval form t) only.

Status
REPORTED
Submitter
~daanturo
Assigned to
No-one
Submitted
2 years ago
Updated
2 years ago
Labels
No labels applied.

~pkal 2 years ago

"~daanturo" outgoing@sr.ht writes:

On Emacs 24.3.1/CentOS, when I try load subr-x, this error appears:

compat--require: Invalid function: if-let

That is an unusual error message...

Reproduction steps from emacs -q:

;; -*- lexical-binding: t; -*-

(require 'package)

(package-initialize)

;; For unknown reasons I can't reach the official elpa.gnu.org on CentOS 7 container
(setq package-archives '(("gnu" . "http://1.15.88.122/gnu/")))

(package-refresh-contents)

(unless (package-installed-p 'nadvice) (package-install 'nadvice))
(unless (package-installed-p 'compat) (package-install 'compat))

(require 'nadvice)

(require 'compat)

(require 'subr-x)

This looks fine. Is the mirror up to date?

Backtrace:

Debugger entered--Lisp error: (invalid-function if-let)
  if-let()
  funcall(if-let)
  (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--)))
  (while --dolist-tail-- (let ((form (car --dolist-tail--))) (funcall (eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--))))
  (let ((--dolist-tail-- (cdr entry))) (while --dolist-tail-- (let
((form (car --dolist-tail--))) (funcall (eval form t)) (setq
--dolist-tail-- (cdr --dolist-tail--)))))
  (let ((load-file-name nil)) (let ((--dolist-tail-- (cdr entry)))
(while --dolist-tail-- (let ((form (car --dolist-tail--))) (funcall
(eval form t)) (setq --dolist-tail-- (cdr --dolist-tail--))))))
  (let ((entry (assq feature after-load-alist))) (let ((load-file-name
nil)) (let ((--dolist-tail-- (cdr entry))) (while --dolist-tail-- (let
((form (car --dolist-tail--))) (funcall (eval form t)) (setq
--dolist-tail-- (cdr --dolist-tail--)))))))
  (if (eq feature (quote subr-x)) (let ((entry (assq feature
after-load-alist))) (let ((load-file-name nil)) (let ((--dolist-tail--
(cdr entry))) (while --dolist-tail-- (let ((form ...)) (funcall (eval
form t)) (setq --dolist-tail-- (cdr --dolist-tail--))))))) (apply
oldfun feature args))

Looks like it's related to this:

(compat-advise require (feature &rest args)
  "Allow for Emacs 24.x to require the inexistent FEATURE subr-x."
  ;; As the compatibility advise around `require` is more a hack than
  ;; of of actual value, the highlighting is suppressed.
  :no-highlight t
  (if (eq feature 'subr-x)
      (let ((entry (assq feature after-load-alist)))
        (let ((load-file-name nil))
          (dolist (form (cdr entry))
            (funcall (eval form t)))))
    (apply oldfun feature args)))

Inspecting after-load-alist:

(subr-x
 (if load-file-name
     (let ((fun (make-symbol "eval-after-load-helper")))
       (fset fun `(lambda (file)
                    (if (not (equal file ',load-file-name))
                        nil
                      (remove-hook 'after-load-functions ',fun)
                      ,'(funcall '(closure (t) nil
                                   (funcall
                                    '(closure (t) nil
                                      (defalias 'if-let #'compat--if-let))))))))
       (add-hook 'after-load-functions fun))
   (funcall '(closure (t) nil
              (funcall
               '(closure (t) nil
                 (defalias 'if-let #'compat--if-let)))))))
(funcall '(closure (t) nil (funcall '(closure (t) nil (defalias 'if-let #'compat--if-let)))))
=> if-let

(eval form t)
=>if-let

(funcall 'if-let)
=> Error!

It seems to be fixed if I change (funcall (eval form t)) to (eval form t) only.

I will have to take some time to take a look at what could be going wrong here. I suspect this might be an issue in compat-macs.el and code being generated when defining if-let... Could you macro-expand the definition the definition in compat-25.el for me to see if it is doing anything unusual?

~daanturo 2 years ago

Could you macro-expand the definition the definition in compat-25.el for me to see if it is doing anything unusual?

(if
    (and t
	 (not
	  (fboundp 'if-let)))
    (progn
      (defmacro if-let
	(spec then &rest else)
	"[Compatibility macro for `if-let']\n\n:realname" compat--if-let :feature 'subr-x
	(declare
	 (indent 2)
	 (debug
	  ([&or
	    (symbolp form)
	    (&rest
	     [&or symbolp
		  (symbolp form)
		  (form)])]
	   body)))
	(when
	    (and
	     (<=
	      (length spec)
	      2)
	     (not
	      (listp
	       (car spec))))
	  (setq spec
		(list spec)))
	`(compat--if-let* ,spec ,then ,(macroexp-progn else)))))

The mentioned mirror was from https://elpamirror.emacs-china.org/ , although I don't live in China.

~pkal 2 years ago

"~daanturo" outgoing@sr.ht writes:

Could you macro-expand the definition the definition in compat-25.el for me to see if it is doing anything unusual?

(if
    (and t
	 (not
	  (fboundp 'if-let)))
    (progn
      (defmacro if-let
	(spec then &rest else)
	"[Compatibility macro for `if-let']\n\n:realname" compat--if-let :feature 'subr-x
	(declare
	 (indent 2)
	 (debug
	  ([&or
	    (symbolp form)
	    (&rest
	     [&or symbolp
		  (symbolp form)
		  (form)])]
	   body)))
	(when
	    (and
	     (<=
	      (length spec)
	      2)
	     (not
	      (listp
	       (car spec))))
	  (setq spec
		(list spec)))
	`(compat--if-let* ,spec ,then ,(macroexp-progn else)))))

This seems fine, what about if-let*? That is being used here (because the two macros are related), and all the "heavy lifting" is done in that macro.

~daanturo 2 years ago

Note the eval-after-load 'subr-x part, it will be called by the require advice.

(funcall (eval
	  
	  `(funcall ',(lambda nil
	                (defalias 'if-let* #'compat--if-let*)))
	  
	  t))

Invalid function: if-let* : looks like the error with if-let.

~daanturo 2 years ago

The expanded macro for if-let* as mentioned above (I forgot to include it):

(progn
  (defmacro compat--if-let*
    (varlist then &rest else)
    "[Compatibility macro for `if-let*']\n\nBind variables according to VARLIST and evaluate THEN or ELSE.\nThis is like `if-let' but doesn't handle a VARLIST of the form\n(SYMBOL SOMETHING) specially."
    (declare
     (indent 2)
     (debug
      ((&rest
	[&or symbolp
	     (symbolp form)
	     (form)])
       body)))
    (let
	((empty
	  (make-symbol "s"))
	 (last t)
	 list)
      (dolist
	  (var varlist)
	(push
	 `(,(if
		(cdr var)
		(car var)
	      empty)
	   (and ,last ,(or
			(cadr var)
			(car var))))
	 list)
	(when
	    (or
	     (cdr var)
	     (consp
	      (car var)))
	  (setq last
		(caar list))))
      `(let* ,(nreverse list)
	 (if ,(caar list)
	     ,then ,@else))))
  (when
      (and t
	   (not
	    (fboundp 'if-let*)))
    (eval-after-load 'subr-x
      `(funcall ',(lambda nil
		    (defalias 'if-let* #'compat--if-let*))))))

~pkal 2 years ago

"~daanturo" outgoing@sr.ht writes:

Note the eval-after-load 'subr-x part, it will be called by the require advice.

(funcall (eval
	  
	  `(funcall ',(lambda nil
	                (defalias 'if-let* #'compat--if-let*)))
	  
	  t))

Invalid function: if-let* : looks like the error with if-let.

The background for what is happening here is to be found in https://nullprogram.com/blog/2018/02/22/ (as referenced in `compat--generate-default').

What this indicates to me is that the advice on require might be mistaken in the assumptions it makes on the structure of `after-load-alist' -- at least how it used to look like on Emacs 24.3.

You mentioned having a fix that appeared to work on your system, right? Could you formalise that into a patch?

Register here or Log in to comment, or comment via email.