Record type hints resolve across namespace boundaries (fields and params)
A ^RecordType hint only resolved against the current namespace's ctor key, so a hint naming a record defined in another namespace degraded to :any. That made a decomposed multi-namespace program much slower than the monolith: per-namespace inference can't see a record param's callers in other namespaces, and the declared hint that could have typed it was dropped. Resolution now works cross-namespace, for both record FIELD hints (defrecord) and fn PARAM hints, in both spellings — ^Vec3 where the type is referred and ^v/Vec3 where the namespace is aliased: - reader keeps a tag's namespace qualifier (^t/Ray -> "t/Ray", was "Ray"). - make-deftype-ctor-impl indexes each ctor closure by value; record-hint-ctor-key resolves a hint name against the COMPILE ns (referred names live there; aliases resolve through it) and maps the type var's root back to its home ctor key. Using the ctor value, not the var's :ns, is what makes :refer work — :refer re-interns a fresh var whose :ns is the referring ns. - the analyzer captures record param hints as arity :phints [name ctor-key]; reinfer-def seeds those param types, so a record param is typed even with no inferred caller — the open-world / cross-ns case. Effect on the multi-namespace ray tracer: per-ns compile 30.4s -> 7.9s with param hints, matching whole-program (8.1s) and the single-ns monolith (8.3s). cross-ns-hints-test covers field + param hints, refer + as, and the reader tag.
This commit is contained in:
parent
4018eb87ed
commit
1525c7adfb
6 changed files with 197 additions and 29 deletions
|
|
@ -23,7 +23,7 @@
|
|||
form-map-pairs form-set-items form-special? compile-ns
|
||||
form-macro? form-expand-1 resolve-global
|
||||
form-sym-meta host-intern! form-syntax-quote-lower
|
||||
record-type? form-position]]))
|
||||
record-type? record-ctor-key form-position]]))
|
||||
|
||||
(declare analyze)
|
||||
|
||||
|
|
@ -61,6 +61,16 @@
|
|||
(defn- add-hint [env nm h]
|
||||
(if h (assoc env :hints (assoc (:hints env) nm h)) env))
|
||||
|
||||
;; The resolved record ctor-key ("ns/->Name") for a ^Type param hint, or nil.
|
||||
;; Unlike hint-of (which collapses any record hint to the coarse :struct guard-
|
||||
;; skip marker), this carries the SPECIFIC record type — cross-namespace aware —
|
||||
;; so the inference can seed the param's type and read its fields shaped/typed,
|
||||
;; not just :any (the lever for a typed multi-namespace program without whole-
|
||||
;; program inference).
|
||||
(defn- phint-of [ctx sym]
|
||||
(let [m (form-sym-meta sym)]
|
||||
(when m (let [t (get m :tag)] (when t (record-ctor-key ctx t))))))
|
||||
|
||||
(defn- analyze-seq [ctx forms env]
|
||||
(let [v (mapv #(analyze ctx % env) forms)
|
||||
n (count v)]
|
||||
|
|
@ -83,18 +93,21 @@
|
|||
(defn- parse-params [ctx pvec]
|
||||
;; :hints is a vector of [name hint] pairs (vector, not a map, so the caller
|
||||
;; folds it with a plain reduce — no reduce-over-map in the kernel subset).
|
||||
(loop [i 0 fixed [] rest-name nil hints []]
|
||||
;; :phints is the parallel vector of [name ctor-key] for record param hints,
|
||||
;; carrying the specific type for the inference to seed.
|
||||
(loop [i 0 fixed [] rest-name nil hints [] phints []]
|
||||
(if (< i (count pvec))
|
||||
(let [p (nth pvec i)]
|
||||
(when-not (form-sym? p) (uncompilable "destructuring fn param"))
|
||||
(if (= "&" (form-sym-name p))
|
||||
(let [r (nth pvec (inc i))]
|
||||
(when-not (form-sym? r) (uncompilable "destructuring fn rest"))
|
||||
(recur (+ i 2) fixed (form-sym-name r) hints))
|
||||
(let [nm (form-sym-name p) h (hint-of ctx p)]
|
||||
(recur (+ i 2) fixed (form-sym-name r) hints phints))
|
||||
(let [nm (form-sym-name p) h (hint-of ctx p) ph (phint-of ctx p)]
|
||||
(recur (inc i) (conj fixed nm) rest-name
|
||||
(if h (conj hints [nm h]) hints)))))
|
||||
{:fixed fixed :rest rest-name :hints hints})))
|
||||
(if h (conj hints [nm h]) hints)
|
||||
(if ph (conj phints [nm ph]) phints)))))
|
||||
{:fixed fixed :rest rest-name :hints hints :phints phints})))
|
||||
|
||||
(defn- analyze-arity [ctx pvec body env fn-name]
|
||||
(let [pp (parse-params ctx (vec (form-vec-items pvec)))
|
||||
|
|
@ -113,7 +126,10 @@
|
|||
env0 (-> (add-locals env names) (with-recur rname))
|
||||
env* (reduce (fn [e pr] (add-hint e (nth pr 0) (nth pr 1))) env0 (:hints pp))
|
||||
arity {:params fixed :recur-name rname
|
||||
:body (analyze-seq ctx body env*)}]
|
||||
:body (analyze-seq ctx body env*)}
|
||||
;; carry record param hints (name -> ctor-key) for the inference to seed
|
||||
;; the param type; only when present so a hintless arity stays a struct.
|
||||
arity (if (seq (:phints pp)) (assoc arity :phints (:phints pp)) arity)]
|
||||
;; :rest only when variadic — an absent :rest reads back nil, same as before,
|
||||
;; but keeps a fixed arity a nil-free struct rather than a phm.
|
||||
(if rst (assoc arity :rest rst) arity)))
|
||||
|
|
|
|||
|
|
@ -1425,7 +1425,20 @@
|
|||
(if (= :fn (get fnode :op))
|
||||
(assoc def-node :init
|
||||
(assoc fnode :arities
|
||||
(mapv (fn [a] (assoc a :body (nth (infer (get a :body) ptmap) 1)))
|
||||
(mapv (fn [a]
|
||||
;; seed declared record param hints (:phints, name ->
|
||||
;; ctor-key) so a record param is typed even with no
|
||||
;; inferred caller type — the open-world / cross-ns
|
||||
;; case. An inferred type in ptmap wins (it's at least
|
||||
;; as precise), so this only fills the gaps.
|
||||
(let [pt (reduce (fn [m pr]
|
||||
(let [nm (nth pr 0)
|
||||
e (get @record-shapes-box (nth pr 1))]
|
||||
(if (and e (not (contains? m nm)))
|
||||
(assoc m nm (record-type-from-entry e type-depth))
|
||||
m)))
|
||||
ptmap (get a :phints))]
|
||||
(assoc a :body (nth (infer (get a :body) pt) 1))))
|
||||
(get fnode :arities))))
|
||||
def-node)))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue