           ,definition
           "This function was patched by `el-patch'.")))))
 
-(defun el-patch-classify-variable (definition)
-  "Classify the items defined by a variable DEFINITION.
-DEFINITION is a list starting with `defvar' or similar."
-  (list (cons 'variable (nth 1 definition))))
-
-(defun el-patch-classify-function (definition)
-  "Classify the items defined by a function DEFINITION.
-DEFINITION is a list starting with `defun' or similar."
-  (list (cons 'function (nth 1 definition))))
-
-(defun el-patch-classify-define-minor-mode (definition)
-  "Classify the items defined by a minor mode DEFINITION.
-DEFINITION is a list starting with `define-minor-mode' or
-similar."
-  (let* ((function-name (nth 1 definition))
-         (variable-name (nth 1 definition))
-         (kw-args (nthcdr 3 definition)))
-    (dotimes (_ 3)
-      (unless (keywordp (car kw-args))
-        (setq kw-args (cdr kw-args))))
-    (while (keywordp (car kw-args))
-      (when (eq (car kw-args) :variable)
-        (setq variable-name (car kw-args)))
-      (setq kw-args (nthcdr 2 kw-args)))
-    (list (cons 'function function-name)
-          (cons 'variable variable-name))))
-
+;;;;; Removing patches
+
+;;;###autoload
+(defun el-patch-unpatch (name type)
+  "Remove the patch given by the PATCH-DEFINITION.
+This restores the original functionality of the object being
+patched. NAME and TYPE are as returned by `el-patch-get'."
+  (interactive (el-patch--select-patch))
+  (if-let ((patch-definition (el-patch-get name type)))
+      (eval `(el-patch--stealthy-eval
+              ,(el-patch--resolve-definition
+                patch-definition nil)
+              "This function was patched and then unpatched by `el-patch'."))
+    (error "There is no patch for %S %S" type name)))
+
+;;;; Defining patch types
+
+;; Use `progn' to cause the entire macro definition to be autoloaded
+;; rather than just a stub.
 ;;;###autoload
 (progn
   (cl-defmacro el-patch-deftype

       (defun ,defun-name ()
         (apply el-patch-require-function ',feature ',args))
       (add-hook 'el-patch-pre-validate-hook #',defun-name))))

;;;; Patch directives

;;;###autoload
(defmacro el-patch-add (&rest args)
  "Patch directive for inserting forms.
In the original definition, the ARGS and their containing form
are removed. In the new definition, the ARGS are spliced into the
containing s-expression."
  (declare (indent 0))
  (ignore args)
  `(error "Can't use `el-patch-add' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-remove (&rest args)
  "Patch directive for removing forms.
In the original definition, the ARGS are spliced into the
containing s-expression. In the new definition, the ARGS and
their containing form are removed."
  (declare (indent 0))
  (ignore args)
  `(error "Can't use `el-patch-remove' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-swap (old new)
  "Patch directive for swapping forms.
In the original definition, OLD is spliced into the containing
s-expression. In the new definition, NEW is spliced instead."
  (declare (indent 0))
  (ignore old new)
  `(error "Can't use `el-patch-swap' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-wrap (&optional triml trimr args)
  "Patch directive for wrapping forms.
TRIML and TRIMR are optional arguments. If only one is provided,
it is assumed to be TRIML. ARGS is required, and it must be a
list.

In the original definition, the ARGS are spliced into the
containing s-expression. If TRIML is provided, the first TRIML of
the ARGS are removed first. If TRIMR is provided, the last TRIMR
are also removed. In the new definition, the ARGS and their
containing list are spliced into the containing s-expression."
  (declare (indent defun))
  (ignore triml trimr args)
  `(error "Can't use `el-patch-wrap' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-splice (&optional triml trimr args)
  "Patch directive for splicing forms.
TRIML and TRIMR are optional arguments. If only one is provided,
it is assumed to be TRIML. ARGS is required, and it must be a
list.

In the original definition, the ARGS and their containing list
are spliced into the containing s-expression. In the new
definition, the ARGS are spliced into the containing
s-expression. If TRIML is provided, the first TRIML of the ARGS
are removed first. If TRIMR is provided, the last TRIMR are also
removed."
  (declare (indent defun))
  (ignore triml trimr args)
  `(error "Can't use `el-patch-splice' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-let (varlist &rest args)
  "Patch directive for creating local el-patch bindings.
Creates local bindings according to VARLIST, then splices ARGS
into both the original and new definitions. You may bind symbols
that are also patch directives, but the bindings will not have
effect if the symbols are used at the beginning of a list (they
will act as patch directives)."
  (declare (indent 1))
  (ignore varlist args)
  `(error "Can't use `el-patch-let' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-literal (&rest args)
  "Patch directive for treating patch directives literally.
ARGS are spliced into the containing s-expression, but are not
processed further by el-patch."
  (declare (indent 0))
  (ignore args)
  `(error "Can't use `el-patch-literal' outside of an `el-patch'"))

;;;###autoload
(defmacro el-patch-concat (&rest args)
  "Patch directive for modifying string literals.
ARGS should resolve to strings; those strings are passed to
`concat' and spliced into the containing s-expression in both the
original and new definitions."
  (declare (indent 0))
  (ignore args)
  `(error "Can't use `el-patch-concat' outside of an `el-patch'"))

;;;; Viewing patches


  :classify el-patch-classify-variable
  :declare ((doc-string 3)
            (indent defun)))

;;;; Validating patches

(defcustom el-patch-pre-validate-hook nil
  "Hook run before `el-patch-validate-all'.
Also run before `el-patch-validate' if a prefix argument is
provided. This hook should contain functions that make sure all
of your patches are defined (for example, you might need to load
some features if your patches are lazily defined)."
  :type 'hook)

(defcustom el-patch-post-validate-hook nil
  "Hook run after `el-patch-validate-all'.
Also run after `el-patch-validate' if a prefix argument is
provided. This hook should contain functions that undo any
patching that might have taken place in
`el-patch-pre-validate-hook', if you do not want the patches to
be defined permanently."
  :type 'hook)

(defun el-patch--classify-definition-type (type)
  "Classifies a definition TYPE as a `function' or `variable'.
TYPE is a symbol `defun', `defmacro', etc."
  (if-let ((classify (assoc type el-patch-deftype-alist)))
      (pcase (plist-get (cdr classify) :classify)
        ('el-patch-classify-function 'function)
        ('el-patch-classify-define-minor-mode 'function)
        ('el-patch-classify-variable 'variable))
    (error "Unexpected definition type `%S'" type)))

(defun el-patch--find-symbol (name type)
  "Return the Lisp form that defines the symbol NAME.
Return nil if such a definition cannot be found. (That would
happen if the definition were generated dynamically.) TYPE is a
symbol `defun', `defmacro', etc. which is used to determine
whether the symbol is a function or variable."
  (let ((classification (el-patch--classify-definition-type type)))
    (when (pcase classification
            ('function (fboundp name))
            ('variable (boundp name)))
      (let* (;; Since Emacs actually opens the source file in a (hidden)
             ;; buffer, it can try to apply local variables, which might
             ;; result in an annoying interactive prompt. Let's disable
             ;; that.
             (enable-local-variables nil)
             (enable-dir-local-variables nil)
             ;; This is supposed to be noninteractive so we also
             ;; suppress all the messages. This has the side effect of
             ;; masking all debugging messages (you can use `insert'
             ;; instead, or temporarily remove these bindings), but
             ;; there are just so many different messages that can
             ;; happen for various reasons and I haven't found any other
             ;; standard way to suppress them.
             (inhibit-message t)
             (message-log-max nil)
             ;; Now we actually do the find-function operation.
             (buffer-point (save-excursion
                             ;; This horrifying bit of hackery on
                             ;; `get-file-buffer' prevents
                             ;; `find-function-noselect' from
                             ;; returning an existing buffer, so that
                             ;; later on when we jump to the
                             ;; definition, we don't temporarily
                             ;; scroll the window if the definition
                             ;; happens to be in the *current* buffer.
                             (cl-letf (((symbol-function #'get-file-buffer)
                                        (symbol-function #'ignore)))
                               (pcase classification
                                 ('function
                                  (find-function-noselect name 'lisp-only))
                                 ('variable
                                  (find-variable-noselect name))))))
             (defun-buffer (car buffer-point))
             (defun-point (cdr buffer-point)))
        (and defun-buffer
             defun-point
             (with-current-buffer defun-buffer
               (save-excursion
                 (goto-char defun-point)
                 (read defun-buffer))))))))

;;;###autoload
(defun el-patch-validate (name type &optional nomsg run-hooks)
  "Validate the patch with given NAME and TYPE.
This means el-patch will attempt to find the original definition
for the function, and verify that it is the same as the original
function assumed by the patch. A warning will be signaled if the
original definition for a patched function cannot be found, or if
there is a difference between the actual and expected original
definitions.

Interactively, use `completing-read' to select a function to
inspect the patch of.

NAME is a symbol naming the object being patched; TYPE is a
symbol `defun', `defmacro', etc.

Returns nil if the patch is not valid, and otherwise returns t.
If NOMSG is non-nil, does not signal a message when the patch is
valid.

If RUN-HOOKS is non-nil, runs `el-patch-pre-validate-hook' and
`el-patch-post-validate-hook'. Interactively, this happens unless
a prefix argument is provided.

See also `el-patch-validate-all'."
  (interactive (progn
                 (unless current-prefix-arg
                   (run-hooks 'el-patch-pre-validate-hook))
                 (append (el-patch--select-patch)
                         (list nil (unless current-prefix-arg
                                     'post-only)))))
  (unless (member run-hooks '(nil post-only))
    (run-hooks 'el-patch-pre-validate-hook))
  (unwind-protect
      (progn
        (let* ((patch-definition (el-patch-get name type))
               (type (car patch-definition))
               (expected-definition (el-patch--resolve-definition
                                     patch-definition nil))
               (name (cadr expected-definition))
               (actual-definition (el-patch--find-symbol name type)))
          (cond
           ((not actual-definition)
            (display-warning
             'el-patch
             (format "Could not find definition of %S `%S'" type name))
            nil)
           ((not (equal expected-definition actual-definition))

            (display-warning
             'el-patch
             (format (concat "Definition of %S `%S' differs from what "
                             "is assumed by its patch")
                     type name))
            nil)
           (t
            (unless nomsg
              (message "Patch is valid"))
            t))))
    (when run-hooks
      (run-hooks 'el-patch-post-validate-hook))))

;;;###autoload
(defun el-patch-validate-all ()
  "Validate all currently defined patches.
Runs `el-patch-pre-validate-hook' and
`el-patch-post-validate-hook'.

See `el-patch-validate'."
  (interactive)
  (run-hooks 'el-patch-pre-validate-hook)
  (unwind-protect
      (let ((patch-count 0)
            (warning-count 0))
        (dolist (name (hash-table-keys el-patch--patches))
          (let ((patch-hash (gethash name el-patch--patches)))
            (dolist (type (hash-table-keys patch-hash))
              (setq patch-count (1+ patch-count))
              (unless (el-patch-validate name type 'nomsg)
                (setq warning-count (1+ warning-count))))))
        (cond
         ((zerop patch-count)
          (user-error "No patches defined"))
         ((zerop warning-count)
          (if (= patch-count 1)
              (message "Patch is valid (only one defined)")
            (message "All %d patches are valid" patch-count)))
         ((= patch-count warning-count)
          (if (= patch-count 1)
              (message "Patch is invalid (only one defined)")
            (message "All %d patches are invalid" patch-count)))
         (t
          (message "%s valid, %s invalid"
                   (if (= warning-count (1- patch-count))
                       "1 patch is"
                     (format "%d patches are" (- patch-count warning-count)))
                   (if (= warning-count 1)
                       "1 patch is"
                     (format "%d patches are" warning-count))))))
    (run-hooks 'el-patch-post-validate-hook)))

;;;;; el-patch-feature

;;;###autoload
(defmacro el-patch-feature (feature &rest args)
