all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
@ 2024-04-13 19:56 Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-14  5:32 ` Eli Zaretskii
  0 siblings, 1 reply; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-13 19:56 UTC (permalink / raw)
  To: 70368

[-- Attachment #1: Type: text/plain, Size: 1630 bytes --]

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



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-COMPILED-Rename-to-CLOSURE.patch --]
[-- Type: text/x-diff, Size: 25386 bytes --]

From 669179d696ab2e3c7ab7b504dd1b6b8827bbfb0b Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier@iro.umontreal.ca>
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


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Use-a-dedicated-type-to-represent-interpreted-functi.patch --]
[-- Type: text/patch, Size: 55634 bytes --]

From 95620b74383f762b6905140a21916bd195d6677f Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier@iro.umontreal.ca>
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) <interpreted-function>:
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.
 \f
 * 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
 		     " <compiled macro>"
 		   " <macro>"))
 		((eq 'lambda (car f))
-		 "<function>")
+		 "<function-like list>")
 		(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 <form>) 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 <form>) 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


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-13 19:56 bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-14  5:32 ` Eli Zaretskii
  2024-04-14 13:49   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 24+ messages in thread
From: Eli Zaretskii @ 2024-04-14  5:32 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

> Date: Sat, 13 Apr 2024 15:56:34 -0400
> From:  Stefan Monnier via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org>
> 
> 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.

I don't think I understand the implications of this on compatibility
of byte-code.  Will the byte-code produced by Emacs 30 after these
changes be compatible or incompatible with previous versions of Emacs?
And what about the compatibility in the other direction?

> 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.

Why is such a distinction needed?

> 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 ...)`.

Are there no downsides, none whatever?  It would sound too good to be
true...

> -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);
>  }

Do the above changes require changes to .gdbinit?  E.g., I see that we
use PVEC_COMPILED there.

> --- 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")

This new #f syntax is not documented anywhere, AFAICT.  If this is the
new printed representation (and maybe also read syntax?) of functions,
it should be documented, like we do with other printed representations.

> --- 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)

What does this change mean for backward compatibility?

> 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 ()))

And this?

> @@ -5571,7 +5575,7 @@ display-call-tree
>  		     " <compiled macro>"
>  		   " <macro>"))
>  		((eq 'lambda (car f))
> -		 "<function>")
> +		 "<function-like list>")
                  ^^^^^^^^^^^^^^^^^^^^
Should this be documented somewhere?

> +DEFUN ("closurep", Fclosurep, Sclosurep,
> +       1, 1, 0,
> +       doc: /* Return t if OBJECT is a function object.  */)

If the doc string is correct, then why is the function called
'closurep'?  It's against mnemonic memory.

> +  (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.  */)
                                                       ^^^^^^^^^^^^^^
"function value"? what's that?

> +  (Lisp_Object object)
> +{
> +  if (CLOSUREP (object) && CONSP (AREF (object, CLOSURE_CODE)))
>      return Qt;
>    return Qnil;

These new primitives should be documented in the ELisp manual, and
perhaps also mentioned in NEWS.

> +DEFUN ("make-interpreted-closure", Fmake_interpreted_closure,
> +       Smake_interpreted_closure, 3, 5, 0,
> +       doc: /* Make an interpreted closure.

Please try to mention the arguments in the first sentence of doc
string.

And this primitive should also be documented in the ELisp manual.

> +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);

I'm probably missing something, but if the doc string says "make an
_interpreted_ closure", why does the implementation call
make-byte-code?  Isn't byte-code a kind-of antithesis of
"interpreted"?

Finally, please quote 'like this' or `like this' in the commit log
messages, not `like this`.  Example:

> 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`.

The line that begins with "* src/lisp.h" is too long, btw, it should
be at most 70 columns, preferably 65.  And there are a few others that
are likewise too long.

Thanks.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-14  5:32 ` Eli Zaretskii
@ 2024-04-14 13:49   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-14 14:45     ` Eli Zaretskii
  2024-04-14 16:18     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-14 13:49 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 70368

> I don't think I understand the implications of this on compatibility
> of byte-code.  Will the byte-code produced by Emacs 30 after these
> changes be compatible or incompatible with previous versions of Emacs?

The byte code is unchanged, so compatible both ways.

Some new `.elc` files may be incompatible with older Emacsen if they
happen to contain a non-compiled function (e.g. via a macro which
somehow gets an interpreted function during the expansion (e.g. via
`eval` of by fetching it from a not-yet-compiled file) and then stashes
it in the returned code).  FWIW, I don't see any occurrence of that in
Emacs's own files, for example.

> And what about the compatibility in the other direction?

There are two kinds of incompatibilities it introduces, in my experience:

- "soft" incompatibilities for code which tries to display the kind of
  the object it receives (such as in a completion table that wants to
  add an icon indicating if something is a macro, a compiled function,
  etc...).  Such code will still work but may display less informative
  info because it may fail to recognize the new objects as being
  interpreted functions.
- "real" incompatibilities for code which digs inside the
  entrails of functions to try and extract specific information.
  This may fail when faced with the new interpreted functions
  but should be easy to fix.
  As long as such code only tries to extract info and does it via
  `help-function-arglist`, `documentation`, and `interactive-form`,
  there's no problem, but some packages may use code inherited from
  a long time ago when `help-function-arglist` didn't exist, or written
  by coders who didn't know better.  I know Hyperbole used to do that,
  but I believe that's been fixed since.
- "hard" incompatibilities for code which really digs inside the
  code of functions.  `vc.el` did that a long time ago,
  `kmacro.el` did as well until OClosures, and Buttercup (a NonGNU ELPA
  package) did until a few months ago.  There are probably one or two
  packages out there that will be affected like Buttercup would have
  been.  FWIW, the Buttercup case was a mix of "hard" and "soft": it
  really looked inside the code, but used that only to provide more
  informative messages and had a fallback case when the code was
  compiled, so it would still work OK.
  
>> 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.
> Why is such a distinction needed?

Indeed, why?
[ More seriously, what I'm saying above is that for those three
  functions, the code used for byte-code objects now also works on
  interpreted functions by virtue of both objects sharing the same
  underlying representation.  ]

>> 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 ...)`.
>
> Are there no downsides, none whatever?  It would sound too good to be
> true...

There's the backward incompatibilty.

> Do the above changes require changes to .gdbinit?  E.g., I see that we
> use PVEC_COMPILED there.

Indeed, I missed that, thanks, fixed.

