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
72
test/integration/cross-ns-hints-test.janet
Normal file
72
test/integration/cross-ns-hints-test.janet
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Cross-namespace ^Type field hints (jolt-3ko follow-up): a record field hinted
|
||||
# with a record type defined in ANOTHER namespace — referred (:refer) or aliased
|
||||
# (:as) in — must resolve to that type's HOME ctor key in the record-shapes
|
||||
# registry, the same as a same-namespace hint does. That resolved key is what
|
||||
# lets the inference type a field read back to the foreign record type instead of
|
||||
# :any (the lever for fast nested-record code across a multi-namespace program).
|
||||
# Guards both the :refer and :as spellings — for record FIELD hints and for
|
||||
# fn PARAM hints (which seed the inference so a record param's reads are typed
|
||||
# across a namespace boundary without whole-program). Also guards that the
|
||||
# reader keeps a tag's namespace qualifier (^g/Pt -> "g/Pt", not "Pt").
|
||||
(use ../../src/jolt/api)
|
||||
(import ../../src/jolt/types :as ty)
|
||||
(import ../../src/jolt/core :as jc)
|
||||
(import ../../src/jolt/reader :as rd)
|
||||
|
||||
(var failures 0)
|
||||
(defn- check [label got want]
|
||||
(unless (deep= got want)
|
||||
(++ failures)
|
||||
(printf "FAIL [%s] got %q want %q" label got want)))
|
||||
|
||||
(def dir (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-xns-hints"))
|
||||
(os/mkdir dir)
|
||||
(os/mkdir (string dir "/geo"))
|
||||
# Pt lives in geo.pt; shape records in geo.shape hint ^Pt across the boundary.
|
||||
(spit (string dir "/geo/pt.clj")
|
||||
"(ns geo.pt)\n(defrecord Pt [x y z])\n")
|
||||
(spit (string dir "/geo/shape.clj")
|
||||
(string "(ns geo.shape (:require [geo.pt :as g :refer [Pt]]))\n"
|
||||
"(defrecord Seg [^Pt a ^Pt b])\n" # :refer field hint
|
||||
"(defrecord Tri [^g/Pt a ^g/Pt b ^g/Pt c])\n" # :as field hint
|
||||
# param hints, both spellings: ^Pt (referred), ^g/Pt (aliased)
|
||||
"(defn mid [^Pt a ^g/Pt b] a)\n"))
|
||||
|
||||
(def ctx (init {:compile? true :direct-linking? true}))
|
||||
(array/push (get (ctx :env) :source-paths) dir)
|
||||
(eval-string ctx "(require '[geo.shape])")
|
||||
(def rs (get (ctx :env) :record-shapes))
|
||||
|
||||
(check ":refer ^Pt field hint resolves to home ctor key"
|
||||
(get (get rs "geo.shape/->Seg") :tags)
|
||||
["geo.pt/->Pt" "geo.pt/->Pt"])
|
||||
(check ":as ^g/Pt field hint resolves to home ctor key"
|
||||
(get (get rs "geo.shape/->Tri") :tags)
|
||||
["geo.pt/->Pt" "geo.pt/->Pt" "geo.pt/->Pt"])
|
||||
# the foreign type's own shape is registered under its home key
|
||||
(check "home type registered"
|
||||
(get (get rs "geo.pt/->Pt") :fields)
|
||||
[:x :y :z])
|
||||
|
||||
# --- param hints: the arity carries [name ctor-key] for each record param, both
|
||||
# the :refer (^Pt) and :as (^g/Pt) spellings resolved to the home key ----------
|
||||
(def shape-ns (ty/ctx-find-ns ctx "geo.shape"))
|
||||
(def mid-ir (get (get (get shape-ns :mappings) "mid") :infer-ir))
|
||||
(def mid-arity (first (jc/vview (get (get mid-ir :init) :arities))))
|
||||
(def phints (when (get mid-arity :phints)
|
||||
(map jc/vview (jc/vview (get mid-arity :phints)))))
|
||||
(check "param hints resolve cross-ns (refer + as)"
|
||||
phints
|
||||
@[@["a" "geo.pt/->Pt"] @["b" "geo.pt/->Pt"]])
|
||||
|
||||
# --- reader keeps a tag's namespace qualifier ---------------------------------
|
||||
(check "reader preserves qualified tag ^g/Pt"
|
||||
(get (get (rd/parse-string "^g/Pt x") :meta) :tag)
|
||||
"g/Pt")
|
||||
(check "reader bare tag ^Pt unchanged"
|
||||
(get (get (rd/parse-string "^Pt x") :meta) :tag)
|
||||
"Pt")
|
||||
|
||||
(if (= 0 failures)
|
||||
(print "cross-ns-hints: all cases passed")
|
||||
(do (printf "cross-ns-hints: %d FAILURES" failures) (os/exit 1)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue