Subject: bug#69739: 30.0.50; `type-of` is not precise enough

From: Stefan Monnier <[email-masked]>
Date: Thu, 14 Mar 2024 12:56:03 -0400

OK, here's a new patch.
I ended up with `cl-type-of` as the name of the new function (after
all, it tries to better match the expected semantics of Common Lisp's
`type-of`).

Comments?  Objections?

        Stefan
>From a1d656bf089af3e55fd08ef5ed3e33f207360593 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <[email-masked]>
Date: Tue, 12 Mar 2024 09:26:24 -0400
Subject: [PATCH 1/2] (cl-type-of): New function to return more precise types
 (bug#69739)

* src/data.c (Fcl_type_of): New function, extracted from `Ftype_of`.
Make it return more precise types for symbols, integers, and subrs.
(Ftype_of): Use it.
(syms_of_data): Define the corresponding new symbols and defsubr
the new function.

* src/comp.c (emit_limple_insn): Use `Fcl_type_of`.

* lisp/emacs-lisp/cl-preloaded.el (subr): Demote it to `atom`.
(subr-native-elisp, subr-primitive): Add `compiled-function` as
parent instead.
(special-form): New type.

* lisp/obsolete/eieio-core.el (cl--generic-struct-tag):
* lisp/emacs-lisp/cl-generic.el (cl--generic-typeof-generalizer):
Use `cl-type-of`.
cl--generic--unreachable-types): Update accordingly.
---
 etc/NEWS                        |  5 +++++
 lisp/emacs-lisp/cl-generic.el   |  6 ++---
 lisp/emacs-lisp/cl-preloaded.el | 12 +++++-----
 lisp/emacs-lisp/eieio-core.el   |  2 +-
 src/comp.c                      |  2 +-
 src/data.c                      | 40 ++++++++++++++++++++++++++++-----
 6 files changed, 50 insertions(+), 17 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 2985169ea91..17f6916cebf 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1628,6 +1628,11 @@ values.
 
 * Lisp Changes in Emacs 30.1
 