> This new #f syntax is not documented anywhere, AFAICT.  If this is the
> new printed representation (and maybe also read syntax?) of functions,

It's only a cl-print representation, it's not `read`able.
The `read`able representation uses the #[...] syntax also used for
bytecode functions.

> it should be documented, like we do with other printed representations.

Where would that be?  I don't see where we document the
#f(compiled-function ...) used for byte-code, which was my inspiration
for the #f(lambda ...).

>> --- 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)
>
> What does this change mean for backward compatibility?

Nothing, it just reflects the fact that this code previously received
interpreted function in the form of (closure ...) and now it receives
them in the form of `interpreted-function` objects.

>> 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 ()))
>
> And this?

Same: this adjust the internal code of `byte-compile` so it works on the
new representation of function values.

>> @@ -5571,7 +5575,7 @@ display-call-tree
>>  		     " <compiled macro>"
>>  		   " <macro>"))
>>  		((eq 'lambda (car f))
>> -		 "<function>")
>> +		 "<function-like list>")
>                   ^^^^^^^^^^^^^^^^^^^^
> Should this be documented somewhere?

This should be an extremely rare occurrence, and `display-call-tree`
itself is not documented, so I doubt it's worth the trouble.
If you prefer I can keep it as `<function>`.

>> +DEFUN ("closurep", Fclosurep, Sclosurep,
>> +       1, 1, 0,
>> +       doc: /* Return t if OBJECT is a function object.  */)
>
> If the doc string is correct, then why is the function called
> 'closurep'?  It's against mnemonic memory.

The term "closure" is kind of funny, indeed: often it's used to say
"this is a piece of code packaged with its environment", but in most
cases where it's used all functions are like that, so the precise
meaning of "closure" can be argued to be exactly the same as "function",
just with a different connotation.  Sometimes the connotation is to
insist on the fact that it captured some variables, but object it's used
to distinguish between the abstract notion of functions and their
runtime representation, where "closure" means "an object which
represents a function".

IOW, the docstring above is meant to say "a function *object*" rather
than "a *function* object".  Compare with `functionp`:

    Return t if OBJECT is a function.

Maybe I should say something like:

    Return t if OBJECT is a closure, i.e. a function object.

?

>> +DEFUN ("interpreted-function-p", Finterpreted_function_p,
>> +       Sinterpreted_function_p, 1, 1, 0,
>> +       doc: /* Return t if OBJECT is an interpreted function value.  */)
>                                                        ^^^^^^^^^^^^^^
> "function value"? what's that?

In general, `eval` takes an source expression and returns a value.

    (lambda (x) (+ x y))

is a list of length 3 which represents a function as a source expression
and `eval` turns it into a value (presumably a closure which will
remember the current binding of `y`).

    (closure .. (x) (+ x y))

used to be the representation used for interpreted function *values*.

>> +  (Lisp_Object object)
>> +{
>> +  if (CLOSUREP (object) && CONSP (AREF (object, CLOSURE_CODE)))
>>      return Qt;
>>    return Qnil;
>
> These new primitives should be documented in the ELisp manual, and
> perhaps also mentioned in NEWS.

OK.

>> +DEFUN ("make-interpreted-closure", Fmake_interpreted_closure,
>> +       Smake_interpreted_closure, 3, 5, 0,
>> +       doc: /* Make an interpreted closure.
>
> Please try to mention the arguments in the first sentence of doc
> string.
>
> And this primitive should also be documented in the ELisp manual.

OK.

>> +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);
>
> I'm probably missing something, but if the doc string says "make an
> _interpreted_ closure", why does the implementation call
> make-byte-code?  Isn't byte-code a kind-of antithesis of
> "interpreted"?

Because `make-byte-code` is the function that we already have which
creates PVEC_CLOSURE (previous called PVEC_COMPILED) objects.  It used
to be used exclusively to create byte-code functions but now that same
representation is used for interpreted function.  I could rename it but
we already have `make-closure` for something slightly different, and
I don't see much benefit to the rename.
I'll add a comment explaining why we use `Fmake_byte_code`,


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-14 13:49   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-14 14:45     ` Eli Zaretskii
  2024-04-14 23:03       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-14 16:18     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 24+ messages in thread
From: Eli Zaretskii @ 2024-04-14 14:45 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: 70368@debbugs.gnu.org
> Date: Sun, 14 Apr 2024 09:49:23 -0400
> 
> > I don't think I understand the implications of this on compatibility
> > of byte-code.  Will the byte-code produced by Emacs 30 after these
> > changes be compatible or incompatible with previous versions of Emacs?
> 
> > And what about the compatibility in the other direction?
> 
> There are two kinds of incompatibilities it introduces, in my experience:
> 
> - "soft" incompatibilities for code which tries to display the kind of
>   the object it receives (such as in a completion table that wants to
>   add an icon indicating if something is a macro, a compiled function,
>   etc...).  Such code will still work but may display less informative
>   info because it may fail to recognize the new objects as being
>   interpreted functions.
> - "real" incompatibilities for code which digs inside the
>   entrails of functions to try and extract specific information.
>   This may fail when faced with the new interpreted functions
>   but should be easy to fix.
>   As long as such code only tries to extract info and does it via
>   `help-function-arglist`, `documentation`, and `interactive-form`,
>   there's no problem, but some packages may use code inherited from
>   a long time ago when `help-function-arglist` didn't exist, or written
>   by coders who didn't know better.  I know Hyperbole used to do that,
>   but I believe that's been fixed since.
> - "hard" incompatibilities for code which really digs inside the
>   code of functions.  `vc.el` did that a long time ago,
>   `kmacro.el` did as well until OClosures, and Buttercup (a NonGNU ELPA
>   package) did until a few months ago.  There are probably one or two
>   packages out there that will be affected like Buttercup would have
>   been.  FWIW, the Buttercup case was a mix of "hard" and "soft": it
>   really looked inside the code, but used that only to provide more
>   informative messages and had a fallback case when the code was
>   compiled, so it would still work OK.

Some of the above should be in NEWS, I think, in the "incompatible
Lisp changes" section.

> > This new #f syntax is not documented anywhere, AFAICT.  If this is the
> > new printed representation (and maybe also read syntax?) of functions,
> 
> It's only a cl-print representation, it's not `read`able.
> The `read`able representation uses the #[...] syntax also used for
> bytecode functions.
> 
> > it should be documented, like we do with other printed representations.
> 
> Where would that be?  I don't see where we document the
> #f(compiled-function ...) used for byte-code, which was my inspiration
> for the #f(lambda ...).

We have the "Printed Representation" node, and then the representation
of each type is documented where the type is documented, in
subsections of "Programming Types".  So somewhere there, I think,
probably in "Function Type"?  And you probbaly want to review
"Byte-Code Type" as well.

> >> @@ -5571,7 +5575,7 @@ display-call-tree
> >>  		     " <compiled macro>"
> >>  		   " <macro>"))
> >>  		((eq 'lambda (car f))
> >> -		 "<function>")
> >> +		 "<function-like list>")
> >                   ^^^^^^^^^^^^^^^^^^^^
> > Should this be documented somewhere?
> 
> This should be an extremely rare occurrence, and `display-call-tree`
> itself is not documented, so I doubt it's worth the trouble.
> If you prefer I can keep it as `<function>`.

I thing <function> is preferable, since <function-like list> is
somewhat mysterious, and what you say above means explaining the fine
differences will be largely a wasted effort.

> >> +DEFUN ("closurep", Fclosurep, Sclosurep,
> >> +       1, 1, 0,
> >> +       doc: /* Return t if OBJECT is a function object.  */)
> >
> > If the doc string is correct, then why is the function called
> > 'closurep'?  It's against mnemonic memory.
> 
> The term "closure" is kind of funny, indeed: often it's used to say
> "this is a piece of code packaged with its environment", but in most
> cases where it's used all functions are like that, so the precise
> meaning of "closure" can be argued to be exactly the same as "function",
> just with a different connotation.  Sometimes the connotation is to
> insist on the fact that it captured some variables, but object it's used
> to distinguish between the abstract notion of functions and their
> runtime representation, where "closure" means "an object which
> represents a function".
> 
> IOW, the docstring above is meant to say "a function *object*" rather
> than "a *function* object".  Compare with `functionp`:
> 
>     Return t if OBJECT is a function.
> 
> Maybe I should say something like:
> 
>     Return t if OBJECT is a closure, i.e. a function object.
> 
> ?

Yes, I think that'd be better.

> >> +DEFUN ("interpreted-function-p", Finterpreted_function_p,
> >> +       Sinterpreted_function_p, 1, 1, 0,
> >> +       doc: /* Return t if OBJECT is an interpreted function value.  */)
> >                                                        ^^^^^^^^^^^^^^
> > "function value"? what's that?
> 
> In general, `eval` takes an source expression and returns a value.
> 
>     (lambda (x) (+ x y))
> 
> is a list of length 3 which represents a function as a source expression
> and `eval` turns it into a value (presumably a closure which will
> remember the current binding of `y`).
> 
>     (closure .. (x) (+ x y))
> 
> used to be the representation used for interpreted function *values*.

The problem is that "function value" can be interpreted as "value
returned by a function".  So I suggest

  Return t if OBJECT is a value that represents an interpreted function.

> >> +      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);
> >
> > I'm probably missing something, but if the doc string says "make an
> > _interpreted_ closure", why does the implementation call
> > make-byte-code?  Isn't byte-code a kind-of antithesis of
> > "interpreted"?
> 
> Because `make-byte-code` is the function that we already have which
> creates PVEC_CLOSURE (previous called PVEC_COMPILED) objects.  It used
> to be used exclusively to create byte-code functions but now that same
> representation is used for interpreted function.  I could rename it but
> we already have `make-closure` for something slightly different, and
> I don't see much benefit to the rename.
> I'll add a comment explaining why we use `Fmake_byte_code`,

