summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/cl-generic.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/cl-generic.el')
-rw-r--r--lisp/emacs-lisp/cl-generic.el844
1 files changed, 844 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
new file mode 100644
index 00000000000..99924ba288f
--- /dev/null
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -0,0 +1,844 @@
+;;; cl-generic.el --- CLOS-style generic functions for Elisp -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+
+;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This implements the most of CLOS's multiple-dispatch generic functions.
+;; To use it you need either (require 'cl-generic) or (require 'cl-lib).
+;; The main entry points are: `cl-defgeneric' and `cl-defmethod'.
+
+;; Missing elements:
+;; - We don't support make-method, call-method, define-method-combination.
+;; CLOS's define-method-combination is IMO overly complicated, and it suffers
+;; from a significant problem: the method-combination code returns a sexp
+;; that needs to be `eval'uated or compiled. IOW it requires run-time
+;; code generation. Given how rarely method-combinations are used,
+;; I just provided a cl-generic-method-combination-function, which
+;; people can use if they are really desperate for such functionality.
+;; - In defgeneric we don't support the options:
+;; declare, :method-combination, :generic-function-class, :method-class,
+;; :method.
+;; Added elements:
+;; - We support aliases to generic functions.
+;; - The kind of thing on which to dispatch can be extended.
+;; There is support in this file for dispatch on:
+;; - (eql <val>)
+;; - plain old types
+;; - type of CL structs
+;; eieio-core adds dispatch on:
+;; - class of eieio objects
+;; - actual class argument, using the syntax (subclass <class>).
+;; - cl-generic-method-combination-function (i.s.o define-method-combination).
+;; - cl-generic-call-method (which replaces make-method and call-method).
+
+;; Efficiency considerations: overall, I've made an effort to make this fairly
+;; efficient for the expected case (e.g. no constant redefinition of methods).
+;; - Generic functions which do not dispatch on any argument are implemented
+;; optimally (just as efficient as plain old functions).
+;; - Generic functions which only dispatch on one argument are fairly efficient
+;; (not a lot of room for improvement, I think).
+;; - Multiple dispatch is implemented rather naively. There's an extra `apply'
+;; function call for every dispatch; we don't optimize each dispatch
+;; based on the set of candidate methods remaining; we don't optimize the
+;; order in which we performs the dispatches either; If/when this
+;; becomes a problem, we can try and optimize it.
+;; - call-next-method could be made more efficient, but isn't too terrible.
+
+;;; Code:
+
+;; Note: For generic functions that dispatch on several arguments (i.e. those
+;; which use the multiple-dispatch feature), we always use the same "tagcodes"
+;; and the same set of arguments on which to dispatch. This works, but is
+;; often suboptimal since after one dispatch, the remaining dispatches can
+;; usually be simplified, or even completely skipped.
+
+;; TODO/FIXME:
+;; - WIBNI we could use something like
+;; (add-function :before (cl-method-function (cl-find-method ...)) ...)
+
+(eval-when-compile (require 'cl-lib))
+(eval-when-compile (require 'pcase))
+
+(defvar cl-generic-tagcode-function
+ (lambda (type _name)
+ (if (eq type t) '(0 . 'cl--generic-type)
+ (error "Unknown specializer %S" type)))
+ "Function to get the Elisp code to extract the tag on which we dispatch.
+Takes a \"parameter-specializer-name\" and a variable name, and returns
+a pair (PRIORITY . CODE) where CODE is an Elisp expression that should be
+used to extract the \"tag\" (from the object held in the named variable)
+that should uniquely determine if we have a match
+\(i.e. the \"tag\" is the value that will be used to dispatch to the proper
+method(s)).
+Such \"tagcodes\" will be or'd together.
+PRIORITY is an integer from 0 to 100 which is used to sort the tagcodes
+in the `or'. The higher the priority, the more specific the tag should be.
+More specifically, if PRIORITY is N and we have two objects X and Y
+whose tag (according to TAGCODE) is `eql', then it should be the case
+that for all other (PRIORITY . TAGCODE) where PRIORITY ≤ N, then
+\(eval TAGCODE) for X is `eql' to (eval TAGCODE) for Y.")
+
+(defvar cl-generic-tag-types-function
+ (lambda (tag) (if (eq tag 'cl--generic-type) '(t)))
+ "Function to get the list of types that a given \"tag\" matches.
+They should be sorted from most specific to least specific.")
+
+(cl-defstruct (cl--generic-method
+ (:constructor nil)
+ (:constructor cl--generic-method-make
+ (specializers qualifiers uses-cnm function))
+ (:predicate nil))
+ (specializers nil :read-only t :type list)
+ (qualifiers nil :read-only t :type (list-of atom))
+ ;; USES-CNM is a boolean indicating if FUNCTION expects an extra argument
+ ;; holding the next-method.
+ (uses-cnm nil :read-only t :type boolean)
+ (function nil :read-only t :type function))
+
+(cl-defstruct (cl--generic
+ (:constructor nil)
+ (:constructor cl--generic-make
+ (name &optional dispatches method-table))
+ (:predicate nil))
+ (name nil :type symbol :read-only t) ;Pointer back to the symbol.
+ ;; `dispatches' holds a list of (ARGNUM . TAGCODES) where ARGNUM is the index
+ ;; of the corresponding argument and TAGCODES is a list of (PRIORITY . EXP)
+ ;; where the EXPs are expressions (to be `or'd together) to compute the tag
+ ;; on which to dispatch and PRIORITY is the priority of each expression to
+ ;; decide in which order to sort them.
+ ;; The most important dispatch is last in the list (and the least is first).
+ (dispatches nil :type (list-of (cons natnum (list-of tagcode))))
+ (method-table nil :type (list-of cl--generic-method)))
+
+(defmacro cl--generic (name)
+ `(get ,name 'cl--generic))
+
+(defun cl-generic-ensure-function (name)
+ (let (generic
+ (origname name))
+ (while (and (null (setq generic (cl--generic name)))
+ (fboundp name)
+ (symbolp (symbol-function name)))
+ (setq name (symbol-function name)))
+ (unless (or (not (fboundp name))
+ (autoloadp (symbol-function name))
+ (and (functionp name) generic))
+ (error "%s is already defined as something else than a generic function"
+ origname))
+ (if generic
+ (cl-assert (eq name (cl--generic-name generic)))
+ (setf (cl--generic name) (setq generic (cl--generic-make name)))
+ (defalias name (cl--generic-make-function generic)))
+ generic))
+
+(defun cl--generic-setf-rewrite (name)
+ (let* ((setter (intern (format "cl-generic-setter--%s" name)))
+ (exp `(unless (eq ',setter (get ',name 'cl-generic-setter))
+ ;; (when (get ',name 'gv-expander)
+ ;; (error "gv-expander conflicts with (setf %S)" ',name))
+ (setf (get ',name 'cl-generic-setter) ',setter)
+ (gv-define-setter ,name (val &rest args)
+ (cons ',setter (cons val args))))))
+ ;; Make sure `setf' can be used right away, e.g. in the body of the method.
+ (eval exp t)
+ (cons setter exp)))
+
+;;;###autoload
+(defmacro cl-defgeneric (name args &rest options-and-methods)
+ "Create a generic function NAME.
+DOC-STRING is the base documentation for this class. A generic
+function has no body, as its purpose is to decide which method body
+is appropriate to use. Specific methods are defined with `cl-defmethod'.
+With this implementation the ARGS are currently ignored.
+OPTIONS-AND-METHODS currently understands:
+- (:documentation DOCSTRING)
+- (declare DECLARATIONS)"
+ (declare (indent 2) (doc-string 3))
+ (let* ((docprop (assq :documentation options-and-methods))
+ (doc (cond ((stringp (car-safe options-and-methods))
+ (pop options-and-methods))
+ (docprop
+ (prog1
+ (cadr docprop)
+ (setq options-and-methods
+ (delq docprop options-and-methods))))))
+ (declarations (assq 'declare options-and-methods)))
+ (when declarations
+ (setq options-and-methods
+ (delq declarations options-and-methods)))
+ `(progn
+ ,(when (eq 'setf (car-safe name))
+ (pcase-let ((`(,setter . ,code) (cl--generic-setf-rewrite
+ (cadr name))))
+ (setq name setter)
+ code))
+ ,@(mapcar (lambda (declaration)
+ (let ((f (cdr (assq (car declaration)
+ defun-declarations-alist))))
+ (cond
+ (f (apply (car f) name args (cdr declaration)))
+ (t (message "Warning: Unknown defun property `%S' in %S"
+ (car declaration) name)
+ nil))))
+ (cdr declarations))
+ (defalias ',name
+ (cl-generic-define ',name ',args ',options-and-methods)
+ ,(help-add-fundoc-usage doc args)))))
+
+(defun cl--generic-mandatory-args (args)
+ (let ((res ()))
+ (while (not (memq (car args) '(nil &rest &optional &key)))
+ (push (pop args) res))
+ (nreverse res)))
+
+;;;###autoload
+(defun cl-generic-define (name args options-and-methods)
+ (let ((generic (cl-generic-ensure-function name))
+ (mandatory (cl--generic-mandatory-args args))
+ (apo (assq :argument-precedence-order options-and-methods)))
+ (setf (cl--generic-dispatches generic) nil)
+ (when apo
+ (dolist (arg (cdr apo))
+ (let ((pos (memq arg mandatory)))
+ (unless pos (error "%S is not a mandatory argument" arg))
+ (push (list (- (length mandatory) (length pos)))
+ (cl--generic-dispatches generic)))))
+ (setf (cl--generic-method-table generic) nil)
+ (cl--generic-make-function generic)))
+
+(defmacro cl-generic-current-method-specializers ()
+ "List of (VAR . TYPE) where TYPE is var's specializer.
+This macro can only be used within the lexical scope of a cl-generic method."
+ (error "cl-generic-current-method-specializers used outside of a method"))
+
+(eval-and-compile ;Needed while compiling the cl-defmethod calls below!
+ (defun cl--generic-fgrep (vars sexp) ;Copied from pcase.el.
+ "Check which of the symbols VARS appear in SEXP."
+ (let ((res '()))
+ (while (consp sexp)
+ (dolist (var (cl--generic-fgrep vars (pop sexp)))
+ (unless (memq var res) (push var res))))
+ (and (memq sexp vars) (not (memq sexp res)) (push sexp res))
+ res))
+
+ (defun cl--generic-lambda (args body)
+ "Make the lambda expression for a method with ARGS and BODY."
+ (let ((plain-args ())
+ (specializers nil)
+ (mandatory t))
+ (dolist (arg args)
+ (push (pcase arg
+ ((or '&optional '&rest '&key) (setq mandatory nil) arg)
+ ((and `(,name . ,type) (guard mandatory))
+ (push (cons name (car type)) specializers)
+ name)
+ (_ arg))
+ plain-args))
+ (setq plain-args (nreverse plain-args))
+ (let ((fun `(cl-function (lambda ,plain-args ,@body)))
+ (macroenv (cons `(cl-generic-current-method-specializers
+ . ,(lambda () specializers))
+ macroexpand-all-environment)))
+ (require 'cl-lib) ;Needed to expand `cl-flet' and `cl-function'.
+ ;; First macroexpand away the cl-function stuff (e.g. &key and
+ ;; destructuring args, `declare' and whatnot).
+ (pcase (macroexpand fun macroenv)
+ (`#'(lambda ,args . ,body)
+ (let* ((parsed-body (macroexp-parse-body body))
+ (cnm (make-symbol "cl--cnm"))
+ (nmp (make-symbol "cl--nmp"))
+ (nbody (macroexpand-all
+ `(cl-flet ((cl-call-next-method ,cnm)
+ (cl-next-method-p ,nmp))
+ ,@(cdr parsed-body))
+ macroenv))
+ ;; FIXME: Rather than `grep' after the fact, the
+ ;; macroexpansion should directly set some flag when cnm
+ ;; is used.
+ ;; FIXME: Also, optimize the case where call-next-method is
+ ;; only called with explicit arguments.
+ (uses-cnm (cl--generic-fgrep (list cnm nmp) nbody)))
+ (cons (not (not uses-cnm))
+ `#'(lambda (,@(if uses-cnm (list cnm)) ,@args)
+ ,@(car parsed-body)
+ ,(if (not (memq nmp uses-cnm))
+ nbody
+ `(let ((,nmp (lambda ()
+ (cl--generic-isnot-nnm-p ,cnm))))
+ ,nbody))))))
+ (f (error "Unexpected macroexpansion result: %S" f)))))))
+
+
+;;;###autoload
+(defmacro cl-defmethod (name args &rest body)
+ "Define a new method for generic function NAME.
+I.e. it defines the implementation of NAME to use for invocations where the
+value of the dispatch argument matches the specified TYPE.
+The dispatch argument has to be one of the mandatory arguments, and
+all methods of NAME have to use the same argument for dispatch.
+The dispatch argument and TYPE are specified in ARGS where the corresponding
+formal argument appears as (VAR TYPE) rather than just VAR.
+
+The optional second argument QUALIFIER is a specifier that
+modifies how the method is combined with other methods, including:
+ :before - Method will be called before the primary
+ :after - Method will be called after the primary
+ :around - Method will be called around everything else
+The absence of QUALIFIER means this is a \"primary\" method.
+
+Other than a type, TYPE can also be of the form `(eql VAL)' in
+which case this method will be invoked when the argument is `eql' to VAL.
+
+\(fn NAME [QUALIFIER] ARGS &rest [DOCSTRING] BODY)"
+ (declare (doc-string 3) (indent 2)
+ (debug
+ (&define ; this means we are defining something
+ [&or name ("setf" :name setf name)]
+ ;; ^^ This is the methods symbol
+ [ &optional keywordp ] ; this is key :before etc
+ list ; arguments
+ [ &optional stringp ] ; documentation string
+ def-body))) ; part to be debugged
+ (let ((qualifiers nil)
+ (setfizer (if (eq 'setf (car-safe name))
+ ;; Call it before we call cl--generic-lambda.
+ (cl--generic-setf-rewrite (cadr name)))))
+ (while (not (listp args))
+ (push args qualifiers)
+ (setq args (pop body)))
+ (pcase-let* ((`(,uses-cnm . ,fun) (cl--generic-lambda args body)))
+ `(progn
+ ,(when setfizer
+ (setq name (car setfizer))
+ (cdr setfizer))
+ ,(and (get name 'byte-obsolete-info)
+ (or (not (fboundp 'byte-compile-warning-enabled-p))
+ (byte-compile-warning-enabled-p 'obsolete))
+ (let* ((obsolete (get name 'byte-obsolete-info)))
+ (macroexp--warn-and-return
+ (macroexp--obsolete-warning name obsolete "generic function")
+ nil)))
+ ;; You could argue that `defmethod' modifies rather than defines the
+ ;; function, so warnings like "not known to be defined" are fair game.
+ ;; But in practice, it's common to use `cl-defmethod'
+ ;; without a previous `cl-defgeneric'.
+ (declare-function ,name "")
+ (cl-generic-define-method ',name ',qualifiers ',args
+ ,uses-cnm ,fun)))))
+
+(defun cl--generic-member-method (specializers qualifiers methods)
+ (while
+ (and methods
+ (let ((m (car methods)))
+ (not (and (equal (cl--generic-method-specializers m) specializers)
+ (equal (cl--generic-method-qualifiers m) qualifiers)))))
+ (setq methods (cdr methods)))
+ methods)
+
+;;;###autoload
+(defun cl-generic-define-method (name qualifiers args uses-cnm function)
+ (let* ((generic (cl-generic-ensure-function name))
+ (mandatory (cl--generic-mandatory-args args))
+ (specializers
+ (mapcar (lambda (arg) (if (consp arg) (cadr arg) t)) mandatory))
+ (method (cl--generic-method-make
+ specializers qualifiers uses-cnm function))
+ (mt (cl--generic-method-table generic))
+ (me (cl--generic-member-method specializers qualifiers mt))
+ (dispatches (cl--generic-dispatches generic))
+ (i 0))
+ (dolist (specializer specializers)
+ (let* ((tagcode (funcall cl-generic-tagcode-function specializer 'arg))
+ (x (assq i dispatches)))
+ (unless x
+ (setq x (list i (funcall cl-generic-tagcode-function t 'arg)))
+ (setf (cl--generic-dispatches generic)
+ (setq dispatches (cons x dispatches))))
+ (unless (member tagcode (cdr x))
+ (setf (cdr x)
+ (nreverse (sort (cons tagcode (cdr x))
+ #'car-less-than-car))))
+ (setq i (1+ i))))
+ (if me (setcar me method)
+ (setf (cl--generic-method-table generic) (cons method mt)))
+ (cl-pushnew `(cl-defmethod . (,(cl--generic-name generic) . ,specializers))
+ current-load-list :test #'equal)
+ (let ((gfun (cl--generic-make-function generic))
+ ;; Prevent `defalias' from recording this as the definition site of
+ ;; the generic function.
+ current-load-list)
+ ;; For aliases, cl--generic-name gives us the actual name.
+ (defalias (cl--generic-name generic) gfun))))
+
+(defmacro cl--generic-with-memoization (place &rest code)
+ (declare (indent 1) (debug t))
+ (gv-letplace (getter setter) place
+ `(or ,getter
+ ,(macroexp-let2 nil val (macroexp-progn code)
+ `(progn
+ ,(funcall setter val)
+ ,val)))))
+
+(defvar cl--generic-dispatchers (make-hash-table :test #'equal))
+
+(defun cl--generic-get-dispatcher (tagcodes dispatch-arg)
+ (cl--generic-with-memoization
+ (gethash (cons dispatch-arg tagcodes) cl--generic-dispatchers)
+ (let ((lexical-binding t)
+ (tag-exp `(or ,@(mapcar #'cdr
+ ;; Minor optimization: since this tag-exp is
+ ;; only used to lookup the method-cache, it
+ ;; doesn't matter if the default value is some
+ ;; constant or nil.
+ (if (macroexp-const-p (car (last tagcodes)))
+ (butlast tagcodes)
+ tagcodes))))
+ (extraargs ()))
+ (dotimes (_ dispatch-arg)
+ (push (make-symbol "arg") extraargs))
+ (byte-compile
+ `(lambda (generic dispatches-left)
+ (let ((method-cache (make-hash-table :test #'eql)))
+ (lambda (,@extraargs arg &rest args)
+ (apply (cl--generic-with-memoization
+ (gethash ,tag-exp method-cache)
+ (cl--generic-cache-miss
+ generic ',dispatch-arg dispatches-left
+ (list ,@(mapcar #'cdr tagcodes))))
+ ,@extraargs arg args))))))))
+
+(defun cl--generic-make-function (generic)
+ (let* ((dispatches (cl--generic-dispatches generic))
+ (dispatch
+ (progn
+ (while (and dispatches
+ (member (cdar dispatches)
+ '(nil ((0 . 'cl--generic-type)))))
+ (setq dispatches (cdr dispatches)))
+ (pop dispatches))))
+ (if (null dispatch)
+ (cl--generic-build-combined-method
+ (cl--generic-name generic)
+ (cl--generic-method-table generic))
+ (let ((dispatcher (cl--generic-get-dispatcher
+ (cdr dispatch) (car dispatch))))
+ (funcall dispatcher generic dispatches)))))
+
+(defvar cl-generic-method-combination-function
+ #'cl--generic-standard-method-combination
+ "Function to build the effective method.
+Called with 2 arguments: NAME and METHOD-ALIST.
+It should return an effective method, i.e. a function that expects the same
+arguments as the methods, and calls those methods in some appropriate order.
+NAME is the name (a symbol) of the corresponding generic function.
+METHOD-ALIST is a list of elements (QUALIFIERS . METHODS) where
+QUALIFIERS is a list of qualifiers, and METHODS is a list of the selected
+methods for that qualifier list.
+The METHODS lists are sorted from most generic first to most specific last.
+The function can use `cl-generic-call-method' to create functions that call those
+methods.")
+
+(defvar cl--generic-combined-method-memoization
+ (make-hash-table :test #'equal :weakness 'value)
+ "Table storing previously built combined-methods.
+This is particularly useful when many different tags select the same set
+of methods, since this table then allows us to share a single combined-method
+for all those different tags in the method-cache.")
+
+(defun cl--generic-build-combined-method (generic-name methods)
+ (cl--generic-with-memoization
+ (gethash (cons generic-name methods)
+ cl--generic-combined-method-memoization)
+ (let ((mets-by-qual ()))
+ (dolist (method methods)
+ (let* ((qualifiers (cl--generic-method-qualifiers method))
+ (x (assoc qualifiers mets-by-qual)))
+ ;; FIXME: sadly, alist-get only uses `assq' and we need `assoc'.
+ ;;(push (cdr qm) (alist-get qualifiers mets-by-qual)))
+ (if x
+ (push method (cdr x))
+ (push (list qualifiers method) mets-by-qual))))
+ (funcall cl-generic-method-combination-function
+ generic-name mets-by-qual))))
+
+(defun cl--generic-no-next-method-function (generic method)
+ (lambda (&rest args)
+ (apply #'cl-no-next-method generic method args)))
+
+(defun cl-generic-call-method (generic-name method &optional fun)
+ "Return a function that calls METHOD.
+FUN is the function that should be called when METHOD calls
+`call-next-method'."
+ (if (not (cl--generic-method-uses-cnm method))
+ (cl--generic-method-function method)
+ (let ((met-fun (cl--generic-method-function method))
+ (next (or fun (cl--generic-no-next-method-function
+ generic-name method))))
+ (lambda (&rest args)
+ (apply met-fun
+ ;; FIXME: This sucks: passing just `next' would
+ ;; be a lot more efficient than the lambda+apply
+ ;; quasi-η, but we need this to implement the
+ ;; "if call-next-method is called with no
+ ;; arguments, then use the previous arguments".
+ (lambda (&rest cnm-args)
+ (apply next (or cnm-args args)))
+ args)))))
+
+(defun cl--generic-standard-method-combination (generic-name mets-by-qual)
+ (dolist (x mets-by-qual)
+ (unless (member (car x) '(() (:after) (:before) (:around)))
+ (error "Unsupported qualifiers in function %S: %S" generic-name (car x))))
+ (cond
+ ((null mets-by-qual)
+ (lambda (&rest args)
+ (apply #'cl-no-applicable-method generic-name args)))
+ ((null (alist-get nil mets-by-qual))
+ (lambda (&rest args)
+ (apply #'cl-no-primary-method generic-name args)))
+ (t
+ (let* ((fun nil)
+ (ab-call (lambda (m) (cl-generic-call-method generic-name m)))
+ (before
+ (mapcar ab-call (reverse (cdr (assoc '(:before) mets-by-qual)))))
+ (after (mapcar ab-call (cdr (assoc '(:after) mets-by-qual)))))
+ (dolist (method (cdr (assoc nil mets-by-qual)))
+ (setq fun (cl-generic-call-method generic-name method fun)))
+ (when (or after before)
+ (let ((next fun))
+ (setq fun (lambda (&rest args)
+ (dolist (bf before)
+ (apply bf args))
+ (prog1
+ (apply next args)
+ (dolist (af after)
+ (apply af args)))))))
+ (dolist (method (cdr (assoc '(:around) mets-by-qual)))
+ (setq fun (cl-generic-call-method generic-name method fun)))
+ fun))))
+
+(defconst cl--generic-nnm-sample (cl--generic-no-next-method-function t t))
+(defconst cl--generic-cnm-sample
+ (funcall (cl--generic-build-combined-method
+ nil (list (cl--generic-method-make () () t #'identity)))))
+
+(defun cl--generic-isnot-nnm-p (cnm)
+ "Return non-nil if CNM is the function that calls `cl-no-next-method'."
+ ;; ¡Big Gross Ugly Hack!
+ ;; `next-method-p' just sucks, we should let it die. But EIEIO did support
+ ;; it, and some packages use it, so we need to support it.
+ (catch 'found
+ (cl-assert (function-equal cnm cl--generic-cnm-sample))
+ (if (byte-code-function-p cnm)
+ (let ((cnm-constants (aref cnm 2))
+ (sample-constants (aref cl--generic-cnm-sample 2)))
+ (dotimes (i (length sample-constants))
+ (when (function-equal (aref sample-constants i)
+ cl--generic-nnm-sample)
+ (throw 'found
+ (not (function-equal (aref cnm-constants i)
+ cl--generic-nnm-sample))))))
+ (cl-assert (eq 'closure (car-safe cl--generic-cnm-sample)))
+ (let ((cnm-env (cadr cnm)))
+ (dolist (vb (cadr cl--generic-cnm-sample))
+ (when (function-equal (cdr vb) cl--generic-nnm-sample)
+ (throw 'found
+ (not (function-equal (cdar cnm-env)
+ cl--generic-nnm-sample))))
+ (setq cnm-env (cdr cnm-env)))))
+ (error "Haven't found no-next-method-sample in cnm-sample")))
+
+(defun cl--generic-cache-miss (generic dispatch-arg dispatches-left tags)
+ (let ((types (apply #'append (mapcar cl-generic-tag-types-function tags)))
+ (methods '()))
+ (dolist (method (cl--generic-method-table generic))
+ (let* ((specializer (or (nth dispatch-arg
+ (cl--generic-method-specializers method))
+ t))
+ (m (member specializer types)))
+ (when m
+ (push (cons (length m) method) methods))))
+ ;; Sort the methods, most specific first.
+ ;; It would be tempting to sort them once and for all in the method-table
+ ;; rather than here, but the order might depend on the actual argument
+ ;; (e.g. for multiple inheritance with defclass).
+ (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car))))
+ (cl--generic-make-function (cl--generic-make (cl--generic-name generic)
+ dispatches-left methods))))
+
+;;; Define some pre-defined generic functions, used internally.
+
+(define-error 'cl-no-method "No method for %S")
+(define-error 'cl-no-next-method "No next method for %S" 'cl-no-method)
+(define-error 'cl-no-primary-method "No primary method for %S" 'cl-no-method)
+(define-error 'cl-no-applicable-method "No applicable method for %S"
+ 'cl-no-method)
+
+(cl-defgeneric cl-no-next-method (generic method &rest args)
+ "Function called when `cl-call-next-method' finds no next method.")
+(cl-defmethod cl-no-next-method (generic method &rest args)
+ (signal 'cl-no-next-method `(,generic ,method ,@args)))
+
+(cl-defgeneric cl-no-applicable-method (generic &rest args)
+ "Function called when a method call finds no applicable method.")
+(cl-defmethod cl-no-applicable-method (generic &rest args)
+ (signal 'cl-no-applicable-method `(,generic ,@args)))
+
+(cl-defgeneric cl-no-primary-method (generic &rest args)
+ "Function called when a method call finds no primary method.")
+(cl-defmethod cl-no-primary-method (generic &rest args)
+ (signal 'cl-no-primary-method `(,generic ,@args)))
+
+(defun cl-call-next-method (&rest _args)
+ "Function to call the next applicable method.
+Can only be used from within the lexical body of a primary or around method."
+ (error "cl-call-next-method only allowed inside primary and around methods"))
+
+(defun cl-next-method-p ()
+ "Return non-nil if there is a next method.
+Can only be used from within the lexical body of a primary or around method."
+ (declare (obsolete "make sure there's always a next method, or catch `cl-no-next-method' instead" "25.1"))
+ (error "cl-next-method-p only allowed inside primary and around methods"))
+
+;;;###autoload
+(defun cl-find-method (generic qualifiers specializers)
+ (car (cl--generic-member-method
+ specializers qualifiers
+ (cl--generic-method-table (cl--generic generic)))))
+
+(defalias 'cl-method-qualifiers 'cl--generic-method-qualifiers)
+
+;;; Add support for describe-function
+
+(defun cl--generic-search-method (met-name)
+ (let ((base-re (concat "(\\(?:cl-\\)?defmethod[ \t]+"
+ (regexp-quote (format "%s" (car met-name)))
+ "\\_>")))
+ (or
+ (re-search-forward
+ (concat base-re "[^&\"\n]*"
+ (mapconcat (lambda (specializer)
+ (regexp-quote
+ (format "%S" (if (consp specializer)
+ (nth 1 specializer) specializer))))
+ (remq t (cdr met-name))
+ "[ \t\n]*)[^&\"\n]*"))
+ nil t)
+ (re-search-forward base-re nil t))))
+
+
+(with-eval-after-load 'find-func
+ (defvar find-function-regexp-alist)
+ (add-to-list 'find-function-regexp-alist
+ `(cl-defmethod . ,#'cl--generic-search-method)))
+
+(defun cl--generic-method-info (method)
+ (let* ((specializers (cl--generic-method-specializers method))
+ (qualifiers (cl--generic-method-qualifiers method))
+ (uses-cnm (cl--generic-method-uses-cnm method))
+ (function (cl--generic-method-function method))
+ (args (help-function-arglist function 'names))
+ (docstring (documentation function))
+ (qual-string
+ (if (null qualifiers) ""
+ (cl-assert (consp qualifiers))
+ (let ((s (prin1-to-string qualifiers)))
+ (concat (substring s 1 -1) " "))))
+ (doconly (if docstring
+ (let ((split (help-split-fundoc docstring nil)))
+ (if split (cdr split) docstring))))
+ (combined-args ()))
+ (if uses-cnm (setq args (cdr args)))
+ (dolist (specializer specializers)
+ (let ((arg (if (eq '&rest (car args))
+ (intern (format "arg%d" (length combined-args)))
+ (pop args))))
+ (push (if (eq specializer t) arg (list arg specializer))
+ combined-args)))
+ (setq combined-args (append (nreverse combined-args) args))
+ (list qual-string combined-args doconly)))
+
+(add-hook 'help-fns-describe-function-functions #'cl--generic-describe)
+(defun cl--generic-describe (function)
+ (let ((generic (if (symbolp function) (cl--generic function))))
+ (when generic
+ (require 'help-mode) ;Needed for `help-function-def' button!
+ (save-excursion
+ (insert "\n\nThis is a generic function.\n\n")
+ (insert (propertize "Implementations:\n\n" 'face 'bold))
+ ;; Loop over fanciful generics
+ (dolist (method (cl--generic-method-table generic))
+ (let* ((info (cl--generic-method-info method)))
+ ;; FIXME: Add hyperlinks for the types as well.
+ (insert (format "%s%S" (nth 0 info) (nth 1 info)))
+ (let* ((met-name (cons function
+ (cl--generic-method-specializers method)))
+ (file (find-lisp-object-file-name met-name 'cl-defmethod)))
+ (when file
+ (insert " in `")
+ (help-insert-xref-button (help-fns-short-filename file)
+ 'help-function-def met-name file
+ 'cl-defmethod)
+ (insert "'.\n")))
+ (insert "\n" (or (nth 2 info) "Undocumented") "\n\n")))))))
+
+;;; Support for (eql <val>) specializers.
+
+(defvar cl--generic-eql-used (make-hash-table :test #'eql))
+
+(add-function :before-until cl-generic-tagcode-function
+ #'cl--generic-eql-tagcode)
+(defun cl--generic-eql-tagcode (type name)
+ (when (eq (car-safe type) 'eql)
+ (puthash (cadr type) type cl--generic-eql-used)
+ `(100 . (gethash ,name cl--generic-eql-used))))
+
+(add-function :before-until cl-generic-tag-types-function
+ #'cl--generic-eql-tag-types)
+(defun cl--generic-eql-tag-types (tag)
+ (if (eq (car-safe tag) 'eql) (list tag)))
+
+;;; Support for cl-defstructs specializers.
+
+(add-function :before-until cl-generic-tagcode-function
+ #'cl--generic-struct-tagcode)
+
+(defun cl--generic-struct-tag (name)
+ `(and (vectorp ,name)
+ (> (length ,name) 0)
+ (let ((tag (aref ,name 0)))
+ (if (eq (symbol-function tag) :quick-object-witness-check)
+ tag))))
+
+(defun cl--generic-struct-tagcode (type name)
+ (and (symbolp type)
+ (get type 'cl-struct-type)
+ (or (null (car (get type 'cl-struct-type)))
+ (error "Can't dispatch on cl-struct %S: type is %S"
+ type (car (get type 'cl-struct-type))))
+ (or (equal '(cl-tag-slot) (car (get type 'cl-struct-slots)))
+ (error "Can't dispatch on cl-struct %S: no tag in slot 0"
+ type))
+ ;; It's tempting to use (and (vectorp ,name) (aref ,name 0))
+ ;; but that would suffer from some problems:
+ ;; - the vector may have size 0.
+ ;; - when called on an actual vector (rather than an object), we'd
+ ;; end up returning an arbitrary value, possibly colliding with
+ ;; other tagcode's values.
+ ;; - it can also result in returning all kinds of irrelevant
+ ;; values which would end up filling up the method-cache with
+ ;; lots of irrelevant/redundant entries.
+ ;; FIXME: We could speed this up by introducing a dedicated
+ ;; vector type at the C level, so we could do something like
+ ;; (and (vector-objectp ,name) (aref ,name 0))
+ `(50 . ,(cl--generic-struct-tag name))))
+
+(add-function :before-until cl-generic-tag-types-function
+ #'cl--generic-struct-tag-types)
+(defun cl--generic-struct-tag-types (tag)
+ ;; FIXME: cl-defstruct doesn't make it easy for us.
+ (and (symbolp tag)
+ ;; A method call shouldn't itself mess with the match-data.
+ (string-match-p "\\`cl-struct-\\(.*\\)" (symbol-name tag))
+ (let ((types (list (intern (substring (symbol-name tag) 10)))))
+ (while (get (car types) 'cl-struct-include)
+ (push (get (car types) 'cl-struct-include) types))
+ (push 'cl-structure-object types) ;The "parent type" of all cl-structs.
+ (nreverse types))))
+
+;;; Dispatch on "system types".
+
+(defconst cl--generic-typeof-types
+ ;; Hand made from the source code of `type-of'.
+ '((integer number) (symbol) (string array sequence) (cons list sequence)
+ ;; Markers aren't `numberp', yet they are accepted wherever integers are
+ ;; accepted, pretty much.
+ (marker) (overlay) (float number) (window-configuration)
+ (process) (window) (subr) (compiled-function) (buffer)
+ (char-table array sequence)
+ (bool-vector array sequence)
+ (frame) (hash-table) (font-spec) (font-entity) (font-object)
+ (vector array sequence)
+ ;; Plus, hand made:
+ (null symbol list sequence)
+ (list sequence)
+ (array sequence)
+ (sequence)
+ (number)))
+
+(add-function :before-until cl-generic-tagcode-function
+ #'cl--generic-typeof-tagcode)
+(defun cl--generic-typeof-tagcode (type name)
+ ;; FIXME: Add support for other types accepted by `cl-typep' such
+ ;; as `character', `atom', `face', `function', ...
+ (and (assq type cl--generic-typeof-types)
+ (progn
+ (if (memq type '(vector array sequence))
+ (message "`%S' also matches CL structs and EIEIO classes" type))
+ ;; FIXME: We could also change `type-of' to return `null' for nil.
+ `(10 . (if ,name (type-of ,name) 'null)))))
+
+(add-function :before-until cl-generic-tag-types-function
+ #'cl--generic-typeof-types)
+(defun cl--generic-typeof-types (tag)
+ (and (symbolp tag)
+ (assq tag cl--generic-typeof-types)))
+
+;;; Just for kicks: dispatch on major-mode
+;;
+;; Here's how you'd use it:
+;; (cl-defmethod foo ((x (major-mode text-mode)) y z) ...)
+;; And then
+;; (foo 'major-mode toto titi)
+;;
+;; FIXME: Better would be to do that via dispatch on an "implicit argument".
+;; E.g. (cl-defmethod foo (y z &context (major-mode text-mode)) ...)
+
+;; (defvar cl--generic-major-modes (make-hash-table :test #'eq))
+;;
+;; (add-function :before-until cl-generic-tagcode-function
+;; #'cl--generic-major-mode-tagcode)
+;; (defun cl--generic-major-mode-tagcode (type name)
+;; (if (eq 'major-mode (car-safe type))
+;; `(50 . (if (eq ,name 'major-mode)
+;; (cl--generic-with-memoization
+;; (gethash major-mode cl--generic-major-modes)
+;; `(cl--generic-major-mode . ,major-mode))))))
+;;
+;; (add-function :before-until cl-generic-tag-types-function
+;; #'cl--generic-major-mode-types)
+;; (defun cl--generic-major-mode-types (tag)
+;; (when (eq (car-safe tag) 'cl--generic-major-mode)
+;; (if (eq tag 'fundamental-mode) '(fundamental-mode t)
+;; (let ((types `((major-mode ,(cdr tag)))))
+;; (while (get (car types) 'derived-mode-parent)
+;; (push (list 'major-mode (get (car types) 'derived-mode-parent))
+;; types))
+;; (unless (eq 'fundamental-mode (car types))
+;; (push '(major-mode fundamental-mode) types))
+;; (nreverse types)))))
+
+;; Local variables:
+;; generated-autoload-file: "cl-loaddefs.el"
+;; End:
+
+(provide 'cl-generic)
+;;; cl-generic.el ends here