diff options
| author | Stefan Monnier | 2025-05-05 23:18:56 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2025-05-05 23:18:56 -0400 |
| commit | fc4d8ce9514dd45ab34dbef6f023347b42ee9fef (patch) | |
| tree | 77ebb9ff9b1bdf8b13a93980b54a4a3446f60c54 | |
| parent | 68a50324a70bd794d7f3228290310093f1515f7b (diff) | |
| download | emacs-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.el | 121 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-lib.el | 13 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-macs.el | 53 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-preloaded.el | 65 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cl-types.el | 299 | ||||
| -rw-r--r-- | lisp/emacs-lisp/oclosure.el | 2 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/cl-extra-tests.el | 92 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/cl-types-tests.el | 96 |
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. | ||
| 1029 | Return an unique list of types OBJECT belongs to, ordered from the | ||
| 1030 | most specific type to the most general. | ||
| 1031 | TYPES 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. |
| 3789 | The type name can then be used in `cl-typecase', `cl-check-type', etc." | 3789 | The type NAME can then be used in `cl-typecase', `cl-check-type', |
| 3790 | (declare (debug cl-defmacro) (doc-string 3) (indent 2)) | 3790 | etc., and to some extent, as method specializer. |
| 3791 | `(cl-eval-when (compile load eval) | 3791 | |
| 3792 | (define-symbol-prop ',name 'cl-deftype-handler | 3792 | ARGLIST 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 |
| 3794 | to the type (see the Info node `(cl)Type Predicates'). | ||
| 3794 | 3795 | ||
| 3795 | (cl-deftype extended-char () '(and character (not base-char))) | 3796 | If there is a `declare' form in BODY, the spec (parents . PARENTS) |
| 3796 | ;; Define fixnum so `cl-typep' recognize it and the type check emitted | 3797 | can specify a list of types NAME is a subtype of. |
| 3797 | ;; by `cl-the' is effective. | 3798 | The list of PARENTS types determines the order of methods invocation, |
| 3799 | and missing PARENTS may cause incorrect ordering of methods, while | ||
| 3800 | extraneous PARENTS may cause use of extraneous methods. | ||
| 3801 | |||
| 3802 | If 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. | ||
| 496 | PARENTS is a list of types NAME is a subtype of, or nil. | ||
| 497 | DOCSTRING 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. | ||
| 48 | That 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. | ||
| 53 | PARENTS is a list of types NAME is a subtype of, or nil. | ||
| 54 | DOCSTRING 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. | ||
| 93 | The type NAME can then be used in `cl-typecase', `cl-check-type', | ||
| 94 | etc., and as argument type for dispatching generic function methods. | ||
| 95 | |||
| 96 | ARGLIST is a Common Lisp argument list of the sort accepted by | ||
| 97 | `cl-defmacro'. BODY forms are evaluated and should return a type | ||
| 98 | specifier that is equivalent to the type (see the Info node `(cl) Type | ||
| 99 | Predicates' in the GNU Emacs Common Lisp Emulation manual). | ||
| 100 | |||
| 101 | If there is a `declare' form in BODY, the spec (parents PARENTS) is | ||
| 102 | recognized to specify a list of types NAME is a subtype of. For | ||
| 103 | instance: | ||
| 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 | |||
| 114 | The list of PARENTS types determines the order of methods invocation, | ||
| 115 | and missing PARENTS may cause incorrect ordering of methods, while | ||
| 116 | extraneous PARENTS may cause use of extraneous methods. | ||
| 117 | |||
| 118 | If 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. | ||
| 198 | Return an unique list of types OBJECT belongs to, ordered from the | ||
| 199 | most specific type to the most general. | ||
| 200 | TYPES 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. | ||
| 268 | NAME is an unquoted symbol representing a cl-type. | ||
| 269 | Signal 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 | ||