test(spec): state, multimethods, protocols — fix reify, get-method, hierarchies

Add spec suites for stateful refs (atoms/volatiles/delays/promises), multimethods
(dispatch + hierarchies), and protocols/types/records. Bugs caught and fixed:
- reify with multiple methods registered only the first (stepped i by 2 over
  one-element specs); now collects every method spec
- get-method/methods/remove-method evaluated their arg to the dispatch fn, but a
  multimethod's methods live on its var — now resolve the var (get-method and
  methods are special forms; remove-method fixed). get-method/methods added.
- no global hierarchy: 2-arg derive modified a throwaway, isa? was hardcoded
  false, parents missing, ancestors/descendants returned arrays (so contains?
  failed), and dispatch ignored derive. Now a global hierarchy backs the 1/2-arg
  derive/isa?/parents/ancestors/descendants (returning sets) and multimethod
  dispatch falls back to it.

Corrected a phase5 port assertion that encoded the old isa? bug.
conformance 218/218, jpm test green.
This commit is contained in:
Yogthos 2026-06-05 00:24:18 -04:00
parent 50d63d896f
commit a24e9bfba0
7 changed files with 174 additions and 29 deletions

View file

@ -6,9 +6,10 @@
(print "22: hierarchy...")
(let [ctx (init)]
(assert (= true (ct-eval ctx "(map? (make-hierarchy))")) "make-hierarchy returns map")
# 2-arity derive/isa? just returns nil/false (no global hierarchy)
# 2-arity derive/isa? use the global hierarchy
(ct-eval ctx "(derive ::square ::shape)")
(assert (= false (ct-eval ctx "(isa? ::square ::shape)")) "isa? 2-arity always false"))
(assert (= true (ct-eval ctx "(isa? ::square ::shape)")) "isa? 2-arity via global hierarchy")
(assert (= true (ct-eval ctx "(isa? ::square ::square)")) "isa? reflexive"))
(print " passed")
# 23. Multimethods — basic dispatch

View file

@ -0,0 +1,28 @@
# Specification: multimethods & hierarchies.
(use ../support/harness)
(defspec "multimethods / dispatch"
["dispatch on value" "\"two\""
"(do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f 2 [_] \"two\") (f 2))"]
["dispatch on keyword fn" "\"circle\""
"(do (defmulti area :shape) (defmethod area :circle [_] \"circle\") (area {:shape :circle}))"]
[":default method" "\"other\""
"(do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f :default [_] \"other\") (f 99))"]
["no match throws" :throws
"(do (defmulti f identity) (defmethod f 1 [_] \"one\") (f 99))"]
["multiple args" "5"
"(do (defmulti g (fn [a b] a)) (defmethod g :add [_ b] b) (g :add 5))"]
["get-method" "\"one\""
"(do (defmulti f identity) (defmethod f 1 [_] \"one\") ((get-method f 1) 1))"]
["remove-method" :throws
"(do (defmulti f identity) (defmethod f 1 [_] \"one\") (remove-method f 1) (f 1))"])
(defspec "multimethods / hierarchies"
["derive + isa?" "true" "(do (derive ::child ::parent) (isa? ::child ::parent))"]
["isa? reflexive" "true" "(isa? ::x ::x)"]
["isa? unrelated" "false" "(do (derive ::a ::b) (isa? ::a ::c))"]
["parents" "true" "(do (derive ::c ::p) (contains? (parents ::c) ::p))"]
["ancestors" "true" "(do (derive ::c ::p) (derive ::p ::g) (contains? (ancestors ::c) ::g))"]
["descendants" "true" "(do (derive ::c ::p) (contains? (descendants ::p) ::c))"]
["dispatch via hierarchy" "\"animal\""
"(do (derive ::dog ::animal) (defmulti speak identity) (defmethod speak ::animal [_] \"animal\") (speak ::dog))"])

View file

