From: Stefan Monnier Date: Mon, 11 Mar 2024 20:12:26 +0000 (-0400) Subject: Use a dedicated type to represent interpreted-function values X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=111821769aa7893a9d5d2be10b9d41d159003063;p=emacs.git Use a dedicated type to represent interpreted-function values Change `function` so that when evaluating #'(lambda ...) we return an object of type `interpreted-function` rather than a list starting with one of `lambda` or `closure`. The new type reuses the existing PVEC_CLOSURE (nee PVEC_COMPILED) tag and tries to align the corresponding elements: - the arglist, the docstring, and the interactive-form go in the same slots as for byte-code functions. - the body of the function goes in the slot used for the bytecode string. - the lexical context goes in the slot used for the constants of bytecoded functions. The first point above means that `help-function-arglist`, `documentation`, and `interactive-form`s don't need to distinguish interpreted and bytecode functions any more. Main benefits of the change: - We can now reliably distinguish a list from a function value. - `cl-defmethod` can dispatch on `interactive-function` and `closure`. Dispatch on `function` also works now for interpreted functions but still won't work for functions represented as lists or as symbols, of course. - Function values are now self-evaluating. That was alrready the case when byte-compiled, but not when interpreted since (eval '(closure ...)) signals a void-function error. That also avoids false-positive warnings about "don't quote your lambdas" when doing things like `(mapcar ',func ...)`. * src/eval.c (Fmake_interpreted_closure): New function. (Ffunction): Use it and change calling convention of `Vinternal_make_interpreted_closure_function`. (FUNCTIONP, Fcommandp, eval_sub, funcall_general, funcall_lambda) (Ffunc_arity, lambda_arity): Simplify. (funcall_lambda): Adjust to new representation. (syms_of_eval): `defsubr` the new function. Remove definition of `Qclosure`. * lisp/emacs-lisp/cconv.el (cconv-make-interpreted-closure): Change calling convention and use `make-interpreted-closure`. * src/data.c (Fcl_type_of): Distinguish `byte-code-function`s from `interpreted-function`s. (Fclosurep, finterpreted_function_p): New functions. (Fbyte_code_function_p): Don't be confused by `interpreted-function`s. (Finteractive_form, Fcommand_modes): Simplify. (syms_of_data): Define new type symbols and `defsubr` the two new functions. * lisp/emacs-lisp/cl-print.el (cl-print-object) : New method. * lisp/emacs-lisp/oclosure.el (oclosure): Refine the parent to be `closure`. (oclosure--fix-type, oclosure-type): Simplify. (oclosure--copy, oclosure--get, oclosure--set): Adjust to new representation. * src/callint.c (Fcall_interactively): Adjust to new representation. * src/lread.c (bytecode_from_rev_list): * lisp/simple.el (function-documentation): * lisp/help.el (help-function-arglist): Remove the old `closure` case and adjust the byte-code case so it handles `interpreted-function`s. * lisp/emacs-lisp/cl-preloaded.el (closure): New type. (byte-code-function): Add it as a parent. (interpreted-function): Adjust parent (the type itself was already added earlier by accident). * lisp/emacs-lisp/bytecomp.el (byte-compile--reify-function): Adjust to new representation. (byte-compile): Use `interpreted-function-p`. * lisp/emacs-lisp/byte-opt.el (byte-compile-inline-expand): Adjust to new representation. (side-effect-free-fns): Add `interpreted-function-p` and `closurep`. * src/profiler.c (trace_hash, ffunction_equal): Simplify. * lisp/profiler.el (profiler-function-equal): Simplify. * lisp/emacs-lisp/nadvice.el (advice--interactive-form-1): Use `interpreted-function-p`; adjust to new representation; and take advantage of the fact that function values are now self-evaluating. * lisp/emacs-lisp/lisp-mode.el (closure): Remove `lisp-indent-function` property. * lisp/emacs-lisp/disass.el (disassemble-internal): Adjust to new representation. * lisp/emacs-lisp/edebug.el (edebug--strip-instrumentation): Use `interpreted-function-p`. * lisp/emacs-lisp/comp-common.el (comp-known-type-specifiers): Add `closurep` and `interpreted-function-p`. * test/lisp/help-fns-tests.el (help-fns-test-lisp-defun): Adjust to more precise type info in `describe-function`. * test/lisp/erc/resources/erc-d/erc-d-tests.el (erc-d--render-entries): Use `interpreted-function-p`. * test/lisp/emacs-lisp/macroexp-resources/vk.el (vk-f4, vk-f5): Don't hardcode function values. * doc/lispref/functions.texi (Anonymous Functions): Don't suggest that function values are lists. Reword "self-quoting" to reflect the fact that #' doesn't return the exact same object. Update examples with the new shape of the return value. * doc/lispref/variables.texi (Lexical Binding): * doc/lispref/lists.texi (Rearrangement): * doc/lispref/control.texi (Handling Errors): Update examples to reflect new representation of function values. (cherry picked from commit f2bccae22bd47a2e7e0937b78ea06131711b935a) --- diff --git a/doc/lispref/compile.texi b/doc/lispref/compile.texi index 08e824d2781..19451f31740 100644 --- a/doc/lispref/compile.texi +++ b/doc/lispref/compile.texi @@ -37,7 +37,7 @@ variable binding for @code{no-byte-compile} into it, like this: * Docs and Compilation:: Dynamic loading of documentation strings. * Eval During Compile:: Code to be evaluated when you compile. * Compiler Errors:: Handling compiler error messages. -* Byte-Code Objects:: The data type used for byte-compiled functions. +* Closure Objects:: The data type used for byte-compiled functions. * Disassembly:: Disassembling byte-code; how to read byte-code. @end menu @@ -120,7 +120,7 @@ replacing the previous definition with the compiled one. The function definition of @var{symbol} must be the actual code for the function; @code{byte-compile} does not handle function indirection. The return value is the byte-code function object which is the compiled -definition of @var{symbol} (@pxref{Byte-Code Objects}). +definition of @var{symbol} (@pxref{Closure Objects}). @example @group @@ -487,21 +487,22 @@ string for details. using @code{error}. If so, set @code{byte-compile-error-on-warn} to a non-@code{nil} value. -@node Byte-Code Objects -@section Byte-Code Function Objects +@node Closure Objects +@section Closure Function Objects @cindex compiled function @cindex byte-code function @cindex byte-code object - Byte-compiled functions have a special data type: they are -@dfn{byte-code function objects}. Whenever such an object appears as -a function to be called, Emacs uses the byte-code interpreter to -execute the byte-code. + Byte-compiled functions use a special data type: they are closures. +Closures are used both for byte-compiled Lisp functions as well as for +interpreted Lisp functions. Whenever such an object appears as +a function to be called, Emacs uses the appropriate interpreter to +execute either the byte-code or the non-compiled Lisp code. - Internally, a byte-code function object is much like a vector; its + Internally, a closure is much like a vector; its elements can be accessed using @code{aref}. Its printed representation is like that for a vector, with an additional @samp{#} -before the opening @samp{[}. It must have at least four elements; +before the opening @samp{[}. It must have at least three elements; there is no maximum number, but only the first six elements have any normal use. They are: @@ -515,20 +516,28 @@ zero to 6, and the maximum number of arguments in bits 8 to 14. If the argument list uses @code{&rest}, then bit 7 is set; otherwise it's cleared. -If @var{argdesc} is a list, the arguments will be dynamically bound +When the closure is a byte-code function, +if @var{argdesc} is a list, the arguments will be dynamically bound before executing the byte code. If @var{argdesc} is an integer, the arguments will be instead pushed onto the stack of the byte-code interpreter, before executing the code. -@item byte-code -The string containing the byte-code instructions. +@item code +For interpreted functions, this element is the (non-empty) list of Lisp +forms that make up the function's body. For byte-compiled functions, it +is the string containing the byte-code instructions. @item constants -The vector of Lisp objects referenced by the byte code. These include -symbols used as function names and variable names. +For byte-compiled functions, this holds the vector of Lisp objects +referenced by the byte code. These include symbols used as function +names and variable names. +For interpreted functions, this is @code{nil} if the function is using the old +dynamically scoped dialect of Emacs Lisp, and otherwise it holds the +function's lexical environment. @item stacksize -The maximum stack size this function needs. +The maximum stack size this function needs. This element is left unused +for interpreted functions. @item docstring The documentation string (if any); otherwise, @code{nil}. The value may @@ -558,8 +567,8 @@ representation. It is the definition of the command @code{make-byte-code}: @defun make-byte-code &rest elements -This function constructs and returns a byte-code function object -with @var{elements} as its elements. +This function constructs and returns a closure which represents the +byte-code function object with @var{elements} as its elements. @end defun You should not try to come up with the elements for a byte-code @@ -567,6 +576,20 @@ function yourself, because if they are inconsistent, Emacs may crash when you call the function. Always leave it to the byte compiler to create these objects; it makes the elements consistent (we hope). +The primitive way to create an interpreted function is with +@code{make-interpreted-closure}: + +@defun make-interpreted-closure args body env &optional docstring iform +This function constructs and returns a closure representing the +interpreted function with arguments @var{args} and whose body is made of +@var{body} which must be a non-@code{nil} list of Lisp forms. @var{env} is the +lexical environment in the same form as used with @code{eval} +(@pxref{Eval}). The documentation @var{docstring} if non-@code{nil} should be +a string, and the interactive form @var{iform} if non-@code{nil} should be of +the form @w{@code{(interactive @var{arg-descriptor})}} (@pxref{Using +Interactive}). +@end defun + @node Disassembly @section Disassembled Byte-Code @cindex disassembled byte-code @@ -595,7 +618,7 @@ name of an existing buffer. Then the output goes there, at point, and point is left before the output. The argument @var{object} can be a function name, a lambda expression -(@pxref{Lambda Expressions}), or a byte-code object (@pxref{Byte-Code +(@pxref{Lambda Expressions}), or a byte-code object (@pxref{Closure Objects}). If it is a lambda expression, @code{disassemble} compiles it and disassembles the resulting compiled code. @end deffn diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi index 06bf51d8072..8b74b7cec5b 100644 --- a/doc/lispref/control.texi +++ b/doc/lispref/control.texi @@ -2412,7 +2412,7 @@ point where we signaled the original error: @group Debugger entered--Lisp error: (error "Oops") signal(error ("Oops")) - (closure (t) (err) (signal 'error (cdr err)))((user-error "Oops")) + #f(lambda (err) [t] (signal 'error (cdr err)))((user-error "Oops")) user-error("Oops") @dots{} eval((handler-bind ((user-error (lambda (err) @dots{} diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index ec93a0b9c8a..339272d1f05 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -323,7 +323,7 @@ Programming Types * Macro Type:: A method of expanding an expression into another expression, more fundamental but less pretty. * Primitive Function Type:: A function written in C, callable from Lisp. -* Byte-Code Type:: A function written in Lisp, then compiled. +* Closure Type:: A function written in Lisp, then compiled. * Record Type:: Compound objects with programmer-defined types. * Type Descriptors:: Objects holding information about types. * Autoload Type:: A type used for automatically loading seldom-used @@ -657,7 +657,7 @@ Byte Compilation * Docs and Compilation:: Dynamic loading of documentation strings. * Eval During Compile:: Code to be evaluated when you compile. * Compiler Errors:: Handling compiler error messages. -* Byte-Code Objects:: The data type used for byte-compiled functions. +* Closure Objects:: The data type used for byte-compiled functions. * Disassembly:: Disassembling byte-code; how to read byte-code. Native Compilation diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index ff635fc54b2..c57de08460f 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -130,7 +130,7 @@ it also encloses an environment of lexical variable bindings. @item byte-code function A function that has been compiled by the byte compiler. -@xref{Byte-Code Type}. +@xref{Closure Type}. @item autoload object @cindex autoload object @@ -227,6 +227,16 @@ Compilation}), or natively-compiled (@pxref{Native Compilation}), or a function loaded from a dynamic module (@pxref{Dynamic Modules}). @end defun +@defun interpreted-function-p object +This function returns @code{t} if @var{object} is an interpreted function. +@end defun + +@defun closurep object +This function returns @code{t} if @var{object} is a closure, which is +a particular kind of function object. Currently closures are used +for all byte-code functions and all interpreted functions. +@end defun + @defun subr-arity subr This works like @code{func-arity}, but only for built-in functions and without symbol indirection. It signals an error for non-built-in @@ -1136,8 +1146,7 @@ Functions}). @xref{describe-symbols example}, for a realistic example of this. When defining a lambda expression that is to be used as an anonymous -function, you can in principle use any method to construct the list. -But typically you should use the @code{lambda} macro, or the +function, you should use the @code{lambda} macro, or the @code{function} special form, or the @code{#'} read syntax: @defmac lambda args [doc] [interactive] body@dots{} @@ -1145,17 +1154,18 @@ This macro returns an anonymous function with argument list @var{args}, documentation string @var{doc} (if any), interactive spec @var{interactive} (if any), and body forms given by @var{body}. -Under dynamic binding, this macro effectively makes @code{lambda} -forms self-quoting: evaluating a form whose @sc{car} is @code{lambda} -yields the form itself: +For example, this macro makes @code{lambda} forms almost self-quoting: +evaluating a form whose @sc{car} is @code{lambda} yields a value that is +almost like the form itself: @example (lambda (x) (* x x)) - @result{} (lambda (x) (* x x)) + @result{} #f(lambda (x) :dynbind (* x x)) @end example -Note that when evaluating under lexical binding the result is a -closure object (@pxref{Closures}). +When evaluating under lexical binding the result is a similar +closure object, where the @code{:dynbind} marker is replaced by the +captured variables (@pxref{Closures}). The @code{lambda} form has one other effect: it tells the Emacs evaluator and byte-compiler that its argument is a function, by using @@ -1164,8 +1174,8 @@ evaluator and byte-compiler that its argument is a function, by using @defspec function function-object @cindex function quoting -This special form returns @var{function-object} without evaluating it. -In this, it is similar to @code{quote} (@pxref{Quoting}). But unlike +This special form returns the function value of the @var{function-object}. +In many ways, it is similar to @code{quote} (@pxref{Quoting}). But unlike @code{quote}, it also serves as a note to the Emacs evaluator and byte-compiler that @var{function-object} is intended to be used as a function. Assuming @var{function-object} is a valid lambda @@ -1495,7 +1505,7 @@ distinguish between a function cell that is void and one set to @group (defun bar (n) (+ n 2)) (symbol-function 'bar) - @result{} (lambda (n) (+ n 2)) + @result{} #f(lambda (n) [t] (+ n 2)) @end group @group (fset 'baz 'bar) @@ -1608,7 +1618,7 @@ argument list and body forms as the remaining elements: @example ;; @r{lexical binding is enabled.} (lambda (x) (* x x)) - @result{} (closure (t) (x) (* x x)) + @result{} #f(lambda (x) [t] (* x x)) @end example @noindent diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi index 14862bdfc2a..ca42942250c 100644 --- a/doc/lispref/lists.texi +++ b/doc/lispref/lists.texi @@ -1249,7 +1249,7 @@ this is not guaranteed to happen): @group (symbol-function 'add-foo) - @result{} (lambda (x) (nconc '(foo) x)) + @result{} #f(lambda (x) [t] (nconc '(foo) x)) @end group @group @@ -1267,7 +1267,7 @@ this is not guaranteed to happen): @group (symbol-function 'add-foo) - @result{} (lambda (x) (nconc '(foo 1 2 3 4) x)) + @result{} #f(lambda (x) [t] (nconc '(foo 1 2 3 4) x)) @end group @end smallexample @end defun diff --git a/doc/lispref/objects.texi b/doc/lispref/objects.texi index aa1e073042f..cf703aba9c8 100644 --- a/doc/lispref/objects.texi +++ b/doc/lispref/objects.texi @@ -244,7 +244,7 @@ latter are unique to Emacs Lisp. * Macro Type:: A method of expanding an expression into another expression, more fundamental but less pretty. * Primitive Function Type:: A function written in C, callable from Lisp. -* Byte-Code Type:: A function written in Lisp, then compiled. +* Closure Type:: A function written in Lisp. * Record Type:: Compound objects with programmer-defined types. * Type Descriptors:: Objects holding information about types. * Autoload Type:: A type used for automatically loading seldom-used @@ -1458,18 +1458,24 @@ with the name of the subroutine. @end group @end example -@node Byte-Code Type -@subsection Byte-Code Function Type +@node Closure Type +@subsection Closure Function Type -@dfn{Byte-code function objects} are produced by byte-compiling Lisp -code (@pxref{Byte Compilation}). Internally, a byte-code function -object is much like a vector; however, the evaluator handles this data -type specially when it appears in a function call. @xref{Byte-Code -Objects}. +@dfn{Closures} are function objects produced when turning a function +definition into a function value. Closures are used both for +byte-compiled Lisp functions as well as for interpreted Lisp functions. +Closures can be produced by byte-compiling Lisp code (@pxref{Byte +Compilation}) or simply by evaluating a lambda expression without +compiling it, resulting in an interpreted function. Internally, +a closure is much like a vector; however, the evaluator +handles this data type specially when it appears in a function call. +@xref{Closure Objects}. The printed representation and read syntax for a byte-code function object is like that for a vector, with an additional @samp{#} before the -opening @samp{[}. +opening @samp{[}. When printed for human consumption, it is printed as +a special kind of list with an additional @samp{#f} before the opening +@samp{(}. @node Record Type @subsection Record Type @@ -2042,10 +2048,7 @@ with references to further information. @xref{Buffer Basics, bufferp}. @item byte-code-function-p -@xref{Byte-Code Type, byte-code-function-p}. - -@item compiled-function-p -@xref{Byte-Code Type, compiled-function-p}. +@xref{Closure Type, byte-code-function-p}. @item case-table-p @xref{Case Tables, case-table-p}. @@ -2056,9 +2059,15 @@ with references to further information. @item char-table-p @xref{Char-Tables, char-table-p}. +@item closurep +@xref{What Is a Function, closurep}. + @item commandp @xref{Interactive Call, commandp}. +@item compiled-function-p +@xref{Closure Type, compiled-function-p}. + @item condition-variable-p @xref{Condition Variables, condition-variable-p}. @@ -2098,6 +2107,9 @@ with references to further information. @item integerp @xref{Predicates on Numbers, integerp}. +@item interpreted-function-p +@xref{What Is a Function, interpreted-function-p}. + @item keymapp @xref{Creating Keymaps, keymapp}. diff --git a/doc/lispref/sequences.texi b/doc/lispref/sequences.texi index d22303b6f5d..7ac1f87532c 100644 --- a/doc/lispref/sequences.texi +++ b/doc/lispref/sequences.texi @@ -1580,7 +1580,7 @@ nonempty vector that is not @code{eq} to any existing vector. The @code{vconcat} function also allows byte-code function objects as arguments. This is a special feature to make it easy to access the entire -contents of a byte-code function object. @xref{Byte-Code Objects}. +contents of a byte-code function object. @xref{Closure Objects}. For other concatenation functions, see @code{mapconcat} in @ref{Mapping Functions}, @code{concat} in @ref{Creating Strings}, and @code{append} diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index 4d61d461deb..16b6b52e5f1 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -1079,7 +1079,7 @@ Here is an example: (let ((x 0)) ; @r{@code{x} is lexically bound.} (setq my-ticker (lambda () (setq x (1+ x))))) - @result{} (closure ((x . 0)) () + @result{} #f(lambda () [(x 0)] (setq x (1+ x))) (funcall my-ticker) diff --git a/etc/NEWS b/etc/NEWS index f7be649cf0a..d2231bc278f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1875,6 +1875,23 @@ documentation and examples. * Incompatible Lisp Changes in Emacs 30.1 ++++ +** Evaluating a 'lambda' returns an object of type 'interpreted-function'. +Instead of representing interpreted functions as lists that start with +either 'lambda' or 'closure', Emacs now represents them as objects +of their own 'interpreted-function' type, which is very similar +to 'byte-code-function' objects (the argument list, docstring, and +interactive forms are placed in the same slots). +Lists that start with 'lambda' are now used only for non-evaluated +functions (in other words, for source code), but for backward compatibility +reasons, 'functionp' still recognizes them as functions and you can +still call them as before. +Thus code that attempts to "dig" into the internal structure of an +interpreted function's object with the likes of 'car' or 'cdr' will +no longer work and will need to use 'aref' instead to extract its +various subparts (when 'interactive-form', 'documentation', and +'help-function-arglist' aren't adequate). + +++ ** 'define-globalized-minor-mode' requires that modes use 'run-mode-hooks'. Minor modes defined with 'define-globalized-minor-mode', such as @@ -2044,6 +2061,14 @@ the region and never want to restrict 'undo' to that region, it is preferable to use the existing 'undo-inhibit-region' symbol property instead of this variable. +** New types 'closure' and 'interpreted-function'. +'interpreted-function' is the new type used for interpreted functions, +and 'closure' is the common parent type of 'interpreted-function' +and 'byte-code-function'. +Those new types come with the associated new predicates +'closurep' and `interpreted-function-p' as well as a new constructor +'make-interpreted-closure'. + ** New function 'help-fns-function-name'. For named functions, it just returns the name and otherwise it returns a short "unique" string that identifies the function. diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index ea163723a3e..3d6b35422b8 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -164,7 +164,7 @@ Earlier variables shadow later ones with the same name.") ;; The byte-code will be really inlined in byte-compile-unfold-bcf. (byte-compile--check-arity-bytecode form fn) `(,fn ,@(cdr form))) - ((or `(lambda . ,_) `(closure . ,_)) + ((pred interpreted-function-p) ;; While byte-compile-unfold-bcf can inline dynbind byte-code into ;; letbind byte-code (or any other combination for that matter), we ;; can only inline dynbind source into dynbind source or lexbind @@ -1870,6 +1870,7 @@ See Info node `(elisp) Integer Basics'." charsetp ;; data.c arrayp atom bare-symbol-p bool-vector-p bufferp byte-code-function-p + interpreted-function-p closurep byteorder car-safe cdr-safe char-or-string-p char-table-p condition-variable-p consp eq floatp indirect-function integer-or-marker-p integerp keywordp listp markerp diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index 2704378fc84..7aae87c50dc 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -2915,9 +2915,14 @@ otherwise, print without quoting." (defun byte-compile--reify-function (fun) "Return an expression which will evaluate to a function value FUN. FUN should be an interpreted closure." - (pcase-let* ((`(closure ,env ,args . ,body) fun) - (`(,preamble . ,body) (macroexp-parse-body body)) - (renv ())) + (let* ((args (aref fun 0)) + (body (aref fun 1)) + (env (aref fun 2)) + (docstring (function-documentation fun)) + (iform (interactive-form fun)) + (preamble `(,@(if docstring (list docstring)) + ,@(if iform (list iform)))) + (renv ())) ;; Turn the function's closed vars (if any) into local let bindings. (dolist (binding env) (cond @@ -2954,11 +2959,11 @@ If FORM is a lambda or a macro, byte-compile it as a function." (if (symbolp form) form "provided")) fun) (t - (when (or (symbolp form) (eq (car-safe fun) 'closure)) + (when (or (symbolp form) (interpreted-function-p fun)) ;; `fun' is a function *value*, so try to recover its ;; corresponding source code. - (when (setq lexical-binding (eq (car-safe fun) 'closure)) - (setq fun (byte-compile--reify-function fun))) + (setq lexical-binding (not (null (aref fun 2)))) + (setq fun (byte-compile--reify-function fun)) (setq need-a-value t)) ;; Expand macros. (setq fun (byte-compile-preprocess fun)) @@ -5148,7 +5153,6 @@ binding slots have been popped." ;; `arglist' is the list of arguments (or t if not recognized). ;; `body' is the body of `lam' (or t if not recognized). ((or `(lambda ,arglist . ,body) - ;; `(closure ,_ ,arglist . ,body) (and `(internal-make-closure ,arglist . ,_) (let body t)) (and (let arglist t) (let body t))) lam)) diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 4ff47971351..e6a78f07762 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el @@ -902,7 +902,7 @@ lexically and dynamically bound symbols actually used by FORM." (delete-dups cconv--dynbindings))))) (cons fvs dyns))))) -(defun cconv-make-interpreted-closure (fun env) +(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 ELisp interpreter (when the code has not been compiled). @@ -911,22 +911,27 @@ 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." - (cl-assert (eq (car-safe fun) 'lambda)) + (cl-assert (consp body)) + (cl-assert (listp args)) (let ((lexvars (delq nil (mapcar #'car-safe env)))) - (if (or (null lexvars) - ;; Functions with a `:closure-dont-trim-context' marker - ;; should keep their whole context untrimmed (bug#59213). - (and (eq :closure-dont-trim-context (nth 2 fun)) - ;; Check the function doesn't just return the magic keyword. - (nthcdr 3 fun))) + (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 ,(cdr fun) by a macroexpanded version - ;; causes bootstrap to fail. - `(closure ,env . ,(cdr fun)) + ;; 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 `#',fun) + (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. @@ -935,10 +940,10 @@ for the lexical bindings." (append env macroexp--dynvars) env))) (macroexpand-all form macroexpand-all-environment))) ;; Since we macroexpanded the body, we may as well use that. - (expanded-fun-cdr + (expanded-fun-body (pcase expanded-form - (`#'(lambda . ,cdr) cdr) - (_ (cdr fun)))) + (`#'(lambda ,_args ,_iform . ,newbody) newbody) + (_ body))) (dynvars (delq nil (mapcar (lambda (b) (if (symbolp b) b)) env))) (fvs (cconv-fv expanded-form lexvars dynvars)) @@ -946,7 +951,8 @@ for the lexical bindings." (cdr fvs)))) ;; Never return a nil env, since nil means to use the dynbind ;; dialect of ELisp. - `(closure ,(or newenv '(t)) . ,expanded-fun-cdr))))) + (make-interpreted-closure args expanded-fun-body (or newenv '(t)) + docstring iform))))) (provide 'cconv) diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el index 83d9e6ee220..fa745396b02 100644 --- a/lisp/emacs-lisp/cl-preloaded.el +++ b/lisp/emacs-lisp/cl-preloaded.el @@ -444,13 +444,24 @@ For this build of Emacs it's %dbit." ) (cl--define-built-in-type compiled-function (function) "Abstract type of functions that have been compiled.") -(cl--define-built-in-type byte-code-function (compiled-function) +(cl--define-built-in-type closure (function) + "Abstract type of functions represented by a vector-like object. +You can access the object's internals with `aref'. +The fields are used as follows: + + 0 [args] Argument list (either a list or an integer) + 1 [code] Either a byte-code string or a list of Lisp forms + 2 [constants] Either vector of constants or a lexical environment + 3 [stackdepth] Maximum amount of stack depth used by the byte-code + 4 [docstring] The documentation, or a reference to it + 5 [iform] The interactive form (if present)") +(cl--define-built-in-type byte-code-function (compiled-function closure) "Type of functions that have been byte-compiled.") (cl--define-built-in-type subr (atom) "Abstract type of functions compiled to machine code.") (cl--define-built-in-type module-function (function) "Type of functions provided via the module API.") -(cl--define-built-in-type interpreted-function (function) +(cl--define-built-in-type interpreted-function (closure) "Type of functions that have not been compiled.") (cl--define-built-in-type special-form (subr) "Type of the core syntactic elements of the Emacs Lisp language.") diff --git a/lisp/emacs-lisp/cl-print.el b/lisp/emacs-lisp/cl-print.el index 39688661eb1..e8e6502e66f 100644 --- a/lisp/emacs-lisp/cl-print.el +++ b/lisp/emacs-lisp/cl-print.el @@ -237,6 +237,38 @@ into a button whose action shows the function's disassembly.") 'byte-code-function object))))) (princ ")" stream))) +(cl-defmethod cl-print-object ((object interpreted-function) stream) + (unless stream (setq stream standard-output)) + (princ "#f(lambda " stream) + (let ((args (help-function-arglist object 'preserve-names))) + ;; It's tempting to print the arglist from the "usage" info in the + ;; doc (e.g. for `&key` args), but that only makes sense if we + ;; *don't* print the body, since otherwise the body will tend to + ;; refer to args that don't appear in the arglist. + (if args + (prin1 args stream) + (princ "()" stream))) + (let ((env (aref object 2))) + (if (null env) + (princ " :dynbind" stream) + (princ " " stream) + (cl-print-object + (vconcat (mapcar (lambda (x) (if (consp x) (list (car x) (cdr x)) x)) + env)) + stream))) + (let* ((doc (documentation object 'raw))) + (when doc + (princ " " stream) + (prin1 doc stream))) + (let ((inter (interactive-form object))) + (when inter + (princ " " stream) + (cl-print-object inter stream))) + (dolist (exp (aref object 1)) + (princ " " stream) + (cl-print-object exp stream)) + (princ ")" stream)) + ;; This belongs in oclosure.el, of course, but some load-ordering issues make it ;; complicated. (cl-defmethod cl-print-object ((object accessor) stream) diff --git a/lisp/emacs-lisp/comp-common.el b/lisp/emacs-lisp/comp-common.el index 4edfe811586..62fd28f772e 100644 --- a/lisp/emacs-lisp/comp-common.el +++ b/lisp/emacs-lisp/comp-common.el @@ -118,7 +118,9 @@ Used to modify the compiler environment." (buffer-substring (function ((or integer marker) (or integer marker)) string)) (bufferp (function (t) boolean)) + (closurep (function (t) boolean)) (byte-code-function-p (function (t) boolean)) + (interpreted-function-p (function (t) boolean)) (capitalize (function ((or integer string)) (or integer string))) (car (function (list) t)) (car-less-than-car (function (list list) boolean)) diff --git a/lisp/emacs-lisp/disass.el b/lisp/emacs-lisp/disass.el index 850cc2085f7..15caee9b29c 100644 --- a/lisp/emacs-lisp/disass.el +++ b/lisp/emacs-lisp/disass.el @@ -129,7 +129,7 @@ redefine OBJECT if it is a symbol." (setq args (help-function-arglist obj)) ;save arg list (setq obj (cdr obj)) ;throw lambda away (setq obj (cdr obj))) - ((byte-code-function-p obj) + ((closurep obj) (setq args (help-function-arglist obj))) (t (error "Compilation failed"))) (if (zerop indent) ; not a nested function @@ -178,7 +178,9 @@ redefine OBJECT if it is a symbol." (t (insert "Uncompiled body: ") (let ((print-escape-newlines t)) - (prin1 (macroexp-progn obj) + (prin1 (macroexp-progn (if (interpreted-function-p obj) + (aref obj 1) + obj)) (current-buffer)))))) (if interactive-p (message ""))) diff --git a/lisp/emacs-lisp/edebug.el b/lisp/emacs-lisp/edebug.el index d3ac8f0c664..94e5a485bd0 100644 --- a/lisp/emacs-lisp/edebug.el +++ b/lisp/emacs-lisp/edebug.el @@ -4254,7 +4254,7 @@ code location is known." ((pred edebug--symbol-prefixed-p) nil) (_ (when (and skip-next-lambda - (not (memq (car-safe fun) '(closure lambda)))) + (not (interpreted-function-p fun))) (warn "Edebug--strip-instrumentation expected an interpreted function:\n%S" fun)) (unless skip-next-lambda (edebug--unwrap-frame new-frame) diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index 3475d944337..601cc7bf712 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el @@ -1347,7 +1347,6 @@ Lisp function does not specify a special indentation." (put 'condition-case 'lisp-indent-function 2) (put 'handler-case 'lisp-indent-function 1) ;CL (put 'unwind-protect 'lisp-indent-function 1) -(put 'closure 'lisp-indent-function 2) (defun indent-sexp (&optional endpos) "Indent each line of the list starting just after point. diff --git a/lisp/emacs-lisp/nadvice.el b/lisp/emacs-lisp/nadvice.el index 5326c520601..36df143a82a 100644 --- a/lisp/emacs-lisp/nadvice.el +++ b/lisp/emacs-lisp/nadvice.el @@ -185,7 +185,7 @@ DOC is a string where \"FUNCTION\" and \"OLDFUN\" are expected.") (defun advice--interactive-form-1 (function) "Like `interactive-form' but preserves the static context if needed." (let ((if (interactive-form function))) - (if (or (null if) (not (eq 'closure (car-safe function)))) + (if (not (and if (interpreted-function-p function))) if (cl-assert (eq 'interactive (car if))) (let ((form (cadr if))) @@ -193,14 +193,14 @@ DOC is a string where \"FUNCTION\" and \"OLDFUN\" are expected.") if ;; The interactive is expected to be run in the static context ;; that the function captured. - (let ((ctx (nth 1 function))) + (let ((ctx (aref function 2))) `(interactive ,(let* ((f (if (eq 'function (car-safe form)) (cadr form) form))) ;; If the form jut returns a function, preserve the fact that ;; it just returns a function, which is an info we use in ;; `advice--make-interactive-form'. (if (eq 'lambda (car-safe f)) - `',(eval form ctx) + (eval form ctx) `(eval ',form ',ctx)))))))))) (defun advice--interactive-form (function) diff --git a/lisp/emacs-lisp/oclosure.el b/lisp/emacs-lisp/oclosure.el index 4da8e61aaa7..165d7c4b6e8 100644 --- a/lisp/emacs-lisp/oclosure.el +++ b/lisp/emacs-lisp/oclosure.el @@ -146,7 +146,7 @@ (setf (cl--find-class 'oclosure) (oclosure--class-make 'oclosure "The root parent of all OClosure types" - nil (list (cl--find-class 'function)) + nil (list (cl--find-class 'closure)) '(oclosure))) (defun oclosure--p (oclosure) (not (not (oclosure-type oclosure)))) @@ -431,75 +431,57 @@ ARGS and BODY are the same as for `lambda'." (defun oclosure--fix-type (_ignore oclosure) "Helper function to implement `oclosure-lambda' via a macro. -This has 2 uses: -- For interpreted code, this converts the representation of type information - by moving it from the docstring to the environment. -- For compiled code, this is used as a marker which cconv uses to check that - immutable fields are indeed not mutated." - (if (byte-code-function-p oclosure) - ;; Actually, this should never happen since `cconv.el' should have - ;; optimized away the call to this function. - oclosure - ;; For byte-coded functions, we store the type as a symbol in the docstring - ;; slot. For interpreted functions, there's no specific docstring slot - ;; so `Ffunction' turns the symbol into a string. - ;; We thus have convert it back into a symbol (via `intern') and then - ;; stuff it into the environment part of the closure with a special - ;; marker so we can distinguish this entry from actual variables. - (cl-assert (eq 'closure (car-safe oclosure))) - (let ((typename (nth 3 oclosure))) ;; The "docstring". - (cl-assert (stringp typename)) - (push (cons :type (intern typename)) - (cadr oclosure)) - oclosure))) +This is used as a marker which cconv uses to check that +immutable fields are indeed not mutated." + (cl-assert (closurep oclosure)) + ;; This should happen only for interpreted closures since `cconv.el' + ;; should have optimized away the call to this function. + oclosure) (defun oclosure--copy (oclosure mutlist &rest args) + (cl-assert (closurep oclosure)) (if (byte-code-function-p oclosure) (apply #'make-closure oclosure (if (null mutlist) args (mapcar (lambda (arg) (if (pop mutlist) (list arg) arg)) args))) - (cl-assert (eq 'closure (car-safe oclosure)) - nil "oclosure not closure: %S" oclosure) - (cl-assert (eq :type (caar (cadr oclosure)))) - (let ((env (cadr oclosure))) - `(closure - (,(car env) - ,@(named-let loop ((env (cdr env)) (args args)) - (when args - (cons (cons (caar env) (car args)) - (loop (cdr env) (cdr args))))) - ,@(nthcdr (1+ (length args)) env)) - ,@(nthcdr 2 oclosure))))) + (cl-assert (consp (aref oclosure 1))) + (cl-assert (null (aref oclosure 3))) + (cl-assert (symbolp (aref oclosure 4))) + (let ((env (aref oclosure 2))) + (make-interpreted-closure + (aref oclosure 0) + (aref oclosure 1) + (named-let loop ((env env) (args args)) + (if (null args) env + (cons (cons (caar env) (car args)) + (loop (cdr env) (cdr args))))) + (aref oclosure 4) + (if (> (length oclosure) 5) + `(interactive ,(aref oclosure 5))))))) (defun oclosure--get (oclosure index mutable) - (if (byte-code-function-p oclosure) - (let* ((csts (aref oclosure 2)) - (v (aref csts index))) - (if mutable (car v) v)) - (cl-assert (eq 'closure (car-safe oclosure))) - (cl-assert (eq :type (caar (cadr oclosure)))) - (cdr (nth (1+ index) (cadr oclosure))))) + (cl-assert (closurep oclosure)) + (let* ((csts (aref oclosure 2))) + (if (vectorp csts) + (let ((v (aref csts index))) + (if mutable (car v) v)) + (cdr (nth index csts))))) (defun oclosure--set (v oclosure index) - (if (byte-code-function-p oclosure) - (let* ((csts (aref oclosure 2)) - (cell (aref csts index))) - (setcar cell v)) - (cl-assert (eq 'closure (car-safe oclosure))) - (cl-assert (eq :type (caar (cadr oclosure)))) - (setcdr (nth (1+ index) (cadr oclosure)) v))) + (cl-assert (closurep oclosure)) + (let ((csts (aref oclosure 2))) + (if (vectorp csts) + (let ((cell (aref csts index))) + (setcar cell v)) + (setcdr (nth index csts) v)))) (defun oclosure-type (oclosure) - "Return the type of OCLOSURE, or nil if the arg is not a OClosure." - (if (byte-code-function-p oclosure) - (let ((type (and (> (length oclosure) 4) (aref oclosure 4)))) - (if (symbolp type) type)) - (and (eq 'closure (car-safe oclosure)) - (let* ((env (car-safe (cdr oclosure))) - (first-var (car-safe env))) - (and (eq :type (car-safe first-var)) - (cdr first-var)))))) + "Return the type of OCLOSURE, or nil if the arg is not an OClosure." + (and (closurep oclosure) + (> (length oclosure) 4) + (let ((type (aref oclosure 4))) + (if (symbolp type) type)))) (defconst oclosure--accessor-prototype ;; Use `oclosure--lambda' to circumvent a bootstrapping problem: diff --git a/lisp/help.el b/lisp/help.el index 46b3200d736..73cce247768 100644 --- a/lisp/help.el +++ b/lisp/help.el @@ -2359,9 +2359,8 @@ the same names as used in the original source code, when possible." ;; If definition is a macro, find the function inside it. (if (eq (car-safe def) 'macro) (setq def (cdr def))) (cond - ((and (byte-code-function-p def) (listp (aref def 0))) (aref def 0)) + ((and (closurep def) (listp (aref def 0))) (aref def 0)) ((eq (car-safe def) 'lambda) (nth 1 def)) - ((eq (car-safe def) 'closure) (nth 2 def)) ((and (featurep 'native-compile) (subrp def) (listp (subr-native-lambda-list def))) diff --git a/lisp/profiler.el b/lisp/profiler.el index 4e02cd1d890..eb72f128c07 100644 --- a/lisp/profiler.el +++ b/lisp/profiler.el @@ -275,10 +275,7 @@ Optional argument MODE means only check for the specified mode (cpu or mem)." (define-hash-table-test 'profiler-function-equal #'function-equal - (lambda (f) (cond - ((byte-code-function-p f) (aref f 1)) - ((eq (car-safe f) 'closure) (cddr f)) - (t f)))) + (lambda (f) (if (closurep f) (aref f 1) f))) (defun profiler-calltree-build-unified (tree log) ;; Let's try to unify all those partial backtraces into a single diff --git a/lisp/simple.el b/lisp/simple.el index 978ed0638e2..c692f8cb9a8 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -2785,15 +2785,14 @@ function as needed." (or (stringp doc) (fixnump doc) (fixnump (cdr-safe doc)))))) (pcase function - ((pred byte-code-function-p) + ((pred closurep) (when (> (length function) 4) (let ((doc (aref function 4))) (when (funcall docstring-p doc) doc)))) ((or (pred stringp) (pred vectorp)) "Keyboard macro.") (`(keymap . ,_) "Prefix command (definition is a keymap associating keystrokes with commands).") - ((or `(lambda ,_args . ,body) `(closure ,_env ,_args . ,body) - `(autoload ,_file . ,body)) + ((or `(lambda ,_args . ,body) `(autoload ,_file . ,body)) (let ((doc (car body))) (when (funcall docstring-p doc) doc))) diff --git a/src/callint.c b/src/callint.c index b31faba8704..9d6f2ab2888 100644 --- a/src/callint.c +++ b/src/callint.c @@ -319,10 +319,10 @@ invoke it (via an `interactive' spec that contains, for instance, an { Lisp_Object funval = Findirect_function (function, Qt); uintmax_t events = num_input_events; + Lisp_Object env = CLOSUREP (funval) && CONSP (AREF (funval, CLOSURE_CODE)) + ? AREF (funval, CLOSURE_CONSTANTS) : Qnil; /* Compute the arg values using the user's expression. */ - specs = Feval (specs, - CONSP (funval) && EQ (Qclosure, XCAR (funval)) - ? CAR_SAFE (XCDR (funval)) : Qnil); + specs = Feval (specs, env); if (events != num_input_events || !NILP (record_flag)) { /* We should record this command on the command history. diff --git a/src/data.c b/src/data.c index 681054ff8cb..ea611ad1abf 100644 --- a/src/data.c +++ b/src/data.c @@ -248,7 +248,9 @@ a fixed set of types. */) return XSUBR (object)->max_args == UNEVALLED ? Qspecial_form : SUBR_NATIVE_COMPILEDP (object) ? Qsubr_native_elisp : Qprimitive_function; - case PVEC_CLOSURE: return Qcompiled_function; + case PVEC_CLOSURE: + return CONSP (AREF (object, CLOSURE_CODE)) + ? Qinterpreted_function : Qbyte_code_function; case PVEC_BUFFER: return Qbuffer; case PVEC_CHAR_TABLE: return Qchar_table; case PVEC_BOOL_VECTOR: return Qbool_vector; @@ -518,12 +520,32 @@ DEFUN ("subrp", Fsubrp, Ssubrp, 1, 1, 0, return Qnil; } +DEFUN ("closurep", Fclosurep, Sclosurep, + 1, 1, 0, + doc: /* Return t if OBJECT is a function of type `closure'. */) + (Lisp_Object object) +{ + if (CLOSUREP (object)) + return Qt; + return Qnil; +} + DEFUN ("byte-code-function-p", Fbyte_code_function_p, Sbyte_code_function_p, 1, 1, 0, doc: /* Return t if OBJECT is a byte-compiled function object. */) (Lisp_Object object) { - if (CLOSUREP (object)) + if (CLOSUREP (object) && STRINGP (AREF (object, CLOSURE_CODE))) + return Qt; + return Qnil; +} + +DEFUN ("interpreted-function-p", Finterpreted_function_p, + Sinterpreted_function_p, 1, 1, 0, + doc: /* Return t if OBJECT is a function of type `interpreted-function'. */) + (Lisp_Object object) +{ + if (CLOSUREP (object) && CONSP (AREF (object, CLOSURE_CODE))) return Qt; return Qnil; } @@ -1174,17 +1196,11 @@ Value, if non-nil, is a list (interactive SPEC). */) else if (CONSP (fun)) { Lisp_Object funcar = XCAR (fun); - if (EQ (funcar, Qclosure) - || EQ (funcar, Qlambda)) + if (EQ (funcar, Qlambda)) { Lisp_Object form = Fcdr (XCDR (fun)); - if (EQ (funcar, Qclosure)) - form = Fcdr (form); Lisp_Object spec = Fassq (Qinteractive, form); - if (NILP (spec) && VALID_DOCSTRING_P (CAR_SAFE (form))) - /* A "docstring" is a sign that we may have an OClosure. */ - genfun = true; - else if (NILP (Fcdr (Fcdr (spec)))) + if (NILP (Fcdr (Fcdr (spec)))) return spec; else return list2 (Qinteractive, Fcar (Fcdr (spec))); @@ -1257,12 +1273,9 @@ The value, if non-nil, is a list of mode name symbols. */) else if (CONSP (fun)) { Lisp_Object funcar = XCAR (fun); - if (EQ (funcar, Qclosure) - || EQ (funcar, Qlambda)) + if (EQ (funcar, Qlambda)) { Lisp_Object form = Fcdr (XCDR (fun)); - if (EQ (funcar, Qclosure)) - form = Fcdr (form); return Fcdr (Fcdr (Fassq (Qinteractive, form))); } } @@ -4224,7 +4237,8 @@ syms_of_data (void) DEFSYM (Qspecial_form, "special-form"); DEFSYM (Qprimitive_function, "primitive-function"); DEFSYM (Qsubr_native_elisp, "subr-native-elisp"); - DEFSYM (Qcompiled_function, "compiled-function"); + DEFSYM (Qbyte_code_function, "byte-code-function"); + DEFSYM (Qinterpreted_function, "interpreted-function"); DEFSYM (Qbuffer, "buffer"); DEFSYM (Qframe, "frame"); DEFSYM (Qvector, "vector"); @@ -4289,6 +4303,8 @@ syms_of_data (void) defsubr (&Smarkerp); defsubr (&Ssubrp); defsubr (&Sbyte_code_function_p); + defsubr (&Sinterpreted_function_p); + defsubr (&Sclosurep); defsubr (&Smodule_function_p); defsubr (&Schar_or_string_p); defsubr (&Sthreadp); diff --git a/src/eval.c b/src/eval.c index a7d860114cf..fd388706108 100644 --- a/src/eval.c +++ b/src/eval.c @@ -510,6 +510,33 @@ usage: (quote ARG) */) return XCAR (args); } +DEFUN ("make-interpreted-closure", Fmake_interpreted_closure, + Smake_interpreted_closure, 3, 5, 0, + doc: /* Make an interpreted closure. +ARGS should be the list of formal arguments. +BODY should be a non-empty list of forms. +ENV should be a lexical environment, like the second argument of `eval'. +IFORM if non-nil should be of the form (interactive ...). */) + (Lisp_Object args, Lisp_Object body, Lisp_Object env, + Lisp_Object docstring, Lisp_Object iform) +{ + CHECK_CONS (body); /* Make sure it's not confused with byte-code! */ + CHECK_LIST (args); + CHECK_LIST (iform); + Lisp_Object ifcdr = Fcdr (iform); + Lisp_Object slots[] = { args, body, env, Qnil, docstring, + NILP (Fcdr (ifcdr)) + ? Fcar (ifcdr) + : CALLN (Fvector, XCAR (ifcdr), XCDR (ifcdr)) }; + /* Adjusting the size is indispensable since, as for byte-code objects, + we distinguish interactive functions by the presence or absence of the + iform slot. */ + Lisp_Object val + = Fvector (!NILP (iform) ? 6 : !NILP (docstring) ? 5 : 3, slots); + XSETPVECTYPE (XVECTOR (val), PVEC_CLOSURE); + return val; +} + DEFUN ("function", Ffunction, Sfunction, 1, UNEVALLED, 0, doc: /* Like `quote', but preferred for objects which are functions. In byte compilation, `function' causes its argument to be handled by @@ -525,33 +552,55 @@ usage: (function ARG) */) if (!NILP (XCDR (args))) xsignal2 (Qwrong_number_of_arguments, Qfunction, Flength (args)); - if (!NILP (Vinternal_interpreter_environment) - && CONSP (quoted) + if (CONSP (quoted) && EQ (XCAR (quoted), Qlambda)) { /* This is a lambda expression within a lexical environment; return an interpreted closure instead of a simple lambda. */ Lisp_Object cdr = XCDR (quoted); - Lisp_Object tmp = cdr; - if (CONSP (tmp) - && (tmp = XCDR (tmp), CONSP (tmp)) - && (tmp = XCAR (tmp), CONSP (tmp)) - && (EQ (QCdocumentation, XCAR (tmp)))) - { /* Handle the special (:documentation
) to build the docstring + Lisp_Object args = Fcar (cdr); + cdr = Fcdr (cdr); + Lisp_Object docstring = Qnil, iform = Qnil; + if (CONSP (cdr)) + { + docstring = XCAR (cdr); + if (STRINGP (docstring)) + { + Lisp_Object tmp = XCDR (cdr); + if (!NILP (tmp)) + cdr = tmp; + else /* It's not a docstring, it's a return value. */ + docstring = Qnil; + } + /* Handle the special (:documentation ) to build the docstring dynamically. */ - Lisp_Object docstring = eval_sub (Fcar (XCDR (tmp))); - if (SYMBOLP (docstring) && !NILP (docstring)) - /* Hack for OClosures: Allow the docstring to be a symbol - * (the OClosure's type). */ - docstring = Fsymbol_name (docstring); - CHECK_STRING (docstring); - cdr = Fcons (XCAR (cdr), Fcons (docstring, XCDR (XCDR (cdr)))); - } - if (NILP (Vinternal_make_interpreted_closure_function)) - return Fcons (Qclosure, Fcons (Vinternal_interpreter_environment, cdr)); + else if (CONSP (docstring) + && EQ (QCdocumentation, XCAR (docstring)) + && (docstring = eval_sub (Fcar (XCDR (docstring))), + true)) + cdr = XCDR (cdr); + else + docstring = Qnil; /* Not a docstring after all. */ + } + if (CONSP (cdr)) + { + iform = XCAR (cdr); + if (CONSP (iform) + && EQ (Qinteractive, XCAR (iform))) + cdr = XCDR (cdr); + else + iform = Qnil; /* Not an interactive-form after all. */ + } + if (NILP (cdr)) + cdr = Fcons (Qnil, Qnil); /* Make sure the body is never empty! */ + + if (NILP (Vinternal_interpreter_environment) + || NILP (Vinternal_make_interpreted_closure_function)) + return Fmake_interpreted_closure + (args, cdr, Vinternal_interpreter_environment, docstring, iform); else - return call2 (Vinternal_make_interpreted_closure_function, - Fcons (Qlambda, cdr), - Vinternal_interpreter_environment); + return call5 (Vinternal_make_interpreted_closure_function, + args, cdr, Vinternal_interpreter_environment, + docstring, iform); } else /* Simply quote the argument. */ @@ -2193,15 +2242,12 @@ then strings and vectors are not accepted. */) else { Lisp_Object body = CDR_SAFE (XCDR (fun)); - if (EQ (funcar, Qclosure)) - body = CDR_SAFE (body); - else if (!EQ (funcar, Qlambda)) + if (!EQ (funcar, Qlambda)) return Qnil; if (!NILP (Fassq (Qinteractive, body))) return Qt; - else if (VALID_DOCSTRING_P (CAR_SAFE (body))) - /* A "docstring" is a sign that we may have an OClosure. */ - genfun = true; + else + return Qnil; } } @@ -2611,8 +2657,7 @@ eval_sub (Lisp_Object form) exp = unbind_to (count1, exp); val = eval_sub (exp); } - else if (EQ (funcar, Qlambda) - || EQ (funcar, Qclosure)) + else if (EQ (funcar, Qlambda)) return apply_lambda (fun, original_args, count); else xsignal1 (Qinvalid_function, original_fun); @@ -2950,7 +2995,7 @@ FUNCTIONP (Lisp_Object object) else if (CONSP (object)) { Lisp_Object car = XCAR (object); - return EQ (car, Qlambda) || EQ (car, Qclosure); + return EQ (car, Qlambda); } else return false; @@ -2980,8 +3025,7 @@ funcall_general (Lisp_Object fun, ptrdiff_t numargs, Lisp_Object *args) Lisp_Object funcar = XCAR (fun); if (!SYMBOLP (funcar)) xsignal1 (Qinvalid_function, original_fun); - if (EQ (funcar, Qlambda) - || EQ (funcar, Qclosure)) + if (EQ (funcar, Qlambda)) return funcall_lambda (fun, numargs, args); else if (EQ (funcar, Qautoload)) { @@ -3165,16 +3209,7 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, Lisp_Object *arg_vector) if (CONSP (fun)) { - if (EQ (XCAR (fun), Qclosure)) - { - Lisp_Object cdr = XCDR (fun); /* Drop `closure'. */ - if (! CONSP (cdr)) - xsignal1 (Qinvalid_function, fun); - fun = cdr; - lexenv = XCAR (fun); - } - else - lexenv = Qnil; + lexenv = Qnil; syms_left = XCDR (fun); if (CONSP (syms_left)) syms_left = XCAR (syms_left); @@ -3189,10 +3224,12 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, Lisp_Object *arg_vector) engine directly. */ if (FIXNUMP (syms_left)) return exec_byte_code (fun, XFIXNUM (syms_left), nargs, arg_vector); - /* Otherwise the bytecode object uses dynamic binding and the - ARGLIST slot contains a standard formal argument list whose - variables are bound dynamically below. */ - lexenv = Qnil; + /* Otherwise the closure either is interpreted + or uses dynamic binding and the ARGLIST slot contains a standard + formal argument list whose variables are bound dynamically below. */ + lexenv = CONSP (AREF (fun, CLOSURE_CODE)) + ? AREF (fun, CLOSURE_CONSTANTS) + : Qnil; } #ifdef HAVE_MODULES else if (MODULE_FUNCTIONP (fun)) @@ -3280,7 +3317,14 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, Lisp_Object *arg_vector) val = XSUBR (fun)->function.a0 (); } else - val = exec_byte_code (fun, 0, 0, NULL); + { + eassert (CLOSUREP (fun)); + val = CONSP (AREF (fun, CLOSURE_CODE)) + /* Interpreted function. */ + ? Fprogn (AREF (fun, CLOSURE_CODE)) + /* Dynbound bytecode. */ + : exec_byte_code (fun, 0, 0, NULL); + } return unbind_to (count, val); } @@ -3330,8 +3374,7 @@ function with `&rest' args, or `unevalled' for a special form. */) funcar = XCAR (function); if (!SYMBOLP (funcar)) xsignal1 (Qinvalid_function, original); - if (EQ (funcar, Qlambda) - || EQ (funcar, Qclosure)) + if (EQ (funcar, Qlambda)) result = lambda_arity (function); else if (EQ (funcar, Qautoload)) { @@ -3352,11 +3395,6 @@ lambda_arity (Lisp_Object fun) if (CONSP (fun)) { - if (EQ (XCAR (fun), Qclosure)) - { - fun = XCDR (fun); /* Drop `closure'. */ - CHECK_CONS (fun); - } syms_left = XCDR (fun); if (CONSP (syms_left)) syms_left = XCAR (syms_left); @@ -4265,7 +4303,6 @@ before making `inhibit-quit' nil. */); DEFSYM (Qcommandp, "commandp"); DEFSYM (Qand_rest, "&rest"); DEFSYM (Qand_optional, "&optional"); - DEFSYM (Qclosure, "closure"); DEFSYM (QCdocumentation, ":documentation"); DEFSYM (Qdebug, "debug"); DEFSYM (Qdebug_early, "debug-early"); @@ -4423,6 +4460,7 @@ alist of active lexical bindings. */); defsubr (&Ssetq); defsubr (&Squote); defsubr (&Sfunction); + defsubr (&Smake_interpreted_closure); defsubr (&Sdefault_toplevel_value); defsubr (&Sset_default_toplevel_value); defsubr (&Sdefvar); diff --git a/src/lread.c b/src/lread.c index 8b614e6220e..983fdb883ff 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3523,25 +3523,32 @@ bytecode_from_rev_list (Lisp_Object elems, Lisp_Object readcharfun) } } - if (!(size >= CLOSURE_STACK_DEPTH + 1 && size <= CLOSURE_INTERACTIVE + 1 + if (!(size >= CLOSURE_STACK_DEPTH && size <= CLOSURE_INTERACTIVE + 1 && (FIXNUMP (vec[CLOSURE_ARGLIST]) || CONSP (vec[CLOSURE_ARGLIST]) || NILP (vec[CLOSURE_ARGLIST])) - && STRINGP (vec[CLOSURE_CODE]) - && VECTORP (vec[CLOSURE_CONSTANTS]) - && FIXNATP (vec[CLOSURE_STACK_DEPTH]))) + && ((STRINGP (vec[CLOSURE_CODE]) /* Byte-code function. */ + && VECTORP (vec[CLOSURE_CONSTANTS]) + && size > CLOSURE_STACK_DEPTH + && (FIXNATP (vec[CLOSURE_STACK_DEPTH]))) + || (CONSP (vec[CLOSURE_CODE]) /* Interpreted function. */ + && (CONSP (vec[CLOSURE_CONSTANTS]) + || NILP (vec[CLOSURE_CONSTANTS])))))) invalid_syntax ("Invalid byte-code object", readcharfun); - if (STRING_MULTIBYTE (vec[CLOSURE_CODE])) - /* BYTESTR must have been produced by Emacs 20.2 or earlier - because it produced a raw 8-bit string for byte-code and - now such a byte-code string is loaded as multibyte with - raw 8-bit characters converted to multibyte form. - Convert them back to the original unibyte form. */ - vec[CLOSURE_CODE] = Fstring_as_unibyte (vec[CLOSURE_CODE]); - - /* Bytecode must be immovable. */ - pin_string (vec[CLOSURE_CODE]); + if (STRINGP (vec[CLOSURE_CODE])) + { + if (STRING_MULTIBYTE (vec[CLOSURE_CODE])) + /* BYTESTR must have been produced by Emacs 20.2 or earlier + because it produced a raw 8-bit string for byte-code and + now such a byte-code string is loaded as multibyte with + raw 8-bit characters converted to multibyte form. + Convert them back to the original unibyte form. */ + vec[CLOSURE_CODE] = Fstring_as_unibyte (vec[CLOSURE_CODE]); + + /* Bytecode must be immovable. */ + pin_string (vec[CLOSURE_CODE]); + } XSETPVECTYPE (XVECTOR (obj), PVEC_CLOSURE); return obj; diff --git a/src/profiler.c b/src/profiler.c index ac23a97b672..6e1dc46abd3 100644 --- a/src/profiler.c +++ b/src/profiler.c @@ -170,9 +170,7 @@ trace_hash (Lisp_Object *trace, int depth) { Lisp_Object f = trace[i]; EMACS_UINT hash1 - = (CLOSUREP (f) ? XHASH (AREF (f, CLOSURE_CODE)) - : (CONSP (f) && CONSP (XCDR (f)) && BASE_EQ (Qclosure, XCAR (f))) - ? XHASH (XCDR (XCDR (f))) : XHASH (f)); + = (CLOSUREP (f) ? XHASH (AREF (f, CLOSURE_CODE)) : XHASH (f)); hash = sxhash_combine (hash, hash1); } return hash; @@ -677,10 +675,6 @@ the same lambda expression, or are really unrelated function. */) res = true; else if (CLOSUREP (f1) && CLOSUREP (f2)) res = EQ (AREF (f1, CLOSURE_CODE), AREF (f2, CLOSURE_CODE)); - else if (CONSP (f1) && CONSP (f2) && CONSP (XCDR (f1)) && CONSP (XCDR (f2)) - && EQ (Qclosure, XCAR (f1)) - && EQ (Qclosure, XCAR (f2))) - res = EQ (XCDR (XCDR (f1)), XCDR (XCDR (f2))); else res = false; return res ? Qt : Qnil; diff --git a/test/lisp/emacs-lisp/macroexp-resources/vk.el b/test/lisp/emacs-lisp/macroexp-resources/vk.el index 5358bcaeb5c..c59a6b9f8f1 100644 --- a/test/lisp/emacs-lisp/macroexp-resources/vk.el +++ b/test/lisp/emacs-lisp/macroexp-resources/vk.el @@ -78,29 +78,31 @@ (defconst vk-val3 (eval-when-compile (vk-f3 0))) -(defconst vk-f4 '(lambda (x) - (defvar vk-v4) - (let ((vk-v4 31) - (y 32)) - (ignore vk-v4 x y) - (list - (vk-variable-kind vk-a) ; dyn - (vk-variable-kind vk-b) ; dyn - (vk-variable-kind vk-v4) ; dyn - (vk-variable-kind x) ; dyn - (vk-variable-kind y))))) ; dyn - -(defconst vk-f5 '(closure (t) (x) - (defvar vk-v5) - (let ((vk-v5 41) - (y 42)) - (ignore vk-v5 x y) - (list - (vk-variable-kind vk-a) ; dyn - (vk-variable-kind vk-b) ; dyn - (vk-variable-kind vk-v5) ; dyn - (vk-variable-kind x) ; lex - (vk-variable-kind y))))) ; lex +(defconst vk-f4 (eval '(lambda (x) + (defvar vk-v4) + (let ((vk-v4 31) + (y 32)) + (ignore vk-v4 x y) + (list + (vk-variable-kind vk-a) ; dyn + (vk-variable-kind vk-b) ; dyn + (vk-variable-kind vk-v4) ; dyn + (vk-variable-kind x) ; dyn + (vk-variable-kind y)))) ; dyn + nil)) + +(defconst vk-f5 (eval '(lambda (x) + (defvar vk-v5) + (let ((vk-v5 41) + (y 42)) + (ignore vk-v5 x y) + (list + (vk-variable-kind vk-a) ; dyn + (vk-variable-kind vk-b) ; dyn + (vk-variable-kind vk-v5) ; dyn + (vk-variable-kind x) ; lex + (vk-variable-kind y)))) ; lex + t)) (defun vk-f6 () (eval '(progn diff --git a/test/lisp/erc/resources/erc-d/erc-d-tests.el b/test/lisp/erc/resources/erc-d/erc-d-tests.el index 78f87399afb..dda1b1ced84 100644 --- a/test/lisp/erc/resources/erc-d/erc-d-tests.el +++ b/test/lisp/erc/resources/erc-d/erc-d-tests.el @@ -367,8 +367,9 @@ (should (equal (funcall it) "foo3foo"))) (ert-info ("Exits clean") - (when (listp (alist-get 'f (erc-d-dialog-vars dialog))) ; may be compiled - (should (eq 'closure (car (alist-get 'f (erc-d-dialog-vars dialog)))))) + (when (interpreted-function-p + (alist-get 'f (erc-d-dialog-vars dialog))) ; may be compiled + (should (aref (alist-get 'f (erc-d-dialog-vars dialog)) 2))) (should-not (funcall it)) (should (equal (erc-d-dialog-vars dialog) `((:a . 1) diff --git a/test/lisp/help-fns-tests.el b/test/lisp/help-fns-tests.el index 1beeb77640c..82350a4bc71 100644 --- a/test/lisp/help-fns-tests.el +++ b/test/lisp/help-fns-tests.el @@ -63,14 +63,14 @@ Return first line of the output of (describe-function-1 FUNC)." (should (string-match regexp result)))) (ert-deftest help-fns-test-lisp-defun () - (let ((regexp (if (featurep 'native-compile) - "a subr-native-elisp in .+subr\\.el" - "a compiled-function in .+subr\\.el")) + (let ((regexp "a \\([^ ]+\\) in .+subr\\.el") (result (help-fns-tests--describe-function 'last))) - (should (string-match regexp result)))) + (should (string-match regexp result)) + (should (member (match-string 1 result) + '("subr-native-elisp" "byte-code-function"))))) (ert-deftest help-fns-test-lisp-defsubst () - (let ((regexp "a compiled-function in .+subr\\.el") + (let ((regexp "a byte-code-function in .+subr\\.el") (result (help-fns-tests--describe-function 'posn-window))) (should (string-match regexp result))))