Function: cconv-make-interpreted-closure

cconv-make-interpreted-closure is a byte-compiled function defined in cconv.el.gz.

Signature

(cconv-make-interpreted-closure ARGS BODY ENV DOCSTRING IFORM)

Documentation

Make a closure for the interpreter.

This is intended to be called at runtime by the Lisp interpreter (when the code has not been compiled). FUN is the closure's source code, must be a lambda form. ENV is the runtime representation of the lexical environment, i.e. a list whose elements can be either plain symbols (which indicate that this symbol should use dynamic scoping) or pairs (SYMBOL . VALUE) for the lexical bindings.

The purpose of this function is to reduce ENV to the part actually used by the function, so we are closer to the ideal of "safe for space". In practice it has two benefits: it makes closures a bit more predictable and human-readable, and more importantly it avoids accidentally including values that we cannot print readably, which can break code that can save closures in files, such as in bookmark or savehist.

Source Code

;; Defined in /usr/src/emacs/lisp/emacs-lisp/cconv.el.gz
(defun cconv-make-interpreted-closure (args body env docstring iform)
  "Make a closure for the interpreter.
This is intended to be called at runtime by the Lisp interpreter (when
the code has not been compiled).
FUN is the closure's source code, must be a lambda form.
ENV is the runtime representation of the lexical environment,
i.e. a list whose elements can be either plain symbols (which indicate
that this symbol should use dynamic scoping) or pairs (SYMBOL . VALUE)
for the lexical bindings.

The purpose of this function is to reduce ENV to the part actually used by the
function, so we are closer to the ideal of \"safe for space\".  In practice it has
two benefits: it makes closures a bit more predictable and human-readable,
and more importantly it avoids accidentally including values that we cannot
print `read'ably, which can break code that can save closures in files, such as
in bookmark or savehist."
  (cl-assert (consp body))
  (cl-assert (listp args))
  ;; Temporarily rebind `internal-make-interpreted-closure-function'
  ;; to avoid infinite recursion during bootstrap, but also if you
  ;; happen to reload or Edebug `cconv.el' or `macroexp.el'.
  ;; FIXME: This means that we will not reduce the ENV of interpreted
  ;; closures built during the computation of another interpreted closure,
  ;; as in: cconv-make-interpreted-closure => macroexpand-all
  ;; => cl-typep--inliner => cconv-make-interpreted-closure.
  ;; We expect these cases are sufficiently infrequent and those closures are
  ;; sufficiently short-lived that it's worth the tradeoff of avoiding
  ;; nasty inf-loops when some of the code of cconv itself is interpreted.
  (let ((internal-make-interpreted-closure-function #'make-interpreted-closure)
        (lexvars (delq nil (mapcar #'car-safe env))))
    (if (or
         ;; Functions with a `:closure-dont-trim-context' marker
         ;; should keep their whole context untrimmed (bug#59213).
         (and (eq :closure-dont-trim-context (car body))
              ;; Check the function doesn't just return the magic keyword.
              (cdr body)
              ;; Drop the magic marker from the closure.
              (setq body (cdr body)))
         ;; There's no var to capture, so skip the analysis.
         (null lexvars))
        ;; The lexical environment is empty, or needs to be preserved,
        ;; so there's no need to look for free variables.
        ;; Attempting to replace body by a macroexpanded version
        ;; caused bootstrap to fail.
        (make-interpreted-closure args body env docstring iform)
      ;; We could try and cache the result of the macroexpansion and
      ;; `cconv-fv' analysis.  Not sure it's worth the trouble.
      (let* ((form `#'(lambda ,args ,iform . ,body))
             (expanded-form
              (let ((lexical-binding t) ;; Tell macros which dialect is in use.
                    ;; Make the macro aware of any defvar declarations in scope.
                    (macroexp--dynvars
                     (if macroexp--dynvars
                         (append env macroexp--dynvars) env)))
                (macroexpand-all form macroexpand-all-environment)))
             ;; Since we macroexpanded the body, we may as well use that.
             (expanded-fun-body
              (pcase expanded-form
                (`#'(lambda ,_args ,_iform . ,newbody) newbody)
                (_ body)))

             (dynvars (delq nil (mapcar (lambda (b) (if (symbolp b) b)) env)))
             (fvs (cconv-fv expanded-form lexvars dynvars))
             (newenv (nconc (mapcar (lambda (fv) (assq fv env)) (car fvs))
                            (cdr fvs))))
        ;; Never return a nil env, since nil means to use the dynbind
        ;; dialect of Elisp.
        (make-interpreted-closure args expanded-fun-body (or newenv '(t))
                                  docstring iform)))))