diff options
| author | Mattias EngdegÄrd | 2023-09-17 12:49:40 +0200 |
|---|---|---|
| committer | Mattias EngdegÄrd | 2023-09-17 17:16:35 +0200 |
| commit | f8ea47ebf45c5ea0cd788667f7bdb805f42e08e0 (patch) | |
| tree | e494cdd6d08b6a38de3ce5d346feb2eb3f501e1c | |
| parent | e0070fc574a8621b2fbb1aaca678b974a3dc5fd5 (diff) | |
| download | emacs-f8ea47ebf45c5ea0cd788667f7bdb805f42e08e0.tar.gz emacs-f8ea47ebf45c5ea0cd788667f7bdb805f42e08e0.zip | |
Expanded defcustom type byte-compilation warnings (bug#65852)
Warn about more kinds of mistakes in :type arguments of `defcustom`
and `define-widget`. These include:
- misplaced keyword args, as in (const red :tag "A reddish hue")
- missing subordinate types, as in (repeat :tag "List of names")
or (choice list string)
- duplicated values, as in (choice (const yes) (const yes))
- misplaced `other` member, as in
(choice (const red) (other nil) (const blue))
- various type name mistakes, as in (vector bool functionp)
* lisp/emacs-lisp/bytecomp.el (byte-compile--defcustom-type-quoted)
(byte-compile-nogroup-warn): Remove.
(byte-compile-normal-call): Remove call to the above.
(bytecomp--cus-warn, bytecomp--check-cus-type)
(bytecomp--custom-declare): New.
| -rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 236 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/bytecomp-tests.el | 52 |
2 files changed, 226 insertions, 62 deletions
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index 7feaf118b86..1474acc1638 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el | |||
| @@ -1618,57 +1618,6 @@ extra args." | |||
| 1618 | (dolist (elt '(format message format-message error)) | 1618 | (dolist (elt '(format message format-message error)) |
| 1619 | (put elt 'byte-compile-format-like t)) | 1619 | (put elt 'byte-compile-format-like t)) |
| 1620 | 1620 | ||
| 1621 | (defun byte-compile--defcustom-type-quoted (type) | ||
| 1622 | "Whether defcustom TYPE contains an accidentally quoted value." | ||
| 1623 | ;; Detect mistakes such as (const 'abc). | ||
| 1624 | ;; We don't actually follow the syntax for defcustom types, but this | ||
| 1625 | ;; should be good enough. | ||
| 1626 | (and (consp type) | ||
| 1627 | (proper-list-p type) | ||
| 1628 | (if (memq (car type) '(const other)) | ||
| 1629 | (assq 'quote type) | ||
| 1630 | (let ((elts (cdr type))) | ||
| 1631 | (while (and elts (not (byte-compile--defcustom-type-quoted | ||
| 1632 | (car elts)))) | ||
| 1633 | (setq elts (cdr elts))) | ||
| 1634 | elts)))) | ||
| 1635 | |||
| 1636 | ;; Warn if a custom definition fails to specify :group, or :type. | ||
| 1637 | (defun byte-compile-nogroup-warn (form) | ||
| 1638 | (let ((keyword-args (cdr (cdr (cdr (cdr form))))) | ||
| 1639 | (name (cadr form))) | ||
| 1640 | (when (eq (car-safe name) 'quote) | ||
| 1641 | (when (eq (car form) 'custom-declare-variable) | ||
| 1642 | (let ((type (plist-get keyword-args :type))) | ||
| 1643 | (cond | ||
| 1644 | ((not type) | ||
| 1645 | (byte-compile-warn-x (cadr name) | ||
| 1646 | "defcustom for `%s' fails to specify type" | ||
| 1647 | (cadr name))) | ||
| 1648 | ((byte-compile--defcustom-type-quoted type) | ||
| 1649 | (byte-compile-warn-x | ||
| 1650 | (cadr name) | ||
| 1651 | "defcustom for `%s' may have accidentally quoted value in type `%s'" | ||
| 1652 | (cadr name) type))))) | ||
| 1653 | (if (and (memq (car form) '(custom-declare-face custom-declare-variable)) | ||
| 1654 | byte-compile-current-group) | ||
| 1655 | ;; The group will be provided implicitly. | ||
| 1656 | nil | ||
| 1657 | (or (and (eq (car form) 'custom-declare-group) | ||
| 1658 | (equal name ''emacs)) | ||
| 1659 | (plist-get keyword-args :group) | ||
| 1660 | (byte-compile-warn-x (cadr name) | ||
| 1661 | "%s for `%s' fails to specify containing group" | ||
| 1662 | (cdr (assq (car form) | ||
| 1663 | '((custom-declare-group . defgroup) | ||
| 1664 | (custom-declare-face . defface) | ||
| 1665 | (custom-declare-variable . defcustom)))) | ||
| 1666 | (cadr name))) | ||
| 1667 | ;; Update the current group, if needed. | ||
| 1668 | (if (and byte-compile-current-file ;Only when compiling a whole file. | ||
| 1669 | (eq (car form) 'custom-declare-group)) | ||
| 1670 | (setq byte-compile-current-group (cadr name))))))) | ||
| 1671 | |||
| 1672 | ;; Warn if the function or macro is being redefined with a different | 1621 | ;; Warn if the function or macro is being redefined with a different |
| 1673 | ;; number of arguments. | 1622 | ;; number of arguments. |
| 1674 | (defun byte-compile-arglist-warn (name arglist macrop) | 1623 | (defun byte-compile-arglist-warn (name arglist macrop) |
| @@ -3695,10 +3644,6 @@ lambda-expression." | |||
| 3695 | (defun byte-compile-normal-call (form) | 3644 | (defun byte-compile-normal-call (form) |
| 3696 | (when (and (symbolp (car form)) | 3645 | (when (and (symbolp (car form)) |
| 3697 | (byte-compile-warning-enabled-p 'callargs (car form))) | 3646 | (byte-compile-warning-enabled-p 'callargs (car form))) |
| 3698 | (if (memq (car form) | ||
| 3699 | '(custom-declare-group custom-declare-variable | ||
| 3700 | custom-declare-face)) | ||
| 3701 | (byte-compile-nogroup-warn form)) | ||
| 3702 | (byte-compile-callargs-warn form)) | 3647 | (byte-compile-callargs-warn form)) |
| 3703 | (if byte-compile-generate-call-tree | 3648 | (if byte-compile-generate-call-tree |
| 3704 | (byte-compile-annotate-call-tree form)) | 3649 | (byte-compile-annotate-call-tree form)) |
| @@ -5269,6 +5214,187 @@ binding slots have been popped." | |||
| 5269 | (pcase form (`(,_ ',var) (byte-compile--declare-var var))) | 5214 | (pcase form (`(,_ ',var) (byte-compile--declare-var var))) |
| 5270 | (byte-compile-normal-call form)) | 5215 | (byte-compile-normal-call form)) |
| 5271 | 5216 | ||
| 5217 | ;; Warn about mistakes in `defcustom', `defface', `defgroup', `define-widget' | ||
| 5218 | |||
| 5219 | (defvar bytecomp--cus-function) | ||
| 5220 | (defvar bytecomp--cus-name) | ||
| 5221 | |||
| 5222 | (defun bytecomp--cus-warn (form format &rest args) | ||
| 5223 | "Emit a warning about a `defcustom' type. | ||
| 5224 | FORM is used to provide location, `bytecomp--cus-function' and | ||
| 5225 | `bytecomp--cus-name' for context." | ||
| 5226 | (let* ((actual-fun (or (cdr (assq bytecomp--cus-function | ||
| 5227 | '((custom-declare-group . defgroup) | ||
| 5228 | (custom-declare-face . defface) | ||
| 5229 | (custom-declare-variable . defcustom)))) | ||
| 5230 | bytecomp--cus-function)) | ||
| 5231 | (prefix (format "in %s%s: " | ||
| 5232 | actual-fun | ||
| 5233 | (if bytecomp--cus-name | ||
| 5234 | (format " for `%s'" bytecomp--cus-name) | ||
| 5235 | "")))) | ||
| 5236 | (apply #'byte-compile-warn-x form (concat prefix format) args))) | ||
| 5237 | |||
| 5238 | (defun bytecomp--check-cus-type (type) | ||
| 5239 | "Warn about common mistakes in the `defcustom' type TYPE." | ||
| 5240 | (let ((invalid-types | ||
| 5241 | '( | ||
| 5242 | ;; Lisp type predicates, often confused with customisation types: | ||
| 5243 | functionp numberp integerp fixnump natnump floatp booleanp | ||
| 5244 | characterp listp stringp consp vectorp symbolp keywordp | ||
| 5245 | hash-table-p facep | ||
| 5246 | ;; other mistakes occasionally seen (oh yes): | ||
| 5247 | or and nil t | ||
| 5248 | interger intger lits bool boolen constant filename | ||
| 5249 | kbd any list-of auto | ||
| 5250 | ;; from botched backquoting | ||
| 5251 | \, \,@ \` | ||
| 5252 | ))) | ||
| 5253 | (cond | ||
| 5254 | ((consp type) | ||
| 5255 | (let* ((head (car type)) | ||
| 5256 | (tail (cdr type))) | ||
| 5257 | (while (and (keywordp (car tail)) (cdr tail)) | ||
| 5258 | (setq tail (cddr tail))) | ||
| 5259 | (cond | ||
| 5260 | ((plist-member (cdr type) :convert-widget) nil) | ||
| 5261 | ((let ((tl tail)) | ||
| 5262 | (and (not (keywordp (car tail))) | ||
| 5263 | (progn | ||
| 5264 | (while (and tl (not (keywordp (car tl)))) | ||
| 5265 | (setq tl (cdr tl))) | ||
| 5266 | (and tl | ||
| 5267 | (progn | ||
| 5268 | (bytecomp--cus-warn | ||
| 5269 | tl "misplaced %s keyword in `%s' type" (car tl) head) | ||
| 5270 | t)))))) | ||
| 5271 | ((memq head '(choice radio)) | ||
| 5272 | (unless tail | ||
| 5273 | (bytecomp--cus-warn type "`%s' without any types inside" head)) | ||
| 5274 | (let ((clauses tail) | ||
| 5275 | (constants nil)) | ||
| 5276 | (while clauses | ||
| 5277 | (let* ((ty (car clauses)) | ||
| 5278 | (ty-head (car-safe ty))) | ||
| 5279 | (when (and (eq ty-head 'other) (cdr clauses)) | ||
| 5280 | (bytecomp--cus-warn ty "`other' not last in `%s'" head)) | ||
| 5281 | (when (memq ty-head '(const other)) | ||
| 5282 | (let ((ty-tail (cdr ty)) | ||
| 5283 | (val nil)) | ||
| 5284 | (while (and (keywordp (car ty-tail)) (cdr ty-tail)) | ||
| 5285 | (when (eq (car ty-tail) :value) | ||
| 5286 | (setq val (cadr ty-tail))) | ||
| 5287 | (setq ty-tail (cddr ty-tail))) | ||
| 5288 | (when ty-tail | ||
| 5289 | (setq val (car ty-tail))) | ||
| 5290 | (when (member val constants) | ||
| 5291 | (bytecomp--cus-warn | ||
| 5292 | ty "duplicated value in `%s': `%S'" head val)) | ||
| 5293 | (push val constants))) | ||
| 5294 | (bytecomp--check-cus-type ty)) | ||
| 5295 | (setq clauses (cdr clauses))))) | ||
| 5296 | ((eq head 'cons) | ||
| 5297 | (unless (= (length tail) 2) | ||
| 5298 | (bytecomp--cus-warn | ||
| 5299 | type "`cons' requires 2 type specs, found %d" (length tail))) | ||
| 5300 | (dolist (ty tail) | ||
| 5301 | (bytecomp--check-cus-type ty))) | ||
| 5302 | ((memq head '(list group vector set repeat)) | ||
| 5303 | (unless tail | ||
| 5304 | (bytecomp--cus-warn type "`%s' without type specs" head)) | ||
| 5305 | (dolist (ty tail) | ||
| 5306 | (bytecomp--check-cus-type ty))) | ||
| 5307 | ((memq head '(alist plist)) | ||
| 5308 | (let ((key-tag (memq :key-type (cdr type))) | ||
| 5309 | (value-tag (memq :value-type (cdr type)))) | ||
| 5310 | (when key-tag | ||
| 5311 | (bytecomp--check-cus-type (cadr key-tag))) | ||
| 5312 | (when value-tag | ||
| 5313 | (bytecomp--check-cus-type (cadr value-tag))))) | ||
| 5314 | ((memq head '(const other)) | ||
| 5315 | (let* ((value-tag (memq :value (cdr type))) | ||
| 5316 | (n (length tail)) | ||
| 5317 | (val (car tail))) | ||
| 5318 | (cond | ||
| 5319 | ((or (> n 1) (and value-tag tail)) | ||
| 5320 | (bytecomp--cus-warn type "`%s' with too many values" head)) | ||
| 5321 | (value-tag | ||
| 5322 | (setq val (cadr value-tag))) | ||
| 5323 | ;; ;; This is a useful check but it results in perhaps | ||
| 5324 | ;; ;; a bit too many complaints. | ||
| 5325 | ;; ((null tail) | ||
| 5326 | ;; (bytecomp--cus-warn | ||
| 5327 | ;; type "`%s' without value is implicitly nil" head)) | ||
| 5328 | ) | ||
| 5329 | (when (memq (car-safe val) '(quote function)) | ||
| 5330 | (bytecomp--cus-warn type "`%s' with quoted value: %S" head val)))) | ||
| 5331 | ((eq head 'quote) | ||
| 5332 | (bytecomp--cus-warn type "type should not be quoted: %s" (cadr type))) | ||
| 5333 | ((memq head invalid-types) | ||
| 5334 | (bytecomp--cus-warn type "`%s' is not a valid type" head)) | ||
| 5335 | ((or (not (symbolp head)) (keywordp head)) | ||
| 5336 | (bytecomp--cus-warn type "irregular type `%S'" head)) | ||
| 5337 | ))) | ||
| 5338 | ((or (not (symbolp type)) (keywordp type)) | ||
| 5339 | (bytecomp--cus-warn type "irregular type `%S'" type)) | ||
| 5340 | ((memq type '( list cons group vector choice radio const other | ||
| 5341 | function-item variable-item set repeat restricted-sexp)) | ||
| 5342 | (bytecomp--cus-warn type "`%s' without arguments" type)) | ||
| 5343 | ((memq type invalid-types) | ||
| 5344 | (bytecomp--cus-warn type "`%s' is not a valid type" type)) | ||
| 5345 | ))) | ||
| 5346 | |||
| 5347 | ;; Unified handler for multiple functions with similar arguments: | ||
| 5348 | ;; (NAME SOMETHING DOC KEYWORD-ARGS...) | ||
| 5349 | (byte-defop-compiler-1 define-widget bytecomp--custom-declare) | ||
| 5350 | (byte-defop-compiler-1 custom-declare-group bytecomp--custom-declare) | ||
| 5351 | (byte-defop-compiler-1 custom-declare-face bytecomp--custom-declare) | ||
| 5352 | (byte-defop-compiler-1 custom-declare-variable bytecomp--custom-declare) | ||
| 5353 | (defun bytecomp--custom-declare (form) | ||
| 5354 | (when (>= (length form) 4) | ||
| 5355 | (let* ((name-arg (nth 1 form)) | ||
| 5356 | (name (and (eq (car-safe name-arg) 'quote) | ||
| 5357 | (symbolp (nth 1 name-arg)) | ||
| 5358 | (nth 1 name-arg))) | ||
| 5359 | (keyword-args (nthcdr 4 form)) | ||
| 5360 | (fun (car form)) | ||
| 5361 | (bytecomp--cus-function fun) | ||
| 5362 | (bytecomp--cus-name name)) | ||
| 5363 | |||
| 5364 | ;; Check :type | ||
| 5365 | (when (memq fun '(custom-declare-variable define-widget)) | ||
| 5366 | (let ((type-tag (memq :type keyword-args))) | ||
| 5367 | (if (null type-tag) | ||
| 5368 | ;; :type only mandatory for `defcustom' | ||
| 5369 | (when (eq fun 'custom-declare-variable) | ||
| 5370 | (bytecomp--cus-warn form "missing :type keyword parameter")) | ||
| 5371 | (let ((dup-type (memq :type (cdr type-tag)))) | ||
| 5372 | (when dup-type | ||
| 5373 | (bytecomp--cus-warn | ||
| 5374 | dup-type "duplicated :type keyword argument"))) | ||
| 5375 | (let ((type-arg (cadr type-tag))) | ||
| 5376 | (when (or (null type-arg) | ||
| 5377 | (eq (car-safe type-arg) 'quote)) | ||
| 5378 | (bytecomp--check-cus-type (cadr type-arg))))))) | ||
| 5379 | |||
| 5380 | ;; Check :group | ||
| 5381 | (when (cond | ||
| 5382 | ((memq fun '(custom-declare-variable custom-declare-face)) | ||
| 5383 | (not byte-compile-current-group)) | ||
| 5384 | ((eq fun 'custom-declare-group) | ||
| 5385 | (not (eq name 'emacs)))) | ||
| 5386 | (unless (plist-get keyword-args :group) | ||
| 5387 | (bytecomp--cus-warn form "fails to specify containing group"))) | ||
| 5388 | |||
| 5389 | ;; Update current group | ||
| 5390 | (when (and name | ||
| 5391 | byte-compile-current-file ; only when compiling a whole file | ||
| 5392 | (eq fun 'custom-declare-group)) | ||
| 5393 | (setq byte-compile-current-group name)))) | ||
| 5394 | |||
| 5395 | (byte-compile-normal-call form)) | ||
| 5396 | |||
| 5397 | |||
| 5272 | (put 'function-put 'byte-hunk-handler 'byte-compile-define-symbol-prop) | 5398 | (put 'function-put 'byte-hunk-handler 'byte-compile-define-symbol-prop) |
| 5273 | (put 'define-symbol-prop 'byte-hunk-handler 'byte-compile-define-symbol-prop) | 5399 | (put 'define-symbol-prop 'byte-hunk-handler 'byte-compile-define-symbol-prop) |
| 5274 | (defun byte-compile-define-symbol-prop (form) | 5400 | (defun byte-compile-define-symbol-prop (form) |
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index 03aed5263b6..a335a7fa1f8 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el | |||
| @@ -1100,7 +1100,7 @@ byte-compiled. Run with dynamic binding." | |||
| 1100 | "fails to specify containing group") | 1100 | "fails to specify containing group") |
| 1101 | 1101 | ||
| 1102 | (bytecomp--define-warning-file-test "warn-defcustom-notype.el" | 1102 | (bytecomp--define-warning-file-test "warn-defcustom-notype.el" |
| 1103 | "fails to specify type") | 1103 | "missing :type keyword parameter") |
| 1104 | 1104 | ||
| 1105 | (bytecomp--define-warning-file-test "warn-defvar-lacks-prefix.el" | 1105 | (bytecomp--define-warning-file-test "warn-defvar-lacks-prefix.el" |
| 1106 | "var.*foo.*lacks a prefix") | 1106 | "var.*foo.*lacks a prefix") |
| @@ -1874,12 +1874,50 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode js-mode python-mode)) \ | |||
| 1874 | (TEST-IN-COMMENTS t) (TEST-IN-STRINGS t) (TEST-IN-CODE t) \ | 1874 | (TEST-IN-COMMENTS t) (TEST-IN-STRINGS t) (TEST-IN-CODE t) \ |
| 1875 | (FIXTURE-FN \\='#\\='electric-pair-mode))" fill-column))) | 1875 | (FIXTURE-FN \\='#\\='electric-pair-mode))" fill-column))) |
| 1876 | 1876 | ||
| 1877 | (ert-deftest bytecomp-test-defcustom-type-quoted () | 1877 | (ert-deftest bytecomp-test-defcustom-type () |
| 1878 | (should-not (byte-compile--defcustom-type-quoted 'integer)) | 1878 | (cl-flet ((dc (type) `(defcustom mytest nil "doc" :type ',type))) |
| 1879 | (should-not (byte-compile--defcustom-type-quoted | 1879 | (bytecomp--with-warning-test |
| 1880 | '(choice (const :tag "foo" bar)))) | 1880 | (rx "type should not be quoted") (dc ''integer)) |
| 1881 | (should (byte-compile--defcustom-type-quoted | 1881 | (bytecomp--with-warning-test |
| 1882 | '(choice (const :tag "foo" 'bar))))) | 1882 | (rx "type should not be quoted") (dc '(choice '(repeat boolean)))) |
| 1883 | (bytecomp--with-warning-test | ||
| 1884 | (rx "misplaced :tag keyword") (dc '(choice (const b :tag "a")))) | ||
| 1885 | (bytecomp--with-warning-test | ||
| 1886 | (rx "`choice' without any types inside") (dc '(choice :tag "a"))) | ||
| 1887 | (bytecomp--with-warning-test | ||
| 1888 | (rx "`other' not last in `choice'") | ||
| 1889 | (dc '(choice (const a) (other b) (const c)))) | ||
| 1890 | (bytecomp--with-warning-test | ||
| 1891 | (rx "duplicated value in `choice': `a'") | ||
| 1892 | (dc '(choice (const a) (const b) (const a)))) | ||
| 1893 | (bytecomp--with-warning-test | ||
| 1894 | (rx "`cons' requires 2 type specs, found 1") | ||
| 1895 | (dc '(cons :tag "a" integer))) | ||
| 1896 | (bytecomp--with-warning-test | ||
| 1897 | (rx "`repeat' without type specs") | ||
| 1898 | (dc '(repeat :tag "a"))) | ||
| 1899 | (bytecomp--with-warning-test | ||
| 1900 | (rx "`const' with too many values") | ||
| 1901 | (dc '(const :tag "a" x y))) | ||
| 1902 | (bytecomp--with-warning-test | ||
| 1903 | (rx "`const' with quoted value") | ||
| 1904 | (dc '(const :tag "a" 'x))) | ||
| 1905 | (bytecomp--with-warning-test | ||
| 1906 | (rx "`bool' is not a valid type") | ||
| 1907 | (dc '(bool :tag "a"))) | ||
| 1908 | (bytecomp--with-warning-test | ||
| 1909 | (rx "irregular type `:tag'") | ||
| 1910 | (dc '(:tag "a"))) | ||
| 1911 | (bytecomp--with-warning-test | ||
| 1912 | (rx "irregular type `\"string\"'") | ||
| 1913 | (dc '(list "string"))) | ||
| 1914 | (bytecomp--with-warning-test | ||
| 1915 | (rx "`list' without arguments") | ||
| 1916 | (dc 'list)) | ||
| 1917 | (bytecomp--with-warning-test | ||
| 1918 | (rx "`integerp' is not a valid type") | ||
| 1919 | (dc 'integerp)) | ||
| 1920 | )) | ||
| 1883 | 1921 | ||
| 1884 | (ert-deftest bytecomp-function-attributes () | 1922 | (ert-deftest bytecomp-function-attributes () |
| 1885 | ;; Check that `byte-compile' keeps the declarations, interactive spec and | 1923 | ;; Check that `byte-compile' keeps the declarations, interactive spec and |