aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2025-05-05 23:18:56 -0400
committerStefan Monnier2025-05-05 23:18:56 -0400
commitfc4d8ce9514dd45ab34dbef6f023347b42ee9fef (patch)
tree77ebb9ff9b1bdf8b13a93980b54a4a3446f60c54
parent68a50324a70bd794d7f3228290310093f1515f7b (diff)
downloademacs-fc4d8ce9514dd45ab34dbef6f023347b42ee9fef.tar.gz
emacs-fc4d8ce9514dd45ab34dbef6f023347b42ee9fef.zip
cl-types: Integrate into CL-Lib
* lisp/emacs-lisp/cl-extra.el (cl--type-unique, cl-types-of) (cl--type-dispatch-list, cl--type-generalizer): Move to `cl-extra.el`. (cl--type-generalizers): New function extracted from "cl-types-of" method of `cl-generic-generalizers`. * lisp/emacs-lisp/cl-lib.el (cl-generic-generalizers): New method to dispatch on derived types. Use `cl--type-generalizers`. * lisp/emacs-lisp/cl-macs.el (cl-deftype): Move from `cl-types.el` and rename from `cl-deftype2`. (extended-char): Tweak definition to fix bootstrapping issues. * lisp/emacs-lisp/cl-preloaded.el (cl--type-list, cl-type-class) (cl--type-deftype): Move from `cl-types.el`. * lisp/emacs-lisp/oclosure.el (oclosure): Don't abuse `cl-deftype` to register the predicate function. * test/lisp/emacs-lisp/cl-extra-tests.el: Move tests from `cl-type-tests.el`.
-rw-r--r--lisp/emacs-lisp/cl-extra.el121
-rw-r--r--lisp/emacs-lisp/cl-lib.el13
-rw-r--r--lisp/emacs-lisp/cl-macs.el53
-rw-r--r--lisp/emacs-lisp/cl-preloaded.el65
-rw-r--r--lisp/emacs-lisp/cl-types.el299
-rw-r--r--lisp/emacs-lisp/oclosure.el2
-rw-r--r--test/lisp/emacs-lisp/cl-extra-tests.el92
-rw-r--r--test/lisp/emacs-lisp/cl-types-tests.el96
8 files changed, 373 insertions, 368 deletions
diff --git a/lisp/emacs-lisp/cl-extra.el b/lisp/emacs-lisp/cl-extra.el
index 6390d17a5b7..bd7bb96dd6a 100644
--- a/lisp/emacs-lisp/cl-extra.el
+++ b/lisp/emacs-lisp/cl-extra.el
@@ -965,6 +965,127 @@ Outputs to the current buffer."
965 (insert (propertize "\nClass Allocated Slots:\n\n" 'face 'bold)) 965 (insert (propertize "\nClass Allocated Slots:\n\n" 'face 'bold))
966 (mapc #'cl--describe-class-slot cslots)))) 966 (mapc #'cl--describe-class-slot cslots))))
967 967
968;;;; Method dispatch on `cl-deftype' types.
969
970;; Extend `cl-deftype' to define data types which are also valid
971;; argument types for dispatching generic function methods (see also
972;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77725>).
973;;
974;; The main entry points are:
975;;
976;; - `cl-deftype', that defines new data types.
977;;
978;; - `cl-types-of', that returns the types an object belongs to.
979
980;; Ensure each type satisfies `eql'.
981(defvar cl--type-unique (make-hash-table :test 'equal)
982 "Record an unique value of each type.")
983
984;; FIXME: `cl-types-of' CPU cost is proportional to the number of types
985;; defined with `cl-deftype', so the more popular it gets, the slower
986;; it becomes. And of course, the cost of each type check is
987;; unbounded, so a single "expensive" type can slow everything down
988;; further.
989;;
990;; The usual dispatch is
991;;
992;; (lambda (arg &rest args)
993;; (let ((f (gethash (cl-typeof arg) precomputed-methods-table)))
994;; (if f
995;; (apply f arg args)
996;; ;; Slow case when encountering a new type
997;; ...)))
998;;
999;; where often the most expensive part is `&rest' (which has to
1000;; allocate a list for those remaining arguments),
1001;;
1002;; So we're talking about replacing
1003;;
1004;; &rest + cl-type-of + gethash + if + apply
1005;;
1006;; with a function that loops over N types, calling `cl-typep' on each
1007;; one of them (`cl-typep' itself being a recursive function that
1008;; basically interprets the type language). This is going to slow
1009;; down dispatch very significantly for those generic functions that
1010;; have a method that dispatches on a user defined type, compared to
1011;; those that don't.
1012;;
1013;; A possible further improvement:
1014;;
1015;; - based on the PARENTS declaration, create a map from builtin-type
1016;; to the set of cl-types that have that builtin-type among their
1017;; parents. That presumes some PARENTS include some builtin-types,
1018;; obviously otherwise the map will be trivial with all cl-types
1019;; associated with the `t' "dummy parent". [ We could even go crazy
1020;; and try and guess PARENTS when not provided, by analyzing the
1021;; type's definition. ]
1022;;
1023;; - in `cl-types-of' start by calling `cl-type-of', then use the map
1024;; to find which cl-types may need to be checked.
1025;;
1026;;;###autoload
1027(defun cl-types-of (object &optional types)
1028 "Return the types OBJECT belongs to.
1029Return an unique list of types OBJECT belongs to, ordered from the
1030most specific type to the most general.
1031TYPES is an internal argument."
1032 (let* ((found nil))
1033 ;; Build a list of all types OBJECT belongs to.
1034 (dolist (type (or types cl--type-list))
1035 (and
1036 ;; If OBJECT is of type, add type to the matching list.
1037 (if types
1038 ;; For method dispatch, we don't need to filter out errors, since
1039 ;; we can presume that method dispatch is used only on
1040 ;; sanely-defined types.
1041 (cl-typep object type)
1042 (condition-case-unless-debug e
1043 (cl-typep object type)
1044 (error (setq cl--type-list (delq type cl--type-list))
1045 (warn "cl-types-of %S: %s"
1046 type (error-message-string e)))))
1047 (push type found)))
1048 (push (cl-type-of object) found)
1049 ;; Return an unique value of the list of types OBJECT belongs to,
1050 ;; which is also the list of specifiers for OBJECT.
1051 (with-memoization (gethash found cl--type-unique)
1052 ;; Compute an ordered list of types from the DAG.
1053 (merge-ordered-lists
1054 (mapcar (lambda (type) (cl--class-allparents (cl--find-class type)))
1055 (nreverse found))))))
1056
1057(defvar cl--type-dispatch-list nil
1058 "List of types that need to be checked during dispatch.")
1059
1060(cl-generic-define-generalizer cl--type-generalizer
1061 ;; FIXME: This priority can't be always right. :-(
1062 ;; E.g. a method dispatching on a type like (or number function),
1063 ;; should take precedence over a method on `t' but not over a method
1064 ;; on `number'. Similarly a method dispatching on a type like
1065 ;; (satisfies (lambda (x) (equal x '(A . B)))) should take precedence
1066 ;; over a method on (head 'A).
1067 ;; Fixing this 100% is impossible so this generalizer is condemned to
1068 ;; suffer from "undefined method ordering" problems, unless/until we
1069 ;; restrict it somehow to a subset that we can handle reliably.
1070 20 ;; "typeof" < "cl-types-of" < "head" priority
1071 (lambda (obj &rest _) `(cl-types-of ,obj cl--type-dispatch-list))
1072 (lambda (tag &rest _) (if (consp tag) tag)))
1073
1074;;;###autoload
1075(defun cl--type-generalizers (type)
1076 ;; Add a new dispatch type to the dispatch list, then
1077 ;; synchronize with `cl--type-list' so that both lists follow
1078 ;; the same type precedence order.
1079 ;; The `merge-ordered-lists' is `cl-types-of' should we make this
1080 ;; ordering unnecessary, but it's still handy for all those types
1081 ;; that don't declare their parents.
1082 (unless (memq type cl--type-dispatch-list)
1083 (setq cl--type-dispatch-list
1084 (seq-intersection cl--type-list
1085 (cons type cl--type-dispatch-list))))
1086 (list cl--type-generalizer))
1087
1088;;;; Trailer
968 1089
969(make-obsolete-variable 'cl-extra-load-hook 1090(make-obsolete-variable 'cl-extra-load-hook
970 "use `with-eval-after-load' instead." "28.1") 1091 "use `with-eval-after-load' instead." "28.1")
diff --git a/lisp/emacs-lisp/cl-lib.el b/lisp/emacs-lisp/cl-lib.el
index 4645b4dffb1..ff014965eb9 100644
--- a/lisp/emacs-lisp/cl-lib.el
+++ b/lisp/emacs-lisp/cl-lib.el
@@ -560,6 +560,19 @@ If ALIST is non-nil, the new pairs are prepended to it."
560 ;; those rare places where we do need it. 560 ;; those rare places where we do need it.
561 ) 561 )
562 562
563(static-if (not (fboundp 'cl-defmethod))
564 ;; `cl-generic' requires `cl-lib' at compile-time, so `cl-lib' can't
565 ;; use `cl-defmethod' before `cl-generic' has been compiled.
566 ;; Also, there is no mechanism to autoload methods, so this can't be
567 ;; moved to `cl-extra.el'.
568 nil
569 (declare-function cl--type-generalizers "cl-extra" (type))
570 (cl-defmethod cl-generic-generalizers :extra "cl-types-of" (type)
571 "Support for dispatch on cl-types."
572 (if (and (symbolp type) (cl-type-class-p (cl--find-class type)))
573 (cl--type-generalizers type)
574 (cl-call-next-method))))
575
563(defun cl--old-struct-type-of (orig-fun object) 576(defun cl--old-struct-type-of (orig-fun object)
564 (or (and (vectorp object) (> (length object) 0) 577 (or (and (vectorp object) (> (length object) 0)
565 (let ((tag (aref object 0))) 578 (let ((tag (aref object 0)))
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index a966ec5eaf6..424ebe2c7ad 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -3786,15 +3786,52 @@ macro that returns its `&whole' argument."
3786;;;###autoload 3786;;;###autoload
3787(defmacro cl-deftype (name arglist &rest body) 3787(defmacro cl-deftype (name arglist &rest body)
3788 "Define NAME as a new data type. 3788 "Define NAME as a new data type.
3789The type name can then be used in `cl-typecase', `cl-check-type', etc." 3789The type NAME can then be used in `cl-typecase', `cl-check-type',
3790 (declare (debug cl-defmacro) (doc-string 3) (indent 2)) 3790etc., and to some extent, as method specializer.
3791 `(cl-eval-when (compile load eval) 3791
3792 (define-symbol-prop ',name 'cl-deftype-handler 3792ARGLIST is a Common Lisp argument list of the sort accepted by
3793 (cl-function (lambda (&cl-defs ('*) ,@arglist) ,@body))))) 3793`cl-defmacro'. BODY forms should return a type specifier that is equivalent
3794to the type (see the Info node `(cl)Type Predicates').
3794 3795
3795(cl-deftype extended-char () '(and character (not base-char))) 3796If there is a `declare' form in BODY, the spec (parents . PARENTS)
3796;; Define fixnum so `cl-typep' recognize it and the type check emitted 3797can specify a list of types NAME is a subtype of.
3797;; by `cl-the' is effective. 3798The list of PARENTS types determines the order of methods invocation,
3799and missing PARENTS may cause incorrect ordering of methods, while
3800extraneous PARENTS may cause use of extraneous methods.
3801
3802If PARENTS is non-nil, ARGLIST must be nil."
3803 (declare (debug cl-defmacro) (doc-string 3) (indent 2))
3804 (pcase-let*
3805 ((`(,decls . ,forms) (macroexp-parse-body body))
3806 (docstring (if (stringp (car decls))
3807 (car decls)
3808 (cadr (assq :documentation decls))))
3809 (declares (assq 'declare decls))
3810 (parent-decl (assq 'parents (cdr declares)))
3811 (parents (cdr parent-decl)))
3812 (when parent-decl
3813 ;; "Consume" the `parents' declaration.
3814 (cl-callf (lambda (x) (delq parent-decl x)) (cdr declares))
3815 (when (equal declares '(declare))
3816 (cl-callf (lambda (x) (delq declares x)) decls)))
3817 (and parents arglist
3818 (error "Parents specified, but arglist not empty"))
3819 `(eval-and-compile ;;cl-eval-when (compile load eval)
3820 ;; FIXME: Where should `cl--type-deftype' go? Currently, code
3821 ;; using `cl-deftype' can use (eval-when-compile (require
3822 ;; 'cl-lib)), so `cl--type-deftype' needs to go either to
3823 ;; `cl-preloaded.el' or it should be autoloaded even when
3824 ;; `cl-lib' is not loaded.
3825 (cl--type-deftype ',name ',parents ',arglist ,docstring)
3826 (define-symbol-prop ',name 'cl-deftype-handler
3827 (cl-function
3828 (lambda (&cl-defs ('*) ,@arglist)
3829 ,@decls
3830 ,@forms))))))
3831
3832(static-if (not (fboundp 'cl--type-deftype))
3833 nil ;; Can't define it yet!
3834 (cl-deftype extended-char () '(and character (not base-char))))
3798 3835
3799;;; Additional functions that we can now define because we've defined 3836;;; Additional functions that we can now define because we've defined
3800;;; `cl-defsubst' and `cl-typep'. 3837;;; `cl-defsubst' and `cl-typep'.
diff --git a/lisp/emacs-lisp/cl-preloaded.el b/lisp/emacs-lisp/cl-preloaded.el
index dfea8d6c8e3..0447191bbc7 100644
--- a/lisp/emacs-lisp/cl-preloaded.el
+++ b/lisp/emacs-lisp/cl-preloaded.el
@@ -465,6 +465,71 @@ The fields are used as follows:
465 (setf (cl--class-parents (cl--find-class 'cl-structure-object)) 465 (setf (cl--class-parents (cl--find-class 'cl-structure-object))
466 (list (cl--find-class 'record)))) 466 (list (cl--find-class 'record))))
467 467
468;;;; Support for `cl-deftype'.
469
470(defvar cl--type-list nil
471 "Precedence list of the defined cl-types.")
472
473;; FIXME: The `cl-deftype-handler' property should arguably be turned
474;; into a field of this struct (but it has performance and
475;; compatibility implications, so let's not make that change for now).
476(cl-defstruct
477 (cl-type-class
478 (:include cl--class)
479 (:noinline t)
480 (:constructor nil)
481 (:constructor cl--type-class-make
482 (name
483 docstring
484 parent-types
485 &aux (parents
486 (mapcar
487 (lambda (type)
488 (or (cl--find-class type)
489 (error "Unknown type: %S" type)))
490 parent-types))))
491 (:copier nil))
492 "Type descriptors for types defined by `cl-deftype'.")
493
494(defun cl--type-deftype (name parents arglist &optional docstring)
495 "Register cl-type with NAME for method dispatching.
496PARENTS is a list of types NAME is a subtype of, or nil.
497DOCSTRING is an optional documentation string."
498 (let* ((class (cl--find-class name)))
499 (when class
500 (or (cl-type-class-p class)
501 ;; FIXME: We have some uses `cl-deftype' in Emacs that
502 ;; "complement" another declaration of the same type,
503 ;; so maybe we should turn this into a warning (and
504 ;; not overwrite the `cl--find-class' in that case)?
505 (error "Type in another class: %S" (type-of class))))
506 ;; Setup a type descriptor for NAME.
507 (setf (cl--find-class name)
508 (cl--type-class-make name docstring parents))
509 ;; Record new type. The constructor of the class
510 ;; `cl-type-class' already ensures that parent types must be
511 ;; defined before their "child" types (i.e. already added to
512 ;; the `cl--type-list' for types defined with `cl-deftype').
513 ;; So it is enough to simply push a new type at the beginning
514 ;; of the list.
515 ;; Redefinition is more complicated, because child types may
516 ;; be in the list, so moving the type to the head can be
517 ;; incorrect. The "cheap" solution is to leave the list
518 ;; unchanged (and hope the redefinition doesn't change the
519 ;; hierarchy too much).
520 ;; Side note: Redefinitions introduce other problems as well
521 ;; because the class object's `parents` slot contains
522 ;; references to `cl--class` objects, so after a redefinition
523 ;; via (setf (cl--find-class FOO) ...), the children's
524 ;; `parents` slots point to the old class object. That's a
525 ;; problem that affects all types and that we don't really try
526 ;; to solve currently.
527 (or (memq name cl--type-list)
528 ;; Exclude types that can't be used without arguments.
529 ;; They'd signal errors in `cl-types-of'!
530 (not (memq (car arglist) '(nil &rest &optional &keys)))
531 (push name cl--type-list))))
532
468;; Make sure functions defined with cl-defsubst can be inlined even in 533;; Make sure functions defined with cl-defsubst can be inlined even in
469;; packages which do not require CL. We don't put an autoload cookie 534;; packages which do not require CL. We don't put an autoload cookie
470;; directly on that function, since those cookies only go to cl-loaddefs. 535;; directly on that function, since those cookies only go to cl-loaddefs.
diff --git a/lisp/emacs-lisp/cl-types.el b/lisp/emacs-lisp/cl-types.el
index a466c309a33..c265e50f0f2 100644
--- a/lisp/emacs-lisp/cl-types.el
+++ b/lisp/emacs-lisp/cl-types.el
@@ -1,5 +1,41 @@
1;; -*- lexical-binding: t; -*- 1;; -*- lexical-binding: t; -*-
2 2
3;;; Old Sizes:
4
5;; % (cd lisp/emacs-lisp/; l cl-*.elc)
6;; -rw-r--r-- 1 monnier monnier 68920 5 mai 13:49 cl-generic.elc
7;; -rw-r--r-- 1 monnier monnier 41841 5 mai 13:49 cl-preloaded.elc
8;; -rw-r--r-- 1 monnier monnier 23037 5 mai 13:58 cl-lib.elc
9;; -rw-r--r-- 1 monnier monnier 32664 5 mai 14:14 cl-extra.elc
10;; -rw-r--r-- 1 monnier monnier 53769 5 mai 14:14 cl-loaddefs.elc
11;; -rw-r--r-- 1 monnier monnier 17921 5 mai 14:14 cl-indent.elc
12;; -rw-r--r-- 1 monnier monnier 18295 5 mai 14:14 cl-print.elc
13;; -rw-r--r-- 1 monnier monnier 101608 5 mai 14:14 cl-macs.elc
14;; -rw-r--r-- 1 monnier monnier 43849 5 mai 14:14 cl-seq.elc
15;; -rw-r--r-- 1 monnier monnier 8691 5 mai 18:53 cl-types.elc
16;; %
17
18;;; After the move:
19
20;; % (cd lisp/emacs-lisp/; l cl-*.elc)
21;; -rw-r--r-- 1 monnier monnier 46390 5 mai 23:04 cl-preloaded.elc
22;; -rw-r--r-- 1 monnier monnier 68920 5 mai 23:04 cl-generic.elc
23;; -rw-r--r-- 1 monnier monnier 23620 5 mai 23:05 cl-lib.elc
24;; -rw-r--r-- 1 monnier monnier 54752 5 mai 23:15 cl-loaddefs.elc
25;; -rw-r--r-- 1 monnier monnier 17921 5 mai 23:05 cl-indent.elc
26;; -rw-r--r-- 1 monnier monnier 34065 5 mai 23:05 cl-extra.elc
27;; -rw-r--r-- 1 monnier monnier 18295 5 mai 23:05 cl-print.elc
28;; -rw-r--r-- 1 monnier monnier 102581 5 mai 23:05 cl-macs.elc
29;; -rw-r--r-- 1 monnier monnier 159 5 mai 23:05 cl-types.elc
30;; -rw-r--r-- 1 monnier monnier 43849 5 mai 23:05 cl-seq.elc
31;; %
32
33;; cl-preloaded: +4549 41841 => 46390
34;; cl-lib: + 583 23037 => 23620
35;; cl-macs: + 973 101608 => 102581
36;; cl-extra +1401 32664 => 34065
37;; cl-loaddefs: + 983 53769 => 54752
38
3;; Data types defined by `cl-deftype' are now recognized as argument 39;; Data types defined by `cl-deftype' are now recognized as argument
4;; types for dispatching generic functions methods. 40;; types for dispatching generic functions methods.
5 41
@@ -9,271 +45,8 @@
9(declare-function cl-remprop "cl-extra" (symbol propname)) 45(declare-function cl-remprop "cl-extra" (symbol propname))
10(declare-function cl--class-children "cl-extra" (class)) 46(declare-function cl--class-children "cl-extra" (class))
11 47
12;; Extend `cl-deftype' to define data types which are also valid
13;; argument types for dispatching generic function methods (see also
14;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77725>).
15;;
16;; The main entry points are:
17;;
18;; - `cl-deftype', that defines new data types.
19;;
20;; - `cl-types-of', that returns the types an object belongs to.
21
22(defvar cl--type-list nil
23 "Precedence list of the defined cl-types.")
24
25;; FIXME: The `cl-deftype-handler' property should arguably be turned
26;; into a field of this struct (but it has performance and
27;; compatibility implications, so let's not make that change for now).
28(cl-defstruct
29 (cl-type-class
30 (:include cl--class)
31 (:noinline t)
32 (:constructor nil)
33 (:constructor cl--type-class-make
34 (name
35 docstring
36 parent-types
37 &aux (parents
38 (mapcar
39 (lambda (type)
40 (or (cl--find-class type)
41 (error "Unknown type: %S" type)))
42 parent-types))))
43 (:copier nil))
44 "Type descriptors for types defined by `cl-deftype'.")
45
46(defun cl--type-p (object)
47 "Return non-nil if OBJECT is a cl-type.
48That is, a type defined by `cl-deftype', of class `cl-type-class'."
49 (and (symbolp object) (cl-type-class-p (cl--find-class object))))
50
51(defun cl--type-deftype (name parents arglist &optional docstring)
52 "Register cl-type with NAME for method dispatching.
53PARENTS is a list of types NAME is a subtype of, or nil.
54DOCSTRING is an optional documentation string."
55 (let* ((class (cl--find-class name)))
56 (when class
57 (or (cl-type-class-p class)
58 ;; FIXME: We have some uses `cl-deftype' in Emacs that
59 ;; "complement" another declaration of the same type,
60 ;; so maybe we should turn this into a warning (and
61 ;; not overwrite the `cl--find-class' in that case)?
62 (error "Type in another class: %S" (type-of class))))
63 ;; Setup a type descriptor for NAME.
64 (setf (cl--find-class name)
65 (cl--type-class-make name docstring parents))
66 ;; Record new type. The constructor of the class
67 ;; `cl-type-class' already ensures that parent types must be
68 ;; defined before their "child" types (i.e. already added to
69 ;; the `cl--type-list' for types defined with `cl-deftype').
70 ;; So it is enough to simply push a new type at the beginning
71 ;; of the list.
72 ;; Redefinition is more complicated, because child types may
73 ;; be in the list, so moving the type to the head can be
74 ;; incorrect. The "cheap" solution is to leave the list
75 ;; unchanged (and hope the redefinition doesn't change the
76 ;; hierarchy too much).
77 ;; Side note: Redefinitions introduce other problems as well
78 ;; because the class object's `parents` slot contains
79 ;; references to `cl--class` objects, so after a redefinition
80 ;; via (setf (cl--find-class FOO) ...), the children's
81 ;; `parents` slots point to the old class object. That's a
82 ;; problem that affects all types and that we don't really try
83 ;; to solve currently.
84 (or (memq name cl--type-list)
85 ;; Exclude types that can't be used without arguments.
86 ;; They'd signal errors in `cl-types-of'!
87 (not (memq (car arglist) '(nil &rest &optional &keys)))
88 (push name cl--type-list))))
89
90;;;###autoload
91(defmacro cl-deftype2 (name arglist &rest body)
92 "Define NAME as a new data type.
93The type NAME can then be used in `cl-typecase', `cl-check-type',
94etc., and as argument type for dispatching generic function methods.
95
96ARGLIST is a Common Lisp argument list of the sort accepted by
97`cl-defmacro'. BODY forms are evaluated and should return a type
98specifier that is equivalent to the type (see the Info node `(cl) Type
99Predicates' in the GNU Emacs Common Lisp Emulation manual).
100
101If there is a `declare' form in BODY, the spec (parents PARENTS) is
102recognized to specify a list of types NAME is a subtype of. For
103instance:
104
105 (cl-deftype2 unsigned-byte (&optional bits)
106 \"Unsigned integer.\"
107 (list \\='integer 0 (if (eq bits \\='*) bits (1- (ash 1 bits)))))
108
109 (cl-deftype2 unsigned-8bits ()
110 \"Unsigned 8-bits integer.\"
111 (declare (parents unsigned-byte))
112 \\='(unsigned-byte 8))
113
114The list of PARENTS types determines the order of methods invocation,
115and missing PARENTS may cause incorrect ordering of methods, while
116extraneous PARENTS may cause use of extraneous methods.
117
118If PARENTS is non-nil, ARGLIST must be nil."
119 (declare (debug cl-defmacro) (doc-string 3) (indent 2))
120 (pcase-let*
121 ((`(,decls . ,forms) (macroexp-parse-body body))
122 (docstring (if (stringp (car decls))
123 (car decls)
124 (cadr (assq :documentation decls))))
125 (declares (assq 'declare decls))
126 (parent-decl (assq 'parents (cdr declares)))
127 (parents (cdr parent-decl)))
128 (when parent-decl
129 ;; "Consume" the `parents' declaration.
130 (cl-callf (lambda (x) (delq parent-decl x)) (cdr declares))
131 (when (equal declares '(declare))
132 (cl-callf (lambda (x) (delq declares x)) decls)))
133 (if (memq name parents)
134 (error "Type in parents: %S" parents))
135 (and parents arglist
136 (error "Parents specified, but arglist not empty"))
137 `(eval-and-compile ;;cl-eval-when (compile load eval)
138 ;; FIXME: Where should `cl--type-deftype' go? Currently, code
139 ;; using `cl-deftype' can use (eval-when-compile (require
140 ;; 'cl-lib)), so `cl--type-deftype' needs to go either to
141 ;; `cl-preloaded.el' or it should be autoloaded even when
142 ;; `cl-lib' is not loaded.
143 (cl--type-deftype ',name ',parents ',arglist ,docstring)
144 (define-symbol-prop ',name 'cl-deftype-handler
145 (cl-function
146 (lambda (&cl-defs ('*) ,@arglist)
147 ,@decls
148 ,@forms))))))
149
150;; Ensure each type satisfies `eql'.
151(defvar cl--type-unique (make-hash-table :test 'equal)
152 "Record an unique value of each type.")
153
154;; FIXME: `cl-types-of' CPU cost is proportional to the number of types
155;; defined with `cl-deftype', so the more popular it gets, the slower
156;; it becomes. And of course, the cost of each type check is
157;; unbounded, so a single "expensive" type can slow everything down
158;; further.
159;;
160;; The usual dispatch is
161;;
162;; (lambda (arg &rest args)
163;; (let ((f (gethash (cl-typeof arg) precomputed-methods-table)))
164;; (if f
165;; (apply f arg args)
166;; ;; Slow case when encountering a new type
167;; ...)))
168;;
169;; where often the most expensive part is `&rest' (which has to
170;; allocate a list for those remaining arguments),
171;;
172;; So we're talking about replacing
173;;
174;; &rest + cl-type-of + gethash + if + apply
175;;
176;; with a function that loops over N types, calling `cl-typep' on each
177;; one of them (`cl-typep' itself being a recursive function that
178;; basically interprets the type language). This is going to slow
179;; down dispatch very significantly for those generic functions that
180;; have a method that dispatches on a user defined type, compared to
181;; those that don't.
182;;
183;; A possible further improvement:
184;;
185;; - based on the PARENTS declaration, create a map from builtin-type
186;; to the set of cl-types that have that builtin-type among their
187;; parents. That presumes some PARENTS include some builtin-types,
188;; obviously otherwise the map will be trivial with all cl-types
189;; associated with the `t' "dummy parent". [ We could even go crazy
190;; and try and guess PARENTS when not provided, by analyzing the
191;; type's definition. ]
192;;
193;; - in `cl-types-of' start by calling `cl-type-of', then use the map
194;; to find which cl-types may need to be checked.
195;;
196(defun cl-types-of (object &optional types)
197 "Return the types OBJECT belongs to.
198Return an unique list of types OBJECT belongs to, ordered from the
199most specific type to the most general.
200TYPES is an internal argument."
201 (let* ((found nil))
202 ;; Build a list of all types OBJECT belongs to.
203 (dolist (type (or types cl--type-list))
204 (and
205 ;; If OBJECT is of type, add type to the matching list.
206 (if types
207 ;; For method dispatch, we don't need to filter out errors, since
208 ;; we can presume that method dispatch is used only on
209 ;; sanely-defined types.
210 (cl-typep object type)
211 (condition-case-unless-debug e
212 (cl-typep object type)
213 (error (setq cl--type-list (delq type cl--type-list))
214 (warn "cl-types-of %S: %s"
215 type (error-message-string e)))))
216 (push type found)))
217 (push (cl-type-of object) found)
218 ;; Return an unique value of the list of types OBJECT belongs to,
219 ;; which is also the list of specifiers for OBJECT.
220 (with-memoization (gethash found cl--type-unique)
221 ;; Compute an ordered list of types from the DAG.
222 (merge-ordered-lists
223 (mapcar (lambda (type) (cl--class-allparents (cl--find-class type)))
224 (nreverse found))))))
225
226;;; Method dispatching
227;;
228
229(defvar cl--type-dispatch-list nil
230 "List of types that need to be checked during dispatch.")
231
232(cl-generic-define-generalizer cl--type-generalizer
233 ;; FIXME: This priority can't be always right. :-(
234 ;; E.g. a method dispatching on a type like (or number function),
235 ;; should take precedence over a method on `t' but not over a method
236 ;; on `number'. Similarly a method dispatching on a type like
237 ;; (satisfies (lambda (x) (equal x '(A . B)))) should take precedence
238 ;; over a method on (head 'A).
239 ;; Fixing this 100% is impossible so this generalizer is condemned to
240 ;; suffer from "undefined method ordering" problems, unless/until we
241 ;; restrict it somehow to a subset that we can handle reliably.
242 20 ;; "typeof" < "cl-types-of" < "head" priority
243 (lambda (obj &rest _) `(cl-types-of ,obj cl--type-dispatch-list))
244 (lambda (tag &rest _) (if (consp tag) tag)))
245
246(cl-defmethod cl-generic-generalizers :extra "cl-types-of" (type)
247 "Support for dispatch on cl-types."
248 (if (cl--type-p type)
249 (progn
250 ;; Add a new dispatch type to the dispatch list, then
251 ;; synchronize with `cl--type-list' so that both lists follow
252 ;; the same type precedence order.
253 ;; The `merge-ordered-lists' is `cl-types-of' should we make this
254 ;; ordering unnecessary, but it's still handy for all those types
255 ;; that don't declare their parents.
256 (unless (memq type cl--type-dispatch-list)
257 (setq cl--type-dispatch-list
258 (seq-intersection cl--type-list
259 (cons type cl--type-dispatch-list))))
260 (list cl--type-generalizer))
261 (cl-call-next-method)))
262 48
263;;; Support for unloading.
264 49
265;; Keep it for now, for testing.
266(defun cl--type-undefine (name)
267 "Remove the definition of cl-type with NAME.
268NAME is an unquoted symbol representing a cl-type.
269Signal an error if NAME has subtypes."
270 (cl-check-type name (satisfies cl--type-p))
271 (when-let* ((children (cl--class-children (cl--find-class name))))
272 (error "Type has children: %S" children))
273 (cl-remprop name 'cl--class)
274 (cl-remprop name 'cl-deftype-handler)
275 (setq cl--type-dispatch-list (delq name cl--type-dispatch-list))
276 (setq cl--type-list (delq name cl--type-list)))
277 50
278(provide 'cl-types) 51(provide 'cl-types)
279 52
diff --git a/lisp/emacs-lisp/oclosure.el b/lisp/emacs-lisp/oclosure.el
index d38429648e6..19823f44d4c 100644
--- a/lisp/emacs-lisp/oclosure.el
+++ b/lisp/emacs-lisp/oclosure.el
@@ -151,7 +151,7 @@
151(defun oclosure--p (oclosure) 151(defun oclosure--p (oclosure)
152 (not (not (oclosure-type oclosure)))) 152 (not (not (oclosure-type oclosure))))
153 153
154(cl-deftype oclosure () '(satisfies oclosure--p)) 154(define-symbol-prop 'oclosure 'cl-deftype-satisfies #'oclosure--p)
155 155
156(defun oclosure--slot-mutable-p (slotdesc) 156(defun oclosure--slot-mutable-p (slotdesc)
157 (not (alist-get :read-only (cl--slot-descriptor-props slotdesc)))) 157 (not (alist-get :read-only (cl--slot-descriptor-props slotdesc))))
diff --git a/test/lisp/emacs-lisp/cl-extra-tests.el b/test/lisp/emacs-lisp/cl-extra-tests.el
index 20d1e532a6f..1f94d71e567 100644
--- a/test/lisp/emacs-lisp/cl-extra-tests.el
+++ b/test/lisp/emacs-lisp/cl-extra-tests.el
@@ -348,4 +348,96 @@
348 (should (cl-tailp l l)) 348 (should (cl-tailp l l))
349 (should (not (cl-tailp '(4 5) l))))) 349 (should (not (cl-tailp '(4 5) l)))))
350 350
351;;;; Method dispatch for derived types.
352
353(cl-deftype multiples-of (&optional m)
354 (let ((multiplep (if (eq m '*)
355 #'ignore
356 (lambda (n) (= 0 (% n m))))))
357 `(and integer (satisfies ,multiplep))))
358
359(cl-deftype multiples-of-2 ()
360 '(multiples-of 2))
361
362(cl-deftype multiples-of-3 ()
363 '(multiples-of 3))
364
365(cl-deftype multiples-of-4 ()
366 (declare (parents multiples-of-2))
367 '(and multiples-of-2 (multiples-of 4)))
368
369(cl-deftype unsigned-byte (&optional bits)
370 "Unsigned integer."
371 `(integer 0 ,(if (eq bits '*) bits (1- (ash 1 bits)))))
372
373(cl-deftype unsigned-16bits ()
374 "Unsigned 16-bits integer."
375 (declare (parents unsigned-byte))
376 '(unsigned-byte 16))
377
378(cl-deftype unsigned-8bits ()
379 "Unsigned 8-bits integer."
380 (declare (parents unsigned-16bits))
381 '(unsigned-byte 8))
382
383(cl-defmethod my-foo ((_n unsigned-byte))
384 (format "unsigned"))
385
386(cl-defmethod my-foo ((_n unsigned-16bits))
387 (format "unsigned 16bits - also %s"
388 (cl-call-next-method)))
389
390(cl-defmethod my-foo ((_n unsigned-8bits))
391 (format "unsigned 8bits - also %s"
392 (cl-call-next-method)))
393
394(ert-deftest cl-types-test ()
395 "Test types definition, cl-types-of and method dispatching."
396
397 ;; Invalid DAG error
398 ;; FIXME: We don't test that any more.
399 ;; (should-error
400 ;; (eval
401 ;; '(cl-deftype unsigned-16bits ()
402 ;; "Unsigned 16-bits integer."
403 ;; (declare (parents unsigned-8bits))
404 ;; '(unsigned-byte 16))
405 ;; lexical-binding
406 ;; ))
407
408 ;; Test that (cl-types-of 4) is (multiples-of-4 multiples-of-2 ...)
409 ;; Test that (cl-types-of 6) is (multiples-of-3 multiples-of-2 ...)
410 ;; Test that (cl-types-of 12) is (multiples-of-4 multiples-of-3 multiples-of-2 ...)
411 (let ((types '(multiples-of-2 multiples-of-3 multiples-of-4)))
412 (should (equal '(multiples-of-2)
413 (seq-intersection (cl-types-of 2) types)))
414
415 (should (equal '(multiples-of-4 multiples-of-2)
416 (seq-intersection (cl-types-of 4) types)))
417
418 (should (equal '(multiples-of-3 multiples-of-2)
419 (seq-intersection (cl-types-of 6) types)))
420
421 (should (member (seq-intersection (cl-types-of 12) types)
422 ;; Order between 3 and 4/2 is undefined.
423 '((multiples-of-3 multiples-of-4 multiples-of-2)
424 (multiples-of-4 multiples-of-2 multiples-of-3))))
425
426 (should (equal '()
427 (seq-intersection (cl-types-of 5) types)))
428 )
429
430 ;;; Method dispatching.
431 (should (equal "unsigned 8bits - also unsigned 16bits - also unsigned"
432 (my-foo 100)))
433
434 (should (equal "unsigned 16bits - also unsigned"
435 (my-foo 256)))
436
437 (should (equal "unsigned"
438 (my-foo most-positive-fixnum)))
439 )
440
441
442
351;;; cl-extra-tests.el ends here 443;;; cl-extra-tests.el ends here
diff --git a/test/lisp/emacs-lisp/cl-types-tests.el b/test/lisp/emacs-lisp/cl-types-tests.el
deleted file mode 100644
index 746270578e7..00000000000
--- a/test/lisp/emacs-lisp/cl-types-tests.el
+++ /dev/null
@@ -1,96 +0,0 @@
1;;; Test `cl-typedef' -*- lexical-binding: t; -*-
2;;
3(require 'ert)
4(require 'cl-types)
5
6(cl-deftype2 multiples-of (&optional m)
7 (let ((multiplep (if (eq m '*)
8 #'ignore
9 (lambda (n) (= 0 (% n m))))))
10 `(and integer (satisfies ,multiplep))))
11
12(cl-deftype2 multiples-of-2 ()
13 '(multiples-of 2))
14
15(cl-deftype2 multiples-of-3 ()
16 '(multiples-of 3))
17
18(cl-deftype2 multiples-of-4 ()
19 (declare (parents multiples-of-2))
20 '(and multiples-of-2 (multiples-of 4)))
21
22(cl-deftype2 unsigned-byte (&optional bits)
23 "Unsigned integer."
24 `(integer 0 ,(if (eq bits '*) bits (1- (ash 1 bits)))))
25
26(cl-deftype2 unsigned-16bits ()
27 "Unsigned 16-bits integer."
28 (declare (parents unsigned-byte))
29 '(unsigned-byte 16))
30
31(cl-deftype2 unsigned-8bits ()
32 "Unsigned 8-bits integer."
33 (declare (parents unsigned-16bits))
34 '(unsigned-byte 8))
35
36(cl-defmethod my-foo ((_n unsigned-byte))
37 (format "unsigned"))
38
39(cl-defmethod my-foo ((_n unsigned-16bits))
40 (format "unsigned 16bits - also %s"
41 (cl-call-next-method)))
42
43(cl-defmethod my-foo ((_n unsigned-8bits))
44 (format "unsigned 8bits - also %s"
45 (cl-call-next-method)))
46
47(ert-deftest cl-types-test ()
48 "Test types definition, cl-types-of and method dispatching."
49
50 ;; Invalid DAG error
51 ;; FIXME: We don't test that any more.
52 ;; (should-error
53 ;; (eval
54 ;; '(cl-deftype2 unsigned-16bits ()
55 ;; "Unsigned 16-bits integer."
56 ;; (declare (parents unsigned-8bits))
57 ;; '(unsigned-byte 16))
58 ;; lexical-binding
59 ;; ))
60
61 ;; Test that (cl-types-of 4) is (multiples-of-4 multiples-of-2 ...)
62 ;; Test that (cl-types-of 6) is (multiples-of-3 multiples-of-2 ...)
63 ;; Test that (cl-types-of 12) is (multiples-of-4 multiples-of-3 multiples-of-2 ...)
64 (let ((types '(multiples-of-2 multiples-of-3 multiples-of-4)))
65 (should (equal '(multiples-of-2)
66 (seq-intersection (cl-types-of 2) types)))
67
68 (should (equal '(multiples-of-4 multiples-of-2)
69 (seq-intersection (cl-types-of 4) types)))
70
71 (should (equal '(multiples-of-3 multiples-of-2)
72 (seq-intersection (cl-types-of 6) types)))
73
74 (should (member (seq-intersection (cl-types-of 12) types)
75 ;; Order between 3 and 4/2 is undefined.
76 '((multiples-of-3 multiples-of-4 multiples-of-2)
77 (multiples-of-4 multiples-of-2 multiples-of-3))))
78
79 (should (equal '()
80 (seq-intersection (cl-types-of 5) types)))
81 )
82
83 ;;; Method dispatching.
84 (should (equal "unsigned 8bits - also unsigned 16bits - also unsigned"
85 (my-foo 100)))
86
87 (should (equal "unsigned 16bits - also unsigned"
88 (my-foo 256)))
89
90 (should (equal "unsigned"
91 (my-foo most-positive-fixnum)))
92 )
93
94(provide 'cl-types-tests)
95
96;;; cl-types-tests.el ends here