;; run-infer.ss — inference / success-type-checking gate (jolt.passes.types).
;;
;; The corpus and unit gates compile through run-passes' const-fold-only branch,
;; so the inference walk (jolt.passes.types) runs only under `jolt build --opt` —
;; buildsmoke exercises it on one trivial app and asserts stdout only. This gate
;; drives the pass DIRECTLY: analyze a source string to IR, then call the public
;; checker/driver entry points (check-form, infer-body, the set-*! registries) and
;; assert their observable output (diagnostic counts, collected calls/escapes). It
;; pins the behavior the inference walk's internal state produces, so a refactor of
;; that state is gate-validatable.
;;
;;   chez --script host/chez/run-infer.ss
(import (chezscheme))

(load "host/chez/rt.ss")
(set-chez-ns! "clojure.core")
(load "host/chez/seed/prelude.ss")
(load "host/chez/post-prelude.ss")
(set-chez-ns! "user")
(load "host/chez/host-contract.ss")
(load "host/chez/seed/image.ss")
(load "host/chez/compile-eval.ss")

(define analyze            (var-deref "jolt.analyzer" "analyze"))
(define check-form         (var-deref "jolt.passes.types" "check-form"))
(define infer-body         (var-deref "jolt.passes.types" "infer-body"))
(define reset-escapes!     (var-deref "jolt.passes.types" "reset-escapes!"))
(define collected-escapes  (var-deref "jolt.passes.types" "collected-escapes"))
(define set-record-shapes! (var-deref "jolt.passes.types" "set-record-shapes!"))
(define set-vtypes!        (var-deref "jolt.passes.types" "set-vtypes!"))
(define set-check-mode!    (var-deref "jolt.passes.types" "set-check-mode!"))
(define run-inference      (var-deref "jolt.passes.types" "run-inference"))
(define take-diags!        (var-deref "jolt.passes.types" "take-diags!"))

;; analyze a source string to its IR node (fresh ctx, ns "user", no passes).
(define (anode src) (analyze (make-analyze-ctx "user") (jolt-ce-read src)))
;; number of success-type diagnostics check-form produces for src.
(define (diags src strict?) (jolt-count (check-form (anode src) strict?)))

(define fails 0)
(define total 0)
(define (check label actual expected)
  (set! total (+ total 1))
  (unless (equal? actual expected)
    (set! fails (+ fails 1))
    (printf "  FAIL ~a: got ~s expected ~s\n" label actual expected)))

;; --- core error-domain checking (strict not required) -----------------------
(check "num-op on keyword"        (diags "(+ 1 :k)" #f) 1)
(check "num-op all numbers"       (diags "(+ 1 2)" #f) 0)
(check "count on number"          (diags "(count 5)" #f) 1)
(check "count on vector"          (diags "(count [1 2])" #f) 0)
(check "lenient (:k 5)"           (diags "(:k 5)" #f) 0)
(check "call a number"            (diags "(5 1)" #f) 1)
(check "nested count return type" (diags "(+ 1 (count :k))" #f) 1)

;; --- walk arms thread the type env ------------------------------------------
(check "let binds kw"             (diags "(let [x :k] (+ x 1))" #f) 1)
(check "let binds ok"             (diags "(let [x 1] (+ x 1))" #f) 0)
(check "if then branch error"     (diags "(if true (+ 1 :k) 2)" #f) 1)
(check "do statement error"       (diags "(do (+ 1 :k) 2)" #f) 1)
(check "mapv seeds element type"  (diags "(mapv (fn [x] (+ x 1)) [:a :b])" #f) 1)
(check "mapv ok element type"     (diags "(mapv (fn [x] (+ x 1)) [1 2])" #f) 0)
(check "reduce seeds element"     (diags "(reduce (fn [acc x] (+ acc x)) 0 [:a])" #f) 1)

;; --- strict user-function domains (checking-box / diag-memo / user-sig) ------
(check "user wrong arg type"      (diags "(do (defn f [x] (+ x 1)) (f :k))" #t) 1)
(check "user wrong arity"         (diags "(do (defn g [x] x) (g 1 2))" #t) 1)
(check "user call ok"             (diags "(do (defn h [x] (+ x 1)) (h 3))" #t) 0)
(check "user domains off w/o strict" (diags "(do (defn f [x] (+ x 1)) (f :k))" #f) 0)
;; recursive user fn terminates (cycle guard) and still flags the bad arg
(check "user recursive terminates"
       (diags "(do (defn rf [x] (+ x (rf x))) (rf :k))" #t) 1)

;; --- infer-body collects calls + escapes ------------------------------------
(reset-escapes!)
(let ((r (infer-body (anode "(do (foo 1) (bar 2) (map inc [1]))") (jolt-hash-map))))
  (check "infer-body calls" (jolt-count (jolt-nth r 2)) 3)        ; foo, bar, map
  (check "infer-body escapes" (jolt-count (collected-escapes)) 1)) ; inc (value position)

;; --- the record-shapes registry feeds call-result types --------------------
;; without shapes a (->P …) call result is :any (accepted); with the registry it
;; types as a struct, so an arithmetic op over it is provably not-a-number.
(check "ctor result :any w/o shapes" (diags "(+ (->P 1) 1)" #f) 0)
(set-record-shapes!
  (jolt-hash-map "user/->P"
                 (jolt-hash-map (keyword #f "fields") (jolt-vector (keyword #f "x"))
                                (keyword #f "tags")   (jolt-vector jolt-nil)
                                (keyword #f "type")   "user.P")))
(check "ctor result struct w/ shapes" (diags "(+ (->P 1) 1)" #f) 1)
(set-record-shapes! (jolt-hash-map))

;; --- the opt-path checker: run-inference emits, take-diags! drains -----------
;; (set-check-mode! on strict?) arms checking during the next run-inference; the
;; diagnostics are stashed for take-diags! to drain once.
(set-check-mode! #t #f)
(run-inference (anode "(+ 1 :k)"))
(check "take-diags drains run-inference" (jolt-count (take-diags!)) 1)
(check "take-diags re-drained empty"     (jolt-count (take-diags!)) 0)
(set-check-mode! #f #f)
(run-inference (anode "(+ 1 :k)"))
(check "no diags when check-mode off"    (jolt-count (take-diags!)) 0)

(if (= fails 0)
    (begin (printf "infer gate: ~a/~a passed\n" total total) (exit 0))
    (begin (printf "infer gate: ~a/~a passed (~a failed)\n" (- total fails) total fails) (exit 1)))
