summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/bytecomp.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/bytecomp.el')
-rw-r--r--lisp/emacs-lisp/bytecomp.el376
1 files changed, 309 insertions, 67 deletions
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 5df1205869c..384a357ee51 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -295,7 +295,8 @@ The information is logged to `byte-compile-log-buffer'."
'(redefine callargs free-vars unresolved
obsolete noruntime interactive-only
make-local mapcar constants suspicious lexical lexical-dynamic
- docstrings docstrings-non-ascii-quotes not-unused)
+ docstrings docstrings-non-ascii-quotes not-unused
+ empty-body)
"The list of warning types used when `byte-compile-warnings' is t.")
(defcustom byte-compile-warnings t
"List of warnings that the byte-compiler should issue (t for almost all).
@@ -316,7 +317,9 @@ Elements of the list may be:
lexical-dynamic
lexically bound variable declared dynamic elsewhere
make-local calls to `make-variable-buffer-local' that may be incorrect.
- mapcar mapcar called for effect.
+ ignored-return-value
+ function called without using the return value where this
+ is likely to be a mistake
not-unused warning about using variables with symbol names starting with _.
constants let-binding of, or assignment to, constants/nonvariables.
docstrings docstrings that are too wide (longer than
@@ -326,9 +329,12 @@ Elements of the list may be:
docstrings-non-ascii-quotes docstrings that have non-ASCII quotes.
This depends on the `docstrings' warning type.
suspicious constructs that usually don't do what the coder wanted.
+ empty-body body argument to a special form or macro is empty.
+ mutate-constant
+ code that mutates program constants such as quoted lists
If the list begins with `not', then the remaining elements specify warnings to
-suppress. For example, (not mapcar) will suppress warnings about mapcar.
+suppress. For example, (not free-vars) will suppress the `free-vars' warning.
The t value means \"all non experimental warning types\", and
excludes the types in `byte-compile--emacs-build-warning-types'.
@@ -493,6 +499,42 @@ Return the compile-time value of FORM."
(cdr form)))
(funcall non-toplevel-case form)))
+
+(defvar bytecomp--copy-tree-seen)
+
+(defun bytecomp--copy-tree-1 (tree)
+ ;; TREE must be a cons.
+ (or (gethash tree bytecomp--copy-tree-seen)
+ (let* ((next (cdr tree))
+ (result (cons nil next))
+ (copy result))
+ (while (progn
+ (puthash tree copy bytecomp--copy-tree-seen)
+ (let ((a (car tree)))
+ (setcar copy (if (consp a)
+ (bytecomp--copy-tree-1 a)
+ a)))
+ (and (consp next)
+ (let ((tail (gethash next bytecomp--copy-tree-seen)))
+ (if tail
+ (progn (setcdr copy tail)
+ nil)
+ (setq tree next)
+ (setq next (cdr next))
+ (let ((prev copy))
+ (setq copy (cons nil next))
+ (setcdr prev copy)
+ t))))))
+ result)))
+
+(defun bytecomp--copy-tree (tree)
+ "Make a copy of TREE, preserving any circular structure therein.
+Only conses are traversed and duplicated, not arrays or any other structure."
+ (if (consp tree)
+ (let ((bytecomp--copy-tree-seen (make-hash-table :test #'eq)))
+ (bytecomp--copy-tree-1 tree))
+ tree))
+
(defconst byte-compile-initial-macro-environment
`(
;; (byte-compiler-options . (lambda (&rest forms)
@@ -528,11 +570,12 @@ Return the compile-time value of FORM."
;; or byte-compile-file-form.
(let* ((print-symbols-bare t) ; Possibly redundant binding.
(expanded
- (byte-run-strip-symbol-positions
- (macroexpand--all-toplevel
- form
- macroexpand-all-environment))))
- (eval expanded lexical-binding)
+ (macroexpand--all-toplevel
+ form
+ macroexpand-all-environment)))
+ (eval (byte-run-strip-symbol-positions
+ (bytecomp--copy-tree expanded))
+ lexical-binding)
expanded)))))
(with-suppressed-warnings
. ,(lambda (warnings &rest body)
@@ -541,15 +584,19 @@ Return the compile-time value of FORM."
;; Later `internal--with-suppressed-warnings' binds it again, this
;; time in order to affect warnings emitted during the
;; compilation itself.
- (let ((byte-compile--suppressed-warnings
- (append warnings byte-compile--suppressed-warnings)))
- ;; This function doesn't exist, but is just a placeholder
- ;; symbol to hook up with the
- ;; `byte-hunk-handler'/`byte-defop-compiler-1' machinery.
- `(internal--with-suppressed-warnings
- ',warnings
- ,(macroexpand-all `(progn ,@body)
- macroexpand-all-environment))))))
+ (if body
+ (let ((byte-compile--suppressed-warnings
+ (append warnings byte-compile--suppressed-warnings)))
+ ;; This function doesn't exist, but is just a placeholder
+ ;; symbol to hook up with the
+ ;; `byte-hunk-handler'/`byte-defop-compiler-1' machinery.
+ `(internal--with-suppressed-warnings
+ ',warnings
+ ,(macroexpand-all `(progn ,@body)
+ macroexpand-all-environment)))
+ (macroexp-warn-and-return
+ (format-message "`with-suppressed-warnings' with empty body")
+ nil '(empty-body with-suppressed-warnings) t warnings)))))
"The default macro-environment passed to macroexpand by the compiler.
Placing a macro here will cause a macro to have different semantics when
expanded by the compiler as when expanded by the interpreter.")
@@ -1569,24 +1616,23 @@ extra args."
"`%s' called with %d args to fill %d format field(s)" (car form)
nargs nfields)))))
-(dolist (elt '(format message error))
+(dolist (elt '(format message format-message error))
(put elt 'byte-compile-format-like t))
-(defun byte-compile--suspicious-defcustom-choice (type)
- "Say whether defcustom TYPE looks odd."
- ;; Check whether there's anything like (choice (const :tag "foo" ;; 'bar)).
+(defun byte-compile--defcustom-type-quoted (type)
+ "Whether defcustom TYPE contains an accidentally quoted value."
+ ;; Detect mistakes such as (const 'abc).
;; We don't actually follow the syntax for defcustom types, but this
;; should be good enough.
- (catch 'found
- (if (and (consp type)
- (proper-list-p type))
- (if (memq (car type) '(const other))
- (when (assq 'quote type)
- (throw 'found t))
- (when (memq t (mapcar #'byte-compile--suspicious-defcustom-choice
- type))
- (throw 'found t)))
- nil)))
+ (and (consp type)
+ (proper-list-p type)
+ (if (memq (car type) '(const other))
+ (assq 'quote type)
+ (let ((elts (cdr type)))
+ (while (and elts (not (byte-compile--defcustom-type-quoted
+ (car elts))))
+ (setq elts (cdr elts)))
+ elts))))
;; Warn if a custom definition fails to specify :group, or :type.
(defun byte-compile-nogroup-warn (form)
@@ -1600,10 +1646,10 @@ extra args."
(byte-compile-warn-x (cadr name)
"defcustom for `%s' fails to specify type"
(cadr name)))
- ((byte-compile--suspicious-defcustom-choice type)
+ ((byte-compile--defcustom-type-quoted type)
(byte-compile-warn-x
(cadr name)
- "defcustom for `%s' has syntactically odd type `%s'"
+ "defcustom for `%s' may have accidentally quoted value in type `%s'"
(cadr name) type)))))
(if (and (memq (car form) '(custom-declare-face custom-declare-variable))
byte-compile-current-group)
@@ -1766,10 +1812,16 @@ It is too wide if it has any lines longer than the largest of
kind name col))
;; There's a "naked" ' character before a symbol/list, so it
;; should probably be quoted with \=.
- (when (string-match-p "\\( [\"#]\\|[ \t]\\|^\\)'[a-z(]" docs)
+ (when (string-match-p (rx (| (in " \t") bol)
+ (? (in "\"#"))
+ "'"
+ (in "A-Za-z" "("))
+ docs)
(byte-compile-warn-x
- name "%s%sdocstring has wrong usage of unescaped single quotes (use \\= or different quoting)"
- kind name))
+ name
+ (concat "%s%sdocstring has wrong usage of unescaped single quotes"
+ " (use \\=%c or different quoting such as %c...%c)")
+ kind name ?' ?` ?'))
;; There's a "Unicode quote" in the string -- it should probably
;; be an ASCII one instead.
(when (byte-compile-warning-enabled-p 'docstrings-non-ascii-quotes)
@@ -3405,7 +3457,7 @@ lambda-expression."
(let* ((fn (car form))
(handler (get fn 'byte-compile))
(interactive-only
- (or (get fn 'interactive-only)
+ (or (function-get fn 'interactive-only)
(memq fn byte-compile-interactive-only-functions))))
(when (memq fn '(set symbol-value run-hooks ;; add-to-list
add-hook remove-hook run-hook-with-args
@@ -3432,15 +3484,54 @@ lambda-expression."
(format "; %s"
(substitute-command-keys
interactive-only)))
- ((and (symbolp 'interactive-only)
+ ((and (symbolp interactive-only)
(not (eq interactive-only t)))
(format-message "; use `%s' instead."
interactive-only))
(t "."))))
+ (let ((mutargs (function-get (car form) 'mutates-arguments)))
+ (when mutargs
+ (dolist (idx (if (eq mutargs 'all-but-last)
+ (number-sequence 1 (- (length form) 2))
+ mutargs))
+ (let ((arg (nth idx form)))
+ (when (and (or (and (eq (car-safe arg) 'quote)
+ (consp (nth 1 arg)))
+ (arrayp arg))
+ (byte-compile-warning-enabled-p
+ 'mutate-constant (car form)))
+ (byte-compile-warn-x form "`%s' on constant %s (arg %d)"
+ (car form)
+ (if (consp arg) "list" (type-of arg))
+ idx))))))
+
(if (eq (car-safe (symbol-function (car form))) 'macro)
(byte-compile-report-error
- (format "`%s' defined after use in %S (missing `require' of a library file?)"
+ (format-message "`%s' defined after use in %S (missing `require' of a library file?)"
(car form) form)))
+
+ (when byte-compile--for-effect
+ (let ((sef (function-get (car form) 'side-effect-free)))
+ (cond
+ ((and sef (or (eq sef 'error-free)
+ byte-compile-delete-errors))
+ ;; This transform is normally done in the Lisp optimiser,
+ ;; so maybe we don't need to bother about it here?
+ (setq form (cons 'progn (cdr form)))
+ (setq handler #'byte-compile-progn))
+ ((and (or sef (function-get (car form) 'important-return-value))
+ ;; Don't warn for arguments to `ignore'.
+ (not (eq byte-compile--for-effect 'for-effect-no-warn))
+ (byte-compile-warning-enabled-p
+ 'ignored-return-value (car form)))
+ (byte-compile-warn-x
+ (car form)
+ "value from call to `%s' is unused%s"
+ (car form)
+ (cond ((eq (car form) 'mapcar)
+ "; use `mapc' or `dolist' instead")
+ (t "")))))))
+
(if (and handler
;; Make sure that function exists.
(and (functionp handler)
@@ -3464,6 +3555,66 @@ lambda-expression."
(byte-compile-discard))
(pop byte-compile-form-stack)))
+(let ((important-return-value-fns
+ '(
+ ;; These functions are side-effect-free except for the
+ ;; behaviour of functions passed as argument.
+ mapcar mapcan mapconcat
+ assoc assoc-string plist-get plist-member
+
+ ;; It's safe to ignore the value of `sort' and `nreverse'
+ ;; when used on arrays, but most calls pass lists.
+ nreverse sort
+
+ match-data
+
+ ;; Warning about these functions causes some false positives that are
+ ;; laborious to eliminate; see bug#61730.
+ ;;delq delete
+ ;;nconc plist-put
+ )))
+ (dolist (fn important-return-value-fns)
+ (put fn 'important-return-value t)))
+
+(let ((mutating-fns
+ ;; FIXME: Should there be a function declaration for this?
+ ;;
+ ;; (FUNC . ARGS) means that FUNC mutates arguments whose indices are
+ ;; in the list ARGS, starting at 1, or all but the last argument if
+ ;; ARGS is `all-but-last'.
+ '(
+ (setcar 1) (setcdr 1) (aset 1)
+ (nreverse 1)
+ (nconc . all-but-last)
+ (nbutlast 1) (ntake 2)
+ (sort 1)
+ (delq 2) (delete 2)
+ (delete-dups 1) (delete-consecutive-dups 1)
+ (plist-put 1)
+ (assoc-delete-all 2) (assq-delete-all 2) (rassq-delete-all 2)
+ (fillarray 1)
+ (store-substring 1)
+ (clear-string 1)
+
+ (add-text-properties 4) (put-text-property 5) (set-text-properties 4)
+ (remove-text-properties 4) (remove-list-of-text-properties 4)
+ (alter-text-property 5)
+ (add-face-text-property 5) (add-display-text-property 5)
+
+ (cl-delete 2) (cl-delete-if 2) (cl-delete-if-not 2)
+ (cl-delete-duplicates 1)
+ (cl-nsubst 3) (cl-nsubst-if 3) (cl-nsubst-if-not 3)
+ (cl-nsubstitute 3) (cl-nsubstitute-if 3) (cl-nsubstitute-if-not 3)
+ (cl-nsublis 2)
+ (cl-nunion 1 2) (cl-nintersection 1 2) (cl-nset-difference 1 2)
+ (cl-nset-exclusive-or 1 2)
+ (cl-nreconc 1)
+ (cl-sort 1) (cl-stable-sort 1) (cl-merge 2 3)
+ )))
+ (dolist (entry mutating-fns)
+ (put (car entry) 'mutates-arguments (cdr entry))))
+
+
(defun byte-compile-normal-call (form)
(when (and (symbolp (car form))
(byte-compile-warning-enabled-p 'callargs (car form)))
@@ -3474,11 +3625,7 @@ lambda-expression."
(byte-compile-callargs-warn form))
(if byte-compile-generate-call-tree
(byte-compile-annotate-call-tree form))
- (when (and byte-compile--for-effect (eq (car form) 'mapcar)
- (byte-compile-warning-enabled-p 'mapcar 'mapcar))
- (byte-compile-warn-x
- (car form)
- "`mapcar' called for effect; use `mapc' or `dolist' instead"))
+
(byte-compile-push-constant (car form))
(mapc 'byte-compile-form (cdr form)) ; wasteful, but faster.
(byte-compile-out 'byte-call (length (cdr form))))
@@ -3736,7 +3883,7 @@ If it is nil, then the handler is \"byte-compile-SYMBOL.\""
'((0 . byte-compile-no-args)
(1 . byte-compile-one-arg)
(2 . byte-compile-two-args)
- (2-and . byte-compile-and-folded)
+ (2-cmp . byte-compile-cmp)
(3 . byte-compile-three-args)
(0-1 . byte-compile-zero-or-one-arg)
(1-2 . byte-compile-one-or-two-args)
@@ -3815,11 +3962,11 @@ If it is nil, then the handler is \"byte-compile-SYMBOL.\""
(byte-defop-compiler cons 2)
(byte-defop-compiler aref 2)
(byte-defop-compiler set 2)
-(byte-defop-compiler (= byte-eqlsign) 2-and)
-(byte-defop-compiler (< byte-lss) 2-and)
-(byte-defop-compiler (> byte-gtr) 2-and)
-(byte-defop-compiler (<= byte-leq) 2-and)
-(byte-defop-compiler (>= byte-geq) 2-and)
+(byte-defop-compiler (= byte-eqlsign) 2-cmp)
+(byte-defop-compiler (< byte-lss) 2-cmp)
+(byte-defop-compiler (> byte-gtr) 2-cmp)
+(byte-defop-compiler (<= byte-leq) 2-cmp)
+(byte-defop-compiler (>= byte-geq) 2-cmp)
(byte-defop-compiler get 2)
(byte-defop-compiler nth 2)
(byte-defop-compiler substring 1-3)
@@ -3883,18 +4030,20 @@ If it is nil, then the handler is \"byte-compile-SYMBOL.\""
(byte-compile-form (nth 2 form))
(byte-compile-out (get (car form) 'byte-opcode) 0)))
-(defun byte-compile-and-folded (form)
- "Compile calls to functions like `<='.
-These implicitly `and' together a bunch of two-arg bytecodes."
- (let ((l (length form)))
- (cond
- ((< l 3) (byte-compile-form `(progn ,(nth 1 form) t)))
- ((= l 3) (byte-compile-two-args form))
- ;; Don't use `cl-every' here (see comment where we require cl-lib).
- ((not (memq nil (mapcar #'macroexp-copyable-p (nthcdr 2 form))))
- (byte-compile-form `(and (,(car form) ,(nth 1 form) ,(nth 2 form))
- (,(car form) ,@(nthcdr 2 form)))))
- (t (byte-compile-normal-call form)))))
+(defun byte-compile-cmp (form)
+ "Compile calls to numeric comparisons such as `<', `=' etc."
+ ;; Lisp-level transforms should already have reduced valid calls to 2 args.
+ (if (not (= (length form) 3))
+ (byte-compile-subr-wrong-args form "1 or more")
+ (byte-compile-two-args
+ (if (macroexp-const-p (nth 1 form))
+ ;; First argument is constant: flip it so that the constant
+ ;; is last, which may allow more lapcode optimisations.
+ (let* ((op (car form))
+ (flipped-op (cdr (assq op '((< . >) (<= . >=)
+ (> . <) (>= . <=) (= . =))))))
+ (list flipped-op (nth 2 form) (nth 1 form)))
+ form))))
(defun byte-compile-three-args (form)
(if (not (= (length form) 4))
@@ -4049,9 +4198,15 @@ This function is never called when `lexical-binding' is nil."
(byte-compile-constant 1)
(byte-compile-out (get '* 'byte-opcode) 0))
(3
- (byte-compile-form (nth 1 form))
- (byte-compile-form (nth 2 form))
- (byte-compile-out (get (car form) 'byte-opcode) 0))
+ (let ((arg1 (nth 1 form))
+ (arg2 (nth 2 form)))
+ (when (and (memq (car form) '(+ *))
+ (macroexp-const-p arg1))
+ ;; Put constant argument last for better LAP optimisation.
+ (cl-rotatef arg1 arg2))
+ (byte-compile-form arg1)
+ (byte-compile-form arg2)
+ (byte-compile-out (get (car form) 'byte-opcode) 0)))
(_
;; >2 args: compile as a single function call.
(byte-compile-normal-call form))))
@@ -4310,7 +4465,8 @@ This function is never called when `lexical-binding' is nil."
(defun byte-compile-ignore (form)
(dolist (arg (cdr form))
- (byte-compile-form arg t))
+ ;; Compile each argument for-effect but suppress unused-value warnings.
+ (byte-compile-form arg 'for-effect-no-warn))
(byte-compile-form nil))
;; Return the list of items in CONDITION-PARAM that match PRED-LIST.
@@ -4571,6 +4727,7 @@ Return (TAIL VAR TEST CASES), where:
(if switch-prefix
(progn
(byte-compile-cond-jump-table (cdr switch-prefix) donetag)
+ (setq clause nil)
(setq clauses (car switch-prefix)))
(setq clause (car clauses))
(cond ((or (eq (car clause) t)
@@ -4835,6 +4992,11 @@ binding slots have been popped."
(dolist (clause (reverse clauses))
(let ((condition (nth 1 clause)))
+ (when (and (eq (car-safe condition) 'quote)
+ (cdr condition) (null (cddr condition)))
+ (byte-compile-warn-x
+ condition "`condition-case' condition should not be quoted: %S"
+ condition))
(unless (consp condition) (setq condition (list condition)))
(dolist (c condition)
(unless (and c (symbolp c))
@@ -5055,7 +5217,10 @@ binding slots have been popped."
(defun byte-compile-suppressed-warnings (form)
(let ((byte-compile--suppressed-warnings
(append (cadadr form) byte-compile--suppressed-warnings)))
- (byte-compile-form (macroexp-progn (cddr form)))))
+ ;; Propagate the for-effect mode explicitly so that warnings about
+ ;; ignored return values can be detected and suppressed correctly.
+ (byte-compile-form (macroexp-progn (cddr form)) byte-compile--for-effect)
+ (setq byte-compile--for-effect nil)))
;; Warn about misuses of make-variable-buffer-local.
(byte-defop-compiler-1 make-variable-buffer-local
@@ -5487,6 +5652,83 @@ and corresponding effects."
(eval form)
form)))
+;; Check for (in)comparable constant values in calls to `eq', `memq' etc.
+
+(defun bytecomp--dodgy-eq-arg-p (x number-ok)
+ "Whether X is a bad argument to `eq' (or `eql' if NUMBER-OK is non-nil)."
+ (pcase x
+ ((or `(quote ,(pred consp)) `(function (lambda . ,_))) t)
+ ((or (pred consp) (pred symbolp)) nil)
+ ((pred integerp)
+ (not (or (<= -536870912 x 536870911) number-ok)))
+ ((pred floatp) (not number-ok))
+ (_ t)))
+
+(defun bytecomp--value-type-description (x)
+ (cond
+ ((proper-list-p x) "list")
+ ((recordp x) "record")
+ (t (symbol-name (type-of x)))))
+
+(defun bytecomp--arg-type-description (x)
+ (pcase x
+ (`(function (lambda . ,_)) "function")
+ (`(quote . ,val) (bytecomp--value-type-description val))
+ (_ (bytecomp--value-type-description x))))
+
+(defun bytecomp--warn-dodgy-eq-arg (form type parenthesis)
+ (macroexp-warn-and-return
+ (format-message "`%s' called with literal %s that may never match (%s)"
+ (car form) type parenthesis)
+ form (list 'suspicious (car form)) t))
+
+(defun bytecomp--check-eq-args (form &optional a b &rest _ignore)
+ (let* ((number-ok (eq (car form) 'eql))
+ (bad-arg (cond ((bytecomp--dodgy-eq-arg-p a number-ok) 1)
+ ((bytecomp--dodgy-eq-arg-p b number-ok) 2))))
+ (if bad-arg
+ (bytecomp--warn-dodgy-eq-arg
+ form
+ (bytecomp--arg-type-description (nth bad-arg form))
+ (format "arg %d" bad-arg))
+ form)))
+
+(put 'eq 'compiler-macro #'bytecomp--check-eq-args)
+(put 'eql 'compiler-macro #'bytecomp--check-eq-args)
+
+(defun bytecomp--check-memq-args (form &optional elem list &rest _ignore)
+ (let* ((fn (car form))
+ (number-ok (eq fn 'memql)))
+ (cond
+ ((bytecomp--dodgy-eq-arg-p elem number-ok)
+ (bytecomp--warn-dodgy-eq-arg
+ form (bytecomp--arg-type-description elem) "arg 1"))
+ ((and (consp list) (eq (car list) 'quote)
+ (proper-list-p (cadr list)))
+ (named-let loop ((elts (cadr list)) (i 1))
+ (if elts
+ (let* ((elt (car elts))
+ (x (cond ((eq fn 'assq) (car-safe elt))
+ ((eq fn 'rassq) (cdr-safe elt))
+ (t elt))))
+ (if (or (symbolp x)
+ (and (integerp x)
+ (or (<= -536870912 x 536870911) number-ok))
+ (and (floatp x) number-ok))
+ (loop (cdr elts) (1+ i))
+ (bytecomp--warn-dodgy-eq-arg
+ form (bytecomp--value-type-description x)
+ (format "element %d of arg 2" i))))
+ form)))
+ (t form))))
+
+(put 'memq 'compiler-macro #'bytecomp--check-memq-args)
+(put 'memql 'compiler-macro #'bytecomp--check-memq-args)
+(put 'assq 'compiler-macro #'bytecomp--check-memq-args)
+(put 'rassq 'compiler-macro #'bytecomp--check-memq-args)
+(put 'remq 'compiler-macro #'bytecomp--check-memq-args)
+(put 'delq 'compiler-macro #'bytecomp--check-memq-args)
+
(provide 'byte-compile)
(provide 'bytecomp)