fix: transient invokable lookup, assoc! odd args, use-after-persistent! invalidation
Real transient correctness gaps surfaced by the clojure-test-suite: - Transients are now invokable for read-only lookup like their persistent forms: ((transient v) i), ((transient m) k [default]), (:k (transient m)), ((transient s) x). jolt-invoke/coll-lookup gained a transient branch (transient-lookup) that indexes :arr / canon-keyed :tbl. Added phm/canon as a public canonicalizer so collection keys compare by value here too. - assoc! accepts an ODD arg count (a missing final value is nil), unlike assoc — core-assoc! now uses nil-safe get for the value instead of erroring. - Using a transient after persistent! (or a second persistent!, or pop! on an empty transient vector) now throws, via a :jolt/persistent invalidation flag checked by the mutating ops. Catches the classic transient footgun. spec: transients-spec gains invokable-lookup (7), assoc!-odd-args (4), invalidation (4). clojure-test-suite pass 1704->1719. Remaining transient fails are accepted lenient divergences (jolt doesn't throw on bad-shape conj!/assoc!/pop! of wrong-typed args; nil set elements). jpm test green.
This commit is contained in:
parent
e75771084e
commit
09532dac05
5 changed files with 83 additions and 10 deletions
|
|
@ -44,3 +44,35 @@
|
|||
"(let [v [1 2 3] _ (persistent! (conj! (transient v) 4))] (= v [1 2 3]))"]
|
||||
["source map unchanged" "true"
|
||||
"(let [m {:a 1} _ (persistent! (assoc! (transient m) :b 2))] (= m {:a 1}))"])
|
||||
|
||||
# Transients are invokable for read-only lookup, like their persistent forms.
|
||||
(defspec "transient / invokable lookup"
|
||||
["vector index" "20" "((transient [10 20 30]) 1)"]
|
||||
["map key as fn" "7" "((transient {:x 7}) :x)"]
|
||||
["map key default" "99" "((transient {:x 7}) :z 99)"]
|
||||
["keyword on transient" "7" "(:x (transient {:x 7}))"]
|
||||
["set membership" "2" "((transient #{1 2 3}) 2)"]
|
||||
["set miss default" ":no" "((transient #{1 2 3}) 42 :no)"]
|
||||
["collection key" ":v" "((transient {[1 2] :v}) [1 2])"])
|
||||
|
||||
# assoc! (unlike assoc) accepts an odd arg count — a missing final value is nil.
|
||||
# (A struct literal can't express an explicit nil value, so assert via contains?.)
|
||||
(defspec "transient / assoc! odd args"
|
||||
["odd arg key present" "true"
|
||||
"(contains? (persistent! (assoc! (transient {}) :a 1 :b)) :b)"]
|
||||
["odd arg value is nil" "true"
|
||||
"(nil? (get (persistent! (assoc! (transient {}) :a 1 :b)) :b))"]
|
||||
["odd arg keeps prior" "1"
|
||||
"(get (persistent! (assoc! (transient {}) :a 1 :b)) :a)"]
|
||||
["vector odd arg" "[9 nil]"
|
||||
"(persistent! (apply assoc! (transient []) [0 9 1]))"])
|
||||
|
||||
# Using a transient after persistent! (or popping an empty one) throws.
|
||||
(defspec "transient / invalidation"
|
||||
["conj! after persistent!" :throws
|
||||
"(let [t (transient [])] (persistent! t) (conj! t 1))"]
|
||||
["assoc! after persistent!" :throws
|
||||
"(let [t (transient {})] (persistent! t) (assoc! t :a 1))"]
|
||||
["persistent! twice" :throws
|
||||
"(let [t (transient [])] (persistent! t) (persistent! t))"]
|
||||
["pop! empty" :throws "(pop! (transient []))"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue