Warn about misplaced or duplicated function/macro declarations

Doc strings, `declare` and `interactive` forms must appear in that
order and at most once each.  Complain if they don't, instead of
silently ignoring the problem (bug#55905).

* lisp/emacs-lisp/byte-run.el (byte-run--parse-body)
(byte-run--parse-declarations): New.
(defmacro, defun): Check for declaration well-formedness as
described above.  Clarify doc strings.  Refactor some common code.
* test/lisp/emacs-lisp/bytecomp-resources/fun-attr-warn.el:
* test/lisp/emacs-lisp/bytecomp-tests.el (bytecomp-fun-attr-warn):
New test.
This commit is contained in:
Mattias Engdegård 2022-06-17 17:06:05 +02:00
parent e9c50055ff
commit 73e75e18d1
3 changed files with 446 additions and 91 deletions

View file

@ -0,0 +1,266 @@
;;; -*- lexical-binding: t -*-
;; Correct
(defun faw-str-decl-code (x)
"something"
(declare (pure t))
(print x))
(defun faw-doc-decl-code (x)
(:documentation "something")
(declare (pure t))
(print x))
(defun faw-str-int-code (x)
"something"
(interactive "P")
(print x))
(defun faw-doc-int-code (x)
(:documentation "something")
(interactive "P")
(print x))
(defun faw-decl-int-code (x)
(declare (pure t))
(interactive "P")
(print x))
(defun faw-str-decl-int-code (x)
"something"
(declare (pure t))
(interactive "P")
(print x))
(defun faw-doc-decl-int-code (x)
(:documentation "something")
(declare (pure t))
(interactive "P")
(print x))
;; Correct (last string is return value)
(defun faw-str ()
"something")
(defun faw-decl-str ()
(declare (pure t))
"something")
(defun faw-decl-int-str ()
(declare (pure t))
(interactive)
"something")
(defun faw-str-str ()
"something"
"something else")
(defun faw-doc-str ()
(:documentation "something")
"something else")
;; Incorrect (bad order)
(defun faw-int-decl-code (x)
(interactive "P")
(declare (pure t))
(print x))
(defun faw-int-str-code (x)
(interactive "P")
"something"
(print x))
(defun faw-int-doc-code (x)
(interactive "P")
(:documentation "something")
(print x))
(defun faw-decl-str-code (x)
(declare (pure t))
"something"
(print x))
(defun faw-decl-doc-code (x)
(declare (pure t))
(:documentation "something")
(print x))
(defun faw-str-int-decl-code (x)
"something"
(interactive "P")
(declare (pure t))
(print x))
(defun faw-doc-int-decl-code (x)
(:documentation "something")
(interactive "P")
(declare (pure t))
(print x))
(defun faw-int-str-decl-code (x)
(interactive "P")
"something"
(declare (pure t))
(print x))
(defun faw-int-doc-decl-code (x)
(interactive "P")
(:documentation "something")
(declare (pure t))
(print x))
(defun faw-int-decl-str-code (x)
(interactive "P")
(declare (pure t))
"something"
(print x))
(defun faw-int-decl-doc-code (x)
(interactive "P")
(declare (pure t))
(:documentation "something")
(print x))
(defun faw-decl-int-str-code (x)
(declare (pure t))
(interactive "P")
"something"
(print x))
(defun faw-decl-int-doc-code (x)
(declare (pure t))
(interactive "P")
(:documentation "something")
(print x))
(defun faw-decl-str-int-code (x)
(declare (pure t))
"something"
(interactive "P")
(print x))
(defun faw-decl-doc-int-code (x)
(declare (pure t))
(:documentation "something")
(interactive "P")
(print x))
;; Incorrect (duplication)
(defun faw-str-str-decl-int-code (x)
"something"
"something else"
(declare (pure t))
(interactive "P")
(print x))
(defun faw-str-doc-decl-int-code (x)
"something"
(:documentation "something else")
(declare (pure t))
(interactive "P")
(print x))
(defun faw-doc-str-decl-int-code (x)
(:documentation "something")
"something else"
(declare (pure t))
(interactive "P")
(print x))
(defun faw-doc-doc-decl-int-code (x)
(:documentation "something")
(:documentation "something else")
(declare (pure t))
(interactive "P")
(print x))
(defun faw-str-decl-str-int-code (x)
"something"
(declare (pure t))
"something else"
(interactive "P")
(print x))
(defun faw-doc-decl-str-int-code (x)
(:documentation "something")
(declare (pure t))
"something else"
(interactive "P")
(print x))
(defun faw-str-decl-doc-int-code (x)
"something"
(declare (pure t))
(:documentation "something else")
(interactive "P")
(print x))
(defun faw-doc-decl-doc-int-code (x)
(:documentation "something")
(declare (pure t))
(:documentation "something else")
(interactive "P")
(print x))
(defun faw-str-decl-decl-int-code (x)
"something"
(declare (pure t))
(declare (indent 1))
(interactive "P")
(print x))
(defun faw-doc-decl-decl-int-code (x)
(:documentation "something")
(declare (pure t))
(declare (indent 1))
(interactive "P")
(print x))
(defun faw-str-decl-int-decl-code (x)
"something"
(declare (pure t))
(interactive "P")
(declare (indent 1))
(print x))
(defun faw-doc-decl-int-decl-code (x)
(:documentation "something")
(declare (pure t))
(interactive "P")
(declare (indent 1))
(print x))
(defun faw-str-decl-int-int-code (x)
"something"
(declare (pure t))
(interactive "P")
(interactive "p")
(print x))
(defun faw-doc-decl-int-int-code (x)
(:documentation "something")
(declare (pure t))
(interactive "P")
(interactive "p")
(print x))
(defun faw-str-int-decl-int-code (x)
"something"
(interactive "P")
(declare (pure t))
(interactive "p")
(print x))
(defun faw-doc-int-decl-int-code (x)
(:documentation "something")
(interactive "P")
(declare (pure t))
(interactive "p")
(print x))

View file

@ -1580,6 +1580,69 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode js-mode python-mode)) \
(should (equal (get fname 'lisp-indent-function) 1))
(should (equal (aref bc 4) "tata\n\n(fn X)")))))
(ert-deftest bytecomp-fun-attr-warn ()
;; Check that warnings are emitted when doc strings, `declare' and
;; `interactive' forms don't come in the proper order, or more than once.
(let* ((filename "fun-attr-warn.el")
(el (ert-resource-file filename))
(elc (concat el "c"))
(text-quoting-style 'grave))
(with-current-buffer (get-buffer-create "*Compile-Log*")
(let ((inhibit-read-only t))
(erase-buffer))
(byte-compile-file el)
(let ((expected
'("70:4: Warning: `declare' after `interactive'"
"74:4: Warning: Doc string after `interactive'"
"79:4: Warning: Doc string after `interactive'"
"84:4: Warning: Doc string after `declare'"
"89:4: Warning: Doc string after `declare'"
"96:4: Warning: `declare' after `interactive'"
"102:4: Warning: `declare' after `interactive'"
"108:4: Warning: `declare' after `interactive'"
"106:4: Warning: Doc string after `interactive'"
"114:4: Warning: `declare' after `interactive'"
"112:4: Warning: Doc string after `interactive'"
"118:4: Warning: Doc string after `interactive'"
"119:4: Warning: `declare' after `interactive'"
"124:4: Warning: Doc string after `interactive'"
"125:4: Warning: `declare' after `interactive'"
"130:4: Warning: Doc string after `declare'"
"136:4: Warning: Doc string after `declare'"
"142:4: Warning: Doc string after `declare'"
"148:4: Warning: Doc string after `declare'"
"159:4: Warning: More than one doc string"
"165:4: Warning: More than one doc string"
"171:4: Warning: More than one doc string"
"178:4: Warning: More than one doc string"
"186:4: Warning: More than one doc string"
"192:4: Warning: More than one doc string"
"200:4: Warning: More than one doc string"
"206:4: Warning: More than one doc string"
"215:4: Warning: More than one `declare' form"
"222:4: Warning: More than one `declare' form"
"230:4: Warning: More than one `declare' form"
"237:4: Warning: More than one `declare' form"
"244:4: Warning: More than one `interactive' form"
"251:4: Warning: More than one `interactive' form"
"258:4: Warning: More than one `interactive' form"
"257:4: Warning: `declare' after `interactive'"
"265:4: Warning: More than one `interactive' form"
"264:4: Warning: `declare' after `interactive'")))
(goto-char (point-min))
(let ((actual nil))
(while (re-search-forward
(rx bol (* (not ":")) ":"
(group (+ digit) ":" (+ digit) ": Warning: "
(or "More than one " (+ nonl) " form"
(: (+ nonl) " after " (+ nonl))))
eol)
nil t)
(push (match-string 1) actual))
(setq actual (nreverse actual))
(should (equal actual expected)))))))
;; Local Variables:
;; no-byte-compile: t
;; End: