1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-06 06:20:55 -08:00

(pcase-mutually-exclusive): Use auto-generated table

The `pcase-mutually-exclusive-predicates` table was not very
efficient since it grew like O(N²) with the number of
predicates.  Replace it with an O(N) table that's auto-generated
from the `built-in-class` objects.

* lisp/emacs-lisp/pcase.el (pcase-mutually-exclusive-predicates):
Delete variable.
(pcase--subtype-bitsets): New function and constant.
(pcase--mutually-exclusive-p): Use them.
* lisp/emacs-lisp/cl-preloaded.el (built-in-class): Don't inline.
This commit is contained in:
Stefan Monnier 2024-03-28 00:06:00 -04:00
parent 1552f8345d
commit f1fe13ea05
3 changed files with 92 additions and 55 deletions

View file

@ -303,6 +303,7 @@
(cl-defstruct (built-in-class (cl-defstruct (built-in-class
(:include cl--class) (:include cl--class)
(:noinline t)
(:constructor nil) (:constructor nil)
(:constructor built-in-class--make (name docstring parents)) (:constructor built-in-class--make (name docstring parents))
(:copier nil)) (:copier nil))

View file

@ -623,62 +623,83 @@ recording whether the var has been referenced by earlier parts of the match."
(defun pcase--and (match matches) (defun pcase--and (match matches)
(if matches `(and ,match ,@matches) match)) (if matches `(and ,match ,@matches) match))
(defconst pcase-mutually-exclusive-predicates (defun pcase--subtype-bitsets ()
'((symbolp . integerp) (let ((built-in-types ()))
(symbolp . numberp) (mapatoms (lambda (sym)
(symbolp . consp) (let ((class (get sym 'cl--class)))
(symbolp . arrayp) (when (and (built-in-class-p class)
(symbolp . vectorp) (get sym 'cl-deftype-satisfies))
(symbolp . stringp) (push (list sym
(symbolp . byte-code-function-p) (get sym 'cl-deftype-satisfies)
(symbolp . compiled-function-p) (cl--class-allparents class))
(symbolp . recordp) built-in-types)))))
(null . integerp) ;; The "true" predicate for `function' type is `cl-functionp'.
(null . numberp) (setcar (nthcdr 1 (assq 'function built-in-types)) 'cl-functionp)
(null . numberp) ;; Sort the types from deepest in the hierarchy so all children
(null . consp) ;; are processed before their parent. It also gives lowest
(null . arrayp) ;; numbers to those types that are subtypes of the largest number
(null . vectorp) ;; of types, which minimize the need to use bignums.
(null . stringp) (setq built-in-types (sort built-in-types
(null . byte-code-function-p) (lambda (x y)
(null . compiled-function-p) (> (length (nth 2 x)) (length (nth 2 y))))))
(null . recordp)
(integerp . consp)
(integerp . arrayp)
(integerp . vectorp)
(integerp . stringp)
(integerp . byte-code-function-p)
(integerp . compiled-function-p)
(integerp . recordp)
(numberp . consp)
(numberp . arrayp)
(numberp . vectorp)
(numberp . stringp)
(numberp . byte-code-function-p)
(numberp . compiled-function-p)
(numberp . recordp)
(consp . arrayp)
(consp . atom)
(consp . vectorp)
(consp . stringp)
(consp . byte-code-function-p)
(consp . compiled-function-p)
(consp . recordp)
(arrayp . byte-code-function-p)
(arrayp . compiled-function-p)
(vectorp . byte-code-function-p)
(vectorp . compiled-function-p)
(vectorp . recordp)
(stringp . vectorp)
(stringp . recordp)
(stringp . byte-code-function-p)
(stringp . compiled-function-p)))
(let ((bitsets (make-hash-table))
(i 1))
(dolist (x built-in-types)
;; Don't dedicate any bit to those predicates which already
;; have a bitset, since it means they're already represented
;; by their subtypes.
(unless (and (nth 1 x) (gethash (nth 1 x) bitsets))
(dolist (parent (nth 2 x))
(let ((pred (nth 1 (assq parent built-in-types))))
(unless (or (eq parent t) (null pred))
(puthash pred (+ i (gethash pred bitsets 0))
bitsets))))
(setq i (+ i i))))
;; Extra predicates that don't have matching types.
(dolist (pred-types '((functionp cl-functionp consp symbolp)
(keywordp symbolp)
(characterp fixnump)
(natnump integerp)
(facep symbolp stringp)
(plistp listp)
(cl-struct-p recordp)
;; ;; FIXME: These aren't quite in the same
;; ;; category since they'll signal errors.
(fboundp symbolp)
))
(puthash (car pred-types)
(apply #'logior
(mapcar (lambda (pred)
(gethash pred bitsets))
(cdr pred-types)))
bitsets))
bitsets)))
(defconst pcase--subtype-bitsets
(if (fboundp 'built-in-class-p)
(pcase--subtype-bitsets)
;; Early bootstrap: we don't have the built-in classes yet, so just
;; use an empty table for now.
(prog1 (make-hash-table)
;; The empty table leads to significantly worse code, so upgrade
;; to the real table as soon as possible (most importantly: before we
;; start compiling code, and hence baking the result into files).
(with-eval-after-load 'cl-preloaded
(defconst pcase--subtype-bitsets (pcase--subtype-bitsets)))))
"Table mapping predicates to their set of types.
These are the set of built-in types for which they may return non-nil.
The sets are represented as bitsets (integers) where each bit represents
a specific leaf type. Which bit represents which type is unspecified.")
;; Extra predicates
(defun pcase--mutually-exclusive-p (pred1 pred2) (defun pcase--mutually-exclusive-p (pred1 pred2)
(or (member (cons pred1 pred2) (let ((subtypes1 (gethash pred1 pcase--subtype-bitsets)))
pcase-mutually-exclusive-predicates) (when subtypes1
(member (cons pred2 pred1) (let ((subtypes2 (gethash pred2 pcase--subtype-bitsets)))
pcase-mutually-exclusive-predicates))) (when subtypes2
(zerop (logand subtypes1 subtypes2)))))))
(defun pcase--split-match (sym splitter match) (defun pcase--split-match (sym splitter match)
(cond (cond
@ -814,7 +835,8 @@ A and B can be one of:
((vectorp (cadr pat)) #'vectorp) ((vectorp (cadr pat)) #'vectorp)
((compiled-function-p (cadr pat)) ((compiled-function-p (cadr pat))
#'compiled-function-p)))) #'compiled-function-p))))
(pcase--mutually-exclusive-p (cadr upat) otherpred)) (and otherpred
(pcase--mutually-exclusive-p (cadr upat) otherpred)))
'(:pcase--fail . nil)) '(:pcase--fail . nil))
;; Since we turn (or 'a 'b 'c) into (pred (memq _ '(a b c))) ;; Since we turn (or 'a 'b 'c) into (pred (memq _ '(a b c)))
;; try and preserve the info we get from that memq test. ;; try and preserve the info we get from that memq test.

View file

@ -160,4 +160,18 @@
(should-error (pcase-setq a) (should-error (pcase-setq a)
:type '(wrong-number-of-arguments))) :type '(wrong-number-of-arguments)))
(ert-deftest pcase-tests-mutually-exclusive ()
(dolist (x '((functionp consp nil)
(functionp stringp t)
(compiled-function-p consp t)
(keywordp symbolp nil)
(keywordp symbol-with-pos-p nil)
(keywordp stringp t)))
(if (nth 2 x)
(should (pcase--mutually-exclusive-p (nth 0 x) (nth 1 x)))
(should-not (pcase--mutually-exclusive-p (nth 0 x) (nth 1 x))))
(if (nth 2 x)
(should (pcase--mutually-exclusive-p (nth 1 x) (nth 0 x)))
(should-not (pcase--mutually-exclusive-p (nth 1 x) (nth 0 x))))))
;;; pcase-tests.el ends here. ;;; pcase-tests.el ends here.