[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

scratch/fcr 0d45186: lisp/emacs-lisp/fcr.el: Signal errors for invalid c

From: Stefan Monnier
Subject: scratch/fcr 0d45186: lisp/emacs-lisp/fcr.el: Signal errors for invalid code
Date: Tue, 21 Dec 2021 09:57:41 -0500 (EST)

branch: scratch/fcr
commit 0d45186882cec71dc687e76fef624ef8d6976358
Author: Stefan Monnier <monnier@iro.umontreal.ca>
Commit: Stefan Monnier <monnier@iro.umontreal.ca>

    lisp/emacs-lisp/fcr.el: Signal errors for invalid code
    * test/lisp/emacs-lisp/fcr-tests.el (fcr-tests): Remove left-over
    debugging messages.
    (fcr-tests--limits): New test.
    * lisp/emacs-lisp/fcr.el (fcr-defstruct): Fill the `index-table` and
    signal an error in case of duplicate slot names.
    (fcr-lambda): Change use of `fcr--fix-type` so `cconv-convert` can use
    it to detect store-converted slots.  Tweak generated code to avoid
    a warning.
    (fcr--fix-type): Adjust accordingly.
    * lisp/emacs-lisp/cconv.el (cconv-convert): Signal an error if we
    store-convert a FCR slot.
 lisp/emacs-lisp/cconv.el            |  8 +++++
 lisp/emacs-lisp/fcr.el              | 66 +++++++++++++++++++++++++++----------
 test/lisp/emacs-lisp/cconv-tests.el |  1 +
 test/lisp/emacs-lisp/fcr-tests.el   | 43 ++++++++++++++++++++----
 4 files changed, 94 insertions(+), 24 deletions(-)

diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el
index 4fdcf2b..679d813 100644
--- a/lisp/emacs-lisp/cconv.el
+++ b/lisp/emacs-lisp/cconv.el
@@ -604,6 +604,14 @@ places where they originally did not directly appear."
     (`(declare . ,_) form)              ;The args don't contain code.
+    (`(fcr--fix-type (ignore . ,vars) ,exp)
+     (dolist (var vars)
+       (let ((x (assq var env)))
+         (pcase (cdr x)
+           (`(car-safe . ,_) (error "Slot %S should not be mutated" var))
+           (_ (cl-assert (null (cdr x)))))))
+     (cconv-convert exp env extend))
     (`(,func . ,forms)
      ;; First element is function or whatever function-like forms are: or, and,
      ;; if, catch, progn, prog1, while, until
diff --git a/lisp/emacs-lisp/fcr.el b/lisp/emacs-lisp/fcr.el
index 548348a..970dcfb 100644
--- a/lisp/emacs-lisp/fcr.el
+++ b/lisp/emacs-lisp/fcr.el
@@ -41,6 +41,20 @@
 ;;; Code:
+;; Slots are currently immutable, tho they can be updated functionally
+;; via the "copiers": we could relax this restriction by either allowing
+;; the function itself to mutate the captured variable/slot or by providing
+;; `setf' accessors to the slots (or both), but this comes with some problems:
+;; - mutation from within the function currently would cause cconv
+;;   to perform store-conversion on the variable, so we'd either have
+;;   to prevent cconv from doing it (which might require a new bytecode op
+;;   to update the in-closure variable), or we'd have to keep track of which
+;;   slots have been store-converted so `fcr-get' can access their value
+;;   correctly.
+;; - If the mutated variable/slot is captured by another (nested) closure
+;;   store-conversion is indispensable, so if we want to avoid store-conversion
+;;   we'd have to disallow such capture.
 (eval-when-compile (require 'cl-lib))
 (eval-when-compile (require 'subr-x))   ;For `named-let'.
@@ -143,7 +157,6 @@
          (slotdescs (append
-                     ;; FIXME: Catch duplicate slot names.
                      (mapcar (lambda (field)
                                (cl--make-slot-descriptor field nil nil
                                                          '((:read-only . t))))
@@ -152,8 +165,9 @@
          (class (fcr--class-make name docstring slotdescs parents
-                                  (cons name allparents)))))
-    ;; FIXME: Use an intermediate function like `cl-struct-define'.
+                                  (cons name allparents))))
+         (it (make-hash-table :test #'eq)))
+    (setf (cl--class-index-table class) it)
        ,(when options (macroexp-warn-and-return
                        (format "Ignored options: %S" options)
@@ -169,12 +183,15 @@
            (mapcar (lambda (desc)
                      (let ((slot (cl--slot-descriptor-name desc)))
                        (cl-incf i)
+                       (when (gethash slot it)
+                         (error "Duplicate slot name: %S" slot))
+                       (setf (gethash slot it) i)
                        ;; Always use a double hyphen: if the user wants to
                        ;; make it public, it can do so with an alias.
                        `(defun ,(intern (format "%S--%S" name slot)) (fcr)
-                        ,(format "Return slot `%S' of FCR, of type `%S'."
-                                 slot name)
-                        (fcr-get fcr ,i))))
+                          ,(format "Return slot `%S' of FCR, of type `%S'."
+                                   slot name)
+                          (fcr-get fcr ,i))))
        ,@(fcr--defstruct-make-copiers copiers slots name))))
@@ -186,6 +203,12 @@
     (put name 'cl-deftype-satisfies predname)))
 (defmacro fcr-lambda (type fields args &rest body)
+  ;; FIXME: Fundamentally `fcr-lambda' should be a special form.
+  ;; We define it here as a macro which expands to something that
+  ;; looks like "normal code" in order to avoid backward compatibility
+  ;; issues with third party macros that do "code walks" and would
+  ;; likely mishandle such a new special form (e.g. `generator.el').
+  ;; But don't be fooled: this macro is tightly bound to `cconv.el'.
   (declare (indent 3) (debug (sexp (&rest (sexp form)) sexp def-body)))
   ;; FIXME: Should `fcr-defstruct' distinguish "optional" from
   ;; "mandatory" slots, and/or provide default values for slots missing
@@ -207,24 +230,31 @@
                             (bind (assq name slotbinds)))
                         ((not bind)
-                         (error "Unknown slots: %S" name))
+                         (error "Unknown slot: %S" name))
                         ((cdr bind)
-                         (error "Duplicate slots: %S" name))
+                         (error "Duplicate slot: %S" name))
                          (let ((temp (gensym "temp")))
                            (setcdr bind (list temp))
                            (cons temp (cdr field)))))))
     ;; FIXME: Optimize temps away when they're provided in the right order?
-    ;; FIXME: Slots not specified in `fields' tend to emit "Variable FOO left
-    ;; uninitialized"!
     `(let ,tempbinds
-       ;; FIXME: Prevent store-conversion for fields vars!
-       ;; FIXME: Make sure the slotbinds whose value is duplicable aren't
-       ;; just value/variable-propagated by the optimizer (tho I think our
-       ;; optimizer is too naive to be a problem currently).
-       (fcr--fix-type
-        (let ,slotbinds
+       (let ,(mapcar (lambda (bind)
+                       (if (cdr bind) bind
+                         ;; Bind to something that doesn't look
+                         ;; like a value to avoid the "Variable
+                         ;; ‘foo’ left uninitialized" warning.
+                         `(,(car bind) (progn nil))))
+                     slotbinds)
+         ;; FIXME: Make sure the slotbinds whose value is duplicable aren't
+         ;; just value/variable-propagated by the optimizer (tho I think our
+         ;; optimizer is too naive to be a problem currently).
+         (fcr--fix-type
+          ;; This `fcr--fix-type' + `ignore' call is used by the compiler (in
+          ;; `cconv.el') to detect and signal an error in case of
+          ;; store-conversion (i.e. if a variable/slot is mutated).
+          (ignore ,@(mapcar #'car slotbinds))
           (lambda ,args
             (:documentation ',type)
@@ -233,8 +263,10 @@
             (if t nil ,@(mapcar #'car slotbinds))
-(defun fcr--fix-type (fcr)
+(defun fcr--fix-type (_ignore fcr)
   (if (byte-code-function-p fcr)
+      ;; Actually, this should never happen since the `cconv.el' should have
+      ;; optimized away the call to this function.
     ;; For byte-coded functions, we store the type as a symbol in the docstring
     ;; slot.  For interpreted functions, there's no specific docstring slot
diff --git a/test/lisp/emacs-lisp/cconv-tests.el 
index d7f9af1..479afe1 100644
--- a/test/lisp/emacs-lisp/cconv-tests.el
+++ b/test/lisp/emacs-lisp/cconv-tests.el
@@ -23,6 +23,7 @@
 (require 'ert)
 (require 'cl-lib)
+(require 'generator)
 (ert-deftest cconv-tests-lambda-:documentation ()
   "Docstring for lambda can be specified with :documentation."
diff --git a/test/lisp/emacs-lisp/fcr-tests.el 
index 379fa27..c9aa00d 100644
--- a/test/lisp/emacs-lisp/fcr-tests.el
+++ b/test/lisp/emacs-lisp/fcr-tests.el
@@ -47,29 +47,58 @@
          (fcr2 (fcr-lambda fcr-test ((name (cl-incf i)) (fst (cl-incf i)))
                  (list fst snd 152 i))))
-    (message "hello-1")
     (should (equal (list (fcr-test--fst fcr1)
                          (fcr-test--snd fcr1)
                          (fcr-test--name fcr1))
                    '(1 2 "hi")))
-    (message "hello-2")
     (should (equal (list (fcr-test--fst fcr2)
                          (fcr-test--snd fcr2)
                          (fcr-test--name fcr2))
                    '(44 nil 43)))
-    (message "hello-3")
     (should (equal (funcall fcr1) '(1 2 44)))
-    (message "hello-4")
     (should (equal (funcall fcr2) '(44 nil 152 44)))
-    (message "hello-5")
     (should (equal (funcall (fcr-test-copy fcr1 :fst 7)) '(7 2 44)))
-    (message "hello-6")
     (should (cl-typep fcr1 'fcr-test))
-    (message "hello-7")
     (should (cl-typep fcr1 'fcr-object))
     (should (member (fcr-test-gen fcr1)
+(ert-deftest fcr-tests--limits ()
+  (should
+   (condition-case err
+       (let ((lexical-binding t)
+             (byte-compile-debug t))
+         (byte-compile '(lambda ()
+                          (let ((inc-where nil))
+                            (fcr-lambda advice ((where 'foo)) ()
+                              (setq inc-where (lambda () (setq where (1+ 
+                              where))))
+         nil)
+     (error
+      (and (eq 'error (car err))
+           (string-match "where.*mutated" (cadr err))))))
+  (should
+   (condition-case err
+       (progn (macroexpand '(fcr-defstruct fcr--foo a a))
+              nil)
+     (error
+      (and (eq 'error (car err))
+           (string-match "Duplicate slot name: a$" (cadr err))))))
+  (should
+   (condition-case err
+       (progn (macroexpand '(fcr-defstruct (fcr--foo (:parent advice)) where))
+              nil)
+     (error
+      (and (eq 'error (car err))
+           (string-match "Duplicate slot name: where$" (cadr err))))))
+  (should
+   (condition-case err
+       (progn (macroexpand '(fcr-lambda advice ((where 1) (where 2)) () where))
+              nil)
+     (error
+      (and (eq 'error (car err))
+           (string-match "Duplicate slot: where$" (cadr err)))))))
 ;;; fcr-tests.el ends here.

reply via email to

[Prev in Thread] Current Thread [Next in Thread]