diff --git a/host/chez/java/class-hierarchy.ss b/host/chez/java/class-hierarchy.ss new file mode 100644 index 0000000..5966610 --- /dev/null +++ b/host/chez/java/class-hierarchy.ss @@ -0,0 +1,204 @@ +;; class-hierarchy.ss — one JVM class/interface graph, the single source of truth +;; for every "what classes does this satisfy" question. value-host-tags (protocol +;; dispatch), instance?, isa?/supers/ancestors, and the exception hierarchy all +;; derive from the ONE table here instead of maintaining parallel hand-kept lists +;; that drift apart. +;; +;; The graph is keyed by canonical (FQN) class name -> its DIRECT super +;; interfaces/classes (also FQN). Transitivity is computed (jch-closure), so a row +;; lists only what a class directly extends/implements, matching the JVM source. +;; +;; It is OPEN: a library registers a class and its supers with +;; jolt.host/register-class-supers! (plus a class-arm in host-class.ss to map its +;; values to that class name), and every derived view picks the class up with no +;; core change. Loaded before records.ss so value-host-tags can derive from it. + +;; canonical-name -> list of direct super canonical-names. Mutable + extensible. +(define jvm-class-parents (make-hashtable string-hash string=?)) +;; closure cache, invalidated whenever the graph is extended. +(define jch-closure-cache (make-hashtable string-hash string=?)) +(define jch-tags-cache (make-hashtable string-hash string=?)) + +;; Merge direct supers for a class (union with any already registered). Public so +;; libraries can graft their own classes onto the modeled hierarchy. +(define (jch-register-supers! name supers) + (let ((cur (hashtable-ref jvm-class-parents name '()))) + (hashtable-set! jvm-class-parents name + (let add ((ss supers) (acc cur)) + (cond ((null? ss) acc) + ((member (car ss) acc) (add (cdr ss) acc)) + (else (add (cdr ss) (append acc (list (car ss))))))))) + (hashtable-clear! jch-closure-cache) + (hashtable-clear! jch-tags-cache)) + +(define (jch-direct-supers name) (hashtable-ref jvm-class-parents name '())) + +;; transitive supers of NAME (canonical), excluding NAME and Object; Object is the +;; universal root supplied by callers. Breadth-first, deduped, stable order. +(define (jch-closure name) + (or (hashtable-ref jch-closure-cache name #f) + (let ((result + (let loop ((pending (jch-direct-supers name)) (seen '())) + (cond ((null? pending) (reverse seen)) + ((member (car pending) seen) (loop (cdr pending) seen)) + (else (loop (append (jch-direct-supers (car pending)) (cdr pending)) + (cons (car pending) seen))))))) + (hashtable-set! jch-closure-cache name result) + result))) + +(define (jch-last-segment s) + (let loop ((i (- (string-length s) 1))) + (cond ((< i 0) s) + ((char=? (string-ref s i) #\.) (substring s (+ i 1) (string-length s))) + ((char=? (string-ref s i) #\$) (substring s (+ i 1) (string-length s))) + (else (loop (- i 1)))))) + +;; The protocol-dispatch / instance? tag list for a value of class NAME: the class +;; and its whole ancestry, each in BOTH canonical and simple spelling (extend-protocol +;; and instance? accept either "Associative" or "clojure.lang.Associative"), plus +;; "Object". Memoized — this is on the hot protocol-dispatch path. +(define (jch-tags name) + (or (hashtable-ref jch-tags-cache name #f) + (let* ((chain (cons name (jch-closure name))) + (result + (let build ((cs chain) (acc '())) + (if (null? cs) + (reverse (cons "Object" acc)) + (let* ((fqn (car cs)) + (simple (jch-last-segment fqn)) + (acc1 (if (member fqn acc) acc (cons fqn acc))) + (acc2 (if (or (string=? simple fqn) (member simple acc1)) + acc1 (cons simple acc1)))) + (build (cdr cs) acc2)))))) + (hashtable-set! jch-tags-cache name result) + result))) + +;; Is WANTED (canonical or simple) the class CHILD (canonical) or one of its +;; ancestors? Object is every class's root. Matched by full name or last segment so +;; "IOException" and "java.io.IOException" both hit. +(define (jch-isa? child wanted) + (let ((wseg (jch-last-segment wanted))) + (or (string=? wanted "java.lang.Object") (string=? wanted "Object") + (let loop ((names (cons child (jch-closure child)))) + (cond ((null? names) #f) + ((or (string=? wanted (car names)) + (string=? wseg (jch-last-segment (car names)))) #t) + (else (loop (cdr names)))))))) + +;; Does the graph model WANTED at all (as a class or as any class's ancestor)? Used +;; by instance? to decide between a definitive #f and 'pass (defer to other arms). +(define jch-known-cache #f) +(define (jch-known? wanted) + (when (not jch-known-cache) + (set! jch-known-cache (make-hashtable string-hash string=?)) + (let-values (((keys vals) (hashtable-entries jvm-class-parents))) + (vector-for-each + (lambda (k supers) + (hashtable-set! jch-known-cache k #t) + (hashtable-set! jch-known-cache (jch-last-segment k) #t) + (for-each (lambda (s) + (hashtable-set! jch-known-cache s #t) + (hashtable-set! jch-known-cache (jch-last-segment s) #t)) + supers)) + keys vals))) + (or (hashtable-ref jch-known-cache wanted #f) + (hashtable-ref jch-known-cache (jch-last-segment wanted) #f))) + +;; A register also invalidates the known-name cache. +(define jch-register-supers!-inner jch-register-supers!) +(set! jch-register-supers! + (lambda (name supers) + (set! jch-known-cache #f) + (jch-register-supers!-inner name supers))) + +;; ---- seed the built-in graph: direct supers only, faithful to the JVM --------- +;; core clojure.lang interfaces +(jch-register-supers! "clojure.lang.IPersistentCollection" '("clojure.lang.Seqable")) +(jch-register-supers! "clojure.lang.ISeq" '("clojure.lang.IPersistentCollection")) +(jch-register-supers! "clojure.lang.Associative" '("clojure.lang.IPersistentCollection" "clojure.lang.ILookup")) +(jch-register-supers! "clojure.lang.IPersistentStack" '("clojure.lang.IPersistentCollection")) +(jch-register-supers! "clojure.lang.IPersistentVector" '("clojure.lang.Associative" "clojure.lang.Sequential" + "clojure.lang.IPersistentStack" "clojure.lang.Reversible" + "clojure.lang.Indexed")) +(jch-register-supers! "clojure.lang.IPersistentMap" '("java.lang.Iterable" "clojure.lang.Associative" "clojure.lang.Counted")) +(jch-register-supers! "clojure.lang.IPersistentSet" '("clojure.lang.IPersistentCollection" "clojure.lang.Counted")) +(jch-register-supers! "clojure.lang.IPersistentList" '("clojure.lang.Sequential" "clojure.lang.IPersistentStack")) +(jch-register-supers! "clojure.lang.IObj" '("clojure.lang.IMeta")) +(jch-register-supers! "clojure.lang.IFn" '("java.lang.Runnable" "java.util.concurrent.Callable")) +(jch-register-supers! "clojure.lang.Fn" '("clojure.lang.IFn")) +(jch-register-supers! "clojure.lang.AFn" '("clojure.lang.IFn")) +(jch-register-supers! "clojure.lang.AFunction" '("clojure.lang.AFn" "clojure.lang.Fn")) +;; java.util collection interfaces +(jch-register-supers! "java.util.List" '("java.util.Collection")) +(jch-register-supers! "java.util.Set" '("java.util.Collection")) +(jch-register-supers! "java.util.Collection" '("java.lang.Iterable")) +;; concrete collection classes +(jch-register-supers! "clojure.lang.APersistentVector" '("clojure.lang.IPersistentVector" "java.util.List")) +(jch-register-supers! "clojure.lang.PersistentVector" '("clojure.lang.APersistentVector" "clojure.lang.IObj" + "java.util.List" "java.lang.Comparable")) +(jch-register-supers! "clojure.lang.APersistentMap" '("clojure.lang.IPersistentMap" "java.util.Map")) +(jch-register-supers! "clojure.lang.PersistentArrayMap" '("clojure.lang.APersistentMap" "clojure.lang.IObj")) +(jch-register-supers! "clojure.lang.PersistentHashMap" '("clojure.lang.APersistentMap" "clojure.lang.IObj")) +(jch-register-supers! "clojure.lang.PersistentTreeMap" '("clojure.lang.APersistentMap" "clojure.lang.IObj" "clojure.lang.Sorted" "clojure.lang.Reversible")) +(jch-register-supers! "clojure.lang.APersistentSet" '("clojure.lang.IPersistentSet" "java.util.Set")) +(jch-register-supers! "clojure.lang.PersistentHashSet" '("clojure.lang.APersistentSet" "clojure.lang.IObj")) +(jch-register-supers! "clojure.lang.PersistentTreeSet" '("clojure.lang.APersistentSet" "clojure.lang.IObj" "clojure.lang.Sorted" "clojure.lang.Reversible")) +(jch-register-supers! "clojure.lang.ASeq" '("clojure.lang.ISeq" "clojure.lang.Sequential" "java.util.List")) +(jch-register-supers! "clojure.lang.PersistentList" '("clojure.lang.ASeq" "clojure.lang.IPersistentList" "clojure.lang.Counted")) +(jch-register-supers! "clojure.lang.PersistentList$EmptyList" '("clojure.lang.PersistentList")) +(jch-register-supers! "clojure.lang.LazySeq" '("clojure.lang.ISeq" "clojure.lang.Sequential" "java.util.List" "clojure.lang.IObj")) +(jch-register-supers! "clojure.lang.Cons" '("clojure.lang.ASeq")) +(jch-register-supers! "clojure.lang.PersistentQueue" '("clojure.lang.IPersistentList" "clojure.lang.IPersistentCollection" "java.util.Collection")) +;; scalars / named / callable +(jch-register-supers! "clojure.lang.Keyword" '("clojure.lang.IFn" "clojure.lang.Named" "java.lang.Comparable")) +(jch-register-supers! "clojure.lang.Symbol" '("clojure.lang.IObj" "clojure.lang.IFn" "clojure.lang.Named" "java.lang.Comparable")) +(jch-register-supers! "clojure.lang.Var" '("clojure.lang.IDeref" "clojure.lang.IFn")) +(jch-register-supers! "clojure.lang.Atom" '("clojure.lang.IDeref")) +(jch-register-supers! "clojure.lang.Ratio" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "clojure.lang.BigInt" '("java.lang.Number")) +(jch-register-supers! "java.lang.String" '("java.lang.CharSequence" "java.lang.Comparable")) +(jch-register-supers! "java.lang.Long" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "java.lang.Integer" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "java.lang.Double" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "java.lang.Float" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "java.math.BigDecimal" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "java.math.BigInteger" '("java.lang.Number" "java.lang.Comparable")) +(jch-register-supers! "java.lang.Boolean" '("java.lang.Comparable")) +(jch-register-supers! "java.lang.Character" '("java.lang.Comparable")) +(jch-register-supers! "java.util.UUID" '("java.lang.Comparable")) +;; exception hierarchy (folds in the former exception-parent table) +(jch-register-supers! "java.lang.Exception" '("java.lang.Throwable")) +(jch-register-supers! "java.lang.RuntimeException" '("java.lang.Exception")) +(jch-register-supers! "clojure.lang.ExceptionInfo" '("java.lang.RuntimeException" "clojure.lang.IExceptionInfo")) +(jch-register-supers! "java.lang.IllegalArgumentException" '("java.lang.RuntimeException")) +(jch-register-supers! "clojure.lang.ArityException" '("java.lang.IllegalArgumentException")) +(jch-register-supers! "java.lang.NumberFormatException" '("java.lang.IllegalArgumentException")) +(jch-register-supers! "java.lang.IllegalStateException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.lang.UnsupportedOperationException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.lang.ArithmeticException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.lang.NullPointerException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.lang.ClassCastException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.lang.IndexOutOfBoundsException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.util.ConcurrentModificationException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.util.NoSuchElementException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.io.UncheckedIOException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.time.DateTimeException" '("java.lang.RuntimeException")) +(jch-register-supers! "java.time.format.DateTimeParseException" '("java.time.DateTimeException")) +(jch-register-supers! "java.lang.InterruptedException" '("java.lang.Exception")) +(jch-register-supers! "java.io.IOException" '("java.lang.Exception")) +(jch-register-supers! "java.io.InterruptedIOException" '("java.io.IOException")) +(jch-register-supers! "java.io.FileNotFoundException" '("java.io.IOException")) +(jch-register-supers! "java.io.UnsupportedEncodingException" '("java.io.IOException")) +(jch-register-supers! "java.net.UnknownHostException" '("java.io.IOException")) +(jch-register-supers! "java.net.SocketException" '("java.io.IOException")) +(jch-register-supers! "java.net.ConnectException" '("java.net.SocketException")) +(jch-register-supers! "java.net.SocketTimeoutException" '("java.io.InterruptedIOException")) +(jch-register-supers! "java.net.MalformedURLException" '("java.io.IOException")) +(jch-register-supers! "javax.net.ssl.SSLException" '("java.io.IOException")) +(jch-register-supers! "java.lang.Error" '("java.lang.Throwable")) +(jch-register-supers! "java.lang.AssertionError" '("java.lang.Error")) +;; Throwable's only super is Object (universal), so no row needed for it. + +;; Public seam: libraries extend the modeled hierarchy. +(def-var! "jolt.host" "register-class-supers!" + (lambda (name supers) (jch-register-supers! name (seq->list supers)) jolt-nil)) diff --git a/host/chez/java/host-static-classes.ss b/host/chez/java/host-static-classes.ss index b8f1a10..f9656fa 100644 --- a/host/chez/java/host-static-classes.ss +++ b/host/chez/java/host-static-classes.ss @@ -767,6 +767,12 @@ (register-instance-check-arm! (lambda (type-sym val) (let ((iface (hsc-last-segment (symbol-t-name type-sym)))) + ;; the value's own class-graph tags (value-host-tags) are authoritative — the + ;; SAME source protocol dispatch reads, so instance? and extend-protocol can't + ;; disagree about the interfaces a builtin implements. + (if (let ((tags (value-host-tags val))) + (or (member (symbol-t-name type-sym) tags) (member iface tags))) + #t (let ((hit (cond ((or (string=? iface "IObj") (string=? iface "IMeta")) (hsc-imeta? val)) ((or (string=? iface "IMapEntry") (string=? iface "MapEntry")) (jolt-map-entry? val)) @@ -827,7 +833,7 @@ ((or (string=? iface "Reader") (string=? iface "BufferedReader")) (reader-jhost? val)) (else 'none)))) - (if (eq? hit 'none) 'pass (if hit #t #f)))))) + (if (eq? hit 'none) 'pass (if hit #t #f))))))) ;; java.lang.Class value: (class x) / (.getClass x) return one. It renders like ;; the JVM — str/.toString -> "class ", pr -> "", .getName -> "" @@ -985,9 +991,20 @@ (define (str-has-dollar? s) (let loop ((i 0)) (and (< i (string-length s)) (or (char=? (string-ref s i) #\$) (loop (+ i 1)))))) (define (class-direct-supers name) - (or (hashtable-ref class-supers-tbl name #f) - (and (str-has-dollar? name) '("clojure.lang.AFunction")) - '())) + ;; union the modeled class graph (jch, direct edges) with any legacy table entry, + ;; so isa?/supers/ancestors see the single hierarchy source plus anything not yet + ;; migrated. The closure below traverses these to the full transitive set. + (let ((jch (jch-direct-supers name)) + (old (hashtable-ref class-supers-tbl name #f))) + (cond ((and (pair? jch) old) + (let merge ((ss old) (acc jch)) + (cond ((null? ss) acc) + ((member (car ss) acc) (merge (cdr ss) acc)) + (else (merge (cdr ss) (append acc (list (car ss)))))))) + ((pair? jch) jch) + (old old) + ((str-has-dollar? name) '("clojure.lang.AFunction")) + (else '())))) ;; transitive closure of direct supers (set semantics via an accumulator list) (define (class-ancestors-list name) (let loop ((pending (class-direct-supers name)) (seen '())) @@ -1037,8 +1054,9 @@ (def-var! "jolt.host" "class-supers" (lambda (x) (let ((name (class-key x))) - (if (and name (hashtable-contains? class-supers-tbl name)) - (list->cseq (hashtable-ref class-supers-tbl name '())) + (if name + (let ((as (class-ancestors-list name))) ; transitive, like the JVM + (if (null? as) jolt-nil (list->cseq as))) jolt-nil)))) (def-var! "jolt.host" "class-ancestors" (lambda (x) diff --git a/host/chez/records.ss b/host/chez/records.ss index 1b4b6a6..ca7e9f5 100644 --- a/host/chez/records.ss +++ b/host/chez/records.ss @@ -473,24 +473,22 @@ ((number? obj) '("Long" "Integer" "BigInteger" "BigInt" "Number" "Object")) ((string? obj) '("String" "CharSequence" "Object")) ((boolean? obj) '("Boolean" "Object")) - ((keyword? obj) '("Keyword" "Named" "Object")) - ((jolt-symbol? obj) '("Symbol" "Named" "Object")) - ((pvec? obj) '("PersistentVector" "APersistentVector" "IPersistentVector" "IPersistentCollection" - "List" "java.util.List" "Sequential" "Collection" "Iterable" "java.lang.Iterable" "Object")) - ((pmap? obj) '("PersistentArrayMap" "APersistentMap" "IPersistentMap" "Associative" - "Map" "java.util.Map" "Iterable" "java.lang.Iterable" "Object")) - ((pset? obj) '("PersistentHashSet" "APersistentSet" "IPersistentSet" "Set" "java.util.Set" "Collection" "Iterable" "java.lang.Iterable" "Object")) + ((keyword? obj) (jch-tags "clojure.lang.Keyword")) + ((jolt-symbol? obj) (jch-tags "clojure.lang.Symbol")) + ((pvec? obj) (jch-tags "clojure.lang.PersistentVector")) + ((pmap? obj) (jch-tags "clojure.lang.PersistentArrayMap")) + ((pset? obj) (jch-tags "clojure.lang.PersistentHashSet")) ;; jolt models every seq as a list (no distinct LazySeq), so a seq also ;; reports PersistentList / IPersistentList / IPersistentStack — extend-protocol ;; clojure.lang.IPersistentList (algo.monads' writer monad) dispatches on one. - ((or (cseq? obj) (empty-list-t? obj)) '("PersistentList" "IPersistentList" "IPersistentStack" "ASeq" "ISeq" "IPersistentCollection" "Sequential" "Collection" "Iterable" "java.lang.Iterable" "Object")) + ((or (cseq? obj) (empty-list-t? obj)) (jch-tags "clojure.lang.PersistentList")) ;; a lazy seq (map/filter/… result) is clojure.lang.LazySeq: a Sequential ;; ISeq, but not a PersistentList — matching the JVM so extend-protocol / ;; instance? on a deferred seq dispatch like an eager one where they should. - ((jolt-lazyseq? obj) '("LazySeq" "ISeq" "IPersistentCollection" "Sequential" "Collection" "Iterable" "java.lang.Iterable" "Object")) + ((jolt-lazyseq? obj) (jch-tags "clojure.lang.LazySeq")) ;; a var is clojure.lang.Var (also IDeref / IFn) — reitit's Expand protocol ;; extends to Var so a #'handler route dispatches. - ((var-cell? obj) '("Var" "clojure.lang.Var" "IDeref" "IFn" "Object")) + ((var-cell? obj) (jch-tags "clojure.lang.Var")) ;; java.net.URI jhost — extend-protocol java.net.URI (hiccup ToURI/ToStr). ((and (jhost? obj) (string=? (jhost-tag obj) "uri")) '("URI" "java.net.URI" "Object")) ;; a ByteBuffer — extend-protocol java.nio.ByteBuffer (aws-api util). @@ -541,7 +539,7 @@ ;; extended to both (data.json's JSONWriter) routes a sql.Date to its impl. ((and (jhost? obj) (string=? (jhost-tag obj) "sql-date")) '("java.sql.Date" "Date" "java.util.Date" "Object")) ;; a bare procedure (fn) — extend-protocol to clojure.lang.{Fn,IFn,AFn}. - ((procedure? obj) '("Fn" "IFn" "AFn" "Object")) + ((procedure? obj) (jch-tags "clojure.lang.AFunction")) ((jolt-nil? obj) '("nil")) ;; a defrecord IS the clojure.lang map/record interfaces, so a protocol ;; extended to IRecord / IPersistentMap / Associative / Seqable / … (and not @@ -671,7 +669,12 @@ (strip-prefix type-name "java.time.") (strip-prefix type-name "clojure.lang.") type-name))) - (and (hashtable-ref host-type-set base #f) base))) + ;; a host class if the literal set lists it OR the class graph models it — both + ;; feed value-host-tags (which emits the same bare segment), so a protocol + ;; extended to any modeled class keys under a tag the value reports. + (and (or (hashtable-ref host-type-set base #f) + (jch-known? base) (jch-known? type-name)) + base))) ;; An extend/extend-type/extend-protocol registration marks the tag as an ;; extender of the protocol (recorded inside type-registry so the per-case prune ;; restores it). deftype/defrecord inline impls go through register-inline-method diff --git a/host/chez/rt.ss b/host/chez/rt.ss index 0c9e13f..ce1326a 100644 --- a/host/chez/rt.ss +++ b/host/chez/rt.ss @@ -356,6 +356,11 @@ ;; jolt-pr-str (above), and the var-cell machinery — so loaded last. (load "host/chez/multimethods.ss") +;; the single JVM class/interface graph — value-host-tags, instance?, isa?/supers, +;; and the exception hierarchy all derive from it. Before records.ss so +;; value-host-tags can build on jch-tags. +(load "host/chez/java/class-hierarchy.ss") + ;; records + protocols: defrecord/deftype/defprotocol/ ;; extend-type/reify. A jrec record type set!-extended into the collection ;; dispatchers + a protocol registry. After multimethods.ss (chez-current-ns) and diff --git a/test/chez/corpus.edn b/test/chez/corpus.edn index 1b7dd55..69840f4 100644 --- a/test/chez/corpus.edn +++ b/test/chez/corpus.edn @@ -3505,4 +3505,7 @@ {:suite "number / toString radix" :label ".toString(radix) renders in base, lowercase" :expected "[\"ff\" \"377\" \"1001\"]" :actual "[(.toString (biginteger 255) 16) (.toString (biginteger 255) 8) (.toString (biginteger 9) 2)]"} {:suite "quote / metadata" :label "a quoted form keeps user metadata, drops reader location" :expected "[{:x true} nil]" :actual "[(meta (quote ^:x (1 2))) (meta (quote (1 2)))]"} {:suite "enumeration-seq" :label "enumeration-seq drives a java.util.Enumeration (StringTokenizer)" :expected "[\"1\" \"2\" \"3\"]" :actual "(vec (enumeration-seq (java.util.StringTokenizer. \"1 2 3\")))"} + {:suite "protocols / interface dispatch" :label "extend-protocol to an interface a builtin implements dispatches (instance? and protocol dispatch agree)" :expected "[:assoc :ifn :seqable]" :actual "(do (defprotocol PA (ma [_])) (extend-protocol PA clojure.lang.Associative (ma [_] :assoc)) (defprotocol PF (mf [_])) (extend-protocol PF clojure.lang.IFn (mf [_] :ifn)) (defprotocol PS (ms [_])) (extend-protocol PS clojure.lang.Seqable (ms [_] :seqable)) [(ma [1 2]) (mf :k) (ms (map inc [1 2]))])"} + {:suite "protocols / instance? matches dispatch" :label "instance? agrees with what a value's class implements" :expected "[true true false true true]" :actual "[(instance? clojure.lang.Associative [1 2]) (instance? clojure.lang.IFn :k) (instance? java.util.Map [1 2]) (instance? clojure.lang.Seqable (map inc [1 2])) (instance? clojure.lang.IPersistentVector [1 2])]"} + {:suite "class / hierarchy views agree" :label "isa?/supers see the modeled exception + collection hierarchy" :expected "[true true true true]" :actual "[(isa? clojure.lang.ExceptionInfo java.lang.RuntimeException) (contains? (supers java.lang.NumberFormatException) java.lang.RuntimeException) (isa? clojure.lang.Keyword clojure.lang.IFn) (contains? (ancestors clojure.lang.PersistentVector) java.util.List)]"} ]