12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085 |
- ;;; 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>
- ;; Version: 1.0
- ;; 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-combine-methods generic function, to which
- ;; people can add methods if they are really desperate for such functionality.
- ;; - In defgeneric we don't support the options:
- ;; declare, :method-combination, :generic-function-class, :method-class.
- ;; Added elements:
- ;; - We support aliases to generic functions.
- ;; - cl-generic-generalizers. This generic function lets you extend the kind
- ;; of thing on which to dispatch. There is support in this file for
- ;; dispatch on:
- ;; - (eql <val>)
- ;; - (head <val>) which checks that the arg is a cons with <val> as its head.
- ;; - 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-combine-methods (i.s.o define-method-combination and
- ;; compute-effective-method).
- ;; - cl-generic-call-method (which replaces make-method and call-method).
- ;; - The standard method combination supports ":extra STRING" qualifiers
- ;; which simply allows adding more methods for the same
- ;; specializers&qualifiers.
- ;; - Methods can dispatch on the context. For that, a method needs to specify
- ;; context arguments, introduced by `&context' (which need to come right
- ;; after the mandatory arguments and before anything like
- ;; &optional/&rest/&key). Each context argument is given as (EXP SPECIALIZER)
- ;; which means that EXP is taken as an expression which computes some context
- ;; and this value is then used to dispatch.
- ;; E.g. (foo &context (major-mode (eql c-mode))) is an arglist specifying
- ;; that this method will only be applicable when `major-mode' has value
- ;; `c-mode'.
- ;; 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 without changes to the byte-compiler,
- ;; 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.
- ;; TODO:
- ;;
- ;; - A generic "filter" generalizer (e.g. could be used to cleanly adds methods
- ;; to cl-generic-combine-methods with a specializer that says it applies only
- ;; when some particular qualifier is used).
- ;; - A way to dispatch on the context (e.g. the major-mode, some global
- ;; variable, you name it).
- ;;; 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.
- (eval-when-compile (require 'cl-lib))
- (eval-when-compile (require 'cl-macs)) ;For cl--find-class.
- (eval-when-compile (require 'pcase))
- (cl-defstruct (cl--generic-generalizer
- (:constructor nil)
- (:constructor cl-generic-make-generalizer
- (priority tagcode-function specializers-function)))
- (priority nil :type integer)
- tagcode-function
- specializers-function)
- (defconst cl--generic-t-generalizer
- (cl-generic-make-generalizer
- 0 (lambda (_name) nil) (lambda (_tag) '(t))))
- (cl-defstruct (cl--generic-method
- (:constructor nil)
- (:constructor cl--generic-make-method
- (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))
- (: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 generalizers))))
- (method-table nil :type (list-of cl--generic-method))
- (options nil :type list))
- (defun cl-generic-function-options (generic)
- "Return the options of the generic function GENERIC."
- (cl--generic-options generic))
- (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))
- ;;;###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)
- - (:argument-precedence-order &rest ARGS)
- - (:method [QUALIFIERS...] ARGS &rest BODY)
- BODY, if present, is used as the body of a default method.
- \(fn NAME ARGS [DOC-STRING] [OPTIONS-AND-METHODS...] &rest BODY)"
- (declare (indent 2) (doc-string 3))
- (let* ((doc (if (stringp (car-safe options-and-methods))
- (pop options-and-methods)))
- (declarations nil)
- (methods ())
- (options ())
- next-head)
- (while (progn (setq next-head (car-safe (car options-and-methods)))
- (or (keywordp next-head)
- (eq next-head 'declare)))
- (pcase next-head
- (`:documentation
- (when doc (error "Multiple doc strings for %S" name))
- (setq doc (cadr (pop options-and-methods))))
- (`declare
- (when declarations (error "Multiple ‘declare’ for %S" name))
- (setq declarations (pop options-and-methods)))
- (`:method (push (cdr (pop options-and-methods)) methods))
- (_ (push (pop options-and-methods) options))))
- (when options-and-methods
- ;; Anything remaining is assumed to be a default method body.
- (push `(,args ,@options-and-methods) methods))
- (when (eq 'setf (car-safe name))
- (require 'gv)
- (setq name (gv-setter (cadr name))))
- `(progn
- ,@(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 ',(nreverse options))
- ,(help-add-fundoc-usage doc args))
- ,@(mapcar (lambda (method) `(cl-defmethod ,name ,@method))
- (nreverse methods)))))
- ;;;###autoload
- (defun cl-generic-define (name args options)
- (pcase-let* ((generic (cl-generic-ensure-function name))
- (`(,spec-args . ,_) (cl--generic-split-args args))
- (mandatory (mapcar #'car spec-args))
- (apo (assq :argument-precedence-order options)))
- (unless (fboundp name)
- ;; If the generic function was fmakunbound, throw away previous methods.
- (setf (cl--generic-dispatches generic) nil)
- (setf (cl--generic-method-table generic) nil))
- (when apo
- (dolist (arg (cdr apo))
- (let ((pos (memq arg mandatory)))
- (unless pos (error "%S is not a mandatory argument" arg))
- (let* ((argno (- (length mandatory) (length pos)))
- (dispatches (cl--generic-dispatches generic))
- (dispatch (or (assq argno dispatches) (list argno))))
- (setf (cl--generic-dispatches generic)
- (cons dispatch (delq dispatch dispatches)))))))
- (setf (cl--generic-options generic) options)
- (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-split-args (args)
- "Return (SPEC-ARGS . PLAIN-ARGS)."
- (let ((plain-args ())
- (specializers nil)
- (mandatory t))
- (dolist (arg args)
- (push (pcase arg
- ((or '&optional '&rest '&key) (setq mandatory nil) arg)
- ('&context
- (unless mandatory
- (error "&context not immediately after mandatory args"))
- (setq mandatory 'context) nil)
- ((let 'nil mandatory) arg)
- ((let 'context mandatory)
- (unless (consp arg)
- (error "Invalid &context arg: %S" arg))
- (push `((&context . ,(car arg)) . ,(cadr arg)) specializers)
- nil)
- (`(,name . ,type)
- (push (cons name (car type)) specializers)
- name)
- (_
- (push (cons arg t) specializers)
- arg))
- plain-args))
- (cons (nreverse specializers)
- (nreverse (delq nil plain-args)))))
- (defun cl--generic-lambda (args body)
- "Make the lambda expression for a method with ARGS and BODY."
- (pcase-let* ((`(,spec-args . ,plain-args)
- (cl--generic-split-args args))
- (fun `(cl-function (lambda ,plain-args ,@body)))
- (macroenv (cons `(cl-generic-current-method-specializers
- . ,(lambda () spec-args))
- 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))
- (while (not (listp args))
- (push args qualifiers)
- (setq args (pop body)))
- (when (eq 'setf (car-safe name))
- (require 'gv)
- (setq name (gv-setter (cadr name))))
- (pcase-let* ((`(,uses-cnm . ,fun) (cl--generic-lambda args body)))
- `(progn
- ,(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 ',(nreverse 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)
- (pcase-let*
- ((generic (cl-generic-ensure-function name))
- (`(,spec-args . ,_) (cl--generic-split-args args))
- (specializers (mapcar (lambda (spec-arg)
- (if (eq '&context (car-safe (car spec-arg)))
- spec-arg (cdr spec-arg)))
- spec-args))
- (method (cl--generic-make-method
- 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 (spec-arg spec-args)
- (let* ((key (if (eq '&context (car-safe (car spec-arg)))
- (car spec-arg) i))
- (generalizers (cl-generic-generalizers (cdr spec-arg)))
- (x (assoc key dispatches)))
- (unless x
- (setq x (cons key (cl-generic-generalizers t)))
- (setf (cl--generic-dispatches generic)
- (setq dispatches (cons x dispatches))))
- (dolist (generalizer generalizers)
- (unless (member generalizer (cdr x))
- (setf (cdr x)
- (sort (cons generalizer (cdr x))
- (lambda (x y)
- (> (cl--generic-generalizer-priority x)
- (cl--generic-generalizer-priority y)))))))
- (setq i (1+ i))))
- ;; We used to (setcar me method), but that can cause false positives in
- ;; the hash-consing table of the method-builder (bug#20644).
- ;; See the related FIXME in cl--generic-build-combined-method.
- (setf (cl--generic-method-table generic) (cons method (delq (car me) mt)))
- (cl-pushnew `(cl-defmethod . (,(cl--generic-name generic) . ,specializers))
- current-load-list :test #'equal)
- ;; FIXME: Try to avoid re-constructing a new function if the old one
- ;; is still valid (e.g. still empty method cache)?
- (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.
- (let ((purify-flag
- ;; BEWARE! Don't purify this function definition, since that leads
- ;; to memory corruption if the hash-tables it holds are modified
- ;; (the GC doesn't trace those pointers).
- nil))
- ;; But do use `defalias', so that it interacts properly with nadvice,
- ;; e.g. for tracing/debug-on-entry.
- (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 (dispatch)
- (cl--generic-with-memoization
- (gethash dispatch cl--generic-dispatchers)
- ;; (message "cl--generic-get-dispatcher (%S)" dispatch)
- (let* ((dispatch-arg (car dispatch))
- (generalizers (cdr dispatch))
- (lexical-binding t)
- (tagcodes
- (mapcar (lambda (generalizer)
- (funcall (cl--generic-generalizer-tagcode-function
- generalizer)
- 'arg))
- generalizers))
- (typescodes
- (mapcar
- (lambda (generalizer)
- `(funcall ',(cl--generic-generalizer-specializers-function
- generalizer)
- ,(funcall (cl--generic-generalizer-tagcode-function
- generalizer)
- 'arg)))
- generalizers))
- (tag-exp
- ;; 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.
- `(or ,@(if (macroexp-const-p (car (last tagcodes)))
- (butlast tagcodes)
- tagcodes)))
- (fixedargs '(arg))
- (dispatch-idx dispatch-arg)
- (bindings nil))
- (when (eq '&context (car-safe dispatch-arg))
- (setq bindings `((arg ,(cdr dispatch-arg))))
- (setq fixedargs nil)
- (setq dispatch-idx 0))
- (dotimes (i dispatch-idx)
- (push (make-symbol (format "arg%d" (- dispatch-idx i 1))) fixedargs))
- ;; FIXME: For generic functions with a single method (or with 2 methods,
- ;; one of which always matches), using a tagcode + hash-table is
- ;; overkill: better just use a `cl-typep' test.
- (byte-compile
- `(lambda (generic dispatches-left methods)
- (let ((method-cache (make-hash-table :test #'eql)))
- (lambda (,@fixedargs &rest args)
- (let ,bindings
- (apply (cl--generic-with-memoization
- (gethash ,tag-exp method-cache)
- (cl--generic-cache-miss
- generic ',dispatch-arg dispatches-left methods
- ,(if (cdr typescodes)
- `(append ,@typescodes) (car typescodes))))
- ,@fixedargs args)))))))))
- (defun cl--generic-make-function (generic)
- (cl--generic-make-next-function generic
- (cl--generic-dispatches generic)
- (cl--generic-method-table generic)))
- (defun cl--generic-make-next-function (generic dispatches methods)
- (let* ((dispatch
- (progn
- (while (and dispatches
- (let ((x (nth 1 (car dispatches))))
- ;; No need to dispatch for t specializers.
- (or (null x) (equal x cl--generic-t-generalizer))))
- (setq dispatches (cdr dispatches)))
- (pop dispatches))))
- (if (not (and dispatch
- ;; If there's no method left, there's no point checking
- ;; further arguments.
- methods))
- (cl--generic-build-combined-method generic methods)
- (let ((dispatcher (cl--generic-get-dispatcher dispatch)))
- (funcall dispatcher generic dispatches 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.")
- (define-error 'cl--generic-cyclic-definition "Cyclic definition: %S")
- (defun cl--generic-build-combined-method (generic methods)
- (if (null methods)
- ;; Special case needed to fix a circularity during bootstrap.
- (cl--generic-standard-method-combination generic methods)
- (let ((f
- (cl--generic-with-memoization
- ;; FIXME: Since the fields of `generic' are modified, this
- ;; hash-table won't work right, because the hashes will change!
- ;; It's not terribly serious, but reduces the effectiveness of
- ;; the table.
- (gethash (cons generic methods)
- cl--generic-combined-method-memoization)
- (puthash (cons generic methods) :cl--generic--under-construction
- cl--generic-combined-method-memoization)
- (condition-case nil
- (cl-generic-combine-methods generic methods)
- ;; Special case needed to fix a circularity during bootstrap.
- (cl--generic-cyclic-definition
- (cl--generic-standard-method-combination generic methods))))))
- (if (eq f :cl--generic--under-construction)
- (signal 'cl--generic-cyclic-definition
- (list (cl--generic-name generic)))
- f))))
- (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 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 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)))))
- ;; Standard CLOS name.
- (defalias 'cl-method-qualifiers #'cl--generic-method-qualifiers)
- (defun cl--generic-standard-method-combination (generic methods)
- (let ((mets-by-qual ()))
- (dolist (method methods)
- (let ((qualifiers (cl-method-qualifiers method)))
- (if (eq (car qualifiers) :extra) (setq qualifiers (cddr qualifiers)))
- (unless (member qualifiers '(() (:after) (:before) (:around)))
- (error "Unsupported qualifiers in function %S: %S"
- (cl--generic-name generic) qualifiers))
- (push method (alist-get (car qualifiers) mets-by-qual))))
- (cond
- ((null mets-by-qual)
- (lambda (&rest args)
- (apply #'cl-no-applicable-method generic args)))
- ((null (alist-get nil mets-by-qual))
- (lambda (&rest args)
- (apply #'cl-no-primary-method generic args)))
- (t
- (let* ((fun nil)
- (ab-call (lambda (m) (cl-generic-call-method generic 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 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 method fun)))
- fun)))))
- (defun cl--generic-cache-miss (generic
- dispatch-arg dispatches-left methods-left types)
- (let ((methods '()))
- (dolist (method methods-left)
- (let* ((specializer (or (if (integerp dispatch-arg)
- (nth dispatch-arg
- (cl--generic-method-specializers method))
- (cdr (assoc 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-next-function generic dispatches-left methods)))
- (cl-defgeneric cl-generic-generalizers (specializer)
- "Return a list of generalizers for a given SPECIALIZER.
- To each kind of `specializer', corresponds a `generalizer' which describes
- how to extract a \"tag\" from an object which will then let us check if this
- object matches the specializer. A typical example of a \"tag\" would be the
- type of an object. It's called a `generalizer' because it
- takes a specific object and returns a more general approximation,
- denoting a set of objects to which it belongs.
- A generalizer gives us the chunk of code which the
- dispatch function needs to use to extract the \"tag\" of an object, as well
- as a function which turns this tag into an ordered list of
- `specializers' that this object matches.
- The code which extracts the tag should be as fast as possible.
- The tags should be chosen according to the following rules:
- - The tags should not be too specific: similar objects which match the
- same list of specializers should ideally use the same (`eql') tag.
- This insures that the cached computation of the applicable
- methods for one object can be reused for other objects.
- - Corollary: objects which don't match any of the relevant specializers
- should ideally all use the same tag (typically nil).
- This insures that this cache does not grow unnecessarily large.
- - Two different generalizers G1 and G2 should not use the same tag
- unless they use it for the same set of objects. IOW, if G1.tag(X1) =
- G2.tag(X2) then G1.tag(X1) = G2.tag(X1) = G1.tag(X2) = G2.tag(X2).
- - If G1.priority > G2.priority and G1.tag(X1) = G1.tag(X2) and this tag is
- non-nil, then you have to make sure that the G2.tag(X1) = G2.tag(X2).
- This is because the method-cache is only indexed with the first non-nil
- tag (by order of decreasing priority).")
- (cl-defgeneric cl-generic-combine-methods (generic methods)
- "Build the effective method made of METHODS.
- It should return a function that expects the same arguments as the methods, and
- calls those methods in some appropriate order.
- GENERIC is the generic function (mostly used for its name).
- METHODS is the list of the selected methods.
- The METHODS list is sorted from most specific first to most generic last.
- The function can use `cl-generic-call-method' to create functions that call those
- methods.")
- ;; Temporary definition to let the next defmethod succeed.
- (fset 'cl-generic-generalizers
- (lambda (_specializer) (list cl--generic-t-generalizer)))
- (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination)
- (cl-defmethod cl-generic-generalizers (specializer)
- "Support for the catch-all t specializer."
- (if (eq specializer t) (list cl--generic-t-generalizer)
- (error "Unknown specializer %S" specializer)))
- (eval-when-compile
- ;; This macro is brittle and only really important in order to be
- ;; able to preload cl-generic without also preloading the byte-compiler,
- ;; So we use `eval-when-compile' so as not keep it available longer than
- ;; strictly needed.
- (defmacro cl--generic-prefill-dispatchers (arg-or-context specializer)
- (unless (integerp arg-or-context)
- (setq arg-or-context `(&context . ,arg-or-context)))
- (unless (fboundp 'cl--generic-get-dispatcher)
- (require 'cl-generic))
- (let ((fun (cl--generic-get-dispatcher
- `(,arg-or-context ,@(cl-generic-generalizers specializer)
- ,cl--generic-t-generalizer))))
- ;; Recompute dispatch at run-time, since the generalizers may be slightly
- ;; different (e.g. byte-compiled rather than interpreted).
- ;; FIXME: There is a risk that the run-time generalizer is not equivalent
- ;; to the compile-time one, in which case `fun' may not be correct
- ;; any more!
- `(let ((dispatch `(,',arg-or-context
- ,@(cl-generic-generalizers ',specializer)
- ,cl--generic-t-generalizer)))
- ;; (message "Prefilling for %S with \n%S" dispatch ',fun)
- (puthash dispatch ',fun cl--generic-dispatchers)))))
- (cl-defmethod cl-generic-combine-methods (generic methods)
- "Standard support for :after, :before, :around, and `:extra NAME' qualifiers."
- (cl--generic-standard-method-combination generic methods))
- (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-make-method () () 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")))
- ;;; 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."
- (signal 'cl-no-next-method `(,(cl--generic-name generic) ,method ,@args)))
- (cl-defgeneric cl-no-applicable-method (generic &rest args)
- "Function called when a method call finds no applicable method."
- (signal 'cl-no-applicable-method `(,(cl--generic-name generic) ,@args)))
- (cl-defgeneric cl-no-primary-method (generic &rest args)
- "Function called when a method call finds no primary method."
- (signal 'cl-no-primary-method `(,(cl--generic-name 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)))))
- ;;; Add support for describe-function
- (defun cl--generic-search-method (met-name)
- "For `find-function-regexp-alist'. Searches for a cl-defmethod.
- MET-NAME is a cons (SYMBOL . SPECIALIZERS)."
- (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))))
- ;; WORKAROUND: This can't be a defconst due to bug#21237.
- (defvar cl--generic-find-defgeneric-regexp "(\\(?:cl-\\)?defgeneric[ \t]+%s\\>")
- (with-eval-after-load 'find-func
- (defvar find-function-regexp-alist)
- (add-to-list 'find-function-regexp-alist
- `(cl-defmethod . ,#'cl--generic-search-method))
- (add-to-list 'find-function-regexp-alist
- `(cl-defgeneric . cl--generic-find-defgeneric-regexp)))
- (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)
- ;; Supposedly this is called from help-fns, so help-fns should be loaded at
- ;; this point.
- (declare-function help-fns-short-filename "help-fns" (filename))
- (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 (substitute-command-keys " in ‘"))
- (help-insert-xref-button (help-fns-short-filename file)
- 'help-function-def met-name file
- 'cl-defmethod)
- (insert (substitute-command-keys "’.\n"))))
- (insert "\n" (or (nth 2 info) "Undocumented") "\n\n")))))))
- (defun cl--generic-specializers-apply-to-type-p (specializers type)
- "Return non-nil if a method with SPECIALIZERS applies to TYPE."
- (let ((applies nil))
- (dolist (specializer specializers)
- (if (memq (car-safe specializer) '(subclass eieio--static))
- (setq specializer (nth 1 specializer)))
- ;; Don't include the methods that are "too generic", such as those
- ;; applying to `eieio-default-superclass'.
- (and (not (memq specializer '(t eieio-default-superclass)))
- (or (equal type specializer)
- (when (symbolp specializer)
- (let ((sclass (cl--find-class specializer))
- (tclass (cl--find-class type)))
- (when (and sclass tclass)
- (member specializer (cl--generic-class-parents tclass))))))
- (setq applies t)))
- applies))
- (defun cl--generic-all-functions (&optional type)
- "Return a list of all generic functions.
- Optional TYPE argument returns only those functions that contain
- methods for TYPE."
- (let ((l nil))
- (mapatoms
- (lambda (symbol)
- (let ((generic (and (fboundp symbol) (cl--generic symbol))))
- (and generic
- (catch 'found
- (if (null type) (throw 'found t))
- (dolist (method (cl--generic-method-table generic))
- (if (cl--generic-specializers-apply-to-type-p
- (cl--generic-method-specializers method) type)
- (throw 'found t))))
- (push symbol l)))))
- l))
- (defun cl--generic-method-documentation (function type)
- "Return info for all methods of FUNCTION (a symbol) applicable to TYPE.
- The value returned is a list of elements of the form
- \(QUALIFIERS ARGS DOC)."
- (let ((generic (cl--generic function))
- (docs ()))
- (when generic
- (dolist (method (cl--generic-method-table generic))
- (when (cl--generic-specializers-apply-to-type-p
- (cl--generic-method-specializers method) type)
- (push (cl--generic-method-info method) docs))))
- docs))
- ;;; Support for (head <val>) specializers.
- ;; For both the `eql' and the `head' specializers, the dispatch
- ;; is unsatisfactory. Basically, in the "common&fast case", we end up doing
- ;;
- ;; (let ((tag (gethash value <tagcode-hashtable>)))
- ;; (funcall (gethash tag <method-cache>)))
- ;;
- ;; whereas we'd like to just do
- ;;
- ;; (funcall (gethash value <method-cache>)))
- ;;
- ;; but the problem is that the method-cache is normally "open ended", so
- ;; a nil means "not computed yet" and if we bump into it, we dutifully fill the
- ;; corresponding entry, whereas we'd want to just fallback on some default
- ;; effective method (so as not to fill the cache with lots of redundant
- ;; entries).
- (defvar cl--generic-head-used (make-hash-table :test #'eql))
- (defconst cl--generic-head-generalizer
- (cl-generic-make-generalizer
- 80 (lambda (name) `(gethash (car-safe ,name) cl--generic-head-used))
- (lambda (tag) (if (eq (car-safe tag) 'head) (list tag)))))
- (cl-defmethod cl-generic-generalizers :extra "head" (specializer)
- "Support for the `(head VAL)' specializers."
- ;; We have to implement `head' here using the :extra qualifier,
- ;; since we can't use the `head' specializer to implement itself.
- (if (not (eq (car-safe specializer) 'head))
- (cl-call-next-method)
- (cl--generic-with-memoization
- (gethash (cadr specializer) cl--generic-head-used) specializer)
- (list cl--generic-head-generalizer)))
- (cl--generic-prefill-dispatchers 0 (head eql))
- ;;; Support for (eql <val>) specializers.
- (defvar cl--generic-eql-used (make-hash-table :test #'eql))
- (defconst cl--generic-eql-generalizer
- (cl-generic-make-generalizer
- 100 (lambda (name) `(gethash ,name cl--generic-eql-used))
- (lambda (tag) (if (eq (car-safe tag) 'eql) (list tag)))))
- (cl-defmethod cl-generic-generalizers ((specializer (head eql)))
- "Support for the `(eql VAL)' specializers."
- (puthash (cadr specializer) specializer cl--generic-eql-used)
- (list cl--generic-eql-generalizer))
- (cl--generic-prefill-dispatchers 0 (eql nil))
- (cl--generic-prefill-dispatchers window-system (eql nil))
- ;;; Support for cl-defstructs specializers.
- (defun cl--generic-struct-tag (name)
- ;; 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))
- `(and (vectorp ,name)
- (> (length ,name) 0)
- (let ((tag (aref ,name 0)))
- (and (symbolp tag)
- (eq (symbol-function tag) :quick-object-witness-check)
- tag))))
- (defun cl--generic-class-parents (class)
- (let ((parents ())
- (classes (list class)))
- ;; BFS precedence. FIXME: Use a topological sort.
- (while (let ((class (pop classes)))
- (cl-pushnew (cl--class-name class) parents)
- (setq classes
- (append classes
- (cl--class-parents class)))))
- (nreverse parents)))
- (defun cl--generic-struct-specializers (tag)
- (and (symbolp tag) (boundp tag)
- (let ((class (symbol-value tag)))
- (when (cl-typep class 'cl-structure-class)
- (cl--generic-class-parents class)))))
- (defconst cl--generic-struct-generalizer
- (cl-generic-make-generalizer
- 50 #'cl--generic-struct-tag
- #'cl--generic-struct-specializers))
- (cl-defmethod cl-generic-generalizers :extra "cl-struct" (type)
- "Support for dispatch on cl-struct types."
- (or
- (when (symbolp type)
- ;; Use the "cl--struct-class*" (inlinable) functions/macros rather than
- ;; the "cl-struct-*" variants which aren't inlined, so that dispatch can
- ;; take place without requiring cl-lib.
- (let ((class (cl--find-class type)))
- (and (cl-typep class 'cl-structure-class)
- (or (null (cl--struct-class-type class))
- (error "Can't dispatch on cl-struct %S: type is %S"
- type (cl--struct-class-type class)))
- (progn (cl-assert (null (cl--struct-class-named class))) t)
- (list cl--generic-struct-generalizer))))
- (cl-call-next-method)))
- (cl--generic-prefill-dispatchers 0 cl--generic-generalizer)
- ;;; 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)))
- (defconst cl--generic-typeof-generalizer
- (cl-generic-make-generalizer
- ;; FIXME: We could also change `type-of' to return `null' for nil.
- 10 (lambda (name) `(if ,name (type-of ,name) 'null))
- (lambda (tag) (and (symbolp tag) (assq tag cl--generic-typeof-types)))))
- (cl-defmethod cl-generic-generalizers :extra "typeof" (type)
- "Support for dispatch on builtin types."
- ;; FIXME: Add support for other types accepted by `cl-typep' such
- ;; as `character', `atom', `face', `function', ...
- (or
- (and (assq type cl--generic-typeof-types)
- (progn
- (if (memq type '(vector array sequence))
- (message "‘%S’ also matches CL structs and EIEIO classes" type))
- (list cl--generic-typeof-generalizer)))
- (cl-call-next-method)))
- (cl--generic-prefill-dispatchers 0 integer)
- ;; Local variables:
- ;; generated-autoload-file: "cl-loaddefs.el"
- ;; End:
- (provide 'cl-generic)
- ;;; cl-generic.el ends here
|