Une fonction ou une macro peut-elle spécifier des avertissements du compilateur d'octets?

15

J'écris une fonction qui, en principe, prend un nombre arbitraire d'arguments. Dans la pratique, cependant, il ne devrait jamais être passé qu'un nombre pair d'arguments, et produira des résultats indésirables autrement.

Voici un exemple factice pour le contexte:

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

Lorsqu'un fichier elisp est compilé en octets, le compilateur d'octets envoie un avertissement lorsqu'il voit une fonction appelée avec le mauvais nombre d'arguments. Évidemment, cela n'arrivera jamais my-caller, car il est défini pour prendre n'importe quel nombre.

Pourtant, il y a peut-être une propriété de symbole que je peux définir, ou une (declare)forme que je peux ajouter à sa définition. Quelque chose pour informer l'utilisateur que cette fonction ne doit recevoir qu'un nombre pair d'arguments.

  1. Existe-t-il un moyen d'informer le compilateur d'octets de cette restriction?
  2. Sinon, est-ce possible avec une macro, au lieu d'une fonction?
Malabarba
la source
"... quand il voit une fonction appelée avec le mauvais nombre d'arguments"?
itsjeyd

Réponses:

13

EDIT : Une meilleure façon de le faire dans les Emacs récents est de définir une macro de compilation pour vérifier le nombre d'arguments. Ma réponse d'origine en utilisant une macro normale est conservée ci-dessous, mais une macro de compilation est supérieure car elle n'empêche pas de passer la fonction à funcallou applyau moment de l'exécution.

Dans les versions récentes d'Emacs, vous pouvez le faire en définissant une macro de compilation pour votre fonction qui vérifie le nombre d'arguments et génère un avertissement (ou même une erreur) si elle ne correspond pas. La seule subtilité est que la macro du compilateur doit retourner le formulaire d'appel de fonction d'origine inchangé pour l'évaluation ou la compilation. Cela se fait en utilisant un &wholeargument et en renvoyant sa valeur. Cela pourrait être accompli comme ceci:

(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

Notez que funcallet applypeuvent maintenant être utilisés, mais ils contournent la vérification des arguments par la macro du compilateur. Malgré leur nom, les macros compilateur semblent également être élargi au cours de « interprété » par l' évaluation C-xC-e, M-xeval-bufferet vous obtiendrez des erreurs sur l' évaluation, ainsi que sur la compilation de cet exemple.


La réponse originale suit:

Voici comment vous pouvez implémenter la suggestion de Jordon "d'utiliser une macro qui fournira des avertissements au moment de l'expansion". Cela s'avère très simple:

(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)

Essayer de compiler ce qui précède dans un fichier échouera (aucun .elcfichier n'est produit), avec un joli message d'erreur cliquable dans le journal de compilation, indiquant:

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

Vous pouvez également remplacer (error …)par (byte-compile-warn …)pour produire un avertissement au lieu d'une erreur, permettant à la compilation de continuer. (Merci à Jordon de l'avoir signalé dans les commentaires).

Étant donné que les macros sont développées au moment de la compilation, aucune pénalité d'exécution n'est associée à cette vérification. Bien sûr, vous ne pouvez pas empêcher d'autres personnes d'appeler my-caller--functiondirectement, mais vous pouvez au moins l'annoncer comme une fonction "privée" en utilisant la convention à double trait d'union.

Un inconvénient notable de l'utilisation d'une macro à cette fin est qu'elle my-callern'est plus une fonction de première classe: vous ne pouvez pas la transmettre à funcallou applyau moment de l'exécution (ou du moins, elle ne fera pas ce que vous attendez). À cet égard, cette solution n'est pas aussi bonne que de pouvoir simplement déclarer un avertissement du compilateur pour une fonction réelle. Bien sûr, l'utilisation applyrendrait impossible de vérifier le nombre d'arguments transmis à la fonction au moment de la compilation, donc c'est peut-être un compromis acceptable.

Jon O.
la source
2
Les avertissements de compilation sont créés avecbyte-compile-warn
Jordon Biondo
Je me demande maintenant si cela pourrait être plus efficacement accompli en définissant une macro de compilation pour la fonction. Cela éliminerait l'inconvénient de ne pas être applyou funcallle wrapper de macro. Je vais l'essayer et modifier ma réponse si cela fonctionne.
Jon O.
11

Oui, vous pouvez utiliser byte-defop-compilerpour spécifier réellement une fonction qui compile votre fonction, byte-defop-compilerpossède des subtilités intégrées pour vous aider à spécifier que vos fonctions doivent générer des avertissements basés sur un certain nombre d'arguments.

Documentation

Ajoutez un formulaire de compilation pour FUNCTION.Si la fonction est un symbole, la variable "byte-SYMBOL" doit nommer l'opcode à utiliser. Si function est une liste, le premier élément est la fonction et le deuxième élément est le symbole du bytecode. Le deuxième élément peut être nul, ce qui signifie qu'il n'y a pas d'opcode. COMPILE-HANDLER est la fonction à utiliser pour compiler cet octet-op, ou peut être les abréviations 0, 1, 2, 3, 0-1 ou 1-2. S'il est nul, le gestionnaire est "byte-compile-SYMBOL.


Usage

Dans votre cas spécifique, vous pouvez utiliser l'une des abréviations pour définir que votre fonction doit recevoir deux arguments.

(byte-defop-compiler my-caller 2)

Maintenant, votre fonction donnera des avertissements lorsqu'elle sera compilée avec autre chose que 2 arguments.

Si vous souhaitez donner des avertissements plus spécifiques et écrire vos propres fonctions de compilation. Regardez byte-compile-one-arget d'autres fonctions similaires dans bytecomp.el pour référence.

Notez que vous ne spécifiez pas seulement une fonction pour gérer la validation, mais aussi la compilation. Encore une fois, les fonctions de compilation dans bytecomp.el vous fourniront une bonne référence.


Des itinéraires plus sûrs

Ce n'est pas quelque chose que j'ai vu documenté ou discuté en ligne, mais dans l'ensemble, je dirais que c'est une voie peu judicieuse à prendre. La route correcte (IMO) serait d'écrire vos défuns avec des signatures descriptives ou d'utiliser une macro qui fournira des avertissements au moment de l'expansion, en vérifiant la longueur de vos arguments et en utilisant byte-compile-warnou errorpour afficher des erreurs. Il peut également être utile eval-when-compilepour vous de faire une vérification des erreurs.

Vous devrez également définir votre fonction avant de l'utiliser, et l'appel à byte-defop-compilerdevra l'être avant que le compilateur ne passe aux appels réels de votre fonction.

Encore une fois, il ne semble pas vraiment être documenté ou conseillé à partir de ce que j'ai vu (pourrait être faux) mais j'imagine que le modèle à suivre ici serait de spécifier une sorte de fichier d'en-tête pour votre paquet qui est plein d'un tas de defuns vides et appelle à byte-defop-compiler. Ce serait essentiellement un package qui est requis avant que votre vrai package puisse être compilé.

Opinion: Sur la base de ce que je sais, ce qui n'est pas grand-chose, parce que je viens d'apprendre tout cela, je vous conseillerais de ne jamais rien faire de tout cela. déjà

Jordon Biondo
la source
1
Connexes: il y a bytecomp-simplify qui enseigne au compilateur d'octets des avertissements supplémentaires.
Wilfred Hughes