proxy [ThreadLocal] via thread-parameter; clojure.test/*testing-vars*

- (proxy [ThreadLocal] [] (initialValue [] body)) now builds a real per-thread
  store backed by a Chez thread-parameter, with a lazy initialValue; .get/.set/
  .remove work. Other proxies stay nil. test.check's no-seed PRNG (next-rng) uses
  one, so gen/sample and gen/generate (and everything built on them) now work.
- clojure.test/*testing-vars* (+ *report-counters*) are bound vars now, so a
  defspec run through its :test metadata / default reporter doesn't hit an unbound
  var.

make test green (+1 corpus row), shakesmoke byte-identical. One re-mint (proxy).
This commit is contained in:
Yogthos 2026-06-27 19:51:49 -04:00
parent f32bd335e3
commit 4d61145e9c
6 changed files with 587 additions and 558 deletions

View file

@ -697,6 +697,22 @@
(def-var! "clojure.core" "__register-class-methods!"
(lambda (tag members) (register-tagged-methods! tag (jmap->static-alist members)) jolt-nil))
;; java.lang.ThreadLocal via a Chez thread-parameter: real per-thread storage with
;; a lazy initialValue (the proxy macro lowers (proxy [ThreadLocal] …) to this).
;; .get returns the thread's value, computing initialValue once; .set / .remove.
(define tl-unset (list 'tl-unset))
(define (jolt-make-thread-local init-thunk)
(make-jhost "threadlocal" (vector (make-thread-parameter tl-unset) init-thunk)))
(register-host-methods! "threadlocal"
(list (cons "get" (lambda (self)
(let* ((st (jhost-state self)) (tp (vector-ref st 0)) (v (tp)))
(if (eq? v tl-unset)
(let ((nv (jolt-invoke (vector-ref st 1)))) (tp nv) nv)
v))))
(cons "set" (lambda (self v) ((vector-ref (jhost-state self) 0) v) jolt-nil))
(cons "remove" (lambda (self) ((vector-ref (jhost-state self) 0) tl-unset) jolt-nil))))
(def-var! "jolt.host" "make-thread-local" jolt-make-thread-local)
;; Pluggable instance? — a library registers (fn [class-name-string val] -> true
;; | false | nil); nil means "not my class, fall through". First non-nil wins.
(define user-instance-checks '())

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -558,8 +558,14 @@
(parse-extend-impls type-impls))))
;; extend is a real FUNCTION — defined above extend-type.
;; JVM proxies are unsupported.
(defmacro proxy [& args] nil)
;; JVM proxies are unsupported in general, EXCEPT (proxy [ThreadLocal] [] (initialValue
;; [] body)) — a per-thread store with a lazy initial value (test.check's no-seed
;; PRNG uses one). Other proxies stay nil.
(defmacro proxy [supers ctor-args & methods]
(when (and (vector? supers) (= 1 (count supers))
(let [s (name (first supers))] (or (= s "ThreadLocal") (= s "InheritableThreadLocal"))))
(let [init (some (fn [m] (when (= "initialValue" (name (first m))) m)) methods)]
`(jolt.host/make-thread-local (fn [] ~@(when init (nnext init)))))))
;; definterface is JVM-only; bind the name to a marker and return the name (not a
;; var), matching the JVM where definterface yields the interface Class.
(defmacro definterface [name-sym & body]

View file

@ -22,6 +22,12 @@
(def once-fixtures (atom []))
(def each-fixtures (atom []))
;; clojure.test/*testing-vars* — the stack of vars under test. Real clojure.test
;; binds it around each test var; test.check's default reporter reads it, so a
;; defspec run through its :test metadata doesn't blow up on an unbound var.
(def ^:dynamic *testing-vars* (list))
(def ^:dynamic *report-counters* nil)
(defn reset-report! []
(reset! counters {:test 0 :pass 0 :fail 0 :error 0 :fails []})
(reset! ctx-stack [])

View file

@ -3385,4 +3385,5 @@
{:suite "numbers / unchecked on doubles" :label "unchecked-multiply of doubles is a double, not a truncated long" :expected "3.0" :actual "(unchecked-multiply 1.5 2.0)"}
{:suite "seq / take infinite count" :label "(take +Infinity coll) takes the whole coll" :expected "7" :actual "(count (take (/ 1.0 0.0) (range 7)))"}
{:suite "host-interop / UUID + Long + shiftLeft" :label "(UUID. msb lsb), .shiftLeft, (Long. n)" :expected "[\"00000000-0000-007b-0000-0000000001c8\" 40 10 42]" :actual "[(str (java.util.UUID. 123 456)) (.shiftLeft 5 3) (.shiftRight 40 2) (Long. 42)]"}
{:suite "host-interop / ThreadLocal proxy" :label "(proxy [ThreadLocal] [] (initialValue [] v)) get/set" :expected "[42 7]" :actual "(let [tl (proxy [ThreadLocal] [] (initialValue [] 42))] [(.get tl) (do (.set tl 7) (.get tl))])"}
]