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).
116 lines
6.2 KiB
Scheme
116 lines
6.2 KiB
Scheme
;; records-interop.ss — JVM-emulation taxonomy split out of records.ss: the
|
|
;; ex-info class accessors, the exception supertype hierarchy, and instance-check
|
|
;; / case-string (the (instance? Class x) decision table). Loaded right after
|
|
;; records.ss; instance-check forward-refs nothing in records.ss at load time.
|
|
|
|
;; pmap? guard: ex-info maps are plain hash-maps, never sorted-map htables — and a
|
|
;; bare jolt-get on a sorted-map would invoke its comparator on :jolt/type and throw.
|
|
(define (ex-info-map? v)
|
|
(and (pmap? v) (jolt=2 (jolt-get v jolt-kw-ex-type jolt-nil) jolt-kw-ex-info)))
|
|
(define (ex-info-class v)
|
|
(let ((c (jolt-get v jolt-kw-class jolt-nil)))
|
|
(if (string? c) c "clojure.lang.ExceptionInfo")))
|
|
;; immediate-parent chain of the JVM exception hierarchy (simple names). Drives
|
|
;; instance? across exception supertypes — (instance? Throwable (ex-info …)) etc.
|
|
(define exception-parent
|
|
'(("ExceptionInfo" . "RuntimeException")
|
|
("RuntimeException" . "Exception")
|
|
("IllegalArgumentException" . "RuntimeException")
|
|
("NumberFormatException" . "IllegalArgumentException")
|
|
("IllegalStateException" . "RuntimeException")
|
|
("UnsupportedOperationException" . "RuntimeException")
|
|
("ArithmeticException" . "RuntimeException")
|
|
("NullPointerException" . "RuntimeException")
|
|
("ClassCastException" . "RuntimeException")
|
|
("IndexOutOfBoundsException" . "RuntimeException")
|
|
("ConcurrentModificationException" . "RuntimeException")
|
|
("NoSuchElementException" . "RuntimeException")
|
|
("UncheckedIOException" . "RuntimeException")
|
|
("InterruptedException" . "Exception")
|
|
("IOException" . "Exception")
|
|
("FileNotFoundException" . "IOException")
|
|
("UnsupportedEncodingException" . "IOException")
|
|
("UnknownHostException" . "IOException")
|
|
("SocketException" . "IOException")
|
|
("ConnectException" . "IOException")
|
|
("SocketTimeoutException" . "IOException")
|
|
("MalformedURLException" . "IOException")
|
|
("SSLException" . "IOException")
|
|
("Exception" . "Throwable")
|
|
("Error" . "Throwable")
|
|
("AssertionError" . "Error")
|
|
("Throwable" . "Object")))
|
|
;; Is `wanted` (simple name) `cls` or a supertype of it? ExceptionInfo also
|
|
;; implements the IExceptionInfo interface.
|
|
(define (exception-isa? cls wanted)
|
|
(let loop ((c cls))
|
|
(cond ((not c) #f)
|
|
((string=? c wanted) #t)
|
|
((and (string=? c "ExceptionInfo") (string=? wanted "IExceptionInfo")) #t)
|
|
(else (let ((p (assoc c exception-parent))) (loop (and p (cdr p))))))))
|
|
|
|
;; instance-check: (type-sym val) — type/protocol membership. Host shims loaded
|
|
;; later (io, inst-time, natives-array, natives-queue, host-static-classes)
|
|
;; register an arm with register-instance-check-arm! instead of set!-wrapping
|
|
;; instance-check; an arm returns #t/#f to decide or 'pass to defer to the next.
|
|
;; Newest arm is checked first (matches the old outermost-wins set! order).
|
|
;; instance-check-base is the JVM taxonomy fallback when no arm decides.
|
|
(define instance-check-registry '())
|
|
(define (register-instance-check-arm! f) ; f: (type-sym val) -> #t | #f | 'pass
|
|
(set! instance-check-registry (cons f instance-check-registry)))
|
|
|
|
(define (instance-check-base type-sym val)
|
|
(let ((tname (symbol-t-name type-sym)))
|
|
(cond
|
|
((jrec? val)
|
|
(let ((tag (jrec-tag val)))
|
|
(or (string=? tag tname)
|
|
(and (> (string-length tag) (string-length tname))
|
|
(string=? (substring tag (- (string-length tag) (string-length tname)) (string-length tag)) tname)))))
|
|
((jreify? val) (let ((short (last-dot tname)))
|
|
;; every Clojure reify implements IObj/IMeta (carries metadata).
|
|
(or (member short '("IObj" "IMeta"))
|
|
(and (memp (lambda (p) (string=? (last-dot p) short)) (jreify-protos val)) #t))))
|
|
((ex-info-map? val) (exception-isa? (last-dot (ex-info-class val)) (last-dot tname)))
|
|
(else (case-string tname val)))))
|
|
|
|
(define (instance-check type-sym0 val)
|
|
;; a Class value as the type arg (instance? (class x) y) -> use its name string.
|
|
(let* ((type-sym (if (jclass? type-sym0) (jclass-name type-sym0) type-sym0))
|
|
(ts (if (and (string? type-sym)
|
|
(or (= 0 (string-length type-sym))
|
|
(not (char=? (string-ref type-sym 0) #\[))))
|
|
(jolt-symbol #f type-sym)
|
|
type-sym)))
|
|
(let loop ((rs instance-check-registry))
|
|
(if (null? rs)
|
|
(instance-check-base ts val)
|
|
(let ((r ((car rs) ts val)))
|
|
(if (eq? r 'pass) (loop (cdr rs)) r))))))
|
|
(define (case-string tname val)
|
|
(cond
|
|
((member tname '("Number" "java.lang.Number")) (number? val))
|
|
((member tname '("Long" "java.lang.Long" "Integer" "java.lang.Integer"))
|
|
(and (number? val) (exact? val) (integer? val)))
|
|
((member tname '("Double" "java.lang.Double" "Float" "java.lang.Float")) (and (number? val) (flonum? val)))
|
|
((member tname '("Ratio" "clojure.lang.Ratio")) (and (number? val) (exact? val) (rational? val) (not (integer? val))))
|
|
((member tname '("String" "java.lang.String" "CharSequence" "java.lang.CharSequence")) (string? val))
|
|
((member tname '("Boolean" "java.lang.Boolean")) (boolean? val))
|
|
((member tname '("Character" "java.lang.Character")) (char? val))
|
|
((member tname '("Keyword" "clojure.lang.Keyword")) (keyword? val))
|
|
((member tname '("Symbol" "clojure.lang.Symbol")) (jolt-symbol? val))
|
|
((member tname '("Atom" "clojure.lang.Atom")) (jolt-atom? val))
|
|
((member tname '("IFn" "clojure.lang.IFn" "Fn" "clojure.lang.Fn")) (procedure? val))
|
|
((member tname '("Pattern" "java.util.regex.Pattern")) (regex-t? val))
|
|
((member tname '("URI" "java.net.URI"))
|
|
(and (jhost? val) (string=? (jhost-tag val) "uri")))
|
|
((member tname '("File" "java.io.File")) (jfile? val))
|
|
((member tname '("UUID" "java.util.UUID")) (juuid? val))
|
|
(else #f)))
|
|
|
|
;; str of a record uses a custom (Object toString) impl if the type defines one
|
|
;; (deftype with no default toString relies on this); otherwise the map form
|
|
;; without the leading # (Clojure's record .toString). converters.ss loads before
|
|
;; records.ss, so this set! sees the registry — forward refs resolve at call time.
|
|
|
|
(def-var! "clojure.core" "instance-check" instance-check)
|