]> git.eshelyaron.com Git - emacs.git/commitdiff
(cl-type-of): New function to return more precise types (bug#69739)
authorStefan Monnier <monnier@iro.umontreal.ca>
Tue, 12 Mar 2024 13:26:24 +0000 (09:26 -0400)
committerEshel Yaron <me@eshelyaron.com>
Mon, 18 Mar 2024 15:45:24 +0000 (16:45 +0100)
* 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.

* doc/lispref/objects.texi (Type Predicates): Document it.

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

test/src/data-tests.el (data-tests--cl-type-of): New test.

(cherry picked from commit 706403f2aa3a306369a0150022da0cba1802ca2b)

doc/lispref/objects.texi
etc/NEWS
lisp/emacs-lisp/cl-generic.el
lisp/emacs-lisp/cl-preloaded.el
lisp/emacs-lisp/eieio-core.el
src/comp.c
src/data.c
test/src/data-tests.el

index 279f449a994932cf4a57aac95c264e70a04dac33..1e448b64296fd6a2f0114620ff4f1b2931e659bb 100644 (file)
@@ -2207,6 +2207,27 @@ slot is returned; @ref{Records}.
 @end example
 @end defun
 
+@defun cl-type-of object
+This function returns a symbol naming @emph{the} type of
+@var{object}.  It usually behaves like @code{type-of}, except
+that it guarantees to return the most precise type possible, which also
+implies that the specific type it returns may change depending on the
+Emacs version.  For this reason, as a rule you should never compare its
+return value against some fixed set of types.
+
+@example
+(cl-type-of 1)
+     @result{} fixnum
+@group
+(cl-type-of 'nil)
+     @result{} null
+(cl-type-of (record 'foo))
+     @result{} foo
+@end group
+@end example
+@end defun
+
+
 @node Equality Predicates
 @section Equality Predicates
 @cindex equality
index 505abed80330facf4d1fafd333356575b22814ed..7d90e0aca4edbe9618f1ad4157ecdcdc6f700f34 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1785,6 +1785,11 @@ the region and never want to restrict 'undo' to that region, it is
 preferable to use the existing 'undo-inhibit-region' symbol property
 instead of this variable.
 
+** New 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
index 949bf70cd7a45aac41e6621ac226c16499086da1..ee792e505b2b43c605242a67c1f664c8d2e75984 100644 (file)
@@ -1334,8 +1334,7 @@ These match if the argument is `eql' to VAL."
 
 (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 @@ These match if the argument is `eql' to VAL."
            (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)
index 515aa99549dfd04efb0b721624dbe0deacf05ef2..3e89afea45238e710fe6d79fd254f3d0e066bd68 100644 (file)
                                           ',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 @@ For this build of Emacs it's %dbit."
   "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))
index a2f7c4172a3f96b8c9844340968f9022c30dc9a5..cf8bd749f2a8782f7e329aec1b93a8ede1ac36c2 100644 (file)
@@ -1046,7 +1046,7 @@ method invocation orders of the involved classes."
 
 (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
index 3f989c722d4131d38aa43d567d5c86492bb04b54..76cf1f3ab6ea9cc427f4727c87208f26f7baf0aa 100644 (file)
@@ -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));
index 35f4c82c68fe1f15d27313a84a30ad7356b81249..5d6b6e0ba9d785e5881a2c424ca5bdd65d64378a 100644 (file)
@@ -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 returned symbol names the most specific possible type of the object.
+for example, (cl-type-of 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 @@ for example, (type-of 1) returns `integer'.  */)
       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 @@ for example, (type-of 1) returns `integer'.  */)
         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 @@ syms_of_data (void)
             "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 @@ syms_of_data (void)
   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 @@ syms_of_data (void)
   defsubr (&Seq);
   defsubr (&Snull);
   defsubr (&Stype_of);
+  defsubr (&Scl_type_of);
   defsubr (&Slistp);
   defsubr (&Snlistp);
   defsubr (&Sconsp);
index ad3b20712549bfb2ce707169d0d4e235a7dc7939..9d76c58224d3b443aa691a97d9c8de0ef85a5246 100644 (file)
@@ -838,4 +838,41 @@ comparing the subr with a much slower Lisp implementation."
     (dolist (sym (list nil t 'xyzzy (make-symbol "")))
       (should (eq sym (bare-symbol (position-symbol sym 0)))))))
 
+(require 'cl-extra)                     ;For `cl--class-children'.
+
+(ert-deftest data-tests--cl-type-of ()
+  ;; Make sure that `cl-type-of' returns the most precise type.
+  ;; Note: This doesn't work for list/vector structs since those types
+  ;; are too difficult/unreliable to detect (so `cl-type-of' only says
+  ;; it's a `cons' or a `vector').
+  (dolist (val (list -2 10 (expt 2 128) nil t 'car
+                     (symbol-function 'car)
+                     (symbol-function 'progn)
+                     (position-symbol 'car 7)))
+    (let* ((type (cl-type-of val))
+           (class (cl-find-class type))
+           (alltypes (cl--class-allparents class))
+           ;; FIXME: Our type DAG is affected by `symbols-with-pos-enabled'.
+           ;; (e.g. `symbolp' returns nil on a sympos if that var is nil).
+           (symbols-with-pos-enabled t))
+      (dolist (parent alltypes)
+        (should (cl-typep val parent))
+        (dolist (subtype (cl--class-children (cl-find-class parent)))
+          (unless (memq subtype alltypes)
+            (unless (memq subtype
+                          ;; FIXME: Some types don't have any associated
+                          ;; predicate,
+                          '( font-spec font-entity font-object
+                             finalizer condvar terminal
+                             native-comp-unit interpreted-function
+                             tree-sitter-compiled-query
+                             tree-sitter-node tree-sitter-parser
+                             ;; `functionp' also matches things of type
+                             ;; `symbol' and `cons'.
+                             ;; FIXME: `subr-primitive-p' also matches
+                             ;; special-forms.
+                             function subr-primitive))
+              (should-not (cl-typep val subtype)))))))))
+
+
 ;;; data-tests.el ends here