@ -0,0 +1,46 @@
# Specification: protocols, types and records.
(use ../support/harness)
(defspec "protocols / defprotocol & dispatch"
["protocol on record" "16"
"(do (defprotocol Shape (area [s])) (defrecord Sq [side] Shape (area [_] (* side side))) (area (->Sq 4)))"]
["protocol on deftype" "16"
"(do (defprotocol Shape (area [s])) (deftype Sq [side] Shape (area [_] (* side side))) (area (->Sq 4)))"]
["multiple methods" "[1 2]"
"(do (defprotocol P (m [s]) (n [s])) (defrecord R [a b] P (m [_] a) (n [_] b)) [(m (->R 1 2)) (n (->R 1 2))])"]
["multiple protocols" "[:a :b]"
"(do (defprotocol P1 (p1 [s])) (defprotocol P2 (p2 [s])) (deftype T [] P1 (p1 [_] :a) P2 (p2 [_] :b)) [(p1 (->T)) (p2 (->T))])"]
["method args" "7"
"(do (defprotocol P (add [s x])) (defrecord R [n] P (add [_ x] (+ n x))) (add (->R 5) 2))"]
["extend-type" "10"
"(do (defprotocol P (twice [s])) (extend-type Number P (twice [n] (* n 2))) (twice 5))"]
["extend-protocol" "[2 4]"
"(do (defprotocol P (dbl [s])) (extend-protocol P Number (dbl [n] (* n 2))) [(dbl 1) (dbl 2)])"])
(defspec "protocols / records"
["record field access" "1"
"(do (defrecord R [a b]) (:a (->R 1 2)))"]
["record map access" "2"
"(do (defrecord R [a b]) (get (->R 1 2) :b))"]
["record equality" "true"
"(do (defrecord R [a b]) (= (->R 1 2) (->R 1 2)))"]
["record inequality" "false"
"(do (defrecord R [a b]) (= (->R 1 2) (->R 3 4)))"]
["map-> factory" "1"
"(do (defrecord R [a b]) (:a (map->R {:a 1 :b 2})))"]
["record? true" "true"
"(do (defrecord R [a]) (record? (->R 1)))"]
["assoc on record" "9"
"(do (defrecord R [a]) (:a (assoc (->R 1) :a 9)))"])
(defspec "protocols / reify & satisfies"
["reify dispatch" "42"
"(do (defprotocol P (m [_])) (m (reify P (m [_] 42))))"]
["reify multi-method" "[1 2]"
"(do (defprotocol P (a [_]) (b [_])) (let [r (reify P (a [_] 1) (b [_] 2))] [(a r) (b r)]))"]
["satisfies? true" "true"
"(do (defprotocol P (m [_])) (defrecord R [] P (m [_] 1)) (satisfies? P (->R)))"]
["satisfies? false" "false"
"(do (defprotocol P (m [_])) (defrecord R []) (satisfies? P (->R)))"]
["instance? record" "true"
"(do (defrecord R [a]) (instance? R (->R 1)))"])

View file

@ -0,0 +1,36 @@
# Specification: stateful reference types (atoms, volatiles, delays, promises).
(use ../support/harness)
(defspec "state / atoms"
["deref @" "0" "(let [a (atom 0)] @a)"]
["deref fn" "0" "(deref (atom 0))"]
["reset!" "5" "(let [a (atom 0)] (reset! a 5) @a)"]
["reset! returns new" "5" "(let [a (atom 0)] (reset! a 5))"]
["swap!" "1" "(let [a (atom 0)] (swap! a inc) @a)"]
["swap! with args" "10" "(let [a (atom 1)] (swap! a + 2 3 4) @a)"]
["swap! returns new" "1" "(let [a (atom 0)] (swap! a inc))"]
["swap-vals!" "[0 1]" "(let [a (atom 0)] (swap-vals! a inc))"]
["reset-vals!" "[0 9]" "(let [a (atom 0)] (reset-vals! a 9))"]
["compare-and-set! ok" "true" "(let [a (atom 0)] (compare-and-set! a 0 1))"]
["compare-and-set! no" "false" "(let [a (atom 0)] (compare-and-set! a 9 1))"]
["atom?" "true" "(do (require (quote [clojure.core])) (instance? clojure.lang.Atom (atom 0)))"])
(defspec "state / watches & validators"
["add-watch fires" "1" "(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (reset! seen 1))) (reset! a 5) @seen)"]
["remove-watch" "0" "(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)"]
["set-validator! ok" "5" "(let [a (atom 0)] (set-validator! a number?) (reset! a 5) @a)"]
["set-validator! rejects" :throws "(let [a (atom 0)] (set-validator! a pos?) (reset! a -1))"])
(defspec "state / volatiles & delays"
["volatile! deref" "0" "(let [v (volatile! 0)] @v)"]
["vreset!" "5" "(let [v (volatile! 0)] (vreset! v 5) @v)"]
["vswap!" "1" "(let [v (volatile! 0)] (vswap! v inc) @v)"]
["delay not forced" "0" "(let [c (atom 0) d (delay (swap! c inc))] @c)"]
["delay force once" "1" "(let [c (atom 0) d (delay (swap! c inc))] (force d) (force d) @c)"]
["delay value" "5" "(let [d (delay 5)] @d)"]
["realized? before" "false" "(let [d (delay 5)] (realized? d))"]
["realized? after" "true" "(let [d (delay 5)] (force d) (realized? d))"])
(defspec "state / promises"
["promise deliver" "5" "(let [p (promise)] (deliver p 5) @p)"]
["promise undelivered" "nil" "(let [p (promise)] @p)"])