aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias EngdegÄrd2023-09-17 12:49:40 +0200
committerMattias EngdegÄrd2023-09-17 17:16:35 +0200
commitf8ea47ebf45c5ea0cd788667f7bdb805f42e08e0 (patch)
treee494cdd6d08b6a38de3ce5d346feb2eb3f501e1c
parente0070fc574a8621b2fbb1aaca678b974a3dc5fd5 (diff)
downloademacs-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.el236
-rw-r--r--test/lisp/emacs-lisp/bytecomp-tests.el52
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.
5224FORM 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