I think this also calls for augmenting the documentation of
make-byte-code to the effect that it could now create closures as
well.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-14 13:49   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-14 14:45     ` Eli Zaretskii
@ 2024-04-14 16:18     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-14 19:11       ` Eli Zaretskii
  1 sibling, 1 reply; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-14 16:18 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 70368

[-- Attachment #1: Type: text/plain, Size: 97 bytes --]

Here's the corresponding additional patch (not yet merged into the main patch).


        Stefan

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: interpreted-function.diff --]
[-- Type: text/x-diff, Size: 13691 bytes --]

diff --git a/doc/lispref/compile.texi b/doc/lispref/compile.texi
index 00602198da5..b5f3b07f083 100644
--- a/doc/lispref/compile.texi
+++ b/doc/lispref/compile.texi
@@ -37,7 +37,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.
 @end menu
 
@@ -120,7 +120,7 @@ Compilation Functions
 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 @@ Compiler Errors
 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 @@ Byte-Code Objects
 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 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 @@ Byte-Code Objects
 @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 @@ Byte-Code Objects
 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-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-nil should be
+a string, and the interactive form @var{iform} if non-nil should be of
+the form @code{(interactive @var{arg-descriptor})} (@pxref{Using
+Interactive}).
+@end defun
+
 @node Disassembly
 @section Disassembled Byte-Code
 @cindex disassembled byte-code
@@ -595,7 +618,7 @@ Disassembly
 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/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 @@ Top
 * 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 @@ Top
 * 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 0062de91354..c57de08460f 100644
--- a/doc/lispref/functions.texi
+++ b/doc/lispref/functions.texi
@@ -130,7 +130,7 @@ What Is a Function
 
 @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 @@ What Is a Function
 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
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 @@ 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.
 * 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 @@ Primitive Function Type
 @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 @@ Type Predicates
 @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 @@ Type Predicates
 @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 @@ Type Predicates
 @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 c9e47624878..4c5525f10c5 100644
--- a/doc/lispref/sequences.texi
+++ b/doc/lispref/sequences.texi
@@ -1583,7 +1583,7 @@ Vector Functions
 
 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/text.texi b/doc/lispref/text.texi
index 0cd4e2c614e..07fb730f0f1 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -6386,8 +6386,12 @@ Change Hooks
 instead.
 @end defvar
 
+@menu
+* Tracking changes::       Keeping track of buffer modifications.
+@end menu
+
 @node Tracking changes
-@subsection Tracking changes
+@subsection Keeping track of buffer modifications
 @cindex track-changes
 @cindex change tracker
 
diff --git a/src/.gdbinit b/src/.gdbinit
index 6c4dda67f06..7645d466a5e 100644
--- a/src/.gdbinit
+++ b/src/.gdbinit
@@ -822,15 +822,22 @@ Print $ as a frame pointer.
 This command assumes $ is an Emacs Lisp frame value.
 end
 
