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

* 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.
This commit is contained in:
Stefan Monnier 2024-03-12 09:26:24 -04:00
parent 1a8b34a503
commit 706403f2aa
8 changed files with 108 additions and 17 deletions

View 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

View file

@ -1647,6 +1647,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

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

View file

@ -339,8 +339,6 @@
',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))

View 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

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

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

View 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