feat: report provably-wrong calls to user functions, opt-in (jolt-zo1)
The success checker fired only against core-fn error domains (stable, not
redefinable). This adds reporting of a call that passes a provably-wrong
type to a USER fn whose body requires otherwise — e.g. a fn that only does
arithmetic on a param, called with a string.
As check-walk sees defs it registers each non-redefinable single-fixed-arity
user fn's {:params :body} in module state (user-sig-box, accumulating across
forms like rtenv-box — a def must precede its call). At a call site (strict
mode only) the body is re-checked with ONE parameter bound to its concrete
argument type, others :any; if that produces a diagnostic the all-:any body
did not, the argument alone is provably wrong and the call is reported.
Monotonic — binding a concrete type can only add error-domain hits — so still
no false positives. A cycle guard (checking-box) terminates mutual recursion.
Gated behind JOLT_TYPE_CHECK_USER (orthogonal to the warn/error level)
because it rests on the closed-world assumption, weaker than the core-fn
case. check-form gains a strict? arity; the default path is unchanged and
user-fn code runs only when the checker is enabled. ^:redef/^:dynamic and
multi/variadic fns are not registered (their body is no stable requirement).
Gate green, suite 4718, conformance 335/335.
This commit is contained in:
parent
9f076937af
commit
824b30defd
3 changed files with 138 additions and 13 deletions
|
|
@ -18,6 +18,10 @@
|
|||
(defn diags [src]
|
||||
(api/normalize-pvecs (check (backend/analyze-form ctx (reader/parse-string src)))))
|
||||
(defn nd [src] (length (diags src)))
|
||||
# strict mode (jolt-zo1): also report provably-wrong calls to user fns
|
||||
(defn nds [src]
|
||||
(length (api/normalize-pvecs
|
||||
(check (backend/analyze-form ctx (reader/parse-string src)) true))))
|
||||
|
||||
# --- provably wrong: REPORTED ------------------------------------------------
|
||||
(assert (= 1 (nd "(inc \"x\")")) "inc on a string")
|
||||
|
|
@ -60,6 +64,27 @@
|
|||
(assert (= 0 (nd "(fn [c] (:r (if c {:r 1} {:g 2})))"))
|
||||
"keyword lookup over a struct union is accepted (no false positive)")
|
||||
|
||||
# --- user-function error domains (jolt-zo1), opt-in strict mode --------------
|
||||
# A call passing a provably-wrong type to a user fn whose body requires
|
||||
# otherwise is reported ONLY in strict mode; the default level never fires on
|
||||
# user fns (closed-world soundness boundary).
|
||||
(assert (= 0 (nd "(do (defn ufa [x] (+ x 1)) (ufa \"s\"))"))
|
||||
"user-fn wrong call NOT reported at the default level")
|
||||
(assert (= 1 (nds "(do (defn ufa [x] (+ x 1)) (ufa \"s\"))"))
|
||||
"strict: arithmetic fn called with a string is reported")
|
||||
(assert (= 0 (nds "(do (defn ufb [x] (+ x 1)) (ufb 5))"))
|
||||
"strict: same fn called with a number is accepted")
|
||||
(assert (= 0 (nds "(do (defn ufc [x] (:k x)) (ufc \"s\"))"))
|
||||
"strict: a body that uses the param leniently is not reported")
|
||||
# cross-form: a def registered by an earlier check is visible to a later call
|
||||
(nds "(defn ufd [x] (count x))")
|
||||
(assert (= 1 (nds "(ufd 42)"))
|
||||
"strict: cross-form call to a seq-only fn with a number is reported")
|
||||
(assert (= 0 (nds "(do (defn ^:redef ufe [x] (+ x 1)) (ufe \"s\"))"))
|
||||
"strict: a ^:redef fn is not a stable requirement, not reported")
|
||||
(assert (= 1 (nds "(do (defn ufrec [x] (ufrec (+ x 1))) (ufrec \"s\"))"))
|
||||
"strict: self-recursion terminates (cycle guard) and the (+ x 1) on a string is reported once")
|
||||
|
||||
# --- the diagnostic carries op + type + a message ----------------------------
|
||||
(def one (in (diags "(inc \"x\")") 0))
|
||||
(assert (= "inc" (get one :op)) "diagnostic names the op")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue