Protocol/interop fixes to run metosin/malli (jolt-ltwk) (#105)

* Protocol/interop fixes to run metosin/malli

Bringing up malli (schema validation) surfaced a batch of protocol and host-interop
gaps. m/validate now works across the schema vocabulary (predicates, :map incl.
nested/optional, :vector, :tuple, :enum, :maybe, :and, bounded int/string).

- extend-type and reify now accept MULTIPLE protocols in one form (each bare
  symbol switches the current protocol). reify records every protocol it
  implements, so instance?/satisfies? recognise all of them.
- Protocol method params support destructuring: reify/extend-type/deftype/
  defrecord emit (fn ...) (which desugars patterns) instead of raw fn*.
- instance? of a PROTOCOL works like satisfies? for reify/record instances,
  matching short names across qualified/bare protocol references.
- @x reads as the qualified clojure.core/deref, so it still derefs where a ns
  excludes and rebinds deref (malli does). Updated reader-test + the reader
  spec/grammar (S11, deref rule).
- Java collection interop on jolt collections: .nth/.count/.valAt/.get/.seq/
  .containsKey route to the clojure.core equivalent (1-arg and 0-arg paths).
- java.util.HashMap capacity/load-factor constructors + .putAll.
- A class used as a value resolves to its instances' type, so Pattern -> the
  regex type (malli keys class-schemas by it).
- Shims for malli's load path: LazilyPersistentVector/createOwning and
  PersistentArrayMap/createWithCheck statics.

m/explain not yet working (jolt-fjb1). Full gate green.

* satisfies? recognizes reify, consistent with instance?

A reify's protocol methods are instance-local, so they aren't in the global type
registry that type-satisfies? consults — satisfies? returned false for a reify
even when it implemented the protocol. Check the protocols the reify records on
itself (the same :jolt/protocols list instance? uses), matching short names like
instance? does. Covers single- and multi-protocol reify.

---------

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-15 03:20:33 +00:00 committed by GitHub
parent d1f73f1740
commit 910c4b6c99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 149 additions and 33 deletions

View file

@ -376,14 +376,24 @@
(register-method (name atype) pname (name k) f)))
(recur (nnext s)))))
(defmacro extend-type [tsym psym & impls]
(defmacro extend-type [tsym & body]
;; register-method is a fn (clojure.core); pass type/protocol/method NAMES as
;; strings (not the symbols) so the call compiles as a plain invoke. A nil
;; type extends on nil values (the host tag is the string "nil").
`(do ~@(map (fn [spec]
`(register-method ~(if (nil? tsym) "nil" (name tsym)) ~(name psym) ~(name (first spec))
(fn* ~(nth spec 1) ~@(drop 2 spec))))
impls)))
;; `body` is one or more protocols, each followed by its method specs:
;; (extend-type T P1 (m1 [_] ..) P2 (m2 [_] ..)) — a bare symbol switches the
;; current protocol (like reify), so multiple protocols extend in one form.
(let [tname (if (nil? tsym) "nil" (name tsym))]
(loop [items (seq body) proto nil forms []]
(if (empty? items)
`(do ~@forms)
(let [x (first items)]
(if (symbol? x)
(recur (rest items) (name x) forms)
(recur (rest items) proto
(conj forms
`(register-method ~tname ~proto ~(name (first x))
(fn ~(nth x 1) ~@(drop 2 x)))))))))))
(defmacro extend-protocol [psym & type-impls]
`(do ~@(map (fn [g] `(extend-type ~(first g) ~psym ~@(rest g)))
@ -400,15 +410,18 @@
;; ordinary map literal that evaluates to {keyword fn}, and the protocol NAME is
;; passed as a string (not the symbol) so the call compiles as a plain invoke.
(defmacro reify [& forms]
(loop [items (seq forms) proto nil methods {}]
;; a reify can implement SEVERAL protocols; collect them all (each bare symbol
;; switches the current protocol, like extend-type) and pass every protocol name
;; to make-reified so (instance? Proto r)/satisfies? recognise all of them.
(loop [items (seq forms) protos [] methods {}]
(if (empty? items)
`(make-reified ~(name proto) ~methods)
`(make-reified ~methods ~@(vec (map name protos)))
(let [x (first items)]
(if (symbol? x)
(recur (rest items) (if proto proto x) methods)
(recur (rest items) proto
(recur (rest items) (conj protos x) methods)
(recur (rest items) protos
(assoc methods (keyword (name (first x)))
`(fn* ~(nth x 1) ~@(drop 2 x)))))))))
`(fn ~(nth x 1) ~@(drop 2 x)))))))))
(defmacro defrecord [name-sym fields & body]
(let [tn (name name-sym)