-define xcompiled
+define xclosure
   xgetptr $
   print (struct Lisp_Vector *) $ptr
   output ($->contents[0])@($->header.size & 0xff)
   echo \n
 end
+document xclosure
+Print $ as a function pointer.
+This command assumes that $ is an Emacs Lisp byte-code or interpreted function value.
+end
+
+define xcompiled
+  xclosure
+end
 document xcompiled
-Print $ as a compiled function pointer.
-This command assumes that $ is an Emacs Lisp compiled value.
+Obsolete alias for "xclosure".
 end
 
 define xwindow
@@ -1038,8 +1045,8 @@ define xpr
       if $vec == PVEC_FRAME
 	xframe
       end
-      if $vec == PVEC_COMPILED
-	xcompiled
+      if $vec == PVEC_CLOSURE
+	xclosure
       end
       if $vec == PVEC_WINDOW
 	xwindow
diff --git a/src/eval.c b/src/eval.c
index 2dfa869b03b..aa8ac4f10af 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -521,6 +521,9 @@ DEFUN ("make-interpreted-closure", Fmake_interpreted_closure,
    Lisp_Object docstring, Lisp_Object iform)
 {
   CHECK_CONS (body);          /* Make sure it's not confused with byte-code! */
+  /* Despite its name 'Fmake_byte_code' works to build all kinds of 'closure'
+     objects, including interpreted functions, rather than only byte-code
+     functions.  */
   if (!NILP (iform))
     {
       iform = Fcdr (iform);

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-14 16:18     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-14 19:11       ` Eli Zaretskii
  0 siblings, 0 replies; 24+ messages in thread
From: Eli Zaretskii @ 2024-04-14 19:11 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: 70368@debbugs.gnu.org
> Date: Sun, 14 Apr 2024 12:18:24 -0400
> 
> Here's the corresponding additional patch (not yet merged into the main patch).

Thanks.

> +For interpreted functions, this is nil if the function is using the old
                                      ^^^
@code{nil}

> +@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-nil list of Lisp forms.  @var{env} is the
                                  ^^^
@code{nil}

> +lexical environment in the same form as used with @code{eval}
> +(@pxref{Eval}).  The documentation @var{docstring} if non-nil should be
                                                             ^^^
Likewise.

> +a string, and the interactive form @var{iform} if non-nil should be of
                                                         ^^^
And likewise.

> +the form @code{(interactive @var{arg-descriptor})} (@pxref{Using
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This should be in @w{..}, to prevent it from being broken across
lines.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-14 14:45     ` Eli Zaretskii
@ 2024-04-14 23:03       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-15 11:23         ` Eli Zaretskii
  0 siblings, 1 reply; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-14 23:03 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 70368

>> There are two kinds of incompatibilities it introduces, in my experience:
>> 
>> - "soft" incompatibilities for code which tries to display the kind of
>>   the object it receives (such as in a completion table that wants to
>>   add an icon indicating if something is a macro, a compiled function,
>>   etc...).  Such code will still work but may display less informative
>>   info because it may fail to recognize the new objects as being
>>   interpreted functions.
>> - "real" incompatibilities for code which digs inside the
>>   entrails of functions to try and extract specific information.
>>   This may fail when faced with the new interpreted functions
>>   but should be easy to fix.
>>   As long as such code only tries to extract info and does it via
>>   `help-function-arglist`, `documentation`, and `interactive-form`,
>>   there's no problem, but some packages may use code inherited from
>>   a long time ago when `help-function-arglist` didn't exist, or written
>>   by coders who didn't know better.  I know Hyperbole used to do that,
>>   but I believe that's been fixed since.
>> - "hard" incompatibilities for code which really digs inside the
>>   code of functions.  `vc.el` did that a long time ago,
>>   `kmacro.el` did as well until OClosures, and Buttercup (a NonGNU ELPA
>>   package) did until a few months ago.  There are probably one or two
>>   packages out there that will be affected like Buttercup would have
>>   been.  FWIW, the Buttercup case was a mix of "hard" and "soft": it
>>   really looked inside the code, but used that only to provide more
>>   informative messages and had a fallback case when the code was
>>   compiled, so it would still work OK.
>
> Some of the above should be in NEWS, I think, in the "incompatible
> Lisp changes" section.

I don't really know what more I could put in there.  The entry I added
states the actual changes and the above "two" are just
direct consequences.

Obviously I could copy&paste that text literally, but it seems to me
it'd just make etc/NEWS longer for no concrete benefit.  Or maybe I'm
just misunderstanding.  How do you imagine a user or developer is going
to make use of the above info?

>> Where would that be?  I don't see where we document the
>> #f(compiled-function ...) used for byte-code, which was my inspiration
>> for the #f(lambda ...).
>
> We have the "Printed Representation" node, and then the representation
> of each type is documented where the type is documented, in
> subsections of "Programming Types".  So somewhere there, I think,
> probably in "Function Type"?  And you probably want to review
> "Byte-Code Type" as well.

AFAICT we currently don't document the cl-print output in there.
I did (in the mean time) update the "Byte-Code Type" to clarify that the
printed representation is the same #[...] as byte codes, and I also
mention the #f(...) notation.

>> This should be an extremely rare occurrence, and `display-call-tree`
>> itself is not documented, so I doubt it's worth the trouble.
>> If you prefer I can keep it as `<function>`.
> I thing <function> is preferable, since <function-like list> is
> somewhat mysterious, and what you say above means explaining the fine
> differences will be largely a wasted effort.

OK.

>> >> +DEFUN ("closurep", Fclosurep, Sclosurep,
>> >> +       1, 1, 0,
>> >> +       doc: /* Return t if OBJECT is a function object.  */)
>> >
>> > If the doc string is correct, then why is the function called
>> > 'closurep'?  It's against mnemonic memory.
>> 
>> The term "closure" is kind of funny, indeed: often it's used to say
>> "this is a piece of code packaged with its environment", but in most
>> cases where it's used all functions are like that, so the precise
>> meaning of "closure" can be argued to be exactly the same as "function",
>> just with a different connotation.  Sometimes the connotation is to
>> insist on the fact that it captured some variables, but object it's used
>> to distinguish between the abstract notion of functions and their
>> runtime representation, where "closure" means "an object which
>> represents a function".
>> 
>> IOW, the docstring above is meant to say "a function *object*" rather
>> than "a *function* object".  Compare with `functionp`:
>> 
>>     Return t if OBJECT is a function.
>> 
>> Maybe I should say something like:
>> 
>>     Return t if OBJECT is a closure, i.e. a function object.
>> 
>> ?
>
> Yes, I think that'd be better.

In the end, I went with:

       doc: /* Return t if OBJECT is a function of type `closure'.  */)

and improved the docstring of the `closure` type, so we get a more
direct access to a short description of the various fields of byte-code
and interpreted-function objects.

> The problem is that "function value" can be interpreted as "value
> returned by a function".

Ah, right.  Thanks.

>> Because `make-byte-code` is the function that we already have which
>> creates PVEC_CLOSURE (previous called PVEC_COMPILED) objects.  It used
>> to be used exclusively to create byte-code functions but now that same
>> representation is used for interpreted function.  I could rename it but
>> we already have `make-closure` for something slightly different, and
>> I don't see much benefit to the rename.
>> I'll add a comment explaining why we use `Fmake_byte_code`,
>
> I think this also calls for augmenting the documentation of
> make-byte-code to the effect that it could now create closures as
> well.

I'm not very happy documenting it, because I think it's an
implementation accident resulting from the historical evolution of
the code.  IOW something we may want to fix.
People who want to create an interpreted function should use
`make-interpreted-function` rather than `make-byte-code`.


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-14 23:03       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-15 11:23         ` Eli Zaretskii
  2024-04-15 12:22           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 24+ messages in thread
From: Eli Zaretskii @ 2024-04-15 11:23 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: 70368@debbugs.gnu.org
> Date: Sun, 14 Apr 2024 19:03:57 -0400
> 
> >> - "soft" incompatibilities for code which tries to display the kind of
> >>   the object it receives (such as in a completion table that wants to
> >>   add an icon indicating if something is a macro, a compiled function,
> >>   etc...).  Such code will still work but may display less informative
> >>   info because it may fail to recognize the new objects as being
> >>   interpreted functions.
> >> - "real" incompatibilities for code which digs inside the
> >>   entrails of functions to try and extract specific information.
> >>   This may fail when faced with the new interpreted functions
> >>   but should be easy to fix.
> >>   As long as such code only tries to extract info and does it via
> >>   `help-function-arglist`, `documentation`, and `interactive-form`,
> >>   there's no problem, but some packages may use code inherited from
> >>   a long time ago when `help-function-arglist` didn't exist, or written
> >>   by coders who didn't know better.  I know Hyperbole used to do that,
> >>   but I believe that's been fixed since.
> >> - "hard" incompatibilities for code which really digs inside the
> >>   code of functions.  `vc.el` did that a long time ago,
> >>   `kmacro.el` did as well until OClosures, and Buttercup (a NonGNU ELPA
> >>   package) did until a few months ago.  There are probably one or two
> >>   packages out there that will be affected like Buttercup would have
> >>   been.  FWIW, the Buttercup case was a mix of "hard" and "soft": it
> >>   really looked inside the code, but used that only to provide more
> >>   informative messages and had a fallback case when the code was
> >>   compiled, so it would still work OK.
> >
> > Some of the above should be in NEWS, I think, in the "incompatible
> > Lisp changes" section.
> 
> I don't really know what more I could put in there.  The entry I added
> states the actual changes and the above "two" are just
> direct consequences.

I think the only problems we need to mention are those with Lisp
programs that "dig" inside the code of functions.  I think it should
be enough to give one or two concrete examples (what internals the
offending code depended on), and tell that these techniques will no
longer work.  You mention above examples of packages that used to do
it, but that's not useful; providing specific examples of what those
packages did will explain the problems much better.

> How do you imagine a user or developer is going to make use of the
> above info?

They will realize which techniques are no longer supposed to work, and
could look into their code to try find such techniques.

> > I think this also calls for augmenting the documentation of
> > make-byte-code to the effect that it could now create closures as
> > well.
> 
> I'm not very happy documenting it, because I think it's an
> implementation accident resulting from the historical evolution of
> the code.  IOW something we may want to fix.

Then maybe a defalias with a suitable name could be the first step in
that direction?

I suggested documenting it because the name of the function no longer
describes accurately what it does, and people might be surprised to
see that it is used to create something other than byte-code.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-15 11:23         ` Eli Zaretskii
@ 2024-04-15 12:22           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-15 13:10             ` Eli Zaretskii
  0 siblings, 1 reply; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-15 12:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 70368

> I think the only problems we need to mention are those with Lisp
> programs that "dig" inside the code of functions.  I think it should
> be enough to give one or two concrete examples (what internals the
> offending code depended on), and tell that these techniques will no
> longer work.  You mention above examples of packages that used to do
> it, but that's not useful; providing specific examples of what those
> packages did will explain the problems much better.

There's nothing very special about it: `car/cdr` on an interpreted
function will fail because it's not represented as a list any more.
You can still extract the various subparts with `aref`, tho.

>> How do you imagine a user or developer is going to make use of the
>> above info?
> They will realize which techniques are no longer supposed to work, and
> could look into their code to try find such techniques.

The "techniques" that won't work any more are any and all accesses to
a function via `car/cdr` (and derivatives, like `assq`), but that should
be immediately obvious from:

    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).

>> > I think this also calls for augmenting the documentation of
>> > make-byte-code to the effect that it could now create closures as
>> > well.
>> 
>> I'm not very happy documenting it, because I think it's an
>> implementation accident resulting from the historical evolution of
>> the code.  IOW something we may want to fix.
>
> Then maybe a defalias with a suitable name could be the first step in
> that direction?
>
> I suggested documenting it because the name of the function no longer
> describes accurately what it does, and people might be surprised to
> see that it is used to create something other than byte-code.

Hmm... I'll see what I can come up with,


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-15 12:22           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-15 13:10             ` Eli Zaretskii
  2024-04-18 16:36               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 24+ messages in thread
From: Eli Zaretskii @ 2024-04-15 13:10 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: 70368@debbugs.gnu.org
> Date: Mon, 15 Apr 2024 08:22:10 -0400
> 
> > I think the only problems we need to mention are those with Lisp
> > programs that "dig" inside the code of functions.  I think it should
> > be enough to give one or two concrete examples (what internals the
> > offending code depended on), and tell that these techniques will no
> > longer work.  You mention above examples of packages that used to do
> > it, but that's not useful; providing specific examples of what those
> > packages did will explain the problems much better.
> 
> There's nothing very special about it: `car/cdr` on an interpreted
> function will fail because it's not represented as a list any more.
> You can still extract the various subparts with `aref`, tho.
> 
> >> How do you imagine a user or developer is going to make use of the
> >> above info?
> > They will realize which techniques are no longer supposed to work, and
> > could look into their code to try find such techniques.
> 
> The "techniques" that won't work any more are any and all accesses to
> a function via `car/cdr` (and derivatives, like `assq`), but that should
> be immediately obvious from:
> 
>     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).

So how about adding after the above just this single sentence:

  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 (but 'aref' will still work to extract its
  various subparts).

> > Then maybe a defalias with a suitable name could be the first step in
> > that direction?
> >
> > I suggested documenting it because the name of the function no longer
> > describes accurately what it does, and people might be surprised to
> > see that it is used to create something other than byte-code.
> 
> Hmm... I'll see what I can come up with,

Thanks.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-15 13:10             ` Eli Zaretskii
@ 2024-04-18 16:36               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-18 16:49                 ` Eli Zaretskii
  0 siblings, 1 reply; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-18 16:36 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 70368

[-- Attachment #1: Type: text/plain, Size: 178 bytes --]

OK, I've updated my patch according to your suggestions, see the result
below (and in the `scratch/interpreted-function` branch).
Any further comment/objection?


        Stefan

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-COMPILED-Rename-to-CLOSURE.patch --]
[-- Type: text/x-diff, Size: 26562 bytes --]

From 7842af6095db4384898725fb4a14ebaa11379a34 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier@iro.umontreal.ca>
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/.gdbinit (xclosure): Rename from `xcompiled`.
(xcompiled): New obsolete alias.
(xpr): Adjust accordingly.  Also adjust to new PVEC_CLOSURE tag name.
---
 src/.gdbinit   | 17 ++++++++++++-----
 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 +++---
 12 files changed, 112 insertions(+), 105 deletions(-)

diff --git a/src/.gdbinit b/src/.gdbinit
index 6c4dda67f06..7645d466a5e 100644
--- a/src/.gdbinit
+++ b/src/.gdbinit
@@ -822,15 +822,22 @@ Print $ as a frame pointer.
 This command assumes $ is an Emacs Lisp frame value.
 end
 
-define xcompiled
+define xclosure
   xgetptr $
   print (struct Lisp_Vector *) $ptr
   output ($->contents[0])@($->header.size & 0xff)
   echo \n
 end
+document xclosure
+Print $ as a function pointer.
+This command assumes that $ is an Emacs Lisp byte-code or interpreted function value.
+end
+
+define xcompiled
+  xclosure
+end
 document xcompiled
-Print $ as a compiled function pointer.
-This command assumes that $ is an Emacs Lisp compiled value.
+Obsolete alias for "xclosure".
 end
 
 define xwindow
@@ -1038,8 +1045,8 @@ define xpr
       if $vec == PVEC_FRAME
 	xframe
       end
-      if $vec == PVEC_COMPILED
-	xcompiled
+      if $vec == PVEC_CLOSURE
+	xclosure
       end
       if $vec == PVEC_WINDOW
 	xwindow
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 de25069d94a..03443ed54ab 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;
@@ -792,14 +792,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;
@@ -897,8 +897,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)
@@ -974,8 +974,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 c5b8a375af4..a7d860114cf 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2151,15 +2151,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));
         }
