Make clojure.spec.alpha load and run

Four general gaps, shaken out by loading clojure.spec.alpha:

- Special forms were shadowable by a same-named macro. analyze-list
  macroexpanded before checking special forms, so a ns that redefs def/and/or
  (spec excludes them via :refer-clojure :exclude) made a bare def resolve to
  the macro instead of the special form, breaking every defn after. Now a head
  in the special-form set is never macroexpanded, matching the reference
  macroexpand1 isSpecial check.

- reify dropped all but the last arity of a multi-arity protocol method (spec
  reifies (specize* [s]) and (specize* [s _])). The macro keyed methods by name
  and overwrote; now it groups arities into one multi-arity fn.

- reify instances did not implement IObj: with-meta threw and (instance?
  clojure.lang.IObj r) was false. Every Clojure reify carries metadata. with-meta
  now copies the reify to a fresh identity (shared method table) and keys its
  meta; instance? IObj/IMeta is true for any reify. This was the registry bug —
  spec's with-name returned nil for specs, so get-spec missed.

- (set! (. Class field) val) was rejected. spec toggles
  clojure.lang.RT/checkSpecAsserts this way; the analyzer now lowers it to a
  jolt.host/set-static-field! call over a mutable-statics table, and a plain
  Class/field read consults that table.

Also: .name/.getName on a Namespace and .ns/.sym on a Var (spec's ns-qualify /
->sym). analyzer + reify are seed sources (re-minted). spec.alpha now does
valid?/conform/cat/keys/explain-str/check-asserts. tick.alpha.interval-test still
needs time-literals data readers (separate).
This commit is contained in:
Yogthos 2026-06-24 19:46:22 -04:00
parent f417516148
commit 7a343351d6
11 changed files with 598 additions and 532 deletions

View file

@ -543,6 +543,9 @@
{:suite "host-interop / ring-codec surface" :label "extend-protocol nil" :expected ":nil" :actual "(do (defprotocol Pn (pn [x])) (extend-protocol Pn nil (pn [n] :nil) Object (pn [o] :obj)) (pn nil))"}
{:suite "host-interop / ring-codec surface" :label "extend-protocol Map covers sorted" :expected ":map" :actual "(do (defprotocol Ps (ps [x])) (extend-protocol Ps Map (ps [m] :map) Object (ps [o] :obj)) (ps (sorted-map 1 2)))"}
{:suite "host-interop / ring-codec surface" :label "reduce over reified IReduceInit" :expected "42" :actual "(reduce + 0 (reify clojure.lang.IReduceInit (reduce [_ f init] (f (f init 40) 2))))"}
{:suite "core / reify" :label "multi-arity method dispatches by arg count" :expected "[:z 9]" :actual "(do (defprotocol P (m [_] [_ a])) (let [r (reify P (m [_] :z) (m [_ x] x))] [(m r) (m r 9)]))"}
{:suite "core / reify" :label "reify implements IObj and carries metadata" :expected "[true 2]" :actual "(do (defprotocol Q (qq [_])) (let [r (reify Q (qq [_] 1))] [(instance? clojure.lang.IObj r) (:k (meta (with-meta r {:k 2})))]))"}
{:suite "core / reify" :label "with-meta leaves the original untouched and keeps dispatch" :expected "[nil {:k 2} 1]" :actual "(do (defprotocol Q (qq [_])) (let [r (reify Q (qq [_] 1)) r2 (with-meta r {:k 2})] [(meta r) (meta r2) (qq r2)]))"}
{:suite "host-interop / class tokens & readers" :label "class name evaluates to canonical string" :expected "java.lang.String" :actual "String"}
{:suite "host-interop / class tokens & readers" :label "dispatch-only class name" :expected "\"java.io.InputStream\"" :actual "InputStream"}
{:suite "host-interop / class tokens & readers" :label "(class x) matches the token" :expected "true" :actual "(= String (class \"abc\"))"}

View file

@ -1,4 +1,6 @@
[
{:suite "special-form precedence" :expr "(do (ns sftest (:refer-clojure :exclude [def])) (defmacro def [n v] :shadowed) (clojure.core/defn ff [] 99) (ff))" :expected "99"}
{:suite "special-form precedence" :expr "(do (set! (. clojure.lang.RT checkSpecAsserts) true) clojure.lang.RT/checkSpecAsserts)" :expected "true"}
{:suite "atomwatch" :expr "(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (reset! seen 1))) (reset! a 5) @seen)" :expected "1"}
{:suite "atomwatch" :expr "(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (swap! seen inc))) (remove-watch a :k) (reset! a 5) @seen)" :expected "0"}
{:suite "atomwatch" :expr "(let [a (atom 0) log (atom [])] (add-watch a :k (fn [k r o n] (swap! log conj [o n]))) (reset! a 1) (reset! a 2) @log)" :expected "[[0 1] [1 2]]"}