From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values Date: Sat, 13 Apr 2024 15:56:34 -0400 Message-ID: Reply-To: Stefan Monnier Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="17132"; mail-complaints-to="usenet@ciao.gmane.io" To: 70368@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sat Apr 13 21:58:15 2024 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1rvjVu-0004Dg-0D for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 13 Apr 2024 21:58:14 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rvjVb-0007lP-PT; Sat, 13 Apr 2024 15:57:55 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rvjVZ-0007kr-TC for bug-gnu-emacs@gnu.org; Sat, 13 Apr 2024 15:57:54 -0400 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rvjVZ-0004HE-EQ for bug-gnu-emacs@gnu.org; Sat, 13 Apr 2024 15:57:53 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1rvjVk-0002rj-18 for bug-gnu-emacs@gnu.org; Sat, 13 Apr 2024 15:58:04 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Stefan Monnier Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sat, 13 Apr 2024 19:58:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 70368 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.171303824310592 (code B ref -1); Sat, 13 Apr 2024 19:58:03 +0000 Original-Received: (at submit) by debbugs.gnu.org; 13 Apr 2024 19:57:23 +0000 Original-Received: from localhost ([127.0.0.1]:33835 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rvjV1-0002kc-Cg for submit@debbugs.gnu.org; Sat, 13 Apr 2024 15:57:23 -0400 Original-Received: from lists.gnu.org ([2001:470:142::17]:60946) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rvjUp-0002ha-TR for submit@debbugs.gnu.org; Sat, 13 Apr 2024 15:57:18 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rvjUZ-0007cs-TE for bug-gnu-emacs@gnu.org; Sat, 13 Apr 2024 15:56:51 -0400 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rvjUS-0004Ay-HP for bug-gnu-emacs@gnu.org; Sat, 13 Apr 2024 15:56:51 -0400 Original-Received: from pmg2.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 87CD180C8F for ; Sat, 13 Apr 2024 15:56:42 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1713038196; bh=AhmD3cGtC435+Qk7mmAiSgFe+grFHbvYGJnnNqYf8X8=; h=From:To:Subject:Date:From; b=AOpTA3wCzlmEV66UOzmy9JvOxSJVczDi9CQ7//VKNuKQtxJq9fHr1h+alH/tP9GPL 7DG40FWgd60UiiFXCDSaYpQE0IVIuy1QCT1sfbPa63zEvJBZyiAlqOh31ClOp2NFHL /xZmKUdINes/9utWdLT9n9aNb06haC64EK1snrTfzcoyTQjdRrGTV31mlaJTXd7Kl8 +810cHMhQi6av7jlw7+2Q4Es4KjCKqz89FNLuYEVnFsLdSmseNGKAYLr49b8x5R/wU vZcSz9OZQSmhwJE06uZGwA0j/jfiP9pmWxn38w/ohjCDdGBfinMc8kaKuJ+lZo9zNh +YAoG3ZtV7rkA== Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg2.iro.umontreal.ca (Proxmox) with ESMTP id 04391807A5 for ; Sat, 13 Apr 2024 15:56:36 -0400 (EDT) Original-Received: from pastel (unknown [45.72.201.215]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id A4A6E12026E for ; Sat, 13 Apr 2024 15:56:35 -0400 (EDT) X-Deddbugs-Cc: monnier@iro.umontreal.ca Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:283236 Archived-At: --=-=-= Content-Type: text/plain Tags: patch The two patches below make Emacs 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 used for byte-code function 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. This removes some ambiguity in cases where the data can be made of a list or a function, such as `run-hooks` or completion tables. - `cl-defmethod` can dispatch on `interactive-function`. 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 already 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 ...)`. Stefan --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-COMPILED-Rename-to-CLOSURE.patch >From 669179d696ab2e3c7ab7b504dd1b6b8827bbfb0b Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sun, 24 Mar 2024 18:32:25 -0400 Subject: [PATCH 1/2] (COMPILED): Rename to CLOSURE In preparation for the use of `PVEC_COMPILED` objects for interpreted functions, rename them to use a more neutral name. * src/lisp.h (enum pvec_type): Rename `PVEC_COMPILED` to `PVEC_CLOSURE`. (enum Lisp_Compiled): Use `CLOSURE_` prefix i.s.o `COMPILED_`. Also use `CODE` rather than `BYTECODE`. (CLOSUREP): Rename from `COMPILEDP`. (enum Lisp_Closure): Rename from `Lisp_Compiled`. * src/alloc.c, src/bytecode.c, src/comp.c, src/data.c, src/eval.c, * src/fns.c, src/lisp.h, src/lread.c, src/pdumper.c, src/print.c, * src/profiler.c: Rename all uses accordingly. --- src/alloc.c | 40 ++++++++++++++++++++-------------------- src/bytecode.c | 20 ++++++++++---------- src/comp.c | 2 +- src/data.c | 22 +++++++++++----------- src/eval.c | 26 +++++++++++++------------- src/fns.c | 14 +++++++------- src/lisp.h | 22 +++++++++++----------- src/lread.c | 40 ++++++++++++++++++++-------------------- src/pdumper.c | 2 +- src/print.c | 6 +++--- src/profiler.c | 6 +++--- 11 files changed, 100 insertions(+), 100 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index 6779d0ca9ce..a8dfde56739 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3481,7 +3481,7 @@ cleanup_vector (struct Lisp_Vector *vector) case PVEC_XWIDGET_VIEW: case PVEC_TS_NODE: case PVEC_SQLITE: - case PVEC_COMPILED: + case PVEC_CLOSURE: case PVEC_CHAR_TABLE: case PVEC_SUB_CHAR_TABLE: case PVEC_RECORD: @@ -3813,17 +3813,17 @@ and (optional) INTERACTIVE-SPEC. usage: (make-byte-code ARGLIST BYTE-CODE CONSTANTS DEPTH &optional DOCSTRING INTERACTIVE-SPEC &rest ELEMENTS) */) (ptrdiff_t nargs, Lisp_Object *args) { - if (! ((FIXNUMP (args[COMPILED_ARGLIST]) - || CONSP (args[COMPILED_ARGLIST]) - || NILP (args[COMPILED_ARGLIST])) - && STRINGP (args[COMPILED_BYTECODE]) - && !STRING_MULTIBYTE (args[COMPILED_BYTECODE]) - && VECTORP (args[COMPILED_CONSTANTS]) - && FIXNATP (args[COMPILED_STACK_DEPTH]))) + if (! ((FIXNUMP (args[CLOSURE_ARGLIST]) + || CONSP (args[CLOSURE_ARGLIST]) + || NILP (args[CLOSURE_ARGLIST])) + && STRINGP (args[CLOSURE_CODE]) + && !STRING_MULTIBYTE (args[CLOSURE_CODE]) + && VECTORP (args[CLOSURE_CONSTANTS]) + && FIXNATP (args[CLOSURE_STACK_DEPTH]))) error ("Invalid byte-code object"); /* Bytecode must be immovable. */ - pin_string (args[COMPILED_BYTECODE]); + pin_string (args[CLOSURE_CODE]); /* We used to purecopy everything here, if purify-flag was set. This worked OK for Emacs-23, but with Emacs-24's lexical binding code, it can be @@ -3833,7 +3833,7 @@ and (optional) INTERACTIVE-SPEC. just wasteful and other times plainly wrong (e.g. those free vars may want to be setcar'd). */ Lisp_Object val = Fvector (nargs, args); - XSETPVECTYPE (XVECTOR (val), PVEC_COMPILED); + XSETPVECTYPE (XVECTOR (val), PVEC_CLOSURE); return val; } @@ -3845,12 +3845,12 @@ DEFUN ("make-closure", Fmake_closure, Smake_closure, 1, MANY, 0, (ptrdiff_t nargs, Lisp_Object *args) { Lisp_Object protofun = args[0]; - CHECK_TYPE (COMPILEDP (protofun), Qbyte_code_function_p, protofun); + CHECK_TYPE (CLOSUREP (protofun), Qbyte_code_function_p, protofun); /* Create a copy of the constant vector, filling it with the closure variables in the beginning. (The overwritten part should just contain placeholder values.) */ - Lisp_Object proto_constvec = AREF (protofun, COMPILED_CONSTANTS); + Lisp_Object proto_constvec = AREF (protofun, CLOSURE_CONSTANTS); ptrdiff_t constsize = ASIZE (proto_constvec); ptrdiff_t nvars = nargs - 1; if (nvars > constsize) @@ -3866,7 +3866,7 @@ DEFUN ("make-closure", Fmake_closure, Smake_closure, 1, MANY, 0, struct Lisp_Vector *v = allocate_vectorlike (protosize, false); v->header = XVECTOR (protofun)->header; memcpy (v->contents, XVECTOR (protofun)->contents, protosize * word_size); - v->contents[COMPILED_CONSTANTS] = constvec; + v->contents[CLOSURE_CONSTANTS] = constvec; return make_lisp_ptr (v, Lisp_Vectorlike); } @@ -6046,7 +6046,7 @@ purecopy (Lisp_Object obj) obj = make_lisp_hash_table (purecopy_hash_table (table)); } - else if (COMPILEDP (obj) || VECTORP (obj) || RECORDP (obj)) + else if (CLOSUREP (obj) || VECTORP (obj) || RECORDP (obj)) { struct Lisp_Vector *objp = XVECTOR (obj); ptrdiff_t nbytes = vector_nbytes (objp); @@ -6059,7 +6059,7 @@ purecopy (Lisp_Object obj) for (i = 0; i < size; i++) vec->contents[i] = purecopy (vec->contents[i]); /* Byte code strings must be pinned. */ - if (COMPILEDP (obj) && size >= 2 && STRINGP (vec->contents[1]) + if (CLOSUREP (obj) && size >= 2 && STRINGP (vec->contents[1]) && !STRING_MULTIBYTE (vec->contents[1])) pin_string (vec->contents[1]); XSETVECTOR (obj, vec); @@ -8014,11 +8014,11 @@ symbol_uses_obj (Lisp_Object symbol, Lisp_Object obj) return (EQ (val, obj) || EQ (sym->u.s.function, obj) || (!NILP (sym->u.s.function) - && COMPILEDP (sym->u.s.function) - && EQ (AREF (sym->u.s.function, COMPILED_BYTECODE), obj)) + && CLOSUREP (sym->u.s.function) + && EQ (AREF (sym->u.s.function, CLOSURE_CODE), obj)) || (!NILP (val) - && COMPILEDP (val) - && EQ (AREF (val, COMPILED_BYTECODE), obj))); + && CLOSUREP (val) + && EQ (AREF (val, CLOSURE_CODE), obj))); } /* Find at most FIND_MAX symbols which have OBJ as their value or @@ -8343,7 +8343,7 @@ syms_of_alloc (void) enum CHECK_LISP_OBJECT_TYPE CHECK_LISP_OBJECT_TYPE; enum DEFAULT_HASH_SIZE DEFAULT_HASH_SIZE; enum Lisp_Bits Lisp_Bits; - enum Lisp_Compiled Lisp_Compiled; + enum Lisp_Closure Lisp_Closure; enum maxargs maxargs; enum MAX_ALLOCA MAX_ALLOCA; enum More_Lisp_Bits More_Lisp_Bits; diff --git a/src/bytecode.c b/src/bytecode.c index 8d7240b9966..51495d226e1 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -479,7 +479,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, Lisp_Object *top = NULL; unsigned char const *pc = NULL; - Lisp_Object bytestr = AREF (fun, COMPILED_BYTECODE); + Lisp_Object bytestr = AREF (fun, CLOSURE_CODE); setup_frame: ; eassert (!STRING_MULTIBYTE (bytestr)); @@ -489,8 +489,8 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, when returning, to detect unwind imbalances. This would require adding a field to the frame header. */ - Lisp_Object vector = AREF (fun, COMPILED_CONSTANTS); - Lisp_Object maxdepth = AREF (fun, COMPILED_STACK_DEPTH); + Lisp_Object vector = AREF (fun, CLOSURE_CONSTANTS); + Lisp_Object maxdepth = AREF (fun, CLOSURE_STACK_DEPTH); ptrdiff_t const_length = ASIZE (vector); ptrdiff_t bytestr_length = SCHARS (bytestr); Lisp_Object *vectorp = XVECTOR (vector)->contents; @@ -794,14 +794,14 @@ #define DEFINE(name, value) [name] = &&insn_ ## name, /* Calls to symbols-with-pos don't need to be on the fast path. */ if (BARE_SYMBOL_P (call_fun)) call_fun = XBARE_SYMBOL (call_fun)->u.s.function; - if (COMPILEDP (call_fun)) + if (CLOSUREP (call_fun)) { - Lisp_Object template = AREF (call_fun, COMPILED_ARGLIST); + Lisp_Object template = AREF (call_fun, CLOSURE_ARGLIST); if (FIXNUMP (template)) { /* Fast path for lexbound functions. */ fun = call_fun; - bytestr = AREF (call_fun, COMPILED_BYTECODE), + bytestr = AREF (call_fun, CLOSURE_CODE), args_template = XFIXNUM (template); nargs = call_nargs; args = call_args; @@ -899,8 +899,8 @@ #define DEFINE(name, value) [name] = &&insn_ ## name, bc->fp = fp; Lisp_Object fun = fp->fun; - Lisp_Object bytestr = AREF (fun, COMPILED_BYTECODE); - Lisp_Object vector = AREF (fun, COMPILED_CONSTANTS); + Lisp_Object bytestr = AREF (fun, CLOSURE_CODE); + Lisp_Object vector = AREF (fun, CLOSURE_CONSTANTS); bytestr_data = SDATA (bytestr); vectorp = XVECTOR (vector)->contents; if (BYTE_CODE_SAFE) @@ -976,8 +976,8 @@ #define DEFINE(name, value) [name] = &&insn_ ## name, struct bc_frame *fp = bc->fp; Lisp_Object fun = fp->fun; - Lisp_Object bytestr = AREF (fun, COMPILED_BYTECODE); - Lisp_Object vector = AREF (fun, COMPILED_CONSTANTS); + Lisp_Object bytestr = AREF (fun, CLOSURE_CODE); + Lisp_Object vector = AREF (fun, CLOSURE_CONSTANTS); bytestr_data = SDATA (bytestr); vectorp = XVECTOR (vector)->contents; if (BYTE_CODE_SAFE) diff --git a/src/comp.c b/src/comp.c index 99f51e07048..d2115de522c 100644 --- a/src/comp.c +++ b/src/comp.c @@ -5199,7 +5199,7 @@ maybe_defer_native_compilation (Lisp_Object function_name, if (!native_comp_jit_compilation || noninteractive || !NILP (Vpurify_flag) - || !COMPILEDP (definition) + || !CLOSUREP (definition) || !STRINGP (Vload_true_file_name) || !suffix_p (Vload_true_file_name, ".elc") || !NILP (Fgethash (Vload_true_file_name, V_comp_no_native_file_h, Qnil))) diff --git a/src/data.c b/src/data.c index c4b9cff8ae0..681054ff8cb 100644 --- a/src/data.c +++ b/src/data.c @@ -248,7 +248,7 @@ DEFUN ("cl-type-of", Fcl_type_of, Scl_type_of, 1, 1, 0, return XSUBR (object)->max_args == UNEVALLED ? Qspecial_form : SUBR_NATIVE_COMPILEDP (object) ? Qsubr_native_elisp : Qprimitive_function; - case PVEC_COMPILED: return Qcompiled_function; + case PVEC_CLOSURE: return Qcompiled_function; case PVEC_BUFFER: return Qbuffer; case PVEC_CHAR_TABLE: return Qchar_table; case PVEC_BOOL_VECTOR: return Qbool_vector; @@ -523,7 +523,7 @@ DEFUN ("byte-code-function-p", Fbyte_code_function_p, Sbyte_code_function_p, doc: /* Return t if OBJECT is a byte-compiled function object. */) (Lisp_Object object) { - if (COMPILEDP (object)) + if (CLOSUREP (object)) return Qt; return Qnil; } @@ -1143,19 +1143,19 @@ DEFUN ("interactive-form", Finteractive_form, Sinteractive_form, 1, 1, 0, (*spec != '(') ? build_string (spec) : Fcar (Fread_from_string (build_string (spec), Qnil, Qnil))); } - else if (COMPILEDP (fun)) + else if (CLOSUREP (fun)) { - if (PVSIZE (fun) > COMPILED_INTERACTIVE) + if (PVSIZE (fun) > CLOSURE_INTERACTIVE) { - Lisp_Object form = AREF (fun, COMPILED_INTERACTIVE); + Lisp_Object form = AREF (fun, CLOSURE_INTERACTIVE); /* The vector form is the new form, where the first element is the interactive spec, and the second is the command modes. */ return list2 (Qinteractive, VECTORP (form) ? AREF (form, 0) : form); } - else if (PVSIZE (fun) > COMPILED_DOC_STRING) + else if (PVSIZE (fun) > CLOSURE_DOC_STRING) { - Lisp_Object doc = AREF (fun, COMPILED_DOC_STRING); + Lisp_Object doc = AREF (fun, CLOSURE_DOC_STRING); /* An invalid "docstring" is a sign that we have an OClosure. */ genfun = !(NILP (doc) || VALID_DOCSTRING_P (doc)); } @@ -1225,11 +1225,11 @@ DEFUN ("command-modes", Fcommand_modes, Scommand_modes, 1, 1, 0, { return XSUBR (fun)->command_modes; } - else if (COMPILEDP (fun)) + else if (CLOSUREP (fun)) { - if (PVSIZE (fun) <= COMPILED_INTERACTIVE) + if (PVSIZE (fun) <= CLOSURE_INTERACTIVE) return Qnil; - Lisp_Object form = AREF (fun, COMPILED_INTERACTIVE); + Lisp_Object form = AREF (fun, CLOSURE_INTERACTIVE); if (VECTORP (form)) /* New form -- the second element is the command modes. */ return AREF (form, 1); @@ -2546,7 +2546,7 @@ DEFUN ("aref", Faref, Saref, 2, 2, 0, ptrdiff_t size = 0; if (VECTORP (array)) size = ASIZE (array); - else if (COMPILEDP (array) || RECORDP (array)) + else if (CLOSUREP (array) || RECORDP (array)) size = PVSIZE (array); else wrong_type_argument (Qarrayp, array); diff --git a/src/eval.c b/src/eval.c index f48d7b0682f..cfed27cf629 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2150,15 +2150,15 @@ DEFUN ("commandp", Fcommandp, Scommandp, 1, 2, 0, return Qt; } /* Bytecode objects are interactive if they are long enough to - have an element whose index is COMPILED_INTERACTIVE, which is + have an element whose index is CLOSURE_INTERACTIVE, which is where the interactive spec is stored. */ - else if (COMPILEDP (fun)) + else if (CLOSUREP (fun)) { - if (PVSIZE (fun) > COMPILED_INTERACTIVE) + if (PVSIZE (fun) > CLOSURE_INTERACTIVE) return Qt; - else if (PVSIZE (fun) > COMPILED_DOC_STRING) + else if (PVSIZE (fun) > CLOSURE_DOC_STRING) { - Lisp_Object doc = AREF (fun, COMPILED_DOC_STRING); + Lisp_Object doc = AREF (fun, CLOSURE_DOC_STRING); /* An invalid "docstring" is a sign that we have an OClosure. */ genfun = !(NILP (doc) || VALID_DOCSTRING_P (doc)); } @@ -2566,7 +2566,7 @@ eval_sub (Lisp_Object form) } } } - else if (COMPILEDP (fun) + else if (CLOSUREP (fun) || SUBR_NATIVE_COMPILED_DYNP (fun) || MODULE_FUNCTIONP (fun)) return apply_lambda (fun, original_args, count); @@ -2944,7 +2944,7 @@ FUNCTIONP (Lisp_Object object) if (SUBRP (object)) return XSUBR (object)->max_args != UNEVALLED; - else if (COMPILEDP (object) || MODULE_FUNCTIONP (object)) + else if (CLOSUREP (object) || MODULE_FUNCTIONP (object)) return true; else if (CONSP (object)) { @@ -2966,7 +2966,7 @@ funcall_general (Lisp_Object fun, ptrdiff_t numargs, Lisp_Object *args) if (SUBRP (fun) && !SUBR_NATIVE_COMPILED_DYNP (fun)) return funcall_subr (XSUBR (fun), numargs, args); - else if (COMPILEDP (fun) + else if (CLOSUREP (fun) || SUBR_NATIVE_COMPILED_DYNP (fun) || MODULE_FUNCTIONP (fun)) return funcall_lambda (fun, numargs, args); @@ -3184,9 +3184,9 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, else xsignal1 (Qinvalid_function, fun); } - else if (COMPILEDP (fun)) + else if (CLOSUREP (fun)) { - syms_left = AREF (fun, COMPILED_ARGLIST); + syms_left = AREF (fun, CLOSURE_ARGLIST); /* Bytecode objects using lexical binding have an integral ARGLIST slot value: pass the arguments to the byte-code engine directly. */ @@ -3314,7 +3314,7 @@ DEFUN ("func-arity", Ffunc_arity, Sfunc_arity, 1, 1, 0, if (SUBRP (function)) result = Fsubr_arity (function); - else if (COMPILEDP (function)) + else if (CLOSUREP (function)) result = lambda_arity (function); #ifdef HAVE_MODULES else if (MODULE_FUNCTIONP (function)) @@ -3362,9 +3362,9 @@ lambda_arity (Lisp_Object fun) else xsignal1 (Qinvalid_function, fun); } - else if (COMPILEDP (fun)) + else if (CLOSUREP (fun)) { - syms_left = AREF (fun, COMPILED_ARGLIST); + syms_left = AREF (fun, CLOSURE_ARGLIST); if (FIXNUMP (syms_left)) return get_byte_code_arity (syms_left); } diff --git a/src/fns.c b/src/fns.c index db5e856d5bd..e987d64319f 100644 --- a/src/fns.c +++ b/src/fns.c @@ -152,7 +152,7 @@ DEFUN ("length", Flength, Slength, 1, 1, 0, val = MAX_CHAR; else if (BOOL_VECTOR_P (sequence)) val = bool_vector_size (sequence); - else if (COMPILEDP (sequence) || RECORDP (sequence)) + else if (CLOSUREP (sequence) || RECORDP (sequence)) val = PVSIZE (sequence); else wrong_type_argument (Qsequencep, sequence); @@ -1054,7 +1054,7 @@ concat_to_list (ptrdiff_t nargs, Lisp_Object *args, Lisp_Object last_tail) else if (NILP (arg)) ; else if (VECTORP (arg) || STRINGP (arg) - || BOOL_VECTOR_P (arg) || COMPILEDP (arg)) + || BOOL_VECTOR_P (arg) || CLOSUREP (arg)) { ptrdiff_t arglen = XFIXNUM (Flength (arg)); ptrdiff_t argindex_byte = 0; @@ -1114,7 +1114,7 @@ concat_to_vector (ptrdiff_t nargs, Lisp_Object *args) { Lisp_Object arg = args[i]; if (!(VECTORP (arg) || CONSP (arg) || NILP (arg) || STRINGP (arg) - || BOOL_VECTOR_P (arg) || COMPILEDP (arg))) + || BOOL_VECTOR_P (arg) || CLOSUREP (arg))) wrong_type_argument (Qsequencep, arg); EMACS_INT len = XFIXNAT (Flength (arg)); result_len += len; @@ -1170,7 +1170,7 @@ concat_to_vector (ptrdiff_t nargs, Lisp_Object *args) } else { - eassert (COMPILEDP (arg)); + eassert (CLOSUREP (arg)); ptrdiff_t size = PVSIZE (arg); memcpy (dst, XVECTOR (arg)->contents, size * sizeof *dst); dst += size; @@ -2949,7 +2949,7 @@ internal_equal (Lisp_Object o1, Lisp_Object o2, enum equal_kind equal_kind, if (size & PSEUDOVECTOR_FLAG) { if (((size & PVEC_TYPE_MASK) >> PSEUDOVECTOR_AREA_BITS) - < PVEC_COMPILED) + < PVEC_CLOSURE) return false; size &= PSEUDOVECTOR_SIZE_MASK; } @@ -3346,7 +3346,7 @@ mapcar1 (EMACS_INT leni, Lisp_Object *vals, Lisp_Object fn, Lisp_Object seq) tail = XCDR (tail); } } - else if (VECTORP (seq) || COMPILEDP (seq)) + else if (VECTORP (seq) || CLOSUREP (seq)) { for (ptrdiff_t i = 0; i < leni; i++) { @@ -5512,7 +5512,7 @@ sxhash_obj (Lisp_Object obj, int depth) case Lisp_Vectorlike: { enum pvec_type pvec_type = PSEUDOVECTOR_TYPE (XVECTOR (obj)); - if (! (PVEC_NORMAL_VECTOR < pvec_type && pvec_type < PVEC_COMPILED)) + if (! (PVEC_NORMAL_VECTOR < pvec_type && pvec_type < PVEC_CLOSURE)) { /* According to the CL HyperSpec, two arrays are equal only if they are 'eq', except for strings and bit-vectors. In diff --git a/src/lisp.h b/src/lisp.h index 3cb4361e75e..526248dd2ba 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -1049,7 +1049,7 @@ DEFINE_GDB_SYMBOL_END (PSEUDOVECTOR_FLAG) PVEC_SQLITE, /* These should be last, for internal_equal and sxhash_obj. */ - PVEC_COMPILED, + PVEC_CLOSURE, PVEC_CHAR_TABLE, PVEC_SUB_CHAR_TABLE, PVEC_RECORD, @@ -3223,16 +3223,16 @@ XFLOAT_DATA (Lisp_Object f) #define IEEE_FLOATING_POINT (FLT_RADIX == 2 && FLT_MANT_DIG == 24 \ && FLT_MIN_EXP == -125 && FLT_MAX_EXP == 128) -/* Meanings of slots in a Lisp_Compiled: */ +/* Meanings of slots in a Lisp_Closure: */ -enum Lisp_Compiled +enum Lisp_Closure { - COMPILED_ARGLIST = 0, - COMPILED_BYTECODE = 1, - COMPILED_CONSTANTS = 2, - COMPILED_STACK_DEPTH = 3, - COMPILED_DOC_STRING = 4, - COMPILED_INTERACTIVE = 5 + CLOSURE_ARGLIST = 0, + CLOSURE_CODE = 1, + CLOSURE_CONSTANTS = 2, + CLOSURE_STACK_DEPTH = 3, + CLOSURE_DOC_STRING = 4, + CLOSURE_INTERACTIVE = 5 }; /* Flag bits in a character. These also get used in termhooks.h. @@ -3307,9 +3307,9 @@ WINDOW_CONFIGURATIONP (Lisp_Object a) } INLINE bool -COMPILEDP (Lisp_Object a) +CLOSUREP (Lisp_Object a) { - return PSEUDOVECTORP (a, PVEC_COMPILED); + return PSEUDOVECTORP (a, PVEC_CLOSURE); } INLINE bool diff --git a/src/lread.c b/src/lread.c index 09a5589fd0c..8b614e6220e 100644 --- a/src/lread.c +++ b/src/lread.c @@ -3498,52 +3498,52 @@ bytecode_from_rev_list (Lisp_Object elems, Lisp_Object readcharfun) Lisp_Object *vec = XVECTOR (obj)->contents; ptrdiff_t size = ASIZE (obj); - if (infile && size >= COMPILED_CONSTANTS) + if (infile && size >= CLOSURE_CONSTANTS) { /* Always read 'lazily-loaded' bytecode (generated by the `byte-compile-dynamic' feature prior to Emacs 30) eagerly, to avoid code in the fast path during execution. */ - if (CONSP (vec[COMPILED_BYTECODE]) - && FIXNUMP (XCDR (vec[COMPILED_BYTECODE]))) - vec[COMPILED_BYTECODE] = get_lazy_string (vec[COMPILED_BYTECODE]); + if (CONSP (vec[CLOSURE_CODE]) + && FIXNUMP (XCDR (vec[CLOSURE_CODE]))) + vec[CLOSURE_CODE] = get_lazy_string (vec[CLOSURE_CODE]); /* Lazily-loaded bytecode is represented by the constant slot being nil and the bytecode slot a (lazily loaded) string containing the print representation of (BYTECODE . CONSTANTS). Unpack the pieces by coerceing the string to unibyte and reading the result. */ - if (NILP (vec[COMPILED_CONSTANTS]) && STRINGP (vec[COMPILED_BYTECODE])) + if (NILP (vec[CLOSURE_CONSTANTS]) && STRINGP (vec[CLOSURE_CODE])) { - Lisp_Object enc = vec[COMPILED_BYTECODE]; + Lisp_Object enc = vec[CLOSURE_CODE]; Lisp_Object pair = Fread (Fcons (enc, readcharfun)); if (!CONSP (pair)) invalid_syntax ("Invalid byte-code object", readcharfun); - vec[COMPILED_BYTECODE] = XCAR (pair); - vec[COMPILED_CONSTANTS] = XCDR (pair); + vec[CLOSURE_CODE] = XCAR (pair); + vec[CLOSURE_CONSTANTS] = XCDR (pair); } } - if (!(size >= COMPILED_STACK_DEPTH + 1 && size <= COMPILED_INTERACTIVE + 1 - && (FIXNUMP (vec[COMPILED_ARGLIST]) - || CONSP (vec[COMPILED_ARGLIST]) - || NILP (vec[COMPILED_ARGLIST])) - && STRINGP (vec[COMPILED_BYTECODE]) - && VECTORP (vec[COMPILED_CONSTANTS]) - && FIXNATP (vec[COMPILED_STACK_DEPTH]))) + if (!(size >= CLOSURE_STACK_DEPTH + 1 && 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]))) invalid_syntax ("Invalid byte-code object", readcharfun); - if (STRING_MULTIBYTE (vec[COMPILED_BYTECODE])) + 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[COMPILED_BYTECODE] = Fstring_as_unibyte (vec[COMPILED_BYTECODE]); + vec[CLOSURE_CODE] = Fstring_as_unibyte (vec[CLOSURE_CODE]); /* Bytecode must be immovable. */ - pin_string (vec[COMPILED_BYTECODE]); + pin_string (vec[CLOSURE_CODE]); - XSETPVECTYPE (XVECTOR (obj), PVEC_COMPILED); + XSETPVECTYPE (XVECTOR (obj), PVEC_CLOSURE); return obj; } @@ -4678,7 +4678,7 @@ substitute_object_recurse (struct subst *subst, Lisp_Object subtree) if (BOOL_VECTOR_P (subtree)) return subtree; /* No sub-objects anyway. */ else if (CHAR_TABLE_P (subtree) || SUB_CHAR_TABLE_P (subtree) - || COMPILEDP (subtree) || HASH_TABLE_P (subtree) + || CLOSUREP (subtree) || HASH_TABLE_P (subtree) || RECORDP (subtree)) length = PVSIZE (subtree); else if (VECTORP (subtree)) diff --git a/src/pdumper.c b/src/pdumper.c index ac8bf6f31f4..2963efc56ab 100644 --- a/src/pdumper.c +++ b/src/pdumper.c @@ -3068,7 +3068,7 @@ dump_vectorlike (struct dump_context *ctx, error_unsupported_dump_object(ctx, lv, "font"); FALLTHROUGH; case PVEC_NORMAL_VECTOR: - case PVEC_COMPILED: + case PVEC_CLOSURE: case PVEC_CHAR_TABLE: case PVEC_SUB_CHAR_TABLE: case PVEC_RECORD: diff --git a/src/print.c b/src/print.c index 0d867b89395..612d63b7e94 100644 --- a/src/print.c +++ b/src/print.c @@ -1299,7 +1299,7 @@ #define PRINT_CIRCLE_CANDIDATE_P(obj) \ (STRINGP (obj) \ || CONSP (obj) \ || (VECTORLIKEP (obj) \ - && (VECTORP (obj) || COMPILEDP (obj) \ + && (VECTORP (obj) || CLOSUREP (obj) \ || CHAR_TABLE_P (obj) || SUB_CHAR_TABLE_P (obj) \ || HASH_TABLE_P (obj) || FONTP (obj) \ || RECORDP (obj))) \ @@ -2091,7 +2091,7 @@ print_vectorlike_unreadable (Lisp_Object obj, Lisp_Object printcharfun, /* Types handled earlier. */ case PVEC_NORMAL_VECTOR: case PVEC_RECORD: - case PVEC_COMPILED: + case PVEC_CLOSURE: case PVEC_CHAR_TABLE: case PVEC_SUB_CHAR_TABLE: case PVEC_HASH_TABLE: @@ -2559,7 +2559,7 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag) print_stack_push_vector ("#s(", ")", obj, 0, PVSIZE (obj), printcharfun); goto next_obj; - case PVEC_COMPILED: + case PVEC_CLOSURE: print_stack_push_vector ("#[", "]", obj, 0, PVSIZE (obj), printcharfun); goto next_obj; diff --git a/src/profiler.c b/src/profiler.c index 5a6a8b48f6b..ac23a97b672 100644 --- a/src/profiler.c +++ b/src/profiler.c @@ -170,7 +170,7 @@ trace_hash (Lisp_Object *trace, int depth) { Lisp_Object f = trace[i]; EMACS_UINT hash1 - = (COMPILEDP (f) ? XHASH (AREF (f, COMPILED_BYTECODE)) + = (CLOSUREP (f) ? XHASH (AREF (f, CLOSURE_CODE)) : (CONSP (f) && CONSP (XCDR (f)) && BASE_EQ (Qclosure, XCAR (f))) ? XHASH (XCDR (XCDR (f))) : XHASH (f)); hash = sxhash_combine (hash, hash1); @@ -675,8 +675,8 @@ DEFUN ("function-equal", Ffunction_equal, Sfunction_equal, 2, 2, 0, bool res; if (EQ (f1, f2)) res = true; - else if (COMPILEDP (f1) && COMPILEDP (f2)) - res = EQ (AREF (f1, COMPILED_BYTECODE), AREF (f2, COMPILED_BYTECODE)); + 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))) -- 2.43.0 --=-=-= Content-Type: text/patch Content-Disposition: attachment; filename=0002-Use-a-dedicated-type-to-represent-interpreted-functi.patch >From 95620b74383f762b6905140a21916bd195d6677f Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Mon, 11 Mar 2024 16:12:26 -0400 Subject: [PATCH 2/2] 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): * src/alloc.c (Fmake_byte_code): Loosen sanity checks to allow building `interpreted-function`s as well. * 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. --- doc/lispref/control.texi | 2 +- doc/lispref/functions.texi | 24 +-- doc/lispref/lists.texi | 4 +- doc/lispref/variables.texi | 2 +- etc/NEWS | 12 ++ lisp/emacs-lisp/byte-opt.el | 3 +- lisp/emacs-lisp/bytecomp.el | 20 ++- lisp/emacs-lisp/cconv.el | 38 +++-- lisp/emacs-lisp/cl-preloaded.el | 6 +- lisp/emacs-lisp/cl-print.el | 32 ++++ lisp/emacs-lisp/comp-common.el | 2 + lisp/emacs-lisp/disass.el | 6 +- lisp/emacs-lisp/edebug.el | 2 +- lisp/emacs-lisp/lisp-mode.el | 1 - lisp/emacs-lisp/nadvice.el | 6 +- lisp/emacs-lisp/oclosure.el | 96 +++++------- lisp/help.el | 3 +- lisp/profiler.el | 5 +- lisp/simple.el | 5 +- src/alloc.c | 23 +-- src/callint.c | 6 +- src/data.c | 41 +++-- src/eval.c | 142 +++++++++++------- src/lread.c | 35 +++-- src/profiler.c | 8 +- test/lisp/emacs-lisp/macroexp-resources/vk.el | 48 +++--- test/lisp/erc/resources/erc-d/erc-d-tests.el | 5 +- test/lisp/help-fns-tests.el | 10 +- 28 files changed, 345 insertions(+), 242 deletions(-) diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi index f9f3389c398..46024e2fdee 100644 --- a/doc/lispref/control.texi +++ b/doc/lispref/control.texi @@ -2411,7 +2411,7 @@ Handling Errors @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/functions.texi b/doc/lispref/functions.texi index ff635fc54b2..0062de91354 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -1136,8 +1136,7 @@ Anonymous Functions 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 +1144,18 @@ Anonymous Functions @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 +1164,8 @@ Anonymous Functions @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 +1495,7 @@ Function Cells @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 +1608,7 @@ Closures @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 1409e51c0d4..06472539744 100644 --- a/doc/lispref/lists.texi +++ b/doc/lispref/lists.texi @@ -1249,7 +1249,7 @@ Rearrangement @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 @@ Rearrangement @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/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 @@ Lexical Binding (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 7a73815179c..b7cd929af04 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1680,6 +1680,18 @@ 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. + +++ ** 'define-globalized-minor-mode' requires that modes use 'run-mode-hooks'. Minor modes defined with 'define-globalized-minor-mode', such as 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 @@ byte-compile-inline-expand ;; 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 @@ byte-optimize-set 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 fb3278c08ab..c82f8ba6ae2 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -2900,9 +2900,14 @@ byte-compile-output-as-comment (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 @@ -2939,11 +2944,11 @@ byte-compile (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)) @@ -5133,7 +5138,6 @@ byte-compile-file-form-defalias ;; `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)) @@ -5571,7 +5575,7 @@ display-call-tree " " " ")) ((eq 'lambda (car f)) - "") + "") (t "???")) (format " (%d callers + %d calls = %d)" ;; Does the optimizer eliminate common subexpressions?-sk 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 @@ cconv-fv (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 @@ cconv-make-interpreted-closure 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 @@ cconv-make-interpreted-closure (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 @@ cconv-make-interpreted-closure (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..c202f1d1064 100644 --- a/lisp/emacs-lisp/cl-preloaded.el +++ b/lisp/emacs-lisp/cl-preloaded.el @@ -444,13 +444,15 @@ function ) (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.") +(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 5e5eee1da9e..3a8f80f6e93 100644 --- a/lisp/emacs-lisp/cl-print.el +++ b/lisp/emacs-lisp/cl-print.el @@ -237,6 +237,38 @@ cl-print-object '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 @@ comp-known-type-specifiers (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 @@ disassemble-internal (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 @@ disassemble-internal (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 b27ffbca908..3414bb592c0 100644 --- a/lisp/emacs-lisp/edebug.el +++ b/lisp/emacs-lisp/edebug.el @@ -4254,7 +4254,7 @@ edebug--strip-instrumentation ((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-indent-defform (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 @@ advice-eval-interactive-spec (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 @@ advice--interactive-form-1 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 @@ oclosure--index-table (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 @@ oclosure-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 d4e39f04e53..10bd2ffec3f 100644 --- a/lisp/help.el +++ b/lisp/help.el @@ -2349,9 +2349,8 @@ help-function-arglist ;; 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 @@ profiler-calltree-build-1 (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 e4629ce3db7..be64f3574e0 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -2703,15 +2703,14 @@ function-documentation (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/alloc.c b/src/alloc.c index a8dfde56739..b0e5d52cc32 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3813,17 +3813,22 @@ and (optional) INTERACTIVE-SPEC. usage: (make-byte-code ARGLIST BYTE-CODE CONSTANTS DEPTH &optional DOCSTRING INTERACTIVE-SPEC &rest ELEMENTS) */) (ptrdiff_t nargs, Lisp_Object *args) { - if (! ((FIXNUMP (args[CLOSURE_ARGLIST]) - || CONSP (args[CLOSURE_ARGLIST]) - || NILP (args[CLOSURE_ARGLIST])) - && STRINGP (args[CLOSURE_CODE]) - && !STRING_MULTIBYTE (args[CLOSURE_CODE]) - && VECTORP (args[CLOSURE_CONSTANTS]) - && FIXNATP (args[CLOSURE_STACK_DEPTH]))) + if (CONSP (args[CLOSURE_CODE])) + ; /* An interpreted closure. */ + else if ((FIXNUMP (args[CLOSURE_ARGLIST]) + || CONSP (args[CLOSURE_ARGLIST]) + || NILP (args[CLOSURE_ARGLIST])) + && STRINGP (args[CLOSURE_CODE]) + && !STRING_MULTIBYTE (args[CLOSURE_CODE]) + && VECTORP (args[CLOSURE_CONSTANTS]) + && FIXNATP (args[CLOSURE_STACK_DEPTH])) + { + /* Bytecode must be immovable. */ + pin_string (args[CLOSURE_CODE]); + } + else error ("Invalid byte-code object"); - /* Bytecode must be immovable. */ - pin_string (args[CLOSURE_CODE]); /* We used to purecopy everything here, if purify-flag was set. This worked OK for Emacs-23, but with Emacs-24's lexical binding code, it can be 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 @@ DEFUN ("call-interactively", Fcall_interactively, Scall_interactively, 1, 3, 0, { 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..4d1fdf0c7fd 100644 --- a/src/data.c +++ b/src/data.c @@ -248,7 +248,9 @@ DEFUN ("cl-type-of", Fcl_type_of, Scl_type_of, 1, 1, 0, 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 object. */) + (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 an interpreted function value. */) + (Lisp_Object object) +{ + if (CLOSUREP (object) && CONSP (AREF (object, CLOSURE_CODE))) return Qt; return Qnil; } @@ -1174,12 +1196,9 @@ DEFUN ("interactive-form", Finteractive_form, Sinteractive_form, 1, 1, 0, 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. */ @@ -1257,12 +1276,9 @@ DEFUN ("command-modes", Fcommand_modes, Scommand_modes, 1, 1, 0, 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 +4240,8 @@ #define PUT_ERROR(sym, tail, msg) \ 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 +4306,8 @@ #define PUT_ERROR(sym, tail, msg) \ 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 cfed27cf629..9a2c767ffee 100644 --- a/src/eval.c +++ b/src/eval.c @@ -510,6 +510,32 @@ DEFUN ("quote", Fquote, Squote, 1, UNEVALLED, 0, 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! */ + if (!NILP (iform)) + { + iform = Fcdr (iform); + return CALLN (Fmake_byte_code, + args, body, env, Qnil, docstring, + NILP (Fcdr (iform)) + ? Fcar (iform) + : CALLN (Fvector, XCAR (iform), XCDR (iform))); + } + else if (!NILP (docstring)) + return CALLN (Fmake_byte_code, args, body, env, Qnil, docstring); + else + return CALLN (Fmake_byte_code, args, body, env); +} + 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 +551,55 @@ DEFUN ("function", Ffunction, Sfunction, 1, UNEVALLED, 0, 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. */ @@ -2192,9 +2240,7 @@ DEFUN ("commandp", Fcommandp, Scommandp, 1, 2, 0, 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; @@ -2610,8 +2656,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); @@ -2949,7 +2994,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; @@ -2979,8 +3024,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)) { @@ -3168,16 +3212,7 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, 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); @@ -3192,10 +3227,12 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, 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)) @@ -3279,7 +3316,14 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, 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); } @@ -3329,8 +3373,7 @@ DEFUN ("func-arity", Ffunc_arity, Sfunc_arity, 1, 1, 0, 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)) { @@ -3351,11 +3394,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); @@ -4266,7 +4304,6 @@ syms_of_eval (void) 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"); @@ -4424,6 +4461,7 @@ syms_of_eval (void) 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 @@ DEFUN ("function-equal", Ffunction_equal, Sfunction_equal, 2, 2, 0, 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 @@ vk-f1 (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 @@ erc-d--render-entries (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 @@ help-fns-test-lisp-macro (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)))) -- 2.43.0 --=-=-=--