@@ -2567,7 +2567,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);
@@ -2945,7 +2945,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))
     {
@@ -2967,7 +2967,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);
@@ -3181,9 +3181,9 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, Lisp_Object *arg_vector)
       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.  */
@@ -3315,7 +3315,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))
@@ -3363,9 +3363,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


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Use-a-dedicated-type-to-represent-interpreted-functi.patch --]
[-- Type: text/x-diff, Size: 66949 bytes --]

From bbe7837f7da0ad0167d2d1dcd032c90dcfe11bf4 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier@iro.umontreal.ca>
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) <interpreted-function>:
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.
---
 doc/lispref/compile.texi                      |  61 +++++---
 doc/lispref/control.texi                      |   2 +-
 doc/lispref/elisp.texi                        |   4 +-
 doc/lispref/functions.texi                    |  36 +++--
 doc/lispref/lists.texi                        |   4 +-
 doc/lispref/objects.texi                      |  38 +++--
 doc/lispref/sequences.texi                    |   2 +-
 doc/lispref/variables.texi                    |   2 +-
 etc/NEWS                                      |  17 ++
 lisp/emacs-lisp/byte-opt.el                   |   3 +-
 lisp/emacs-lisp/bytecomp.el                   |  18 ++-
 lisp/emacs-lisp/cconv.el                      |  38 +++--
 lisp/emacs-lisp/cl-preloaded.el               |  15 +-
 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/callint.c                                 |   6 +-
 src/data.c                                    |  46 ++++--
 src/eval.c                                    | 147 +++++++++++-------
 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 +-
 31 files changed, 428 insertions(+), 275 deletions(-)

