Skip to content

Commit 36b6287

Browse files
authoredFeb 6, 2020
Fix instrumentation of small map literals (#665)
Map literal instrumentation was relying on some limited heuristic before. Now we use Clojure's underlying array-map implementation for small map literals and fall back to a sorted order of keys if that is not the case. If this fails as well we simply instrument only the values of the map in the order they appear in a sequential pass.
1 parent ba4a5b6 commit 36b6287

File tree

3 files changed

+63
-17
lines changed

3 files changed

+63
-17
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
* [#663](https://github.com/clojure-emacs/cider-nrepl/pull/663): Fix non-recognized `recur` inside `case` in the debugger.
1010
* [#664](https://github.com/clojure-emacs/cider-nrepl/pull/664): Fix continue-all in conditional break.
11+
* [#665](https://github.com/clojure-emacs/cider-nrepl/pull/665): Fix form location in map literals.
1112

1213
## 0.23.0 (2019-01-18)
1314

‎src/cider/nrepl/middleware/util/instrument.clj

+31-17
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
;;;; ## Instrumentation
2828
;;;
29-
;;; The top-level instrumenting function is `read-and-instrument`. See
29+
;;; The top-level instrumenting function is `instrument-tagged-code`. See
3030
;;; its doc for more information.
3131
;;;
3232
;;; Each of the other `instrument-*` functions is responsible for
@@ -208,7 +208,7 @@
208208
Only forms with a ::breakfunction metadata will be
209209
instrumented, and even then only if it makes sense (e.g., the
210210
binding names in a let form are never instrumented).
211-
See `read-and-instrument` for more information."
211+
See `instrument-tagged-code` for more information."
212212
[form]
213213
(condp #(%1 %2) form
214214
;; Function call, macro call, or special form.
@@ -225,7 +225,7 @@
225225
;;;
226226
;;; The following functions are used to populate with metadata and
227227
;;; macroexpand code before instrumenting it. This is where we
228-
;;; calculate all the ::coor vectors. See `read-and-instrument`.
228+
;;; calculate all the ::coor vectors. See `instrument-tagged-code`.
229229
(defn- walk-indexed
230230
"Walk through form calling (f coor element).
231231
The value of coor is a vector of indices representing element's
@@ -236,20 +236,34 @@
236236
(let [map-inner (fn [forms]
237237
(map-indexed #(walk-indexed (conj coor %1) f %2)
238238
forms))
239-
;; Maps are unordered, but we can try to use the keys (and
240-
;; they're important enough that we're willing to risk
241-
;; getting the position wrong).
242-
result (cond (map? form) (into {} (map (fn [[k v]]
243-
[k (walk-indexed (conj coor (pr-str k)) f v)])
244-
form))
245-
;; Order of sets is unpredictable, unfortunately.
246-
(set? form) form
247-
;; Borrowed from clojure.walk/walk
248-
(list? form) (apply list (map-inner form))
249-
(instance? clojure.lang.IMapEntry form) (vec (map-inner form))
250-
(seq? form) (doall (map-inner form))
251-
(coll? form) (into (empty form) (map-inner form))
252-
:else form)]
239+
;; Clojure uses array-maps up to some map size (8 currently).
240+
;; So for small maps we take advantage of that, otherwise fall
241+
;; back to the heuristic below.
242+
;; Maps are unordered, but we can try to use the keys as order
243+
;; hoping they can be compared one by one and that the user
244+
;; has specified them in that order. If that fails we don't
245+
;; instrument the map. We also don't instrument sets.
246+
;; This depends on Clojure implementation details.
247+
walk-indexed-map (fn [map]
248+
(map-indexed (fn [i [k v]]
249+
[(walk-indexed (conj coor (* 2 i)) f k)
250+
(walk-indexed (conj coor (inc (* 2 i))) f v)])
251+
map))
252+
result (cond
253+
(map? form) (if (<= (count form) 8)
254+
(into {} (walk-indexed-map form))
255+
(try
256+
(into (sorted-map) (walk-indexed-map (into (sorted-map) form)))
257+
(catch Exception e
258+
form)))
259+
;; Order of sets is unpredictable, unfortunately.
260+
(set? form) form
261+
;; Borrowed from clojure.walk/walk
262+
(list? form) (apply list (map-inner form))
263+
(instance? clojure.lang.IMapEntry form) (vec (map-inner form))
264+
(seq? form) (doall (map-inner form))
265+
(coll? form) (into (empty form) (map-inner form))
266+
:else form)]
253267
(f coor (m/merge-meta result (meta form))))))
254268

255269
(defn coord<

‎test/clj/cider/nrepl/middleware/debug_integration_test.clj

+31
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,37 @@
429429
(<-- {:value "4"})
430430
(<-- {:status ["done"]}))
431431

432+
;;; Tests for map literals
433+
434+
(deftest small-literal-map-test
435+
(--> :eval
436+
"#dbg
437+
(seq
438+
{1 (+ 2 (+ 3 2))
439+
(inc 3) (+ 12345)})")
440+
(<-- {:debug-value "5" :coor [1 1 2]}) ; (+ 3 2)
441+
(--> :next)
442+
(<-- {:debug-value "7" :coor [1 1]}) ; (+ 2 (+ 3 2))
443+
(--> :next)
444+
(<-- {:debug-value "4" :coor [1 2]}) ; (inc 3)
445+
(--> :next)
446+
(<-- {:debug-value "12345" :coor [1 3]}) ; (+ 12345)
447+
(--> :next)
448+
(<-- {:value "([1 7] [4 12345])"}) ; (seq ...)
449+
(<-- {:status ["done"]}))
450+
451+
(deftest larger-literal-map-test
452+
(--> :eval
453+
"#dbg (count {1 2 3 (inc 4) 5 6 7 8 9 10 11 12 13 14 15 (inc 16) 17 (inc 18)})")
454+
(<-- {:debug-value "5" :coor [1 3]}) ; (inc 4)
455+
(--> :next)
456+
(<-- {:debug-value "17" :coor [1 15]}) ; (inc 15)
457+
(--> :next)
458+
(<-- {:debug-value "19" :coor [1 17]}) ; (inc 18)
459+
(--> :next)
460+
(<-- {:value "9"}) ; (count ...)
461+
(<-- {:status ["done"]}))
462+
432463
;;; Tests for conditional breakpoints
433464

434465
(deftest conditional-in-for-test

0 commit comments

Comments
 (0)
Please sign in to comment.