diff --git a/jolt-core/clojure/core/30-macros.clj b/jolt-core/clojure/core/30-macros.clj index 9df03ca..c80411e 100644 --- a/jolt-core/clojure/core/30-macros.clj +++ b/jolt-core/clojure/core/30-macros.clj @@ -445,8 +445,12 @@ ~@(map (fn [spec] (let [argv (nth spec 1) inst (first argv) + ;; hint `this` with the record type so the inference + ;; types it (jolt-3ko) and its field reads bare-index + ;; instead of going through the runtime tag guard. + hinted (assoc argv 0 (vary-meta inst assoc :tag (name name-sym))) binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))] - `(~(first spec) ~argv (let [~@binds] ~@(drop 2 spec))))) + `(~(first spec) ~hinted (let [~@binds] ~@(drop 2 spec))))) specs)))] `(do ;; deftype already defines ->name (= the ctor); no (name. …) interop needed, diff --git a/jolt-core/jolt/passes.clj b/jolt-core/jolt/passes.clj index 9b3af8f..27111af 100644 --- a/jolt-core/jolt/passes.clj +++ b/jolt-core/jolt/passes.clj @@ -34,6 +34,10 @@ [node ctx] (if (inline-enabled? ctx) (let [_ (set-rec-shapes! (record-shapes ctx)) ;; record ctor fold (jolt-15jq) + ;; resolve ^Record param hints (incl. defrecord/extend-type method + ;; `this`) to bare field reads per-form, not only under whole-program + ;; (jolt-3ko). Same shapes the inline pass uses. + _ (set-record-shapes! (record-shapes ctx)) opt (loop [i 0 n (const-fold node)] (reset! dirty false) (let [n2 (const-fold (scalar-replace (flatten-lets (inline-node n ctx))))] diff --git a/jolt-core/jolt/passes/types.clj b/jolt-core/jolt/passes/types.clj index 6027331..105070a 100644 --- a/jolt-core/jolt/passes/types.clj +++ b/jolt-core/jolt/passes/types.clj @@ -526,11 +526,21 @@ [:any (assoc node :args (mapv (fn [a] (nth (infer a tenv) 1)) (get node :args)))] (= op :fn) ;; a closure inherits the enclosing tenv so CAPTURED locals keep their - ;; types (e.g. a reduce closure that calls (f captured-struct ...)); its own - ;; params/rest shadow to :any (unknown until Phase 1 types them via callers). + ;; types (e.g. a reduce closure that calls (f captured-struct ...)). Its own + ;; params shadow to :any UNLESS a param carries a ^Record declared hint + ;; (:phints, name -> ctor-key) — then seed it to that record type so field + ;; reads off it bare-index per-form, not only under whole-program. This is + ;; what makes a protocol method's `this` (hinted by defrecord/extend-type) + ;; read its fields without the runtime tag guard (jolt-3ko). [:any (assoc node :arities (mapv (fn [a] - (let [pe (reduce (fn [e p] (assoc e p :any)) tenv (get a :params)) + (let [phm (reduce (fn [m pr] (assoc m (nth pr 0) (nth pr 1))) + {} (get a :phints)) + pe (reduce (fn [e p] + (assoc e p + (let [ent (get @record-shapes-box (get phm p))] + (if ent (record-type-from-entry ent type-depth) :any)))) + tenv (get a :params)) pe (if (get a :rest) (assoc pe (get a :rest) :any) pe)] (assoc a :body (nth (infer (get a :body) pe) 1)))) (get node :arities)))] diff --git a/src/jolt/core_extra.janet b/src/jolt/core_extra.janet index 7df0423..e6f1eb1 100644 --- a/src/jolt/core_extra.janet +++ b/src/jolt/core_extra.janet @@ -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)