diff --git a/doc/lispref/compile.texi b/doc/lispref/compile.texi
index 00602198da5..3fbf39b349d 100644
--- a/doc/lispref/compile.texi
+++ b/doc/lispref/compile.texi
@@ -37,7 +37,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.
 @end menu
 
@@ -120,7 +120,7 @@ Compilation Functions
 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 @@ Compiler Errors
 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 @@ Byte-Code Objects
 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 @@ Byte-Code Objects
 @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 @@ Byte-Code Objects
 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 @@ Disassembly
 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 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/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 @@ Top
 * 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 @@ Top
 * 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 @@ What Is a Function
 
 @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 @@ What Is a Function
 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 @@ 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 +1154,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 +1174,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 +1505,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 +1618,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/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 @@ 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.
 * 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 @@ Primitive Function Type
 @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 @@ Type Predicates
 @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 @@ Type Predicates
 @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 @@ Type Predicates
 @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 c9e47624878..4c5525f10c5 100644
--- a/doc/lispref/sequences.texi
+++ b/doc/lispref/sequences.texi
@@ -1583,7 +1583,7 @@ Vector Functions
 
 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 @@ 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 78a1307b6a4..eeca290d2e8 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1740,6 +1740,23 @@ documentation and examples.
 \f
 * 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' used 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
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..59aa9098768 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))
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..fa745396b02 100644
--- a/lisp/emacs-lisp/cl-preloaded.el
+++ b/lisp/emacs-lisp/cl-preloaded.el
@@ -444,13 +444,24 @@ 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.
+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 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/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..ea611ad1abf 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 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 @@ 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.  */
-	    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 @@ 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 +4237,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 +4303,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 a7d860114cf..6812cbf4835 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! */
+  CHECK_LIST (args);
+  CHECK_LIST (iform);
+  Lisp_Object slots[] = { args,  body, env, Qnil, docstring,
+			  NILP (Fcdr (iform))
+			  ? Fcar (iform)
+			  : CALLN (Fvector, XCAR (iform), XCDR (iform)) };
+  /* 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 +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 <form>) 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 <form>) 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 +2241,12 @@ 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;
-	  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 +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);
@@ -2950,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;
@@ -2980,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))
 	{
@@ -3165,16 +3208,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 +3223,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 +3316,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 +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))
 	{
@@ -3352,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);
@@ -4265,7 +4302,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");
@@ -4423,6 +4459,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


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-18 16:36               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-18 16:49                 ` Eli Zaretskii
  2024-04-28 16:05                   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 24+ messages in thread
From: Eli Zaretskii @ 2024-04-18 16:49 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: 70368@debbugs.gnu.org
> Date: Thu, 18 Apr 2024 12:36:36 -0400
> 
> OK, I've updated my patch according to your suggestions, see the result
> below (and in the `scratch/interpreted-function` branch).
> Any further comment/objection?

Just a few minor nits, really.

> >From 7842af6095db4384898725fb4a14ebaa11379a34 Mon Sep 17 00:00:00 2001
> From: Stefan Monnier <monnier@iro.umontreal.ca>
> 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/.gdbinit (xclosure): Rename from `xcompiled`.
> (xcompiled): New obsolete alias.
> (xpr): Adjust accordingly.  Also adjust to new PVEC_CLOSURE tag name.

You are still quoting `like this`.

> +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' used instead to extract its
                                              ^^^^
That "used" should be removed.

> +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;
> +}

This new function should be in NEWS.

> +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)

Likewise.

> +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)

Likewise.

Thanks.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-18 16:49                 ` Eli Zaretskii
@ 2024-04-28 16:05                   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29  9:05                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29 11:38                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-28 16:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 70368-done

> Just a few minor nits, really.

Thanks, and pushed.  Closing,


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-28 16:05                   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-29  9:05                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29 17:15                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29 11:38                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-29  9:05 UTC (permalink / raw)
  To: 70368; +Cc: monnier

Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of
text editors" <bug-gnu-emacs@gnu.org> writes:

> Thanks, and pushed.  Closing,

Thanks - but seems you broke some use cases of the old defadvice - I am
greeted with this *Compile Log* when starting Emacs:

| Error (bytecomp): Wrong type argument: arrayp, (lambda
| (ad--addoit-function &optional nopush edit) (let (ad-return-value) (setq
| ad-return-value (with-no-warnings (funcall ad--addoit-function nopush
| edit))) (setq ring-bell-function isearchp-orig-ring-bell-fn)
| ad-return-value))
|
| Error (bytecomp): Wrong type argument: arrayp, (lambda
| (ad--addoit-function &optional nopush edit) (let (ad-return-value) (setq
| ad-return-value (with-no-warnings (funcall ad--addoit-function nopush
| edit))) (if isearchp-auto-keep-filter-predicate-flag (setq
| isearchp-kept-filter-predicate isearch-filter-predicate) (setq
| isearch-filter-predicate isearchp-kept-filter-predicate)) (setq
| ring-bell-function isearchp-orig-ring-bell-fn) ad-return-value))
|
| Error (bytecomp): Wrong type argument: arrayp, (lambda
| (ad--addoit-function symbol type library) (let (ad-return-value) (prog1
| (setq ad-return-value (with-no-warnings (funcall ad--addoit-function
| symbol type library))) (when (symbolp symbol) (unless (cdr
| ad-return-value) (with-current-buffer (find-file-noselect library) (let
| ((sn (symbol-name symbol))) (when (and (null type) (string-match
| "\\`\\(hydra-[a-z-A-Z0-9]+\\)/\\(.*\\)\\'" sn) (re-search-forward
| (concat "(defhydra " (match-string 1 sn)) nil t)) (goto-char
| (match-beginning 0))) (cons (current-buffer) (point)))))))
| ad-return-value))
|
| Error (bytecomp): Wrong type argument: arrayp, (lambda
| (ad--addoit-function line &optional buffer relative) (let
| (ad-return-value) (let nil (setq ad-return-value (with-no-warnings
| (funcall ad--addoit-function line buffer relative))) (if (and
| folding-mode (or (folding-point-folded-p (point)) (<= (point)
| (point-min-marker)) (>= (point) (point-max-marker)))) (let ((line line))
| (if folding-shift-in-on-goto (progn (folding-show-all) (goto-char 1)
| (and (< 1 line) (not (folding-use-overlays-p)) (re-search-forward "[
| .]" nil 0 (1- line))) (let ((goal (point))) (while (prog2
| (beginning-of-line) (if folding-shift-in-on-goto (progn
| (folding-show-current-entry t t) (folding-point-folded-p goal))
| (folding-shift-in t)) (goto-char goal))) (folding-narrow-to-region (and
| folding-narrow-by-default (point-min)) (point-max) t))) (if (or
| folding-stack (folding-point-folded-p (point)))
| (folding-open-buffer)))))) ad-return-value))

These pop up even when just loading the .el sources of libraries,
e.g. folding.el or isearch+.  Could you please have a look?


Thx,

Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-28 16:05                   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29  9:05                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-29 11:38                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29 17:18                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-29 11:38 UTC (permalink / raw)
  To: 70368; +Cc: monnier

Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of
text editors" <bug-gnu-emacs@gnu.org> writes:

> Thanks, and pushed.  Closing,

Here is something else: `edebug-pop-to-backtrace' now emits lots of
warnings like this:

| Warning (emacs): Edebug--strip-instrumentation expected an interpreted function:
| apply


Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-29  9:05                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-29 17:15                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 0 replies; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-29 17:15 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 70368

> Thanks - but seems you broke some use cases of the old defadvice - I am
> greeted with this *Compile Log* when starting Emacs:
>
> | Error (bytecomp): Wrong type argument: arrayp, (lambda
> | (ad--addoit-function &optional nopush edit) (let (ad-return-value) (setq
> | ad-return-value (with-no-warnings (funcall ad--addoit-function nopush
> | edit))) (setq ring-bell-function isearchp-orig-ring-bell-fn)
> | ad-return-value))

I just pushed a patch which should fix this, thanks.


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-29 11:38                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-29 17:18                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29 17:30                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 12:49                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-29 17:18 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 70368

> Here is something else: `edebug-pop-to-backtrace' now emits lots of
> warnings like this:
> | Warning (emacs): Edebug--strip-instrumentation expected an interpreted function:
> | apply

Could you post an actual recipe?

The patch changed `edebug.el` only in the following way:

            (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))

so AFAICT the old code would have burped on `apply` just as well.

And I don't get such a warning on my attempts to use `edebug-pop-to-backtrace`.


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-29 17:18                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-29 17:30                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 12:49                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 0 replies; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-29 17:30 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> The patch changed `edebug.el` only in the following way:
>
>             (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))
>
> so AFAICT the old code would have burped on `apply` just as well.
>
> And I don't get such a warning on my attempts to use
> `edebug-pop-to-backtrace`.

Hmm - I don't get them with emacs -Q either.  Will have to have a second
look what makes the difference.

Thanks so far,

Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-29 17:18                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-29 17:30                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-30 12:49                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 13:51                           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-30 12:49 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> Could you post an actual recipe?
>
> so AFAICT the old code would have burped on `apply` just as well.

It's indeed a special case.  I'm doing this:

#+begin_src emacs-lisp
(advice-add 'edebug-enter :before
  (lambda (&rest _) "Turn off `view-mode' which would shadow edebug bindings."
    (when (fboundp 'view-mode)
      (view-mode -1)))
  '((name . turn-off-view-mode)))
#+end_src

Then hitting d in any Edebug recursive edit will pop up the mentioned
warning.

Maybe you know how to silently skip the frame in this case, too?
Else I will find a way to silence the warning for myself in some
unorthodox way.

Thx,

Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-30 12:49                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-30 13:51                           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 15:02                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 15:19                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 2 replies; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-30 13:51 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 70368

Michael Heerdegen [2024-04-30 14:49:58] wrote:
> Stefan Monnier <monnier@iro.umontreal.ca> writes:
>> Could you post an actual recipe?
>> so AFAICT the old code would have burped on `apply` just as well.
>
> It's indeed a special case.  I'm doing this:
>
> #+begin_src emacs-lisp
> (advice-add 'edebug-enter :before
>   (lambda (&rest _) "Turn off `view-mode' which would shadow edebug bindings."
>     (when (fboundp 'view-mode)
>       (view-mode -1)))
>   '((name . turn-off-view-mode)))
> #+end_src
>
> Then hitting d in any Edebug recursive edit will pop up the
> mentioned warning.

And you're sure this did not occur before commit f2bccae22bd4?
In my test, the same warning appears in Emacs compiled just before
commit f2bccae22bd4.

> Maybe you know how to silently skip the frame in this case, too?

You can just silence the warning.  All it means is that your backtrace
will not be as clean as it "should".

The problem is raised inside the code that tries to display a backtrace
that pretends the code is not annotated.
More specifically, the annotated contains things like:

    (edebug-enter <BLABLA> (lambda () <REALCODE>))

so `edebug--strip-instrumentation` walks (down) the frames of the
backtrace, and when it sees `edebug-enter` it hides that frame but it
should also hide the frame for the (lambda () <REALCODE>) so as to
pretend that we jumped right into <REALCODE>.
Normally, those two frames follow each other right away, but your advice
makes it so that after `edebug-enter` you have a bunch of other frames
calling your advice and then calling the original code which finally
calls the (lambda () <REALCODE>) we want to strip.
Ideally, we'd want to strip all of that away, but it's somewhere between
difficult and impossible to do so reliably.  The code errs on the side
of leaving the backtrace less clean than it should rather than take the
risk of stripping away "real" code.

> Else I will find a way to silence the warning for myself in some
> unorthodox way.

I can see a few other options, but I suggest you open another bug report
for that (with a title like "view-mode should not shadow edebug bindings").


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-30 13:51                           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-30 15:02                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 15:19                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 0 replies; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-30 15:02 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> > Then hitting d in any Edebug recursive edit will pop up the
> > mentioned warning.
>
> And you're sure this did not occur before commit f2bccae22bd4?

No.

> In my test, the same warning appears in Emacs compiled just before
> commit f2bccae22bd4.

I guess you're right.  The warning had been added 03/24 this year, and I
guess I just did not use this stripping code since then or simply
ignored the warnings.  Because every minute I spend with recursive
debugging I age one additional month.

Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-30 13:51                           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 15:02                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-30 15:19                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-30 18:34                               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-30 15:19 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> I can see a few other options, but I suggest you open another bug report
> for that (with a title like "view-mode should not shadow edebug bindings").

Good idea.  Ok wait...hmm... this already had been reported in #11018
ages ago - and also has been fixed.  Seems my advice is only a leftover.

Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-30 15:19                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-30 18:34                               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-05-01 12:35                                 ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 24+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-30 18:34 UTC (permalink / raw)
  To: Michael Heerdegen; +Cc: 70368

>> I can see a few other options, but I suggest you open another bug report
>> for that (with a title like "view-mode should not shadow edebug bindings").
> Good idea.  Ok wait...hmm... this already had been reported in #11018
> ages ago - and also has been fixed.  Seems my advice is only a leftover.

🙂


        Stefan






^ permalink raw reply	[flat|nested] 24+ messages in thread

* bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values
  2024-04-30 18:34                               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-05-01 12:35                                 ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 0 replies; 24+ messages in thread
From: Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-05-01 12:35 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 70368

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> 🙂

Yep.  Emacs works very well with your change.  Would have expected more
trouble...


Thanks,

Michael.





^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2024-05-01 12:35 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-04-13 19:56 bug#70368: [PATCH] Use a dedicated type to represent interpreted-function values Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-14  5:32 ` Eli Zaretskii
2024-04-14 13:49   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-14 14:45     ` Eli Zaretskii
2024-04-14 23:03       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-15 11:23         ` Eli Zaretskii
2024-04-15 12:22           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-15 13:10             ` Eli Zaretskii
2024-04-18 16:36               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-18 16:49                 ` Eli Zaretskii
2024-04-28 16:05                   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-29  9:05                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-29 17:15                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-29 11:38                     ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-29 17:18                       ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-29 17:30                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-30 12:49                         ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-30 13:51                           ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-30 15:02                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-30 15:19                             ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-30 18:34                               ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-05-01 12:35                                 ` Michael Heerdegen via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-14 16:18     ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-14 19:11       ` Eli Zaretskii

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.