diff options
| author | David Ponce | 2025-04-29 10:48:37 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2025-04-29 10:48:37 -0400 |
| commit | 4323ff209f2f73ca4e6d389de69eb310988c0b1f (patch) | |
| tree | f27fe1303c49d6b607716b717eb9ed266cebb438 | |
| parent | dfbeb7478ecd817f888927154858c380fb60390f (diff) | |
| download | emacs-4323ff209f2f73ca4e6d389de69eb310988c0b1f.tar.gz emacs-4323ff209f2f73ca4e6d389de69eb310988c0b1f.zip | |
(cl-types-of): Speed up by caching more of its work
* lisp/emacs-lisp/cl-types.el (cl--type-parents): Make it a proper function.
(cl--type-children): Use `cl--class-children` and make it a `defsubst`.
(cl--type-dag): η-reduce and make it a `defsubst`.
(cl--type-undefine): Also reset `cl--type-error`.
(cl--type-deftype): Modify `cl--type-list` atomically so we never need
to restore it upon error. Don't test bogus parent here.
(cl-deftype2): Test bogus parent here instead. Also, better preserve
the declarations for the lambda.
(cl-types-of): Do less uncached work.
| -rw-r--r-- | lisp/emacs-lisp/cl-types.el | 122 |
1 files changed, 71 insertions, 51 deletions
diff --git a/lisp/emacs-lisp/cl-types.el b/lisp/emacs-lisp/cl-types.el index 0a384e09d79..c10ce4a24fb 100644 --- a/lisp/emacs-lisp/cl-types.el +++ b/lisp/emacs-lisp/cl-types.el | |||
| @@ -3,9 +3,11 @@ | |||
| 3 | ;; Data types defined by `cl-deftype' are now recognized as argument | 3 | ;; Data types defined by `cl-deftype' are now recognized as argument |
| 4 | ;; types for dispatching generic functions methods. | 4 | ;; types for dispatching generic functions methods. |
| 5 | 5 | ||
| 6 | ;; Will be removed when included in cl-lib. | 6 | ;; Needed until merged in existing libraries. |
| 7 | (require 'cl-lib) | 7 | (require 'cl-lib) |
| 8 | (eval-when-compile (require 'cl-macs)) ;For cl--find-class. | 8 | (eval-when-compile (require 'cl-macs)) ;For cl--find-class. |
| 9 | (declare-function cl-remprop "cl-extra" (symbol propname)) | ||
| 10 | (declare-function cl--class-children "cl-extra" (class)) | ||
| 9 | 11 | ||
| 10 | ;; Extend `cl-deftype' to define data types which are also valid | 12 | ;; Extend `cl-deftype' to define data types which are also valid |
| 11 | ;; argument types for dispatching generic function methods (see also | 13 | ;; argument types for dispatching generic function methods (see also |
| @@ -42,62 +44,60 @@ | |||
| 42 | "Type descriptors for types defined by `cl-deftype'.") | 44 | "Type descriptors for types defined by `cl-deftype'.") |
| 43 | 45 | ||
| 44 | (defun cl--type-p (object) | 46 | (defun cl--type-p (object) |
| 45 | "Return non-nil if OBJECT is a used defined type. | 47 | "Return non-nil if OBJECT is a cl-type. |
| 46 | That is, a type of class `cl-type-class'." | 48 | That is, a type defined by `cl-deftype', of class `cl-type-class'." |
| 47 | (and (symbolp object) (cl-type-class-p (cl--find-class object)))) | 49 | (and (symbolp object) (cl-type-class-p (cl--find-class object)))) |
| 48 | 50 | ||
| 49 | (defmacro cl--type-parents (name) | 51 | (defsubst cl--type-parents (name) |
| 50 | "Get parents of type with NAME. | 52 | "Get parents of type with NAME. |
| 51 | NAME is a symbol representing a type." | 53 | NAME is a symbol representing a type. |
| 52 | `(cl--class-allparents (cl--find-class ,name))) | 54 | Return a possibly empty list of types." |
| 55 | (cl--class-allparents (cl--find-class name))) | ||
| 53 | 56 | ||
| 54 | (defun cl--type-children (name) | 57 | (defsubst cl--type-children (name) |
| 55 | "Get children of the type with NAME. | 58 | "Get children of the type with NAME. |
| 56 | NAME is a symbol representing a type. | 59 | NAME is a symbol representing a type. |
| 57 | Return a possibly empty list of types." | 60 | Return a possibly empty list of types." |
| 58 | (cl-check-type name (satisfies cl--type-p)) | 61 | (cl--class-children (cl--find-class name))) |
| 59 | (let (children) | ||
| 60 | (dolist (elt cl--type-list) | ||
| 61 | (or (eq name elt) | ||
| 62 | (if (memq name (cl--type-parents elt)) | ||
| 63 | (push elt children)))) | ||
| 64 | children)) | ||
| 65 | 62 | ||
| 66 | (defun cl--type-dag () | 63 | (defsubst cl--type-dag (types) |
| 67 | "Return a DAG from the list of defined types." | 64 | "Return a DAG from the list of TYPES." |
| 68 | (mapcar (lambda (type) (cl--type-parents type)) cl--type-list)) | 65 | (mapcar #'cl--type-parents types)) |
| 69 | 66 | ||
| 70 | ;; Keep it for now, for testing. | 67 | ;; Keep it for now, for testing. |
| 71 | (defun cl--type-undefine (name) | 68 | (defun cl--type-undefine (name) |
| 72 | "Remove the definitions of type with NAME. | 69 | "Remove the definition of cl-type with NAME. |
| 73 | NAME is an unquoted symbol representing a type. | 70 | NAME is an unquoted symbol representing a cl-type. |
| 74 | Signal an error if other types inherit from NAME." | 71 | Signal an error if NAME has subtypes." |
| 75 | (declare-function cl-remprop "cl-extra" (symbol propname)) | ||
| 76 | (cl-check-type name (satisfies cl--type-p)) | 72 | (cl-check-type name (satisfies cl--type-p)) |
| 77 | (when-let* ((children (and (cl--type-p name) | 73 | (when-let* ((children (and (cl--type-p name) |
| 78 | (cl--type-children name)))) | 74 | (cl--type-children name)))) |
| 79 | (error "Type has children: %S" children)) | 75 | (error "Type has children: %S" children)) |
| 76 | (cl-remprop name 'cl--type-error) | ||
| 80 | (cl-remprop name 'cl--class) | 77 | (cl-remprop name 'cl--class) |
| 81 | (cl-remprop name 'cl-deftype-handler) | 78 | (cl-remprop name 'cl-deftype-handler) |
| 82 | (setq cl--type-list (delq name cl--type-list))) | 79 | (setq cl--type-list (delq name cl--type-list))) |
| 83 | 80 | ||
| 84 | (defun cl--type-deftype (name parents &optional docstring) | 81 | (defun cl--type-deftype (name parents &optional docstring) |
| 85 | "Generalize type with NAME for method dispatching. | 82 | ;; FIXME: Should we also receive the arglist? |
| 83 | "Generalize cl-type with NAME for method dispatching. | ||
| 86 | PARENTS is a list of types NAME is a subtype of, or nil. | 84 | PARENTS is a list of types NAME is a subtype of, or nil. |
| 87 | DOCSTRING is an optional documentation string." | 85 | DOCSTRING is an optional documentation string." |
| 88 | (let ((oldtlist (copy-sequence cl--type-list)) | 86 | (let ((typelist cl--type-list) |
| 89 | (oldplist (copy-sequence (symbol-plist name)))) | 87 | (oldplist (copy-sequence (symbol-plist name)))) |
| 90 | (condition-case err | 88 | (condition-case err |
| 91 | (let* ((class (cl--find-class name)) | 89 | (let* ((class (cl--find-class name)) |
| 92 | (recorded (memq name cl--type-list))) | 90 | (recorded (memq name typelist))) |
| 93 | (if (null class) | 91 | (if (null class) |
| 94 | (or (null recorded) | 92 | (or (null recorded) |
| 95 | (error "Type generalized, but doesn't exist")) | 93 | (error "Type generalized, but doesn't exist")) |
| 96 | (or recorded (error "Type exists, but not generalized")) | 94 | (or recorded (error "Type exists, but not generalized")) |
| 97 | (or (cl-type-class-p class) | 95 | (or (cl-type-class-p class) |
| 96 | ;; FIXME: We have some uses `cl-deftype' in Emacs that | ||
| 97 | ;; "complement" another declaration of the same type, | ||
| 98 | ;; so maybe we should turn this into a warning (and | ||
| 99 | ;; not overwrite the `cl--find-class' in that case)? | ||
| 98 | (error "Type in another class: %S" (type-of class)))) | 100 | (error "Type in another class: %S" (type-of class)))) |
| 99 | (if (memq name parents) | ||
| 100 | (error "Type in parents: %S" parents)) | ||
| 101 | ;; Setup a type descriptor for NAME. | 101 | ;; Setup a type descriptor for NAME. |
| 102 | (setf (cl--find-class name) | 102 | (setf (cl--find-class name) |
| 103 | (cl--type-class-make name docstring parents)) | 103 | (cl--type-class-make name docstring parents)) |
| @@ -105,18 +105,23 @@ DOCSTRING is an optional documentation string." | |||
| 105 | ;; Clear any previous error mark. | 105 | ;; Clear any previous error mark. |
| 106 | (cl-remprop name 'cl--type-error) | 106 | (cl-remprop name 'cl--type-error) |
| 107 | ;; Record new type to include its dependency in the DAG. | 107 | ;; Record new type to include its dependency in the DAG. |
| 108 | (push name cl--type-list)) | 108 | (push name typelist)) |
| 109 | ;; `cl-types-of' iterates through all known types to collect | 109 | ;; `cl-types-of' iterates through all known types to collect |
| 110 | ;; all those an object belongs to, sorted from the most | 110 | ;; all those an object belongs to, sorted from the most |
| 111 | ;; specific type to the more general type. So, keep the | 111 | ;; specific type to the more general type. So, keep the |
| 112 | ;; global list in this order. | 112 | ;; global list in this order. |
| 113 | ;; FIXME: This global operation is a bit worrisome, because it | ||
| 114 | ;; scales poorly with the number of types. I guess it's OK | ||
| 115 | ;; for now because `cl-deftype' is not very popular, but it'll | ||
| 116 | ;; probably need to be replaced at some point. Maybe we | ||
| 117 | ;; should simply require that the parents be defined already, | ||
| 118 | ;; then we can just `push' the new type, knowing it's in | ||
| 119 | ;; topological order by construction. | ||
| 113 | (setq cl--type-list | 120 | (setq cl--type-list |
| 114 | (merge-ordered-lists | 121 | (merge-ordered-lists |
| 115 | (cl--type-dag) | 122 | (cl--type-dag typelist) |
| 116 | (lambda (_) (error "Invalid dependency graph"))))) | 123 | (lambda (_) (error "Invalid dependency graph"))))) |
| 117 | (error | 124 | (error |
| 118 | ;; On error restore previous data. | ||
| 119 | (setq cl--type-list oldtlist) | ||
| 120 | (setf (symbol-plist name) oldplist) | 125 | (setf (symbol-plist name) oldplist) |
| 121 | (error (format "Define %S failed: %s" | 126 | (error (format "Define %S failed: %s" |
| 122 | name (error-message-string err))))))) | 127 | name (error-message-string err))))))) |
| @@ -155,16 +160,30 @@ If PARENTS is non-nil, ARGLIST must be nil." | |||
| 155 | ((`(,decls . ,forms) (macroexp-parse-body body)) | 160 | ((`(,decls . ,forms) (macroexp-parse-body body)) |
| 156 | (docstring (if (stringp (car decls)) | 161 | (docstring (if (stringp (car decls)) |
| 157 | (car decls) | 162 | (car decls) |
| 158 | (cadr (assq :documentation decls)))) | 163 | (cadr (assq :documentation decls)))) |
| 159 | (parents (cdr (assq 'parents (cdr (assq 'declare decls)))))) | 164 | (declares (assq 'declare decls)) |
| 165 | (parent-decl (assq 'parents (cdr declares))) | ||
| 166 | (parents (cdr parent-decl))) | ||
| 167 | (when parent-decl | ||
| 168 | ;; "Consume" the `parents' declaration. | ||
| 169 | (cl-callf (lambda (x) (delq parent-decl x)) (cdr declares)) | ||
| 170 | (when (equal declares '(declare)) | ||
| 171 | (cl-callf (lambda (x) (delq declares x)) decls))) | ||
| 172 | (if (memq name parents) | ||
| 173 | (error "Type in parents: %S" parents)) | ||
| 160 | (and parents arglist | 174 | (and parents arglist |
| 161 | (error "Parents specified, but arglist not empty")) | 175 | (error "Parents specified, but arglist not empty")) |
| 162 | (if docstring (setq forms (cons docstring forms))) | ||
| 163 | `(eval-and-compile ;;cl-eval-when (compile load eval) | 176 | `(eval-and-compile ;;cl-eval-when (compile load eval) |
| 177 | ;; FIXME: Where should `cl--type-deftype' go? Currently, code | ||
| 178 | ;; using `cl-deftype' can use (eval-when-compile (require | ||
| 179 | ;; 'cl-lib)), so `cl--type-deftype' needs to go either to | ||
| 180 | ;; `cl-preloaded.el' or it should be autoloaded even when | ||
| 181 | ;; `cl-lib' is not loaded. | ||
| 164 | (cl--type-deftype ',name ',parents ,docstring) | 182 | (cl--type-deftype ',name ',parents ,docstring) |
| 165 | (define-symbol-prop ',name 'cl-deftype-handler | 183 | (define-symbol-prop ',name 'cl-deftype-handler |
| 166 | (cl-function | 184 | (cl-function |
| 167 | (lambda (&cl-defs ('*) ,@arglist) | 185 | (lambda (&cl-defs ('*) ,@arglist) |
| 186 | ,@decls | ||
| 168 | ,@forms)))))) | 187 | ,@forms)))))) |
| 169 | 188 | ||
| 170 | ;; Ensure each type satisfies `eql'. | 189 | ;; Ensure each type satisfies `eql'. |
| @@ -226,8 +245,8 @@ If PARENTS is non-nil, ARGLIST must be nil." | |||
| 226 | "Return the types OBJECT belongs to. | 245 | "Return the types OBJECT belongs to. |
| 227 | Return an unique list of types OBJECT belongs to, ordered from the | 246 | Return an unique list of types OBJECT belongs to, ordered from the |
| 228 | most specific type to the most general." | 247 | most specific type to the most general." |
| 229 | (let ((found (list (cl--type-parents (cl-type-of object))))) | 248 | (let (found) |
| 230 | ;; Build a DAG of all types OBJECT belongs to. | 249 | ;; Build a list of all types OBJECT belongs to. |
| 231 | (dolist (type cl--type-list) | 250 | (dolist (type cl--type-list) |
| 232 | (and | 251 | (and |
| 233 | ;; Skip type, if it previously produced an error. | 252 | ;; Skip type, if it previously produced an error. |
| @@ -241,24 +260,25 @@ most specific type to the most general." | |||
| 241 | ;; of another type, assuming that, most of the time, `assq' | 260 | ;; of another type, assuming that, most of the time, `assq' |
| 242 | ;; will be faster than `cl-typep'. | 261 | ;; will be faster than `cl-typep'. |
| 243 | (null (assq type found)) | 262 | (null (assq type found)) |
| 244 | ;; If OBJECT is of type, add type and its parents to the DAG. | 263 | ;; If OBJECT is of type, add type to the matching list. |
| 245 | (condition-case e | 264 | (condition-case-unless-debug e |
| 246 | (cl-typep object type) | 265 | (cl-typep object type) |
| 247 | (error (cl--type-error type e))) | 266 | (error (cl--type-error type e))) |
| 248 | ;; (dolist (p (cl--type-parents type)) | 267 | (push type found))) |
| 249 | ;; (push (cl--type-parents p) found)) | 268 | ;; Return an unique value of the list of types OBJECT belongs to, |
| 250 | ;; Equivalent to the `dolist' above, but faster: avoid to | 269 | ;; which is also the list of specifiers for OBJECT. |
| 251 | ;; recompute several lists of parents we already know. | ||
| 252 | (let ((pl (cl--type-parents type))) | ||
| 253 | (while pl | ||
| 254 | (push pl found) | ||
| 255 | (setq pl (cdr pl)))))) | ||
| 256 | ;; Compute an ordered list of types from the collected DAG. | ||
| 257 | (setq found (merge-ordered-lists found)) | ||
| 258 | ;; Return an unique value of this list of types, which is also the | ||
| 259 | ;; list of specifiers for this type. | ||
| 260 | (with-memoization (gethash found cl--type-unique) | 270 | (with-memoization (gethash found cl--type-unique) |
| 261 | found))) | 271 | ;; Compute a DAG from the collected matching types. |
| 272 | (let (dag) | ||
| 273 | (dolist (type found) | ||
| 274 | (let ((pl (cl--type-parents type))) | ||
| 275 | (while pl | ||
| 276 | (push pl dag) | ||
| 277 | (setq pl (cdr pl))))) | ||
| 278 | ;; Compute an ordered list of types from the DAG. | ||
| 279 | (merge-ordered-lists | ||
| 280 | (nreverse (cons (cl--type-parents (cl-type-of object)) | ||
| 281 | dag))))))) | ||
| 262 | 282 | ||
| 263 | ;;; Method dispatching | 283 | ;;; Method dispatching |
| 264 | ;; | 284 | ;; |
| @@ -268,7 +288,7 @@ most specific type to the most general." | |||
| 268 | (lambda (tag &rest _) (if (consp tag) tag))) | 288 | (lambda (tag &rest _) (if (consp tag) tag))) |
| 269 | 289 | ||
| 270 | (cl-defmethod cl-generic-generalizers :extra "cl-types-of" (type) | 290 | (cl-defmethod cl-generic-generalizers :extra "cl-types-of" (type) |
| 271 | "Support for dispatch on types." | 291 | "Support for dispatch on cl-types." |
| 272 | (if (cl--type-p type) | 292 | (if (cl--type-p type) |
| 273 | (list cl--type-generalizer) | 293 | (list cl--type-generalizer) |
| 274 | (cl-call-next-method))) | 294 | (cl-call-next-method))) |