diff options
author | Basil L. Contovounesios <contovob@tcd.ie> | 2021-08-04 00:48:50 +0100 |
---|---|---|
committer | Basil L. Contovounesios <contovob@tcd.ie> | 2021-08-14 11:24:54 +0100 |
commit | 37d48edf6d406a4730caa0393f7695de2bfadfcc (patch) | |
tree | f2fad1ae506e4def91c2c59be3b74ffaea3ee46b /lisp/emacs-lisp | |
parent | 1bfbb2b706db6a7ca9420b27d22a737deccdd5b0 (diff) | |
download | emacs-37d48edf6d406a4730caa0393f7695de2bfadfcc.tar.gz emacs-37d48edf6d406a4730caa0393f7695de2bfadfcc.tar.bz2 emacs-37d48edf6d406a4730caa0393f7695de2bfadfcc.zip |
Fix merging of ambiguous nil maps
* lisp/emacs-lisp/map.el: Bump version to 3.1.
(map--merge): New merging subroutine that uses a hash table in place
of lists, for both efficiency and avoiding ambiguities (bug#49848).
(map-merge): Rewrite in terms of map--merge.
(map-merge-with): Ditto. This ensures that FUNCTION is called
whenever two keys are merged, even if they are not eql (which could
happen until now). It also makes map-merge-with consistent with
map-merge, thus achieving greater overall predictability.
* etc/NEWS: Announce this weakening of guarantees.
* test/lisp/emacs-lisp/map-tests.el (test-map-merge)
(test-map-merge-with): Don't depend on specific orderings. Test
that nil is correctly merged into a plist.
Diffstat (limited to 'lisp/emacs-lisp')
-rw-r--r-- | lisp/emacs-lisp/map.el | 60 |
1 files changed, 37 insertions, 23 deletions
diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el index c59342875db..988a62a4e34 100644 --- a/lisp/emacs-lisp/map.el +++ b/lisp/emacs-lisp/map.el @@ -5,7 +5,7 @@ ;; Author: Nicolas Petton <nicolas@petton.fr> ;; Maintainer: emacs-devel@gnu.org ;; Keywords: extensions, lisp -;; Version: 3.0 +;; Version: 3.1 ;; Package-Requires: ((emacs "26")) ;; This file is part of GNU Emacs. @@ -371,37 +371,51 @@ The default implementation delegates to `map-do'." map) t)) +(defun map--merge (merge type &rest maps) + "Merge into a map of TYPE all the key/value pairs in MAPS. +MERGE is a function that takes the target MAP, a KEY, and a +VALUE, merges KEY and VALUE into MAP, and returns the result. +MAP may be of a type other than TYPE." + ;; Use a hash table internally if `type' is a list. This avoids + ;; both quadratic lookup behavior and the type ambiguity of nil. + (let* ((tolist (memq type '(list alist plist))) + (result (map-into (pop maps) + ;; Use same testfn as `map-elt' gv setter. + (cond ((eq type 'plist) '(hash-table :test eq)) + (tolist '(hash-table :test equal)) + (type))))) + (dolist (map maps) + (map-do (lambda (key value) + (setq result (funcall merge result key value))) + map)) + ;; Convert internal representation to desired type. + (if tolist (map-into result type) result))) + (defun map-merge (type &rest maps) "Merge into a map of TYPE all the key/value pairs in MAPS. See `map-into' for all supported values of TYPE." - (let ((result (map-into (pop maps) type))) - (while maps - ;; FIXME: When `type' is `list', we get an O(N^2) behavior. - ;; For small tables, this is fine, but for large tables, we - ;; should probably use a hash-table internally which we convert - ;; to an alist in the end. - (map-do (lambda (key value) - (setf (map-elt result key) value)) - (pop maps))) - result)) + (apply #'map--merge + (lambda (result key value) + (setf (map-elt result key) value) + result) + type maps)) (defun map-merge-with (type function &rest maps) "Merge into a map of TYPE all the key/value pairs in MAPS. -When two maps contain the same (`eql') key, call FUNCTION on the two +When two maps contain the same key, call FUNCTION on the two values and use the value returned by it. Each of MAPS can be an alist, plist, hash-table, or array. See `map-into' for all supported values of TYPE." - (let ((result (map-into (pop maps) type)) - (not-found (list nil))) - (while maps - (map-do (lambda (key value) - (cl-callf (lambda (old) - (if (eql old not-found) - value - (funcall function old value))) - (map-elt result key not-found))) - (pop maps))) - result)) + (let ((not-found (list nil))) + (apply #'map--merge + (lambda (result key value) + (cl-callf (lambda (old) + (if (eql old not-found) + value + (funcall function old value))) + (map-elt result key not-found)) + result) + type maps))) (cl-defgeneric map-into (map type) "Convert MAP into a map of TYPE.") |