aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2015-01-14 14:37:10 -0500
committerStefan Monnier2015-01-14 14:37:10 -0500
commit9def17e92bbb61e877bf092b562a92946cf43210 (patch)
tree5af1af25989bb45fcf7029fbf9ebf66281466232
parente7db8e8d5de70be5e047c961cdfbf692d52e33c6 (diff)
downloademacs-9def17e92bbb61e877bf092b562a92946cf43210.tar.gz
emacs-9def17e92bbb61e877bf092b562a92946cf43210.zip
* lisp/emacs-lisp/cl-generic.el: New file.
* lisp/emacs-lisp/cl-macs.el (cl-flet): Allow (FUN EXP) forms. (cl-load-time-value, cl-labels): Use closures rather than backquoted lambdas. (cl-macrolet): Use `eval' to create the function value, and support CL style arguments in for the defined macros. * test/automated/cl-generic-tests.el: New file.
-rw-r--r--etc/NEWS2
-rw-r--r--lisp/ChangeLog10
-rw-r--r--lisp/emacs-lisp/cl-generic.el605
-rw-r--r--lisp/emacs-lisp/cl-macs.el52
-rw-r--r--test/ChangeLog4
-rw-r--r--test/automated/cl-generic-tests.el131
6 files changed, 787 insertions, 17 deletions
diff --git a/etc/NEWS b/etc/NEWS
index b3267e1ce60..f291c0c9ad9 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -480,6 +480,8 @@ As a result of the above, these commands are now obsolete:
480 480
481* New Modes and Packages in Emacs 25.1 481* New Modes and Packages in Emacs 25.1
482 482
483** cl-generic.el provides CLOS-style multiple-dispatch generic functions.
484
483** scss-mode (a minor variant of css-mode) 485** scss-mode (a minor variant of css-mode)
484 486
485** let-alist is a new macro (and a package) that allows one to easily 487** let-alist is a new macro (and a package) that allows one to easily
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 6e2adc9b8e1..e0fb3cced0c 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,15 @@
12015-01-14 Stefan Monnier <monnier@iro.umontreal.ca> 12015-01-14 Stefan Monnier <monnier@iro.umontreal.ca>
2 2
3 * emacs-lisp/cl-generic.el: New file.
4
5 * emacs-lisp/cl-macs.el (cl-flet): Allow (FUN EXP) forms.
6 (cl-load-time-value, cl-labels): Use closures rather than
7 backquoted lambdas.
8 (cl-macrolet): Use `eval' to create the function value, and support CL
9 style arguments in for the defined macros.
10
112015-01-14 Stefan Monnier <monnier@iro.umontreal.ca>
12
3 * net/eww.el: Use lexical-binding. 13 * net/eww.el: Use lexical-binding.
4 (eww-links-at-point): Remove unused arg. 14 (eww-links-at-point): Remove unused arg.
5 (eww-mode-map): Inherit from special-mode-map. 15 (eww-mode-map): Inherit from special-mode-map.
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
new file mode 100644
index 00000000000..19e4ce0fbef
--- /dev/null
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -0,0 +1,605 @@
1;;; cl-generic.el --- CLOS-style generic functions for Elisp -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2015 Stefan Monnier
4
5;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6
7;; This program is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; This program is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; This implements the most of CLOS's multiple-dispatch generic functions.
23;; To use it you need either (require 'cl-generic) or (require 'cl-lib).
24;; The main entry points are: `cl-defgeneric' and `cl-defmethod'.
25
26;; Missing elements:
27;; - We don't support next-method-p, make-method, call-method,
28;; define-method-combination.
29;; - Method and generic function objects: CLOS defines methods as objects
30;; (same for generic functions), whereas we don't offer such an abstraction.
31;; - `no-next-method' should receive the "calling method" object, but since we
32;; don't have such a thing, we pass nil instead.
33;; - In defgeneric we don't support the options:
34;; declare, :method-combination, :generic-function-class, :method-class,
35;; :method.
36;; Added elements:
37;; - We support aliases to generic functions.
38;; - The kind of thing on which to dispatch can be extended.
39;; There is support in this file for (eql <val>) dispatch as well as dispatch
40;; on the type of CL structs, and eieio-core.el adds support for EIEIO
41;; defclass objects.
42
43;;; Code:
44
45;; Note: For generic functions that dispatch on several arguments (i.e. those
46;; which use the multiple-dispatch feature), we always use the same "tagcodes"
47;; and the same set of arguments on which to dispatch. This works, but is
48;; often suboptimal since after one dispatch, the remaining dispatches can
49;; usually be simplified, or even completely skipped.
50
51(eval-when-compile (require 'cl-lib))
52(eval-when-compile (require 'pcase))
53
54(defvar cl-generic-tagcode-function
55 (lambda (type _name)
56 (if (eq type t) '(0 . 'cl--generic-type)
57 (error "Unknown specializer %S" type)))
58 "Function to get the Elisp code to extract the tag on which we dispatch.
59Takes a \"parameter-specializer-name\" and a variable name, and returns
60a pair (PRIORITY . CODE) where CODE is an Elisp expression that should be
61used to extract the \"tag\" (from the object held in the named variable)
62that should uniquely determine if we have a match
63\(i.e. the \"tag\" is the value that will be used to dispatch to the proper
64method(s)).
65Such \"tagcodes\" will be or'd together.
66PRIORITY is an integer from 0 to 100 which is used to sort the tagcodes
67in the `or'. The higher the priority, the more specific the tag should be.
68More specifically, if PRIORITY is N and we have two objects X and Y
69whose tag (according to TAGCODE) is `eql', then it should be the case
70that for all other (PRIORITY . TAGCODE) where PRIORITY ≤ N, then
71\(eval TAGCODE) for X is `eql' to (eval TAGCODE) for Y.")
72
73(defvar cl-generic-tag-types-function
74 (lambda (tag) (if (eq tag 'cl--generic-type) '(t)))
75 "Function to get the list of types that a given \"tag\" matches.
76They should be sorted from most specific to least specific.")
77
78(cl-defstruct (cl--generic
79 (:constructor nil)
80 (:constructor cl--generic-make
81 (name &optional dispatches method-table))
82 (:predicate nil))
83 (name nil :read-only t) ;Pointer back to the symbol.
84 ;; `dispatches' holds a list of (ARGNUM . TAGCODES) where ARGNUM is the index
85 ;; of the corresponding argument and TAGCODES is a list of (PRIORITY . EXP)
86 ;; where the EXPs are expressions (to be `or'd together) to compute the tag
87 ;; on which to dispatch and PRIORITY is the priority of each expression to
88 ;; decide in which order to sort them.
89 ;; The most important dispatch is last in the list (and the least is first).
90 dispatches
91 ;; `method-table' is a list of
92 ;; ((SPECIALIZERS . QUALIFIER) USES-CNM . FUNCTION), where
93 ;; USES-CNM is a boolean indicating if FUNCTION calls `cl-call-next-method'
94 ;; (and hence expects an extra argument holding the next-method).
95 method-table)
96
97(defmacro cl--generic (name)
98 `(get ,name 'cl--generic))
99
100(defun cl-generic-ensure-function (name)
101 (let (generic
102 (origname name))
103 (while (and (null (setq generic (cl--generic name)))
104 (fboundp name)
105 (symbolp (symbol-function name)))
106 (setq name (symbol-function name)))
107 (unless (or (not (fboundp name))
108 (and (functionp name) generic))
109 (error "%s is already defined as something else than a generic function"
110 origname))
111 (if generic
112 (cl-assert (eq name (cl--generic-name generic)))
113 (setf (cl--generic name) (setq generic (cl--generic-make name)))
114 (defalias name (cl--generic-make-function generic)))
115 generic))
116
117(defun cl--generic-setf-rewrite (name)
118 (let ((setter (intern (format "cl-generic-setter--%s" name))))
119 (cons setter
120 `(eval-and-compile
121 (unless (eq ',setter (get ',name 'cl-generic-setter))
122 ;; (when (get ',name 'gv-expander)
123 ;; (error "gv-expander conflicts with (setf %S)" ',name))
124 (setf (get ',name 'cl-generic-setter) ',setter)
125 (gv-define-setter ,name (val &rest args)
126 (cons ',setter (cons val args))))))))
127
128;;;###autoload
129(defmacro cl-defgeneric (name args &rest options-and-methods)
130 "Create a generic function NAME.
131DOC-STRING is the base documentation for this class. A generic
132function has no body, as its purpose is to decide which method body
133is appropriate to use. Specific methods are defined with `defmethod'.
134With this implementation the ARGS are currently ignored.
135OPTIONS-AND-METHODS is currently only used to specify the docstring,
136via (:documentation DOCSTRING)."
137 (declare (indent 2) (doc-string 3))
138 (let* ((docprop (assq :documentation options-and-methods))
139 (doc (cond ((stringp (car-safe options-and-methods))
140 (pop options-and-methods))
141 (docprop
142 (prog1
143 (cadr docprop)
144 (setq options-and-methods
145 (delq docprop options-and-methods)))))))
146 `(progn
147 ,(when (eq 'setf (car-safe name))
148 (pcase-let ((`(,setter . ,code) (cl--generic-setf-rewrite
149 (cadr name))))
150 (setq name setter)
151 code))
152 (defalias ',name
153 (cl-generic-define ',name ',args ',options-and-methods)
154 ,doc))))
155
156(defun cl--generic-mandatory-args (args)
157 (let ((res ()))
158 (while (not (memq (car args) '(nil &rest &optional &key)))
159 (push (pop args) res))
160 (nreverse res)))
161
162;;;###autoload
163(defun cl-generic-define (name args options-and-methods)
164 (let ((generic (cl-generic-ensure-function name))
165 (mandatory (cl--generic-mandatory-args args))
166 (apo (assq :argument-precedence-order options-and-methods)))
167 (setf (cl--generic-dispatches generic) nil)
168 (when apo
169 (dolist (arg (cdr apo))
170 (let ((pos (memq arg mandatory)))
171 (unless pos (error "%S is not a mandatory argument" arg))
172 (push (list (- (length mandatory) (length pos)))
173 (cl--generic-dispatches generic)))))
174 (setf (cl--generic-method-table generic) nil)
175 (cl--generic-make-function generic)))
176
177(defvar cl-generic-current-method-specializers nil
178 ;; This is let-bound during macro-expansion of method bodies, so that those
179 ;; bodies can be optimized knowing that the specializers have matched.
180 ;; FIXME: This presumes the formal arguments aren't modified via `setq' and
181 ;; aren't shadowed either ;-(
182 ;; FIXME: This might leak outside the scope of the method if, during
183 ;; macroexpansion of the method, something causes some other macroexpansion
184 ;; (e.g. an autoload).
185 "List of (VAR . TYPE) where TYPE is var's specializer.")
186
187(eval-and-compile ;Needed while compiling the cl-defmethod calls below!
188 (defun cl--generic-fgrep (vars sexp) ;Copied from pcase.el.
189 "Check which of the symbols VARS appear in SEXP."
190 (let ((res '()))
191 (while (consp sexp)
192 (dolist (var (cl--generic-fgrep vars (pop sexp)))
193 (unless (memq var res) (push var res))))
194 (and (memq sexp vars) (not (memq sexp res)) (push sexp res))
195 res))
196
197 (defun cl--generic-lambda (args body with-cnm)
198 "Make the lambda expression for a method with ARGS and BODY."
199 (let ((plain-args ())
200 (cl-generic-current-method-specializers nil)
201 (doc-string (if (stringp (car-safe body)) (pop body)))
202 (mandatory t))
203 (dolist (arg args)
204 (push (pcase arg
205 ((or '&optional '&rest '&key) (setq mandatory nil) arg)
206 ((and `(,name . ,type) (guard mandatory))
207 (push (cons name (car type))
208 cl-generic-current-method-specializers)
209 name)
210 (_ arg))
211 plain-args))
212 (setq plain-args (nreverse plain-args))
213 (let ((fun `(cl-function (lambda ,plain-args
214 ,@(if doc-string (list doc-string))
215 ,@body))))
216 (if (not with-cnm)
217 (cons nil fun)
218 ;; First macroexpand away the cl-function stuff (e.g. &key and
219 ;; destructuring args, `declare' and whatnot).
220 (pcase (macroexpand fun macroexpand-all-environment)
221 (`#'(lambda ,args . ,body)
222 (require 'cl-lib) ;Needed to expand `cl-flet'.
223 (let* ((doc-string (and doc-string (stringp (car body))
224 (pop body)))
225 (cnm (make-symbol "cl--cnm"))
226 (nbody (macroexpand-all
227 `(cl-flet ((cl-call-next-method ,cnm))
228 ,@body)
229 macroexpand-all-environment))
230 ;; FIXME: Rather than `grep' after the fact, the
231 ;; macroexpansion should directly set some flag when cnm
232 ;; is used.
233 ;; FIXME: Also, optimize the case where call-next-method is
234 ;; only called with explicit arguments.
235 (uses-cnm (cl--generic-fgrep (list cnm) nbody)))
236 (cons (not (not uses-cnm))
237 `#'(lambda (,@(if uses-cnm (list cnm)) ,@args)
238 ,@(if doc-string (list doc-string))
239 ,nbody))))
240 (f (error "Unexpected macroexpansion result: %S" f))))))))
241
242
243;;;###autoload
244(defmacro cl-defmethod (name args &rest body)
245 "Define a new method for generic function NAME.
246I.e. it defines the implementation of NAME to use for invocations where the
247value of the dispatch argument matches the specified TYPE.
248The dispatch argument has to be one of the mandatory arguments, and
249all methods of NAME have to use the same argument for dispatch.
250The dispatch argument and TYPE are specified in ARGS where the corresponding
251formal argument appears as (VAR TYPE) rather than just VAR.
252
253The optional second argument QUALIFIER is a specifier that
254modifies how the method is combined with other methods, including:
255 :before - Method will be called before the primary
256 :after - Method will be called after the primary
257 :around - Method will be called around everything else
258The absence of QUALIFIER means this is a \"primary\" method.
259
260Other than a type, TYPE can also be of the form `(eql VAL)' in
261which case this method will be invoked when the argument is `eql' to VAL.
262
263\(fn NAME [QUALIFIER] ARGS &rest [DOCSTRING] BODY)"
264 (declare (doc-string 3) (indent 2))
265 (let ((qualifiers nil))
266 (while (keywordp args)
267 (push args qualifiers)
268 (setq args (pop body)))
269 (pcase-let* ((with-cnm (not (memq (car qualifiers) '(:before :after))))
270 (`(,uses-cnm . ,fun) (cl--generic-lambda args body with-cnm)))
271 `(progn
272 ,(when (eq 'setf (car-safe name))
273 (pcase-let ((`(,setter . ,code) (cl--generic-setf-rewrite
274 (cadr name))))
275 (setq name setter)
276 code))
277 (cl-generic-define-method ',name ',qualifiers ',args
278 ,uses-cnm ,fun)))))
279
280;;;###autoload
281(defun cl-generic-define-method (name qualifiers args uses-cnm function)
282 (when (> (length qualifiers) 1)
283 (error "We only support a single qualifier per method: %S" qualifiers))
284 (unless (memq (car qualifiers) '(nil :primary :around :after :before))
285 (error "Unsupported qualifier in: %S" qualifiers))
286 (let* ((generic (cl-generic-ensure-function name))
287 (mandatory (cl--generic-mandatory-args args))
288 (specializers
289 (mapcar (lambda (arg) (if (consp arg) (cadr arg) t)) mandatory))
290 (key (cons specializers (or (car qualifiers) ':primary)))
291 (mt (cl--generic-method-table generic))
292 (me (assoc key mt))
293 (dispatches (cl--generic-dispatches generic))
294 (i 0))
295 (dolist (specializer specializers)
296 (let* ((tagcode (funcall cl-generic-tagcode-function specializer 'arg))
297 (x (assq i dispatches)))
298 (if (not x)
299 (setf (cl--generic-dispatches generic)
300 (setq dispatches (cons (list i tagcode) dispatches)))
301 (unless (member tagcode (cdr x))
302 (setf (cdr x)
303 (nreverse (sort (cons tagcode (cdr x))
304 #'car-less-than-car)))))
305 (setq i (1+ i))))
306 (if me (setcdr me (cons uses-cnm function))
307 (setf (cl--generic-method-table generic)
308 (cons `(,key ,uses-cnm . ,function) mt))
309 ;; For aliases, cl--generic-name gives us the actual name.
310 (defalias (cl--generic-name generic)
311 (cl--generic-make-function generic)))))
312
313(defmacro cl--generic-with-memoization (place &rest code)
314 (declare (indent 1) (debug t))
315 (gv-letplace (getter setter) place
316 `(or ,getter
317 ,(macroexp-let2 nil val (macroexp-progn code)
318 `(progn
319 ,(funcall setter val)
320 ,val)))))
321
322(defvar cl--generic-dispatchers (make-hash-table :test #'equal))
323
324(defun cl--generic-get-dispatcher (tagcodes dispatch-arg)
325 (cl--generic-with-memoization
326 (gethash (cons dispatch-arg tagcodes) cl--generic-dispatchers)
327 (let ((lexical-binding t)
328 (extraargs ()))
329 (dotimes (_ dispatch-arg)
330 (push (make-symbol "arg") extraargs))
331 (byte-compile
332 `(lambda (generic dispatches-left)
333 (let ((method-cache (make-hash-table :test #'eql)))
334 (lambda (,@extraargs arg &rest args)
335 (apply (cl--generic-with-memoization
336 (gethash (or ,@(mapcar #'cdr tagcodes)) method-cache)
337 (cl--generic-cache-miss
338 generic ',dispatch-arg dispatches-left
339 (list ,@(mapcar #'cdr tagcodes))))
340 ,@extraargs arg args))))))))
341
342(defun cl--generic-make-function (generic)
343 (let* ((dispatches (cl--generic-dispatches generic))
344 (dispatch
345 (progn
346 (while (and dispatches
347 (member (cdar dispatches)
348 '(nil ((0 . 'cl--generic-type)))))
349 (setq dispatches (cdr dispatches)))
350 (pop dispatches))))
351 (if (null dispatch)
352 (cl--generic-build-combined-method
353 (cl--generic-name generic)
354 (cl--generic-method-table generic))
355 (let ((dispatcher (cl--generic-get-dispatcher
356 (cdr dispatch) (car dispatch))))
357 (funcall dispatcher generic dispatches)))))
358
359(defun cl--generic-nest (fun methods)
360 (pcase-dolist (`(,uses-cnm . ,method) methods)
361 (setq fun
362 (if (not uses-cnm) method
363 (let ((next fun))
364 (lambda (&rest args)
365 (apply method
366 ;; FIXME: This sucks: passing just `next' would
367 ;; be a lot more efficient than the lambda+apply
368 ;; quasi-η, but we need this to implement the
369 ;; "if call-next-method is called with no
370 ;; arguments, then use the previous arguments".
371 (lambda (&rest cnm-args)
372 (apply next (or cnm-args args)))
373 args))))))
374 fun)
375
376(defvar cl--generic-combined-method-memoization
377 (make-hash-table :test #'equal :weakness 'value)
378 "Table storing previously built combined-methods.
379This is particularly useful when many different tags select the same set
380of methods, since this table then allows us to share a single combined-method
381for all those different tags in the method-cache.")
382
383(defun cl--generic-build-combined-method (generic-name methods)
384 (let ((mets-by-qual ()))
385 (dolist (qm methods)
386 (push (cdr qm) (alist-get (cdar qm) mets-by-qual)))
387 (cl--generic-with-memoization
388 (gethash (cons generic-name mets-by-qual)
389 cl--generic-combined-method-memoization)
390 (cond
391 ((null mets-by-qual) (lambda (&rest args)
392 (cl-no-applicable-method generic-name args)))
393 (t
394 (let* ((fun (lambda (&rest args)
395 ;; FIXME: CLOS passes as second arg the "calling method".
396 ;; We don't currently have "method objects" like CLOS
397 ;; does so we can't really do it the CLOS way.
398 ;; The closest would be to pass the lambda corresponding
399 ;; to the method, but the caller wouldn't be able to do
400 ;; much with it anyway. So we pass nil for now.
401 (apply #'cl-no-next-method generic-name nil args)))
402 ;; We use `cdr' to drop the `uses-cnm' annotations.
403 (before
404 (mapcar #'cdr (reverse (alist-get :before mets-by-qual))))
405 (after (mapcar #'cdr (alist-get :after mets-by-qual))))
406 (setq fun (cl--generic-nest fun (alist-get :primary mets-by-qual)))
407 (when (or after before)
408 (let ((next fun))
409 (setq fun (lambda (&rest args)
410 (dolist (bf before)
411 (apply bf args))
412 (apply next args)
413 (dolist (af after)
414 (apply af args))))))
415 (cl--generic-nest fun (alist-get :around mets-by-qual))))))))
416
417(defun cl--generic-cache-miss (generic dispatch-arg dispatches-left tags)
418 (let ((types (apply #'append (mapcar cl-generic-tag-types-function tags)))
419 (methods '()))
420 (dolist (method-desc (cl--generic-method-table generic))
421 (let ((m (member (nth dispatch-arg (caar method-desc)) types)))
422 (when m
423 (push (cons (length m) method-desc) methods))))
424 ;; Sort the methods, most specific first.
425 ;; It would be tempting to sort them once and for all in the method-table
426 ;; rather than here, but the order might depend on the actual argument
427 ;; (e.g. for multiple inheritance with defclass).
428 (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car))))
429 (cl--generic-make-function (cl--generic-make (cl--generic-name generic)
430 dispatches-left methods))))
431
432;;; Define some pre-defined generic functions, used internally.
433
434(define-error 'cl-no-method "No method for %S")
435(define-error 'cl-no-next-method "No next method for %S" 'cl-no-method)
436(define-error 'cl-no-applicable-method "No applicable method for %S"
437 'cl-no-method)
438
439(cl-defgeneric cl-no-next-method (generic method &rest args)
440 "Function called when `cl-call-next-method' finds no next method.")
441(cl-defmethod cl-no-next-method ((generic t) method &rest args)
442 (signal 'cl-no-next-method `(,generic ,method ,@args)))
443
444(cl-defgeneric cl-no-applicable-method (generic &rest args)
445 "Function called when a method call finds no applicable method.")
446(cl-defmethod cl-no-applicable-method ((generic t) &rest args)
447 (signal 'cl-no-applicable-method `(,generic ,@args)))
448
449(defun cl-call-next-method (&rest _args)
450 "Function to call the next applicable method.
451Can only be used from within the lexical body of a primary or around method."
452 (error "cl-call-next-method only allowed inside primary and around methods"))
453
454;;; Add support for describe-function
455
456(add-hook 'help-fns-describe-function-functions 'cl--generic-describe)
457(defun cl--generic-describe (function)
458 ;; FIXME: Fix up the main "in `<file>'" hyperlink, and add such hyperlinks
459 ;; for each method.
460 (let ((generic (if (symbolp function) (cl--generic function))))
461 (when generic
462 (save-excursion
463 (insert "\n\nThis is a generic function.\n\n")
464 (insert (propertize "Implementations:\n\n" 'face 'bold))
465 ;; Loop over fanciful generics
466 (pcase-dolist (`((,type . ,qualifier) . ,method)
467 (cl--generic-method-table generic))
468 (insert "`")
469 (if (symbolp type)
470 ;; FIXME: Add support for cl-structs in help-variable.
471 (help-insert-xref-button (symbol-name type)
472 'help-variable type)
473 (insert (format "%S" type)))
474 (insert (format "' %S %S\n"
475 (car qualifier)
476 (let ((args (help-function-arglist method)))
477 ;; Drop cl--generic-next arg if present.
478 (if (memq (car qualifier) '(:after :before))
479 args (cdr args)))))
480 (insert (or (documentation method) "Undocumented") "\n\n"))))))
481
482;;; Support for (eql <val>) specializers.
483
484(defvar cl--generic-eql-used (make-hash-table :test #'eql))
485
486(add-function :before-until cl-generic-tagcode-function
487 #'cl--generic-eql-tagcode)
488(defun cl--generic-eql-tagcode (type name)
489 (when (eq (car-safe type) 'eql)
490 (puthash (cadr type) type cl--generic-eql-used)
491 `(100 . (gethash ,name cl--generic-eql-used))))
492
493(add-function :before-until cl-generic-tag-types-function
494 #'cl--generic-eql-tag-types)
495(defun cl--generic-eql-tag-types (tag)
496 (if (eq (car-safe tag) 'eql) (list tag)))
497
498;;; Support for cl-defstructs specializers.
499
500(add-function :before-until cl-generic-tagcode-function
501 #'cl--generic-struct-tagcode)
502(defun cl--generic-struct-tagcode (type name)
503 (and (symbolp type)
504 (get type 'cl-struct-type)
505 (or (eq 'vector (car (get type 'cl-struct-type)))
506 (error "Can't dispatch on cl-struct %S: type is %S"
507 type (car (get type 'cl-struct-type))))
508 (or (equal '(cl-tag-slot) (car (get type 'cl-struct-slots)))
509 (error "Can't dispatch on cl-struct %S: no tag in slot 0"
510 type))
511 ;; We could/should check the vector has length >0,
512 ;; but really, mixing vectors and structs is a bad idea,
513 ;; so let's not waste time trying to handle the case
514 ;; of an empty vector.
515 ;; BEWARE: this returns a bogus tag for non-struct vectors.
516 `(50 . (and (vectorp ,name) (aref ,name 0)))))
517
518(add-function :before-until cl-generic-tag-types-function
519 #'cl--generic-struct-tag-types)
520(defun cl--generic-struct-tag-types (tag)
521 ;; FIXME: cl-defstruct doesn't make it easy for us.
522 (and (symbolp tag)
523 ;; A method call shouldn't itself mess with the match-data.
524 (string-match-p "\\`cl-struct-\\(.*\\)" (symbol-name tag))
525 (let ((types (list (intern (substring (symbol-name tag) 10)))))
526 (while (get (car types) 'cl-struct-include)
527 (push (get (car types) 'cl-struct-include) types))
528 (push 'cl-struct types) ;The "parent type" of all cl-structs.
529 (nreverse types))))
530
531;;; Dispatch on "old-style types".
532
533(defconst cl--generic-typeof-types
534 ;; Hand made from the source code of `type-of'.
535 '((integer number) (symbol) (string array) (cons list)
536 ;; Markers aren't `numberp', yet they are accepted wherever integers are
537 ;; accepted, pretty much.
538 (marker) (overlay) (float number) (window-configuration)
539 (process) (window) (subr) (compiled-function) (buffer) (char-table array)
540 (bool-vector array)
541 (frame) (hash-table) (font-spec) (font-entity) (font-object)
542 (vector array)
543 ;; Plus, hand made:
544 (null list symbol)
545 (list)
546 (array)
547 (number)))
548
549(add-function :before-until cl-generic-tagcode-function
550 #'cl--generic-typeof-tagcode)
551(defun cl--generic-typeof-tagcode (type name)
552 ;; FIXME: Add support for other types accepted by `cl-typep' such
553 ;; as `character', `atom', `face', `function', ...
554 (and (assq type cl--generic-typeof-types)
555 (progn
556 (if (memq type '(vector array))
557 (message "`%S' also matches CL structs and EIEIO classes" type))
558 ;; FIXME: We could also change `type-of' to return `null' for nil.
559 `(10 . (if ,name (type-of ,name) 'null)))))
560
561(add-function :before-until cl-generic-tag-types-function
562 #'cl--generic-typeof-types)
563(defun cl--generic-typeof-types (tag)
564 (and (symbolp tag)
565 (assq tag cl--generic-typeof-types)))
566
567;;; Just for kicks: dispatch on major-mode
568;;
569;; Here's how you'd use it:
570;; (cl-defmethod foo ((x (major-mode text-mode)) y z) ...)
571;; And then
572;; (foo 'major-mode toto titi)
573;;
574;; FIXME: Better would be to do that via dispatch on an "implicit argument".
575
576;; (defvar cl--generic-major-modes (make-hash-table :test #'eq))
577;;
578;; (add-function :before-until cl-generic-tagcode-function
579;; #'cl--generic-major-mode-tagcode)
580;; (defun cl--generic-major-mode-tagcode (type name)
581;; (if (eq 'major-mode (car-safe type))
582;; `(50 . (if (eq ,name 'major-mode)
583;; (cl--generic-with-memoization
584;; (gethash major-mode cl--generic-major-modes)
585;; `(cl--generic-major-mode . ,major-mode))))))
586;;
587;; (add-function :before-until cl-generic-tag-types-function
588;; #'cl--generic-major-mode-types)
589;; (defun cl--generic-major-mode-types (tag)
590;; (when (eq (car-safe tag) 'cl--generic-major-mode)
591;; (if (eq tag 'fundamental-mode) '(fundamental-mode t)
592;; (let ((types `((major-mode ,(cdr tag)))))
593;; (while (get (car types) 'derived-mode-parent)
594;; (push (list 'major-mode (get (car types) 'derived-mode-parent))
595;; types))
596;; (unless (eq 'fundamental-mode (car types))
597;; (push '(major-mode fundamental-mode) types))
598;; (nreverse types)))))
599
600;; Local variables:
601;; generated-autoload-file: "cl-loaddefs.el"
602;; End:
603
604(provide 'cl-generic)
605;;; cl-generic.el ends here
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index fff5b27315c..0070599af6f 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -625,14 +625,20 @@ The result of the body appears to the compiler as a quoted constant."
625 (set `(setq ,temp ,form))) 625 (set `(setq ,temp ,form)))
626 (if (and (fboundp 'byte-compile-file-form-defmumble) 626 (if (and (fboundp 'byte-compile-file-form-defmumble)
627 (boundp 'this-kind) (boundp 'that-one)) 627 (boundp 'this-kind) (boundp 'that-one))
628 (fset 'byte-compile-file-form 628 ;; Else, we can't output right away, so we have to delay it to the
629 `(lambda (form) 629 ;; next time we're at the top-level.
630 (fset 'byte-compile-file-form 630 ;; FIXME: Use advice-add/remove.
631 ',(symbol-function 'byte-compile-file-form)) 631 (fset 'byte-compile-file-form
632 (byte-compile-file-form ',set) 632 (let ((old (symbol-function 'byte-compile-file-form)))
633 (byte-compile-file-form form))) 633 (lambda (form)
634 (print set (symbol-value 'byte-compile--outbuffer))) 634 (fset 'byte-compile-file-form old)
635 `(symbol-value ',temp)) 635 (byte-compile-file-form set)
636 (byte-compile-file-form form))))
637 ;; If we're not in the middle of compiling something, we can
638 ;; output directly to byte-compile-outbuffer, to make sure
639 ;; temp is set before we use it.
640 (print set byte-compile--outbuffer))
641 temp)
636 `',(eval form))) 642 `',(eval form)))
637 643
638 644
@@ -1824,18 +1830,30 @@ a `let' form, except that the list of symbols can be computed at run-time."
1824(defmacro cl-flet (bindings &rest body) 1830(defmacro cl-flet (bindings &rest body)
1825 "Make local function definitions. 1831 "Make local function definitions.
1826Like `cl-labels' but the definitions are not recursive. 1832Like `cl-labels' but the definitions are not recursive.
1833Each binding can take the form (FUNC EXP) where
1834FUNC is the function name, and EXP is an expression that returns the
1835function value to which it should be bound, or it can take the more common
1836form \(FUNC ARGLIST BODY...) which is a shorthand
1837for (FUNC (lambda ARGLIST BODY)).
1827 1838
1828\(fn ((FUNC ARGLIST BODY...) ...) FORM...)" 1839\(fn ((FUNC ARGLIST BODY...) ...) FORM...)"
1829 (declare (indent 1) (debug ((&rest (cl-defun)) cl-declarations body))) 1840 (declare (indent 1) (debug ((&rest (cl-defun)) cl-declarations body)))
1830 (let ((binds ()) (newenv macroexpand-all-environment)) 1841 (let ((binds ()) (newenv macroexpand-all-environment))
1831 (dolist (binding bindings) 1842 (dolist (binding bindings)
1832 (let ((var (make-symbol (format "--cl-%s--" (car binding))))) 1843 (let ((var (make-symbol (format "--cl-%s--" (car binding))))
1833 (push (list var `(cl-function (lambda . ,(cdr binding)))) binds) 1844 (args-and-body (cdr binding)))
1845 (if (and (= (length args-and-body) 1) (symbolp (car args-and-body)))
1846 ;; Optimize (cl-flet ((fun var)) body).
1847 (setq var (car args-and-body))
1848 (push (list var (if (= (length args-and-body) 1)
1849 (car args-and-body)
1850 `(cl-function (lambda . ,args-and-body))))
1851 binds))
1834 (push (cons (car binding) 1852 (push (cons (car binding)
1835 `(lambda (&rest cl-labels-args) 1853 (lambda (&rest cl-labels-args)
1836 (cl-list* 'funcall ',var 1854 (cl-list* 'funcall var cl-labels-args)))
1837 cl-labels-args)))
1838 newenv))) 1855 newenv)))
1856 ;; FIXME: Eliminate those functions which aren't referenced.
1839 `(let ,(nreverse binds) 1857 `(let ,(nreverse binds)
1840 ,@(macroexp-unprogn 1858 ,@(macroexp-unprogn
1841 (macroexpand-all 1859 (macroexpand-all
@@ -1869,9 +1887,8 @@ in closures will only work if `lexical-binding' is in use.
1869 (let ((var (make-symbol (format "--cl-%s--" (car binding))))) 1887 (let ((var (make-symbol (format "--cl-%s--" (car binding)))))
1870 (push (list var `(cl-function (lambda . ,(cdr binding)))) binds) 1888 (push (list var `(cl-function (lambda . ,(cdr binding)))) binds)
1871 (push (cons (car binding) 1889 (push (cons (car binding)
1872 `(lambda (&rest cl-labels-args) 1890 (lambda (&rest cl-labels-args)
1873 (cl-list* 'funcall ',var 1891 (cl-list* 'funcall var cl-labels-args)))
1874 cl-labels-args)))
1875 newenv))) 1892 newenv)))
1876 (macroexpand-all `(letrec ,(nreverse binds) ,@body) 1893 (macroexpand-all `(letrec ,(nreverse binds) ,@body)
1877 ;; Don't override lexical-let's macro-expander. 1894 ;; Don't override lexical-let's macro-expander.
@@ -1898,7 +1915,8 @@ This is like `cl-flet', but for macros instead of functions.
1898 (res (cl--transform-lambda (cdar bindings) name))) 1915 (res (cl--transform-lambda (cdar bindings) name)))
1899 (eval (car res)) 1916 (eval (car res))
1900 (macroexpand-all (macroexp-progn body) 1917 (macroexpand-all (macroexp-progn body)
1901 (cons (cons name `(lambda ,@(cdr res))) 1918 (cons (cons name
1919 (eval `(cl-function (lambda ,@(cdr res))) t))
1902 macroexpand-all-environment)))))) 1920 macroexpand-all-environment))))))
1903 1921
1904(defconst cl--old-macroexpand 1922(defconst cl--old-macroexpand
diff --git a/test/ChangeLog b/test/ChangeLog
index 83bb8bf00c7..211a06c2cbd 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,3 +1,7 @@
12015-01-14 Stefan Monnier <monnier@iro.umontreal.ca>
2
3 * automated/cl-generic-tests.el: New file.
4
12015-01-08 Stefan Monnier <monnier@iro.umontreal.ca> 52015-01-08 Stefan Monnier <monnier@iro.umontreal.ca>
2 6
3 * automated/eieio-tests.el (eieio-test-23-inheritance-check): Don't use 7 * automated/eieio-tests.el (eieio-test-23-inheritance-check): Don't use
diff --git a/test/automated/cl-generic-tests.el b/test/automated/cl-generic-tests.el
new file mode 100644
index 00000000000..5c5e5d1c7ce
--- /dev/null
+++ b/test/automated/cl-generic-tests.el
@@ -0,0 +1,131 @@
1;;; cl-generic-tests.el --- Tests for cl-generic.el functionality -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2015 Stefan Monnier
4
5;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6
7;; This program is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; This program is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;;; Code:
23
24(require 'ert)
25(require 'cl-lib)
26
27(cl-defgeneric cl--generic-1 (x y))
28(cl-defgeneric (setf cl--generic-1) (v y z) "My generic doc.")
29
30(ert-deftest cl-generic-test-0 ()
31 (cl-defgeneric cl--generic-1 (x y))
32 (cl-defmethod cl--generic-1 ((x t) y) (cons x y))
33 (should (equal (cl--generic-1 'a 'b) '(a . b))))
34
35(ert-deftest cl-generic-test-1-eql ()
36 (cl-defgeneric cl--generic-1 (x y))
37 (cl-defmethod cl--generic-1 ((x t) y) (cons x y))
38 (cl-defmethod cl--generic-1 ((_x (eql 4)) _y)
39 (cons "quatre" (cl-call-next-method)))
40 (cl-defmethod cl--generic-1 ((_x (eql 5)) _y)
41 (cons "cinq" (cl-call-next-method)))
42 (cl-defmethod cl--generic-1 ((_x (eql 6)) y)
43 (cons "six" (cl-call-next-method 'a y)))
44 (should (equal (cl--generic-1 'a nil) '(a)))
45 (should (equal (cl--generic-1 4 nil) '("quatre" 4)))
46 (should (equal (cl--generic-1 5 nil) '("cinq" 5)))
47 (should (equal (cl--generic-1 6 nil) '("six" a))))
48
49(cl-defstruct cl-generic-struct-parent a b)
50(cl-defstruct (cl-generic-struct-child1 (:include cl-generic-struct-parent)) c)
51(cl-defstruct (cl-generic-struct-child11 (:include cl-generic-struct-child1)) d)
52(cl-defstruct (cl-generic-struct-child2 (:include cl-generic-struct-parent)) e)
53
54(ert-deftest cl-generic-test-2-struct ()
55 (cl-defgeneric cl--generic-1 (x y) "My doc.")
56 (cl-defmethod cl--generic-1 ((x t) y) "Doc 1." (cons x y))
57 (cl-defmethod cl--generic-1 ((_x cl-generic-struct-parent) y)
58 "Doc 2." (cons "parent" (cl-call-next-method 'a y)))
59 (cl-defmethod cl--generic-1 ((_x cl-generic-struct-child1) _y)
60 (cons "child1" (cl-call-next-method)))
61 (cl-defmethod cl--generic-1 :around ((_x t) _y)
62 (cons "around" (cl-call-next-method)))
63 (cl-defmethod cl--generic-1 :around ((_x cl-generic-struct-child11) _y)
64 (cons "child11" (cl-call-next-method)))
65 (cl-defmethod cl--generic-1 ((_x cl-generic-struct-child2) _y)
66 (cons "child2" (cl-call-next-method)))
67 (should (equal (cl--generic-1 (make-cl-generic-struct-child1) nil)
68 '("around" "child1" "parent" a)))
69 (should (equal (cl--generic-1 (make-cl-generic-struct-child2) nil)
70 '("around""child2" "parent" a)))
71 (should (equal (cl--generic-1 (make-cl-generic-struct-child11) nil)
72 '("child11" "around""child1" "parent" a))))
73
74(ert-deftest cl-generic-test-3-setf ()
75 (cl-defmethod (setf cl--generic-1) (v (y t) z) (list v y z))
76 (cl-defmethod (setf cl--generic-1) (v (_y (eql 4)) z) (list v "four" z))
77 (should (equal (setf (cl--generic-1 'a 'b) 'v) '(v a b)))
78 (should (equal (setf (cl--generic-1 4 'b) 'v) '(v "four" b)))
79 (let ((x ()))
80 (should (equal (setf (cl--generic-1 (progn (push 1 x) 'a)
81 (progn (push 2 x) 'b))
82 (progn (push 3 x) 'v))
83 '(v a b)))
84 (should (equal x '(3 2 1)))))
85
86(ert-deftest cl-generic-test-4-overlapping-tagcodes ()
87 (cl-defgeneric cl--generic-1 (x y) "My doc.")
88 (cl-defmethod cl--generic-1 ((y t) z) (list y z))
89 (cl-defmethod cl--generic-1 ((_y (eql 4)) _z)
90 (cons "four" (cl-call-next-method)))
91 (cl-defmethod cl--generic-1 ((_y integer) _z)
92 (cons "integer" (cl-call-next-method)))
93 (cl-defmethod cl--generic-1 ((_y number) _z)
94 (cons "number" (cl-call-next-method)))
95 (should (equal (cl--generic-1 'a 'b) '(a b)))
96 (should (equal (cl--generic-1 1 'b) '("integer" "number" 1 b)))
97 (should (equal (cl--generic-1 4 'b) '("four" "integer" "number" 4 b))))
98
99(ert-deftest cl-generic-test-5-alias ()
100 (cl-defgeneric cl--generic-1 (x y) "My doc.")
101 (defalias 'cl--generic-2 #'cl--generic-1)
102 (cl-defmethod cl--generic-1 ((y t) z) (list y z))
103 (cl-defmethod cl--generic-2 ((_y (eql 4)) _z)
104 (cons "four" (cl-call-next-method)))
105 (should (equal (cl--generic-1 4 'b) '("four" 4 b))))
106
107(ert-deftest cl-generic-test-6-multiple-dispatch ()
108 (cl-defgeneric cl--generic-1 (x y) "My doc.")
109 (cl-defmethod cl--generic-1 (x y) (list x y))
110 (cl-defmethod cl--generic-1 (_x (_y integer))
111 (cons "y-int" (cl-call-next-method)))
112 (cl-defmethod cl--generic-1 ((_x integer) _y)
113 (cons "x-int" (cl-call-next-method)))
114 (cl-defmethod cl--generic-1 ((_x integer) (_y integer))
115 (cons "x&y-int" (cl-call-next-method)))
116 (should (equal (cl--generic-1 1 2) '("x&y-int" "x-int" "y-int" 1 2))))
117
118(ert-deftest cl-generic-test-7-apo ()
119 (cl-defgeneric cl--generic-1 (x y)
120 (:documentation "My doc.") (:argument-precedence-order y x))
121 (cl-defmethod cl--generic-1 (x y) (list x y))
122 (cl-defmethod cl--generic-1 (_x (_y integer))
123 (cons "y-int" (cl-call-next-method)))
124 (cl-defmethod cl--generic-1 ((_x integer) _y)
125 (cons "x-int" (cl-call-next-method)))
126 (cl-defmethod cl--generic-1 ((_x integer) (_y integer))
127 (cons "x&y-int" (cl-call-next-method)))
128 (should (equal (cl--generic-1 1 2) '("x&y-int" "y-int" "x-int" 1 2))))
129
130(provide 'cl-generic-tests)
131;;; cl-generic-tests.el ends here