fix: multimethod :default key dispatch (Clojure semantics); defrecord inline protocol methods with field scope; fix 2 phase5 tests to Clojure default-key semantics

This commit is contained in:
Yogthos 2026-06-04 14:39:48 -04:00
parent fc6c630d5d
commit 73a9cb08f3
3 changed files with 56 additions and 18 deletions

View file

@ -1639,10 +1639,42 @@
(def map-sym {:jolt/type :symbol :ns nil :name map-name})
(def map-body @[{:jolt/type :symbol :ns nil :name "fn"} @[{:jolt/type :symbol :ns nil :name (string "m")}] map-call])
@[{:jolt/type :symbol :ns nil :name "do"}
(def out @[{:jolt/type :symbol :ns nil :name "do"}
dt-form
@[{:jolt/type :symbol :ns nil :name "def"} arrow-sym arrow-body]
@[{:jolt/type :symbol :ns nil :name "def"} map-sym map-body]])
# Process inline protocol/interface implementations:
# (defrecord T [fs] Proto (m [this] body) ... Proto2 (m2 [this] body))
# Emit an extend-type per protocol. Each method body is wrapped in a let that
# binds the record's fields from the instance (first method param), matching
# Clojure's field-in-scope semantics for deftype/defrecord methods.
(var i 0)
(while (< i (length body))
(def elem (in body i))
(if (and (struct? elem) (= :symbol (elem :jolt/type)))
# protocol name; collect following method specs
(let [proto-sym elem
et @[{:jolt/type :symbol :ns nil :name "extend-type"} name-sym proto-sym]]
(++ i)
(while (and (< i (length body)) (not (and (struct? (in body i)) (= :symbol ((in body i) :jolt/type)))))
(let [spec (in body i)
mname (spec 0)
argv (spec 1)
mbody (tuple/slice spec 2)
instance (in argv 0)
# (let [f0 (core-get instance :f0) ...] body...)
field-binds @[]
_ (each f fields-vec
(array/push field-binds f)
(array/push field-binds @[{:jolt/type :symbol :ns nil :name "get"}
instance (keyword (f :name))]))
wrapped @[{:jolt/type :symbol :ns nil :name "let"}
(tuple/slice (tuple ;field-binds)) ;mbody]]
(array/push et @[mname argv wrapped]))
(++ i))
(array/push out et))
(++ i)))
out)
# resolve stub — returns nil (symbols not found in Jolt's clojure.core)

View file

@ -860,10 +860,11 @@
(if (keyword? raw)
(fn [x] (get x raw))
raw))
# Parse options: :default val and :hierarchy h
# Parse options: :default dispatch-key (defaults to :default)
# and :hierarchy h
opts (tuple/slice form 3)
default-val (do
(var dv nil) (var i 0)
default-key (do
(var dv :default) (var i 0)
(while (< i (length opts))
(if (= :default (in opts i))
(do (set dv (in opts (+ i 1))) (set i (length opts)))
@ -881,23 +882,23 @@
method (get methods dv)]
(if method
(apply method args)
(if hierarchy
(let [found (do
(var f nil) (var i 0)
(let [ks (keys methods)]
(while (and (nil? f) (< i (length ks)))
(if (isa? hierarchy dv (ks i)) (set f (get methods (ks i))))
(++ i))) f)]
(if found (apply found args)
(if (not (nil? default-val)) default-val
# hierarchy-based match
(let [found (if hierarchy
(do (var f nil) (var i 0)
(let [ks (keys methods)]
(while (and (nil? f) (< i (length ks)))
(if (isa? hierarchy dv (ks i)) (set f (get methods (ks i))))
(++ i))) f)
nil)]
(if found (apply found args)
# fall back to the method registered under the default key
(let [dm (get methods default-key)]
(if dm (apply dm args)
(error (string "No method in multimethod "
(name-sym :name) " for dispatch value: " dv)))))
(if (not (nil? default-val)) default-val
(error (string "No method in multimethod "
(name-sym :name) " for dispatch value: " dv)))))))]
(name-sym :name) " for dispatch value: " dv)))))))))]
(def v (ns-intern ns (name-sym :name) mm-fn))
(put v :jolt/methods methods)
(when default-val (put v :jolt/default default-val))
(put v :jolt/default default-key)
(when hierarchy (put v :jolt/hierarchy hierarchy))
(var-get v))
"defmethod" (let [mm-sym (in form 1)

View file

@ -27,6 +27,9 @@
(let [ctx (init)]
(ct-eval ctx "(defmulti classify :type :default :unknown)")
(ct-eval ctx "(defmethod classify :a [_] :alpha)")
# :default :unknown renames the catch-all dispatch key; a method must be
# registered under it (Clojure semantics).
(ct-eval ctx "(defmethod classify :unknown [_] :unknown)")
(assert (= :alpha (ct-eval ctx "(classify {:type :a})")) "known dispatch")
(assert (= :unknown (ct-eval ctx "(classify {:type :z})")) "default fallback"))
(print " passed")
@ -40,6 +43,8 @@
(ct-eval ctx "(defmulti animal-sound (fn [x] x) :default :unknown :hierarchy h)")
(ct-eval ctx "(defmethod animal-sound ::animal [_] \"rawr\")")
(ct-eval ctx "(defmethod animal-sound ::dog [_] \"woof\")")
# catch-all method registered under the renamed default key :unknown
(ct-eval ctx "(defmethod animal-sound :unknown [_] :unknown)")
(assert (= "woof" (ct-eval ctx "(animal-sound ::dog)")) "direct dispatch")
(assert (= "rawr" (ct-eval ctx "(animal-sound ::mammal)")) "hierarchy fallback")
(assert (= :unknown (ct-eval ctx "(animal-sound ::rock)")) "default fallback"))