Byte-compiler对cl-loop宏的检查不够智能

如题所示,举例代码如下(摘自最近正在写的一个elisp工具)

(defun argee--get-definition (arg)
  "Get definition of the argument ARG."
  (cl-loop for def in argee--argument-definitions
           for (_whole argname) = (argee-match-string
                                   argee-common-argument-regexp
                                   arg)
           for argname = (string-remove-prefix
                          argee-negated-boolean-option-prefix
                          argname)
           for name = (argee-argument-name def)
           for alias = (argee-argument-alias def)
           when (or (string= argname name)
                    (member argname alias))
           return def))

编译这个函数

(byte-compile #'argee--get-definition)

编译器警告

Warning: Unused lexical variable ‘argname’

然而有好好的用到argname啊,兄。

我认为这是Emacs的一个bug,不过似乎没有那么容易复现。

Context

这算是啥子?

这个whole我不用啊…_开头的变量不就是告诉编译器这个变量不使用的么,让编译器不要报警

第二个 argname 没用到,第一个用到了。这有个简单的例子:

(defun bar ()
  (let (x)
    (let ((x (length x)))
      123)))

用这个例子来重现:

(defun foo ()
  (cl-loop for i below 3
           for i = (+ i 100)
           collect 123))

;; Warning: Unused lexical variable `i'

所以说最好换个名字,就很清楚了:

(cl-loop for i below 3
         for j = (+ i 100)
         collect 123)

;; Warning: Unused lexical variable `j'
(loop for (a b) = c ...)
           ^ 這是非法的

GG,从來沒用过。

为什么非法?这不是cl-loop内置的解构赋值么?还是说里头有什么讲究?

这没问题,cl-loop 能够解构 List:

(cl-loop repeat 1
         for (a b) = '(1 2)
         collect (+ a b))
;; => (3)

参考 cl-destructuring-bind

1 个赞

所以如前所说,这不是 Bug。只是如果 Emacs 要是能准确地指出第几行第几列就好了。

所以用

(defun argee--get-definition (arg)
  "Get definition of the argument ARG."
  (cl-loop for def in argee--argument-definitions
           for (_whole argname) = (argee-match-string
                                   argee-common-argument-regexp
                                   arg)
           do (setq argname 
                    (string-remove-prefix
                          argee-negated-boolean-option-prefix
                          argname))
           for name = (argee-argument-name def)
           for alias = (argee-argument-alias def)
           when (or (string= argname name)
                    (member argname alias))
           return def))

其实,cl-loop的解构是穷人版本,只能按位置匹配或者匹配rest。cl-destructuring-bind的解构支持full CL lambda-list(参数列表)那才是大杀器

为解构提供默认值

(cl-destructuring-bind (&optional a (b 3)) '(3)
         (list a b))
;; => (3 3)

直接解构plist

(cl-destructuring-bind (&key a (b 4)) '(:a 3)
         (list a b))
;; => (3 4)

为什么我觉得这是bug,是因为let*cl-destructuring-bind&aux参数都没有这种问题

(cl-destructuring-bind (a b &aux (a (1+ a))) '(2 3)
         (list a b))
;; => (3 3) Without byte-compiler warning
(let* ((a 3)
         (a 4))
         (list a))
;; => (4) Without byte-compiler warning
(byte-compile '(cl-destructuring-bind (a b &aux (a (+ 1 2))) '(2 3)
                 (list a b)))

这样就会有 Warning

老是忘开 lexical-binding,不开就沒报错了。


Common Lisp 用 (declare ignorable)

讲道理和 ignorable 没关系,CL 一样会对这个报错。

这里有警告,提示(应该是第一个)a 没用到。

下面这个函数会提示 3 个 a 没用到:

(defun foo ()
  (let* ((a 1)
         (a 2)
         (a 3)
         (a 4))
    (list a)))

我好像理解错了,问题在

(loop for (a b) = '(1 2) for a = (+ 1 a b) return a)

但是这样是可以的

(defun ok ()
         (let ((lexical-binding t))
           (let* ((a 3)
                  (a (1+ a)))
             (list a))))

我的例子里,第二次的for赋值也有用到第一次的值。反而报错了,感觉还是cl-loop实现的bug。或者说,是loop宏的标准里有bug

总算明白了你的意思,用个更简单的例子重现:

(defun foo ()
  (cl-loop for i = 1
           for i = (1+ i)
           return i))

;; Unused lexical variable `x'

cl-loop 展开会得到

(let* ((i nil)
       (i nil))
  (setq i 1)
  (setq i (1+ i))
  i)

因此会报告 Unused lexical variable x,你可以用一个新名字规避这个问题。而且,你的例子中应该用 with,而不是 for

2 个赞

䃼充一下,Common Lisp 会直接报错

Duplicated LOOP iteration variable I.
1 个赞