feat: success-type checker (RFC 0006) — flag provably-wrong core calls
Reuse the structural inference from RFC 0005 as a loose type checker. It reports a core-fn call only when an argument's inferred type is concrete and lies in that op's throwing error domain, and accepts everything ambiguous (:any, a union that joined to :any, :truthy). By construction it never produces a false positive: a correct program has nothing to report even in error mode. The curated error-domain table starts with the clearest throwing cases: arithmetic on a provable non-number, and count/first/rest/next/seq/nth on a provable non-seqable scalar. Lenient operations like (get 5 :k) and (:k 5), which return nil rather than throw, are deliberately not listed. Checking is decoupled from specialization: it runs whenever JOLT_TYPE_CHECK is warn or error, regardless of :inline?, reading the knob at compile time so no rebuild is needed. warn prints to stderr, error fails the form's compilation, off (the default) skips it entirely. Core init stays clean under the flag. jolt-y3b
This commit is contained in:
parent
9bc7b27245
commit
9867c33079
3 changed files with 221 additions and 8 deletions
57
test/integration/type-check-test.janet
Normal file
57
test/integration/type-check-test.janet
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Success-type checking (RFC 0006, jolt-y3b). The structural inference of
|
||||
# RFC 0005, reused as a loose checker: flag a core-fn call ONLY when an argument
|
||||
# is PROVABLY the wrong type (concrete and in the op's throwing error domain).
|
||||
# Ambiguous cases (:any, unions, :truthy) are accepted — no false positives.
|
||||
(import ../../src/jolt/api :as api)
|
||||
(import ../../src/jolt/backend :as backend)
|
||||
(import ../../src/jolt/types :as types)
|
||||
(import ../../src/jolt/reader :as reader)
|
||||
|
||||
(print "Success-type checking (jolt-y3b)...")
|
||||
|
||||
(os/setenv "JOLT_DIRECT_LINK" "1")
|
||||
(def ctx (api/init {:compile? true}))
|
||||
(def pns (types/ctx-find-ns ctx "jolt.passes"))
|
||||
(def check (types/var-get (types/ns-find pns "check-form")))
|
||||
|
||||
# diagnostics (a Janet tuple of diag structs) for a source form
|
||||
(defn diags [src]
|
||||
(api/normalize-pvecs (check (backend/analyze-form ctx (reader/parse-string src)))))
|
||||
(defn nd [src] (length (diags src)))
|
||||
|
||||
# --- provably wrong: REPORTED ------------------------------------------------
|
||||
(assert (= 1 (nd "(inc \"x\")")) "inc on a string")
|
||||
(assert (= 1 (nd "(+ 1 \"x\")")) "+ with a string arg")
|
||||
(assert (= 1 (nd "(count :foo)")) "count of a keyword")
|
||||
(assert (= 1 (nd "(count 5)")) "count of a number")
|
||||
(assert (= 1 (nd "(first 42)")) "first of a number")
|
||||
(assert (= 1 (nd "(nth :k 0)")) "nth of a keyword")
|
||||
(assert (= 1 (nd "(let [n \"x\"] (inc n))")) "inc on a let-bound string")
|
||||
(assert (= 1 (nd "(inc (count :k))")) "inner count of keyword reported (inc of :num is fine)")
|
||||
|
||||
# --- ambiguous / lenient: ACCEPTED (no false positive) -----------------------
|
||||
(assert (= 0 (nd "(:k 5)")) "keyword lookup on a number returns nil, not an error")
|
||||
(assert (= 0 (nd "(get 5 :k)")) "get on a number returns nil, not an error")
|
||||
(assert (= 0 (nd "(fn [x] (inc x))")) "inc on an unknown (:any) param accepted")
|
||||
(assert (= 0 (nd "(fn [c] (inc (if c 1 \"x\")))")) "inc on a {:num | :str} branch -> :any, accepted")
|
||||
(assert (= 0 (nd "(count \"ab\")")) "count of a string is fine")
|
||||
(assert (= 0 (nd "(count [1 2 3])")) "count of a vector is fine")
|
||||
(assert (= 0 (nd "(first [1 2 3])")) "first of a vector is fine")
|
||||
(assert (= 0 (nd "(inc (count [1 2 3]))")) "count of vector + inc of :num both fine")
|
||||
(assert (= 0 (nd "(inc (first [1 2 3]))")) "first of vector -> :num, inc fine")
|
||||
|
||||
# --- the diagnostic carries op + type + a message ----------------------------
|
||||
(def one (in (diags "(inc \"x\")") 0))
|
||||
(assert (= "inc" (get one :op)) "diagnostic names the op")
|
||||
(assert (string/find "number" (get one :msg)) "message says a number is required")
|
||||
|
||||
# --- end-to-end: strictness drives compilation (decoupled from :inline?) -----
|
||||
# error mode aborts a provably-wrong form's compilation; a correct form compiles.
|
||||
(os/setenv "JOLT_TYPE_CHECK" "error")
|
||||
(assert (not (first (protect (api/eval-string ctx "(count :nope)"))))
|
||||
"error mode aborts a provably-wrong form")
|
||||
(assert (first (protect (api/eval-string ctx "(count [1 2 3])")))
|
||||
"error mode accepts a correct form")
|
||||
(os/setenv "JOLT_TYPE_CHECK" "off")
|
||||
|
||||
(print "Success-type checking passed!")
|
||||
Loading…
Add table
Add a link
Reference in a new issue