+** New function 'cl-type-of'.
+This function is like 'type-of' except that it sometimes returns
+a more precise type.  For example, for nil and t it returns 'null'
+and 'boolean' respectively, instead of just 'symbol'.
+
 ** Built-in types have now corresponding classes.
 At the Lisp level, this means that things like (cl-find-class 'integer)
 will now return a class object, and at the UI level it means that
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
index 613ecf82a92..62abe8d1589 100644
--- a/lisp/emacs-lisp/cl-generic.el
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -1334,8 +1334,7 @@ cl-generic-generalizers
 
 (defconst cl--generic--unreachable-types
   ;; FIXME: Try to make that list empty?
-  '(fixnum bignum boolean keyword
-    special-form subr-primitive subr-native-elisp)
+  '(keyword)
   "Built-in classes on which we cannot dispatch for technical reasons.")
 
 (defun cl--generic-type-specializers (tag &rest _)
@@ -1345,8 +1344,7 @@ cl--generic-type-specializers
            (cl--class-allparents class)))))
 
 (cl-generic-define-generalizer cl--generic-typeof-generalizer
-  ;; FIXME: We could also change `type-of' to return `null' for nil.
-  10 (lambda (name &rest _) `(if ,name (type-of ,name) 'null))
+  10 (lambda (name &rest _) `(cl-type-of ,name))
   #'cl--generic-type-specializers)
 
 (cl-defmethod cl-generic-generalizers :extra "typeof" (type)
diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el
index 515aa99549d..3e89afea452 100644
--- a/lisp/emacs-lisp/cl-preloaded.el
+++ b/lisp/emacs-lisp/cl-preloaded.el
@@ -339,8 +339,6 @@ cl--define-built-in-type
                                           ',parents))))))
 
 ;; FIXME: Our type DAG has various quirks:
-;; - `subr' says it's a `compiled-function' but that's not true
-;;   for those subrs that are special forms!
 ;; - Some `keyword's are also `symbol-with-pos' but that's not reflected
 ;;   in the DAG.
 ;; - An OClosure can be an interpreted function or a `byte-code-function',
@@ -428,15 +426,17 @@ compiled-function
   "Abstract type of functions that have been compiled.")
 (cl--define-built-in-type byte-code-function (compiled-function)
   "Type of functions that have been byte-compiled.")
-(cl--define-built-in-type subr (compiled-function)
+(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)
   "Type of functions that have not been compiled.")
-(cl--define-built-in-type subr-native-elisp (subr)
-  "Type of function that have been compiled by the native compiler.")
-(cl--define-built-in-type subr-primitive (subr)
+(cl--define-built-in-type special-form (subr)
+  "Type of the core syntactic elements of the Emacs Lisp language.")
+(cl--define-built-in-type subr-native-elisp (subr compiled-function)
+  "Type of functions that have been compiled by the native compiler.")
+(cl--define-built-in-type subr-primitive (subr compiled-function)
   "Type of functions hand written in C.")
 
 (unless (cl--class-parents (cl--find-class 'cl-structure-object))
diff --git a/lisp/emacs-lisp/eieio-core.el b/lisp/emacs-lisp/eieio-core.el
index a2f7c4172a3..cf8bd749f2a 100644
--- a/lisp/emacs-lisp/eieio-core.el
+++ b/lisp/emacs-lisp/eieio-core.el
@@ -1046,7 +1046,7 @@ 'inconsistent-class-hierarchy
 
 (defun cl--generic-struct-tag (name &rest _)
   ;; Use exactly the same code as for `typeof'.
-  `(if ,name (type-of ,name) 'null))
+  `(cl-type-of ,name))
 
 (cl-generic-define-generalizer eieio--generic-generalizer
   ;; Use the exact same tagcode as for cl-struct, so that methods
diff --git a/src/comp.c b/src/comp.c
index 3f989c722d4..76cf1f3ab6e 100644
--- a/src/comp.c
+++ b/src/comp.c
@@ -2442,7 +2442,7 @@ emit_limple_insn (Lisp_Object insn)
     {
       Lisp_Object arg1 = arg[1];
 
-      if (EQ (Ftype_of (arg1), Qcomp_mvar))
+      if (EQ (Fcl_type_of (arg1), Qcomp_mvar))
 	res = emit_mvar_rval (arg1);
       else if (EQ (FIRST (arg1), Qcall))
 	res = emit_limple_call (XCDR (arg1));
diff --git a/src/data.c b/src/data.c
index 35f4c82c68f..a961dd3108c 100644
--- a/src/data.c
+++ b/src/data.c
@@ -193,16 +193,37 @@ DEFUN ("null", Fnull, Snull, 1, 1, 0,
 DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0,
        doc: /* Return a symbol representing the type of OBJECT.
 The symbol returned names the object's basic type;
-for example, (type-of 1) returns `integer'.  */)
+for example, (type-of 1) returns `integer'.
+Contrary to `cl-type-of' the returned type is not always the most
+precise type possible, because instead this function tries to preserve
+compatibility with the return value of previous Emacs versions.  */)
+  (Lisp_Object object)
+{
+  return SYMBOLP (object)    ? Qsymbol
+         : INTEGERP (object) ? Qinteger
+         : SUBRP (object)    ? Qsubr
+         : Fcl_type_of (object);
+}
+
+DEFUN ("cl-type-of", Fcl_type_of, Scl_type_of, 1, 1, 0,
+       doc: /* Return a symbol representing the type of OBJECT.
+The symbol returned names the most specific possible type of the object.
+for example, (object-type nil) returns `null'.
+The specific type returned may change depending on Emacs versions,
+so we recommend you use `cl-typep', `cl-typecase', or other predicates
+rather than compare the return value of this function against
+a fixed set of types.  */)
   (Lisp_Object object)
 {
   switch (XTYPE (object))
     {
     case_Lisp_Int:
-      return Qinteger;
+      return Qfixnum;
 
     case Lisp_Symbol:
-      return Qsymbol;
+      return NILP (object) ? Qnull
+             : EQ (object, Qt) ? Qboolean
+             : Qsymbol;
 
     case Lisp_String:
       return Qstring;
@@ -215,7 +236,7 @@ DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0,
       switch (PSEUDOVECTOR_TYPE (XVECTOR (object)))
         {
         case PVEC_NORMAL_VECTOR: return Qvector;
-	case PVEC_BIGNUM: return Qinteger;
+	case PVEC_BIGNUM: return Qbignum;
 	case PVEC_MARKER: return Qmarker;
 	case PVEC_SYMBOL_WITH_POS: return Qsymbol_with_pos;
 	case PVEC_OVERLAY: return Qoverlay;
@@ -224,7 +245,10 @@ DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0,
         case PVEC_WINDOW_CONFIGURATION: return Qwindow_configuration;
         case PVEC_PROCESS: return Qprocess;
         case PVEC_WINDOW: return Qwindow;
-        case PVEC_SUBR: return Qsubr;
+        case PVEC_SUBR:
+          return XSUBR (object)->max_args == UNEVALLED ? Qspecial_form
+                 : SUBR_NATIVE_COMPILEDP (object) ? Qsubr_native_elisp
+                 : Qsubr_primitive;
         case PVEC_COMPILED: return Qcompiled_function;
         case PVEC_BUFFER: return Qbuffer;
         case PVEC_CHAR_TABLE: return Qchar_table;
@@ -4202,7 +4226,9 @@ #define PUT_ERROR(sym, tail, msg)			\
 	     "Variable binding depth exceeds max-specpdl-size");
 
   /* Types that type-of returns.  */
+  DEFSYM (Qboolean, "boolean");
   DEFSYM (Qinteger, "integer");
+  DEFSYM (Qbignum, "bignum");
   DEFSYM (Qsymbol, "symbol");
   DEFSYM (Qstring, "string");
   DEFSYM (Qcons, "cons");
@@ -4218,6 +4244,9 @@ #define PUT_ERROR(sym, tail, msg)			\
   DEFSYM (Qprocess, "process");
   DEFSYM (Qwindow, "window");
   DEFSYM (Qsubr, "subr");
+  DEFSYM (Qspecial_form, "special-form");
+  DEFSYM (Qsubr_primitive, "subr-primitive");
+  DEFSYM (Qsubr_native_elisp, "subr-native-elisp");
   DEFSYM (Qcompiled_function, "compiled-function");
   DEFSYM (Qbuffer, "buffer");
   DEFSYM (Qframe, "frame");
@@ -4255,6 +4284,7 @@ #define PUT_ERROR(sym, tail, msg)			\
   defsubr (&Seq);
   defsubr (&Snull);
   defsubr (&Stype_of);
+  defsubr (&Scl_type_of);
   defsubr (&Slistp);
   defsubr (&Snlistp);
   defsubr (&Sconsp);
-- 
2.43.0

>From 637b22ee275762e3a88342f7c0c1e9a997f2ed97 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <[email-masked]>
Date: Thu, 14 Mar 2024 12:49:08 -0400
Subject: [PATCH 2/2] Followup changes to `cl-type-of`

These changes came up while working on `cl-type-of` but are not
directly related to the new `cl-type-of`.
The BASE_PURESIZE bump was needed at some point on one of my
machine, not sure why.

* src/puresize.h (BASE_PURESIZE): Bump up.
* src/sqlite.c (bind_value): Don't use `Ftype_of`.
* lisp/emacs-lisp/seq.el (seq-remove-at-position): Simplify.
* lisp/emacs-lisp/cl-preloaded.el (finalizer):
New (previously missing) type.
* doc/lispref/objects.texi (Type Predicates): Minor tweaks.
---
 doc/lispref/objects.texi        |  6 +++---
 lisp/emacs-lisp/cl-preloaded.el |  1 +
 lisp/emacs-lisp/seq.el          |  3 +--
 src/lisp.h                      |  6 ++----
 src/puresize.h                  |  2 +-
 src/sqlite.c                    | 17 ++++++-----------
 6 files changed, 14 insertions(+), 21 deletions(-)

diff --git a/doc/lispref/objects.texi b/doc/lispref/objects.texi
index 279f449a994..804f52ba8ac 100644
--- a/doc/lispref/objects.texi
+++ b/doc/lispref/objects.texi
@@ -1485,8 +1485,8 @@ Type Descriptors
 @subsection Type Descriptors
 
   A @dfn{type descriptor} is a @code{record} which holds information
-about a type.  Slot 1 in the record must be a symbol naming the type, and
-@code{type-of} relies on this to return the type of @code{record}
+about a type.  The first slot in the record must be a symbol naming the type,
+and @code{type-of} relies on this to return the type of @code{record}
 objects.  No other type descriptor slot is used by Emacs; they are
 free for use by Lisp extensions.
 
@@ -2175,7 +2175,7 @@ Type Predicates
 function @code{type-of}.  Recall that each object belongs to one and
 only one primitive type; @code{type-of} tells you which one (@pxref{Lisp
 Data Types}).  But @code{type-of} knows nothing about non-primitive
-types.  In most cases, it is more convenient to use type predicates than
+types.  In most cases, it is preferable to use type predicates than
 @code{type-of}.
 
 @defun type-of object
diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el
index 3e89afea452..0e8704a93c1 100644
--- a/lisp/emacs-lisp/cl-preloaded.el
+++ b/lisp/emacs-lisp/cl-preloaded.el
@@ -365,6 +365,7 @@ frame
 (cl--define-built-in-type buffer atom)
 (cl--define-built-in-type window atom)
 (cl--define-built-in-type process atom)
+(cl--define-built-in-type finalizer atom)
 (cl--define-built-in-type window-configuration atom)
 (cl--define-built-in-type overlay atom)
 (cl--define-built-in-type number-or-marker atom
diff --git a/lisp/emacs-lisp/seq.el b/lisp/emacs-lisp/seq.el
index 20077db9e60..a20cff16982 100644
--- a/lisp/emacs-lisp/seq.el
+++ b/lisp/emacs-lisp/seq.el
@@ -362,8 +362,7 @@ seq-remove-at-position
 
 The result is a sequence of the same type as SEQUENCE."
   (seq-concatenate
-   (let ((type (type-of sequence)))
-     (if (eq type 'cons) 'list type))
+   (if (listp sequence) 'list (type-of sequence))
    (seq-subseq sequence 0 n)
    (seq-subseq sequence (1+ n))))
 
diff --git a/src/lisp.h b/src/lisp.h
index f353e4956eb..f86758c88fb 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -569,10 +569,8 @@ #define ENUM_BF(TYPE) enum TYPE
    your object -- this way, the same object could be used to represent
    several disparate C structures.
 
-   In addition, you need to add switch branches in data.c for Ftype_of.
-
-   You also need to add the new type to the constant
-   `cl--typeof-types' in lisp/emacs-lisp/cl-preloaded.el.  */
+   In addition, you need to add switch branches in data.c for Fcl_type_of
+   and `cl--define-builtin-type` in lisp/emacs-lisp/cl-preloaded.el.  */

 /* A Lisp_Object is a tagged pointer or integer.  Ordinarily it is a
diff --git a/src/puresize.h b/src/puresize.h
index ac5d2da30dc..2a716872832 100644
--- a/src/puresize.h
+++ b/src/puresize.h
@@ -47,7 +47,7 @@ #define SITELOAD_PURESIZE_EXTRA 0
 #endif
 
 #ifndef BASE_PURESIZE
-#define BASE_PURESIZE (2750000 + SYSTEM_PURESIZE_EXTRA + SITELOAD_PURESIZE_EXTRA)
+#define BASE_PURESIZE (3000000 + SYSTEM_PURESIZE_EXTRA + SITELOAD_PURESIZE_EXTRA)
 #endif
 
 /* Increase BASE_PURESIZE by a ratio depending on the machine's word size.  */
diff --git a/src/sqlite.c b/src/sqlite.c
index 7a018b28aa4..261080da673 100644
--- a/src/sqlite.c
+++ b/src/sqlite.c
@@ -349,9 +349,7 @@ bind_values (sqlite3 *db, sqlite3_stmt *stmt, Lisp_Object values)
 	  value = XCAR (values);
 	  values = XCDR (values);
 	}
-      Lisp_Object type = Ftype_of (value);
-
-      if (EQ (type, Qstring))
+      if (STRINGP (value))
 	{
 	  Lisp_Object encoded;
 	  bool blob = false;
@@ -385,14 +383,11 @@ bind_values (sqlite3 *db, sqlite3_stmt *stmt, Lisp_Object values)
 				       SSDATA (encoded), SBYTES (encoded),
 				       NULL);
 	}
-      else if (EQ (type, Qinteger))
-	{
-	  if (BIGNUMP (value))
-	    ret = sqlite3_bind_int64 (stmt, i + 1, bignum_to_intmax (value));
-	  else
-	    ret = sqlite3_bind_int64 (stmt, i + 1, XFIXNUM (value));
-	}
-      else if (EQ (type, Qfloat))
+      else if (FIXNUMP (value))
+	ret = sqlite3_bind_int64 (stmt, i + 1, XFIXNUM (value));
+      else if (BIGNUMP (value))
+	ret = sqlite3_bind_int64 (stmt, i + 1, bignum_to_intmax (value));
+      else if (FLOATP (value))
 	ret = sqlite3_bind_double (stmt, i + 1, XFLOAT_DATA (value));
       else if (NILP (value))
 	ret = sqlite3_bind_null (stmt, i + 1);
-- 
2.43.0