函数或宏可以指定字节编译器警告吗?


15

我正在编写一个函数,该函数原则上接受任意数量的参数。但是,实际上,它只能传递 偶数个参数,否则会产生不良结果。

这是上下文的虚拟示例:

(defun my-caller (&rest args)
  (while args
    (call-other-function (pop args) (pop args))))

当对elisp文件进行字节编译时,如果字节编译器看到调用了错误数量的参数的函数,则会引发警告。显然,这绝不会发生my-caller,因为它被定义为可以接受任何数字。

仍然可能有一个我可以设置的符号属性,或者一个(declare)我可以添加到其定义中的表格。通知用户此函数应仅被赋予偶数个参数的信息。

  1. 有没有办法告知字节编译器此限制?
  2. 如果不是,是否可以使用宏而不是函数?

“ ...当看到函数被错误数量的参数调用时”?
itsjeyd 2014年

Answers:


13

编辑:在最近的Emacs中执行此操作的更好方法是通过定义编译器宏来检查参数数量。我使用普通宏的原始答案保留在下面,但是编译器宏比较好,因为它不会阻止将函数传递给运行时funcallapply在运行时传递。

在最新版本的Emacs中,您可以通过为函数定义一个编译器宏来执行此操作,该宏检查参数的数量并在不匹配时产生警告(甚至是错误)。唯一的妙处是,编译器宏应返回未更改的原始函数调用形式,以进行评估或编译。这是通过使用&whole参数并返回其值来完成的。可以这样完成:

(require 'cl-lib)

(defun my-caller (&rest args)
  (while args
    (message "%S %S" (pop args) (pop args))))

(define-compiler-macro my-caller (&whole form &rest args)
  (when (not (cl-evenp (length args)))
    (byte-compile-warn "`my-caller' requires an even number of arguments"))
  form)

(my-caller 1 2 3 4)
(my-caller 1 2)
(funcall #'my-caller 1 2 3 4)       ; ok
(apply #'my-caller '(1 2))          ; also ok
(my-caller 1)                       ; produces a warning
(funcall #'my-caller 1 2 3)         ; no warning!
(apply #'my-caller '(1 2 3))        ; also no warning

需要注意的是funcallapply现在可以使用,但它们绕过编译器宏参数检查。尽管他们的名字,编译器宏也似乎在通过的“解释”评估过程中进行扩展C-xC-eM-xeval-buffer,所以你会得到的评估,以及对编译这个例子中的错误。


原始答案如下:

这是实现Jordon建议“使用在扩展时将提供警告的宏”的建议。事实证明这很容易:

(require 'cl-lib)

(defmacro my-caller (&rest args)
  (if (cl-evenp (length args))
      `(my-caller--function ,@args)
    (error "Function `my-caller' requires an even number of arguments")))

(defun my-caller--function (&rest args)
  ;; function body goes here
  args)

(my-caller 1 2 3 4)
(my-caller 1 2 3)

尝试在文件中编译以上内容将失败(不生成.elc文件),并在编译日志中显示一个不错的可点击错误消息,内容为:

test.el:14:1:Error: `my-caller' requires an even number of arguments

您也可以替换为(error …)(byte-compile-warn …)以产生警告而不是错误,从而允许继续编译。(感谢乔顿在评论中指出了这一点)。

由于宏是在编译时扩展的,因此与该检查无关的运行时代价。当然,您不能阻止其他人my-caller--function直接呼叫,但是您至少可以使用双连字符约定将其宣传为“私有”功能。

为此目的使用宏的一个显着缺点是它my-caller不再是一流的函数:您不能将其传递给运行时funcallapply在运行时传递(或至少它不能满足您的期望)。在这方面,该解决方案不如能够简单地为实函数声明编译器警告那样好。当然,使用apply它将使无法在编译时检查传递给函数的参数数量,因此也许这是一个可以接受的折衷方案。


2
编译警告与创建byte-compile-warn
佐敦比翁

我现在想知道是否可以通过为函数定义一个编译器宏来更有效地完成此操作。这将消除不是对的缺点applyfuncall宏观包装。我会尝试一下,如果可以的话,请编辑我的答案。
乔恩·O.

11

是的,您可以byte-defop-compiler用来实际指定用于编译函数的函数,byte-defop-compiler并且具有一些内置的细微之处,可以帮助您指定函数应基于大量的args发出警告。

文献资料

为FUNCTION添加一个编译器表单。如果函数是一个符号,则变量“ byte-SYMBOL”必须命名要使用的操作码。如果function是一个列表,则第一个元素是函数,第二个元素是字节码符号。第二个元素可以为nil,表示没有操作码。COMPILE-HANDLER是用于编译此字节运算的函数,也可以是缩写0、1、2、3、0-1或1-2。如果为零,则处理程序为“ byte-compile-SYMBOL”。


用法

在您的特定情况下,可以使用缩写之一来定义应为您的函数指定两个args。

(byte-defop-compiler my-caller 2)

现在,您的函数将在使用2个args以外的任何参数进行编译时发出警告。

如果要给出更具体的警告并编写自己的编译器函数。请查看byte-compile-one-argbytecomp.el 中的及其他类似函数以供参考。

请注意,您不仅在指定处理验证的功能,而且实际上也在编译。再次,bytecomp.el中的编译函数将为您提供良好的参考。


更安全的路线

这不是我看到的在线记录或讨论的内容,但总的来说,我认为这是不明智的选择。正确的方法(IMO)将是使用描述性签名编写defun,或者使用将在扩展时提供警告的宏,检查args的长度以及使用byte-compile-warnerror显示错误。利用它eval-when-compile进行错误检查也可能使您受益。

您还需要在使用函数之前对其进行定义,并且对的调用byte-defop-compiler必须在编译器对函数的实际调用之前进行。

再次,似乎并没有从我所看到的内容中得到真正的记录或建议(可能是错误的),但是我想这里要遵循的模式是为您的程序包指定某种头文件,其中包含一堆空的defuns并致电byte-defop-compiler。基本上,这是可以在编译实际包之前需要的包。

意见:根据我所知道的(不是很多),因为我刚刚了解了所有这些内容,因此建议您不要执行任何此类操作。曾经


1
相关:有一个bytecomp-simplify可以教字节编译器其他警告。
Wilfred Hughes
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.