diff options
| author | Mattias EngdegÄrd | 2022-06-03 20:31:10 +0200 |
|---|---|---|
| committer | Mattias EngdegÄrd | 2022-06-14 20:19:59 +0200 |
| commit | 6825e5686a4bf21f5d5a0ae1af889097cfa2f597 (patch) | |
| tree | a296141574d6ac3bc085df708d3feeb817f83204 | |
| parent | 175bc8e5a53740432c844b5aae1981d4f47c96f7 (diff) | |
| download | emacs-6825e5686a4bf21f5d5a0ae1af889097cfa2f597.tar.gz emacs-6825e5686a4bf21f5d5a0ae1af889097cfa2f597.zip | |
Normalise setq during macro-expansion
Early normalisation of setq during macroexpand-all allows later
stages, cconv, byte-opt and codegen, to be simplified and duplicated
checks to be eliminated.
* lisp/emacs-lisp/macroexp.el (macroexp--expand-all):
Normalise all setq forms to a sequence of (setq VAR EXPR).
Emit warnings if necessary.
* lisp/emacs-lisp/cconv.el (cconv-convert, cconv-analyze-form):
* lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker):
* lisp/emacs-lisp/bytecomp.el (byte-compile-setq):
Simplify.
* test/lisp/emacs-lisp/bytecomp-tests.el: Adapt and add tests.
* test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-nonvariable.el;
* test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-odd.el:
New files.
| -rw-r--r-- | lisp/emacs-lisp/byte-opt.el | 41 | ||||
| -rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 26 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cconv.el | 47 | ||||
| -rw-r--r-- | lisp/emacs-lisp/macroexp.el | 48 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-nonvariable.el | 3 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-odd.el | 3 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/bytecomp-tests.el | 8 |
7 files changed, 101 insertions, 75 deletions
diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 69795f9c112..0e10e332b29 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el | |||
| @@ -463,32 +463,21 @@ for speeding up processing.") | |||
| 463 | ;; is a *value* and shouldn't appear in the car. | 463 | ;; is a *value* and shouldn't appear in the car. |
| 464 | (`((closure . ,_) . ,_) form) | 464 | (`((closure . ,_) . ,_) form) |
| 465 | 465 | ||
| 466 | (`(setq . ,args) | 466 | (`(setq ,var ,expr) |
| 467 | (let ((var-expr-list nil)) | 467 | (let ((lexvar (assq var byte-optimize--lexvars)) |
| 468 | (while args | 468 | (value (byte-optimize-form expr nil))) |
| 469 | (unless (and (consp args) | 469 | (when lexvar |
| 470 | (symbolp (car args)) (consp (cdr args))) | 470 | (setcar (cdr lexvar) t) ; Mark variable to be kept. |
| 471 | (byte-compile-warn-x form "malformed setq form: %S" form)) | 471 | (setcdr (cdr lexvar) nil) ; Inhibit further substitution. |
| 472 | (let* ((var (car args)) | 472 | |
| 473 | (expr (cadr args)) | 473 | (when (memq var byte-optimize--aliased-vars) |
| 474 | (lexvar (assq var byte-optimize--lexvars)) | 474 | ;; Cancel aliasing of variables aliased to this one. |
| 475 | (value (byte-optimize-form expr nil))) | 475 | (dolist (v byte-optimize--lexvars) |
| 476 | (when lexvar | 476 | (when (eq (nth 2 v) var) |
| 477 | (setcar (cdr lexvar) t) ; Mark variable to be kept. | 477 | ;; V is bound to VAR but VAR is now mutated: |
| 478 | (setcdr (cdr lexvar) nil) ; Inhibit further substitution. | 478 | ;; cancel aliasing. |
| 479 | 479 | (setcdr (cdr v) nil))))) | |
| 480 | (when (memq var byte-optimize--aliased-vars) | 480 | `(,fn ,var ,value))) |
| 481 | ;; Cancel aliasing of variables aliased to this one. | ||
| 482 | (dolist (v byte-optimize--lexvars) | ||
| 483 | (when (eq (nth 2 v) var) | ||
| 484 | ;; V is bound to VAR but VAR is now mutated: | ||
| 485 | ;; cancel aliasing. | ||
| 486 | (setcdr (cdr v) nil))))) | ||
| 487 | |||
| 488 | (push var var-expr-list) | ||
| 489 | (push value var-expr-list)) | ||
| 490 | (setq args (cddr args))) | ||
| 491 | (cons fn (nreverse var-expr-list)))) | ||
| 492 | 481 | ||
| 493 | (`(defvar ,(and (pred symbolp) name) . ,rest) | 482 | (`(defvar ,(and (pred symbolp) name) . ,rest) |
| 494 | (let ((optimized-rest (and rest | 483 | (let ((optimized-rest (and rest |
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index ab21fba8a27..1f868d2217c 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el | |||
| @@ -4225,25 +4225,13 @@ This function is never called when `lexical-binding' is nil." | |||
| 4225 | (byte-defop-compiler-1 quote) | 4225 | (byte-defop-compiler-1 quote) |
| 4226 | 4226 | ||
| 4227 | (defun byte-compile-setq (form) | 4227 | (defun byte-compile-setq (form) |
| 4228 | (let* ((args (cdr form)) | 4228 | (cl-assert (= (length form) 3)) ; normalised in macroexp |
| 4229 | (len (length args))) | 4229 | (let ((var (nth 1 form)) |
| 4230 | (if (= (logand len 1) 1) | 4230 | (expr (nth 2 form))) |
| 4231 | (progn | 4231 | (byte-compile-form expr) |
| 4232 | (byte-compile-report-error | 4232 | (unless byte-compile--for-effect |
| 4233 | (format-message | 4233 | (byte-compile-out 'byte-dup 0)) |
| 4234 | "missing value for `%S' at end of setq" (car (last args)))) | 4234 | (byte-compile-variable-set var) |
| 4235 | (byte-compile-form | ||
| 4236 | `(signal 'wrong-number-of-arguments '(setq ,len)) | ||
| 4237 | byte-compile--for-effect)) | ||
| 4238 | (if args | ||
| 4239 | (while args | ||
| 4240 | (byte-compile-form (car (cdr args))) | ||
| 4241 | (or byte-compile--for-effect (cdr (cdr args)) | ||
| 4242 | (byte-compile-out 'byte-dup 0)) | ||
| 4243 | (byte-compile-variable-set (car args)) | ||
| 4244 | (setq args (cdr (cdr args)))) | ||
| 4245 | ;; (setq), with no arguments. | ||
| 4246 | (byte-compile-form nil byte-compile--for-effect))) | ||
| 4247 | (setq byte-compile--for-effect nil))) | 4235 | (setq byte-compile--for-effect nil))) |
| 4248 | 4236 | ||
| 4249 | (byte-defop-compiler-1 set-default) | 4237 | (byte-defop-compiler-1 set-default) |
diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 1a501f50bfc..b12f1db677e 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el | |||
| @@ -555,29 +555,19 @@ places where they originally did not directly appear." | |||
| 555 | `(,(car form) ,(cconv-convert form1 env extend) | 555 | `(,(car form) ,(cconv-convert form1 env extend) |
| 556 | :fun-body ,(cconv--convert-function () body env form1))) | 556 | :fun-body ,(cconv--convert-function () body env form1))) |
| 557 | 557 | ||
| 558 | (`(setq . ,forms) ; setq special form | 558 | (`(setq ,var ,expr) |
| 559 | (if (= (logand (length forms) 1) 1) | 559 | (let ((var-new (or (cdr (assq var env)) var)) |
| 560 | ;; With an odd number of args, let bytecomp.el handle the error. | 560 | (value (cconv-convert expr env extend))) |
| 561 | form | 561 | (pcase var-new |
| 562 | (let ((prognlist ())) | 562 | ((pred symbolp) `(,(car form) ,var-new ,value)) |
| 563 | (while forms | 563 | (`(car-safe ,iexp) `(setcar ,iexp ,value)) |
| 564 | (let* ((sym (pop forms)) | 564 | ;; This "should never happen", but for variables which are |
| 565 | (sym-new (or (cdr (assq sym env)) sym)) | 565 | ;; mutated+captured+unused, we may end up trying to `setq' |
| 566 | (value (cconv-convert (pop forms) env extend))) | 566 | ;; on a closed-over variable, so just drop the setq. |
| 567 | (push (pcase sym-new | 567 | (_ ;; (byte-compile-report-error |
| 568 | ((pred symbolp) `(,(car form) ,sym-new ,value)) | 568 | ;; (format "Internal error in cconv of (setq %s ..)" |
| 569 | (`(car-safe ,iexp) `(setcar ,iexp ,value)) | 569 | ;; sym-new)) |
| 570 | ;; This "should never happen", but for variables which are | 570 | value)))) |
| 571 | ;; mutated+captured+unused, we may end up trying to `setq' | ||
| 572 | ;; on a closed-over variable, so just drop the setq. | ||
| 573 | (_ ;; (byte-compile-report-error | ||
| 574 | ;; (format "Internal error in cconv of (setq %s ..)" | ||
| 575 | ;; sym-new)) | ||
| 576 | value)) | ||
| 577 | prognlist))) | ||
| 578 | (if (cdr prognlist) | ||
| 579 | `(progn . ,(nreverse prognlist)) | ||
| 580 | (car prognlist))))) | ||
| 581 | 571 | ||
| 582 | (`(,(and (or 'funcall 'apply) callsym) ,fun . ,args) | 572 | (`(,(and (or 'funcall 'apply) callsym) ,fun . ,args) |
| 583 | ;; These are not special forms but we treat them separately for the needs | 573 | ;; These are not special forms but we treat them separately for the needs |
| @@ -751,14 +741,13 @@ This function does not return anything but instead fills the | |||
| 751 | (cconv-analyze-form (cadr (pop body-forms)) env)) | 741 | (cconv-analyze-form (cadr (pop body-forms)) env)) |
| 752 | (cconv--analyze-function vrs body-forms env form)) | 742 | (cconv--analyze-function vrs body-forms env form)) |
| 753 | 743 | ||
| 754 | (`(setq . ,forms) | 744 | (`(setq ,var ,expr) |
| 755 | ;; If a local variable (member of env) is modified by setq then | 745 | ;; If a local variable (member of env) is modified by setq then |
| 756 | ;; it is a mutated variable. | 746 | ;; it is a mutated variable. |
| 757 | (while forms | 747 | (let ((v (assq var env))) ; v = non nil if visible |
| 758 | (let ((v (assq (car forms) env))) ; v = non nil if visible | 748 | (when v |
| 759 | (when v (setf (nth 2 v) t))) | 749 | (setf (nth 2 v) t))) |
| 760 | (cconv-analyze-form (cadr forms) env) | 750 | (cconv-analyze-form expr env)) |
| 761 | (setq forms (cddr forms)))) | ||
| 762 | 751 | ||
| 763 | (`((lambda . ,_) . ,_) ; First element is lambda expression. | 752 | (`((lambda . ,_) . ,_) ; First element is lambda expression. |
| 764 | (byte-compile-warn-x | 753 | (byte-compile-warn-x |
diff --git a/lisp/emacs-lisp/macroexp.el b/lisp/emacs-lisp/macroexp.el index 51c6e8e0ca2..bae303c213c 100644 --- a/lisp/emacs-lisp/macroexp.el +++ b/lisp/emacs-lisp/macroexp.el | |||
| @@ -369,6 +369,54 @@ Assumes the caller has bound `macroexpand-all-environment'." | |||
| 369 | (macroexp--all-forms body)) | 369 | (macroexp--all-forms body)) |
| 370 | (cdr form)) | 370 | (cdr form)) |
| 371 | form))) | 371 | form))) |
| 372 | (`(setq ,(and var (pred symbolp) | ||
| 373 | (pred (not booleanp)) (pred (not keywordp))) | ||
| 374 | ,expr) | ||
| 375 | ;; Fast path for the setq common case. | ||
| 376 | (let ((new-expr (macroexp--expand-all expr))) | ||
| 377 | (if (eq new-expr expr) | ||
| 378 | form | ||
| 379 | `(,fn ,var ,new-expr)))) | ||
| 380 | (`(setq . ,args) | ||
| 381 | ;; Normalise to a sequence of (setq SYM EXPR). | ||
| 382 | ;; Malformed code is translated to code that signals an error | ||
| 383 | ;; at run time. | ||
| 384 | (let ((nargs (length args))) | ||
| 385 | (if (/= (logand nargs 1) 0) | ||
| 386 | (macroexp-warn-and-return | ||
| 387 | "odd number of arguments in `setq' form" | ||
| 388 | `(signal 'wrong-number-of-arguments '(setq ,nargs)) | ||
| 389 | nil 'compile-only fn) | ||
| 390 | (let ((assignments nil)) | ||
| 391 | (while (consp (cdr-safe args)) | ||
| 392 | (let* ((var (car args)) | ||
| 393 | (expr (cadr args)) | ||
| 394 | (new-expr (macroexp--expand-all expr)) | ||
| 395 | (assignment | ||
| 396 | (if (and (symbolp var) | ||
| 397 | (not (booleanp var)) (not (keywordp var))) | ||
| 398 | `(,fn ,var ,new-expr) | ||
| 399 | (macroexp-warn-and-return | ||
| 400 | (format-message "attempt to set %s `%s'" | ||
| 401 | (if (symbolp var) | ||
| 402 | "constant" | ||
| 403 | "non-variable") | ||
| 404 | var) | ||
| 405 | (cond | ||
| 406 | ((keywordp var) | ||
| 407 | ;; Accept `(setq :a :a)' for compatibility. | ||
| 408 | `(if (eq ,var ,new-expr) | ||
| 409 | ,var | ||
| 410 | (signal 'setting-constant (list ',var)))) | ||
| 411 | ((symbolp var) | ||
| 412 | `(signal 'setting-constant (list ',var))) | ||
| 413 | (t | ||
| 414 | `(signal 'wrong-type-argument | ||
| 415 | (list 'symbolp ',var)))) | ||
| 416 | nil 'compile-only var)))) | ||
| 417 | (push assignment assignments)) | ||
| 418 | (setq args (cddr args))) | ||
| 419 | (cons 'progn (nreverse assignments)))))) | ||
| 372 | (`(,(and fun `(lambda . ,_)) . ,args) | 420 | (`(,(and fun `(lambda . ,_)) . ,args) |
| 373 | ;; Embedded lambda in function position. | 421 | ;; Embedded lambda in function position. |
| 374 | ;; If the byte-optimizer is loaded, try to unfold this, | 422 | ;; If the byte-optimizer is loaded, try to unfold this, |
diff --git a/test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-nonvariable.el b/test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-nonvariable.el new file mode 100644 index 00000000000..5a56913cd9b --- /dev/null +++ b/test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-nonvariable.el | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | ;;; -*- lexical-binding: t -*- | ||
| 2 | (defun foo () | ||
| 3 | (setq (a) nil)) | ||
diff --git a/test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-odd.el b/test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-odd.el new file mode 100644 index 00000000000..9ce80de08cd --- /dev/null +++ b/test/lisp/emacs-lisp/bytecomp-resources/warn-variable-setq-odd.el | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | ;;; -*- lexical-binding: t -*- | ||
| 2 | (defun foo (a b) | ||
| 3 | (setq a 1 b)) | ||
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index 27098d0bb1c..9abc17a1c41 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el | |||
| @@ -951,11 +951,17 @@ byte-compiled. Run with dynamic binding." | |||
| 951 | "let-bind nonvariable") | 951 | "let-bind nonvariable") |
| 952 | 952 | ||
| 953 | (bytecomp--define-warning-file-test "warn-variable-set-constant.el" | 953 | (bytecomp--define-warning-file-test "warn-variable-set-constant.el" |
| 954 | "variable reference to constant") | 954 | "attempt to set constant") |
| 955 | 955 | ||
| 956 | (bytecomp--define-warning-file-test "warn-variable-set-nonvariable.el" | 956 | (bytecomp--define-warning-file-test "warn-variable-set-nonvariable.el" |
| 957 | "variable reference to nonvariable") | 957 | "variable reference to nonvariable") |
| 958 | 958 | ||
| 959 | (bytecomp--define-warning-file-test "warn-variable-setq-nonvariable.el" | ||
| 960 | "attempt to set non-variable") | ||
| 961 | |||
| 962 | (bytecomp--define-warning-file-test "warn-variable-setq-odd.el" | ||
| 963 | "odd number of arguments") | ||
| 964 | |||
| 959 | (bytecomp--define-warning-file-test | 965 | (bytecomp--define-warning-file-test |
| 960 | "warn-wide-docstring-autoload.el" | 966 | "warn-wide-docstring-autoload.el" |
| 961 | "autoload .foox. docstring wider than .* characters") | 967 | "autoload .foox. docstring wider than .* characters") |