aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2018-12-17 14:51:01 -0500
committerStefan Monnier2018-12-17 14:51:01 -0500
commit55838e4e6a176317367c6759e0520395e80c856f (patch)
tree49f0293f48e1084e076a936e8b3476f35efe7cf5
parent2c3f7f9c45985c36fd9e86c334b49b10e8c8c270 (diff)
downloademacs-55838e4e6a176317367c6759e0520395e80c856f.tar.gz
emacs-55838e4e6a176317367c6759e0520395e80c856f.zip
* lisp/emacs-lisp/map.el: Avoid special casing lists.
(map-not-inplace, map-inplace): New errors. (map-insert): New generic function. (map-put!): Signal map-not-inplace rather than a generic 'error'. (map-elt): Use map-not-inplace and map-insert to avoid hardcoding a special case for lists. * test/lisp/emacs-lisp/map-tests.el (test-map-put!): Rename from test-map-put. Also test the errors signaled.
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/emacs-lisp/map.el51
-rw-r--r--test/lisp/emacs-lisp/map-tests.el20
3 files changed, 54 insertions, 20 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 327276eef9b..95647bbda4f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -307,8 +307,9 @@ the node "(emacs) Directory Variables" of the user manual.
307** map.el 307** map.el
308*** Now defined via generic functions that can be extended via cl-defmethod. 308*** Now defined via generic functions that can be extended via cl-defmethod.
309*** Deprecate the 'map-put' macro in favor of a new 'map-put!' function. 309*** Deprecate the 'map-put' macro in favor of a new 'map-put!' function.
310*** map-contains-key now returns a boolean rather than the key. 310*** 'map-contains-key' now returns a boolean rather than the key.
311*** Deprecate the 'testfn' args of 'map-elt' and 'map-contains-key'. 311*** Deprecate the 'testfn' args of 'map-elt' and 'map-contains-key'.
312*** New generic function 'map-insert'.
312 313
313--- 314---
314** Follow mode 315** Follow mode
diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el
index 78cedd3ab10..d5051fcd98a 100644
--- a/lisp/emacs-lisp/map.el
+++ b/lisp/emacs-lisp/map.el
@@ -95,12 +95,13 @@ Returns the result of evaluating the form associated with MAP-VAR's type."
95 (t (error "Unsupported map type `%S': %S" 95 (t (error "Unsupported map type `%S': %S"
96 (type-of ,map-var) ,map-var))))) 96 (type-of ,map-var) ,map-var)))))
97 97
98(define-error 'map-not-inplace "Cannot modify map in-place: %S")
99
98(cl-defgeneric map-elt (map key &optional default testfn) 100(cl-defgeneric map-elt (map key &optional default testfn)
99 "Lookup KEY in MAP and return its associated value. 101 "Lookup KEY in MAP and return its associated value.
100If KEY is not found, return DEFAULT which defaults to nil. 102If KEY is not found, return DEFAULT which defaults to nil.
101 103
102TESTFN is deprecated. Its default depends on the MAP argument. 104TESTFN is deprecated. Its default depends on the MAP argument.
103If MAP is a list, the default is `eql' to lookup KEY.
104 105
105In the base definition, MAP can be an alist, hash-table, or array." 106In the base definition, MAP can be an alist, hash-table, or array."
106 (declare 107 (declare
@@ -110,15 +111,16 @@ In the base definition, MAP can be an alist, hash-table, or array."
110 (macroexp-let2* nil 111 (macroexp-let2* nil
111 ;; Eval them once and for all in the right order. 112 ;; Eval them once and for all in the right order.
112 ((key key) (default default) (testfn testfn)) 113 ((key key) (default default) (testfn testfn))
113 `(if (listp ,mgetter) 114 (funcall do `(map-elt ,mgetter ,key ,default)
114 ;; Special case the alist case, since it can't be handled by the 115 (lambda (v)
115 ;; map--put function. 116 `(condition-case nil
116 ,(gv-get `(alist-get ,key (gv-synthetic-place 117 ;; Silence warnings about the hidden 4th arg.
117 ,mgetter ,msetter) 118 (with-no-warnings (map-put! ,mgetter ,key ,v ,testfn))
118 ,default nil ,testfn) 119 (map-not-inplace
119 do) 120 ,(funcall msetter
120 ,(funcall do `(map-elt ,mgetter ,key ,default) 121 `(map-insert ,mgetter ,key ,v))))))))))
121 (lambda (v) `(map-put! ,mgetter ,key ,v))))))))) 122 ;; `testfn' is deprecated.
123 (advertised-calling-convention (map key &optional default) "27.1"))
122 (map--dispatch map 124 (map--dispatch map
123 :list (alist-get key map default nil testfn) 125 :list (alist-get key map default nil testfn)
124 :hash-table (gethash key map default) 126 :hash-table (gethash key map default)
@@ -336,17 +338,36 @@ MAP can be a list, hash-table or array."
336;; FIXME: I wish there was a way to avoid this η-redex! 338;; FIXME: I wish there was a way to avoid this η-redex!
337(cl-defmethod map-into (map (_type (eql list))) (map-pairs map)) 339(cl-defmethod map-into (map (_type (eql list))) (map-pairs map))
338 340
339(cl-defgeneric map-put! (map key value) 341(cl-defgeneric map-put! (map key value &optional testfn)
340 "Associate KEY with VALUE in MAP and return VALUE. 342 "Associate KEY with VALUE in MAP and return VALUE.
341If KEY is already present in MAP, replace the associated value 343If KEY is already present in MAP, replace the associated value
342with VALUE." 344with VALUE.
345This operates by modifying MAP in place.
346If it cannot do that, it signals the `map-not-inplace' error.
347If you want to insert an element without modifying MAP, use `map-insert'."
348 ;; `testfn' only exists for backward compatibility with `map-put'!
349 (declare (advertised-calling-convention (map key value) "27.1"))
343 (map--dispatch map 350 (map--dispatch map
344 :list (let ((p (assoc key map))) 351 :list (let ((oldmap map))
345 (if p (setcdr p value) 352 (setf (alist-get key map key nil (or testfn #'equal)) value)
346 (error "No place to change the mapping for %S" key))) 353 (unless (eq oldmap map)
354 (signal 'map-not-inplace (list map))))
347 :hash-table (puthash key value map) 355 :hash-table (puthash key value map)
356 ;; FIXME: If `key' is too large, should we signal `map-not-inplace'
357 ;; and let `map-insert' grow the array?
348 :array (aset map key value))) 358 :array (aset map key value)))
349 359
360(define-error 'map-inplace "Can only modify map in place: %S")
361
362(cl-defgeneric map-insert (map key value)
363 "Return a new map like MAP except that it associates KEY with VALUE.
364This does not modify MAP.
365If you want to insert an element in place, use `map-put!'."
366 (if (listp map)
367 (cons (cons key value) map)
368 ;; FIXME: Should we signal an error or use copy+put! ?
369 (signal 'map-inplace (list map))))
370
350;; There shouldn't be old source code referring to `map--put', yet we do 371;; There shouldn't be old source code referring to `map--put', yet we do
351;; need to keep it for backward compatibility with .elc files where the 372;; need to keep it for backward compatibility with .elc files where the
352;; expansion of `setf' may call this function. 373;; expansion of `setf' may call this function.
diff --git a/test/lisp/emacs-lisp/map-tests.el b/test/lisp/emacs-lisp/map-tests.el
index 885b09be985..4dd67d48d40 100644
--- a/test/lisp/emacs-lisp/map-tests.el
+++ b/test/lisp/emacs-lisp/map-tests.el
@@ -76,13 +76,25 @@ Evaluate BODY for each created map.
76 'b 76 'b
77 '2)))) 77 '2))))
78 78
79(ert-deftest test-map-put () 79(ert-deftest test-map-put! ()
80 (with-maps-do map 80 (with-maps-do map
81 (setf (map-elt map 2) 'hello) 81 (setf (map-elt map 2) 'hello)
82 (should (eq (map-elt map 2) 'hello))) 82 (should (eq (map-elt map 2) 'hello)))
83 (with-maps-do map 83 (with-maps-do map
84 (map-put map 2 'hello) 84 (map-put map 2 'hello)
85 (should (eq (map-elt map 2) 'hello))) 85 (should (eq (map-elt map 2) 'hello)))
86 (with-maps-do map
87 (map-put! map 2 'hello)
88 (should (eq (map-elt map 2) 'hello))
89 (if (not (hash-table-p map))
90 (should-error (map-put! map 5 'value)
91 ;; For vectors, it could arguably signal
92 ;; map-not-inplace as well, but it currently doesn't.
93 :type (if (listp map)
94 'map-not-inplace
95 'error))
96 (map-put! map 5 'value)
97 (should (eq (map-elt map 5) 'value))))
86 (let ((ht (make-hash-table))) 98 (let ((ht (make-hash-table)))
87 (setf (map-elt ht 2) 'a) 99 (setf (map-elt ht 2) 'a)
88 (should (eq (map-elt ht 2) 100 (should (eq (map-elt ht 2)
@@ -92,7 +104,7 @@ Evaluate BODY for each created map.
92 (should (eq (map-elt alist 2) 104 (should (eq (map-elt alist 2)
93 'a))) 105 'a)))
94 (let ((vec [3 4 5])) 106 (let ((vec [3 4 5]))
95 (should-error (setf (map-elt vec 3) 6)))) 107 (should-error (setf (map-elt vec 3) 6))))
96 108
97(ert-deftest test-map-put-alist-new-key () 109(ert-deftest test-map-put-alist-new-key ()
98 "Regression test for Bug#23105." 110 "Regression test for Bug#23105."
@@ -105,9 +117,9 @@ Evaluate BODY for each created map.
105 (let ((alist (list (cons "a" 1) (cons "b" 2))) 117 (let ((alist (list (cons "a" 1) (cons "b" 2)))
106 ;; Make sure to use a non-eq "a", even when compiled. 118 ;; Make sure to use a non-eq "a", even when compiled.
107 (noneq-key (string ?a))) 119 (noneq-key (string ?a)))
108 (map-put alist noneq-key 3 'equal) 120 (map-put alist noneq-key 3 #'equal)
109 (should-not (cddr alist)) 121 (should-not (cddr alist))
110 (map-put alist noneq-key 9) 122 (map-put alist noneq-key 9 #'eql)
111 (should (cddr alist)))) 123 (should (cddr alist))))
112 124
113(ert-deftest test-map-put-return-value () 125(ert-deftest test-map-put-return-value ()