feat: negative/never types — calling a non-function, wrong-arity (jolt-wwy)
Two provably-wrong cases the inference already has the facts for, closing the
last RFC 0006 open question:
- Calling a non-function. At an :invoke whose callee is provably :num or :str
(the only non-callable types — keywords/maps/vectors/sets are IFn), report
"cannot call a number as a function". Default level (no closed-world: the
callee type is inferred at the call site). Covers (5 1), ("hi" 0),
((+ 1 2) :k), a let-bound number, and a var holding a number (via vtype-box
in direct-link). A union is non-callable only when every member is, so
((if c 1 :k) x) is accepted (:kw is callable). Verified zero false positives
on the ray tracer, which calls maps/keywords/vectors as fns throughout.
- Wrong arity to a user fn. The registered single-fixed-arity sig (jolt-zo1)
makes a mismatched arg count provably throw; reported under the
JOLT_TYPE_CHECK_USER opt-in (same closed-world boundary; ^:redef/variadic
skipped). Caught at compile time before the runtime arity error.
Both fold into the existing infer walk, carry :pos for file:line:col, and keep
no-false-positives. Gate green, suite 4718, conformance 335/335, runtime bench
even (compile-time only).
This commit is contained in:
parent
e071d09170
commit
328f88636e
3 changed files with 100 additions and 23 deletions
|
|
@ -142,6 +142,26 @@
|
|||
(os/setenv "JOLT_TYPE_CHECK" nil)
|
||||
r)
|
||||
(fn [s] (nil? (string/find "type error" s))))
|
||||
# negative/never types (jolt-wwy): calling a non-function is reported by default
|
||||
# in direct-link; wrong-arity to a user fn under the JOLT_TYPE_CHECK_USER opt-in
|
||||
(def tcn (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-tcneg-" (os/time) ".clj"))
|
||||
(spit tcn "(ns tcn)\n\n(defn nope []\n (let [n 5] (n 1)))\n")
|
||||
(check "direct-link reports calling a number as a function"
|
||||
(do (os/setenv "JOLT_DIRECT_LINK" "1")
|
||||
(def r (run-err tcn))
|
||||
(os/setenv "JOLT_DIRECT_LINK" nil)
|
||||
r)
|
||||
(has "cannot call a number as a function"))
|
||||
(def tca (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-tcarity-" (os/time) ".clj"))
|
||||
(spit tca "(ns tca)\n\n(defn f [x y] (+ x y))\n(defn g [] (f 1))\n")
|
||||
(check "JOLT_TYPE_CHECK_USER reports wrong arity to a user fn"
|
||||
(do (os/setenv "JOLT_DIRECT_LINK" "1")
|
||||
(os/setenv "JOLT_TYPE_CHECK_USER" "1")
|
||||
(def r (run-err tca))
|
||||
(os/setenv "JOLT_DIRECT_LINK" nil)
|
||||
(os/setenv "JOLT_TYPE_CHECK_USER" nil)
|
||||
r)
|
||||
(has "wrong number of args (1) passed to `f` (expected 2)"))
|
||||
|
||||
(if (> fails 0)
|
||||
(error (string "cli-test: " fails " failing check(s)"))
|
||||
|
|
|
|||
|
|
@ -45,6 +45,23 @@
|
|||
(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")
|
||||
|
||||
# --- calling a non-function (jolt-wwy): :num and :str are not callable --------
|
||||
(assert (= 1 (nd "(5 1)")) "calling a number is reported")
|
||||
(assert (= 1 (nd "(\"hi\" 0)")) "calling a string is reported")
|
||||
(assert (= 1 (nd "((+ 1 2) :k)")) "calling an arithmetic result (a :num) is reported")
|
||||
(assert (= 1 (nd "(let [n 5] (n 1))")) "calling a let-bound number is reported")
|
||||
(assert (= 1 (nd "(let [s \"x\"] (s 0))")) "calling a let-bound string is reported")
|
||||
# (a var holding a number, e.g. (def nn 5) (nn 1), is caught in direct-link
|
||||
# mode via vtype-box; the standalone checker has no var value types)
|
||||
# callable values: keyword/map/vector/set as IFn — NOT reported
|
||||
(assert (= 0 (nd "(:k {:k 1})")) "keyword call is fine")
|
||||
(assert (= 0 (nd "({:a 1} :a)")) "map call is fine")
|
||||
(assert (= 0 (nd "([10 20] 1)")) "vector call is fine")
|
||||
(assert (= 0 (nd "(#{1 2} 1)")) "set call is fine")
|
||||
(assert (= 0 (nd "(fn [c] ((if c 1 :k) 0))")) "union {:num | :kw} callee accepted (:kw is callable)")
|
||||
(assert (= 0 (nd "(fn [f] (f 1))")) "calling an unknown (:any) param accepted")
|
||||
(assert (= 1 (nd "(fn [c] ((if c 1 \"x\") 0))")) "union {:num | :str} callee — both non-callable — reported")
|
||||
|
||||
# --- bounded unions (jolt-pz5): report only when EVERY member is in the error
|
||||
# domain; accept when any member is valid. Differing branches used to collapse
|
||||
# to :any (accepted); now they form {:union #{...}} and are checked per-member.
|
||||
|
|
@ -85,6 +102,18 @@
|
|||
"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")
|
||||
# wrong arity to a user fn (jolt-wwy), strict mode: the registered fixed arity
|
||||
# makes a mismatched call provably throw, regardless of argument types
|
||||
(assert (= 1 (nds "(do (defn uar [x y] (+ x y)) (uar 1))"))
|
||||
"strict: 2-arg fn called with 1 arg is reported")
|
||||
(assert (= 1 (nds "(do (defn uar2 [x] x) (uar2 1 2 3))"))
|
||||
"strict: 1-arg fn called with 3 args is reported")
|
||||
(assert (= 0 (nds "(do (defn uar3 [x y] (+ x y)) (uar3 1 2))"))
|
||||
"strict: correct arity accepted")
|
||||
(assert (= 0 (nd "(do (defn uar4 [x y] (+ x y)) (uar4 1))"))
|
||||
"default level does NOT report user-fn arity (closed-world, opt-in)")
|
||||
(assert (= 0 (nds "(do (defn ^:redef uar5 [x y] (+ x y)) (uar5 1))"))
|
||||
"strict: ^:redef fn arity not checked (could be redefined)")
|
||||
|
||||
# --- the diagnostic carries op + type + a message ----------------------------
|
||||
(def one (in (diags "(inc \"x\")") 0))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue