Specialize record field reads in method bodies; fix with-meta on symbols (#141)

A protocol method reads its fields through the generic guarded keyword lookup
because the method's `this` param is untyped. defrecord now hints `this` with
the record type, the per-form inference seeds ^Record-hinted params (the
:fn branch previously typed all params :any — only the whole-program path
seeded phints), and run-passes feeds the inference the record shapes. So a
hinted param's field reads bare-index instead of going through the :jolt/type
tag guard.

This needed a with-meta fix: (with-meta sym ..) returned a proto'd table, so
symbol? was false and the macro-attached hint broke fn destructuring. Symbols
now carry metadata in-place in their struct (matching how the reader attaches
^hint), keeping symbol? true, as in Clojure.

Modest on dispatch (~3-5%): the field read is a small fraction of a dispatch;
the machinery (record-tag + protocol lookup + wrapper) dominates, which is the
inline-cache target (jolt-ez5h). But it's a correctness fix and lets any
^Record-hinted code — not just methods — drop the field-read guard per-form,
not only under whole-program.

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-16 15:14:59 +00:00 committed by GitHub
parent 7d0b1d5695
commit 6772e28eae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 32 additions and 6 deletions

View file

@ -55,9 +55,17 @@
(defn core-with-meta [obj meta]
# Functions and scalars can't carry metadata in Jolt's model — return as-is
# rather than crashing (Clojure attaches meta only to IObj values).
(if (or (function? obj) (cfunction? obj) (number? obj) (boolean? obj)
(nil? obj) (string? obj) (keyword? obj) (buffer? obj))
(cond
(or (function? obj) (cfunction? obj) (number? obj) (boolean? obj)
(nil? obj) (string? obj) (keyword? obj) (buffer? obj))
obj
# Symbols carry metadata IN-PLACE in their struct's :meta field (this is how
# the reader attaches ^hint and keeps symbol? true — see reader/read-meta).
# The table-proto path below would make (symbol? (with-meta sym ..)) false and
# break destructuring/hint reading, so keep a symbol a symbol.
(and (struct? obj) (= :symbol (obj :jolt/type)))
(struct ;(kvs obj) :meta meta)
true
(do
(var new-obj @{})
(each k (keys obj)