aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Ponce2025-04-29 10:48:37 -0400
committerStefan Monnier2025-04-29 10:48:37 -0400
commit4323ff209f2f73ca4e6d389de69eb310988c0b1f (patch)
treef27fe1303c49d6b607716b717eb9ed266cebb438
parentdfbeb7478ecd817f888927154858c380fb60390f (diff)
downloademacs-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.el122
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.
46That is, a type of class `cl-type-class'." 48That 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.
51NAME is a symbol representing a type." 53NAME is a symbol representing a type.
52 `(cl--class-allparents (cl--find-class ,name))) 54Return 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.
56NAME is a symbol representing a type. 59NAME is a symbol representing a type.
57Return a possibly empty list of types." 60Return 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.
73NAME is an unquoted symbol representing a type. 70NAME is an unquoted symbol representing a cl-type.
74Signal an error if other types inherit from NAME." 71Signal 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.
86PARENTS is a list of types NAME is a subtype of, or nil. 84PARENTS is a list of types NAME is a subtype of, or nil.
87DOCSTRING is an optional documentation string." 85DOCSTRING 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.
227Return an unique list of types OBJECT belongs to, ordered from the 246Return an unique list of types OBJECT belongs to, ordered from the
228most specific type to the most general." 247most 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)))