Fix six JVM divergences surfaced by rewrite-clj
Running the rewrite-clj test suite under jolt exposed six bugs, each fixed here:
- `for`/`doseq` `:let` bindings never went through `destructure`, so a
destructuring pattern (`:let [{:keys [y]} x]`) hit `let*` raw and failed to
compile. Emit `let`, like Clojure.
- `with-open` couldn't close a deftype/defrecord that implements a `close` method
(java.io.Closeable / AutoCloseable, e.g. tools.reader's readers) — `__close`
only knew jhost readers and map `:close` fns. Dispatch a record's `close`.
- A deftype/defrecord method param named like a field didn't shadow the field
(the field's let-binding wrapped the params). Params now shadow, as in Clojure.
- A deftype whose simple name collided with a built-in host class clobbered it in
the global ctor table, so `(java.io.PushbackReader. …)` built tools.reader's
same-named deftype. Register deftypes/built-ins by FQN, don't let a deftype
overwrite a built-in's simple name, and qualify a bare `(Name. …)` to the
deftype's FQN only in the ns that defined it.
- `clojure.walk` was lazy over a non-list seq (missing `doall`), so a walk whose
fn has side effects read stale state. Make it eager, like Clojure.
- `Character/isWhitespace` used an ASCII-only check that missed U+2028 and other
Unicode whitespace. Use the JVM's Unicode set (minus the no-break spaces it
excludes).
Regressions: corpus rows (for-let destructure, method-param shadow, walk eager,
isWhitespace), a unit row (with-open closes a record), and smoke checks (the
class-name collision, run in a fresh -e process so the deftype doesn't leak).
One divergence remains unfixed: a submatch from a losing regex alternation branch
leaks when the winning branch has a quantified group (a bug in the vendored
irregex engine, not jolt) — tracked separately.
This commit is contained in:
parent
0bad467372
commit
77e80dab9c
14 changed files with 640 additions and 572 deletions
|
|
@ -514,7 +514,9 @@
|
|||
sub (wrap-mods (rest mods) inner)]
|
||||
(if (= (first m) :when)
|
||||
`(if ~(nth m 1) ~sub [])
|
||||
`(let* ~(nth m 1) ~sub)))))
|
||||
;; `let` (not let*) so a :let binding may itself
|
||||
;; destructure — (for [x xs :let [{:keys [y]} x]] …).
|
||||
`(let ~(nth m 1) ~sub)))))
|
||||
build (fn build [idx groups]
|
||||
(let [g (nth groups idx)
|
||||
my-bind (nth g 0)
|
||||
|
|
|
|||
|
|
@ -385,11 +385,18 @@
|
|||
;; field binds / live-read instance (see defrecord's mk-clause).
|
||||
(let [argv (mapv (fn [p] (if (= p (quote _)) (gensym "_p") p)) (nth spec 1))
|
||||
inst (first argv)
|
||||
;; A method param shadows a same-named field (Clojure
|
||||
;; semantics): don't let-bind a field the param already
|
||||
;; provides, and treat those params as shadowing so a
|
||||
;; mutable field's live-read rewrite doesn't override them.
|
||||
pnames (set (map name argv))
|
||||
;; let-bind only immutable fields; mutable ones are read live
|
||||
;; via rewrite-body so a set! within the method is observed.
|
||||
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))])
|
||||
(filter (fn [f] (not (mutable? f))) fields)))
|
||||
mbody (map (fn [bf] (rewrite-body inst #{} bf)) (drop 2 spec))]
|
||||
(filter (fn [f] (and (not (mutable? f))
|
||||
(not (contains? pnames (name f)))))
|
||||
fields)))
|
||||
mbody (map (fn [bf] (rewrite-body inst (set argv) bf)) (drop 2 spec))]
|
||||
(list argv (list* 'let binds mbody))))
|
||||
groups (group-by-head body)
|
||||
;; merge clauses by method NAME across ALL protocols into one multi-arity
|
||||
|
|
@ -616,7 +623,11 @@
|
|||
(let [argv (mapv (fn [p] (if (= p (quote _)) (gensym "_p") p)) (nth spec 1))
|
||||
inst (first argv)
|
||||
hinted (assoc argv 0 (vary-meta inst assoc :tag (name name-sym)))
|
||||
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))]
|
||||
;; a method param shadows a same-named field (Clojure
|
||||
;; semantics), so don't rebind a field the param provides.
|
||||
pnames (set (map name argv))
|
||||
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))])
|
||||
(remove (fn [f] (contains? pnames (name f))) fields)))]
|
||||
(list hinted (list* 'let binds (drop 2 spec)))))
|
||||
groups (group-by-head body)
|
||||
;; merge clauses by name across protocols into one multi-arity fn (see
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue