;; ========================================================================== ;; test-strict.sx — Tests for strict typing mode ;; ;; Requires: test-framework.sx loaded first. ;; Modules tested: eval.sx (strict-check-args, set-strict!, value-matches-type?) ;; ;; When *strict* is true, primitive calls check arg types at runtime. ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; value-matches-type? — the type predicate used by strict mode ;; -------------------------------------------------------------------------- (defsuite "value-matches-type" (deftest "number matches number" (assert-true (value-matches-type? 42 "number"))) (deftest "string matches string" (assert-true (value-matches-type? "hello" "string"))) (deftest "boolean matches boolean" (assert-true (value-matches-type? true "boolean"))) (deftest "nil matches nil" (assert-true (value-matches-type? nil "nil"))) (deftest "list matches list" (assert-true (value-matches-type? (list 1 2) "list"))) (deftest "dict matches dict" (assert-true (value-matches-type? (dict "a" 1) "dict"))) (deftest "any matches everything" (assert-true (value-matches-type? 42 "any")) (assert-true (value-matches-type? "s" "any")) (assert-true (value-matches-type? nil "any")) (assert-true (value-matches-type? (list) "any"))) (deftest "wrong type fails" (assert-false (value-matches-type? "hello" "number")) (assert-false (value-matches-type? 42 "string")) (assert-false (value-matches-type? nil "number")) (assert-false (value-matches-type? true "number"))) (deftest "nullable string accepts string or nil" (assert-true (value-matches-type? "hello" "string?")) (assert-true (value-matches-type? nil "string?")) (assert-false (value-matches-type? 42 "string?"))) (deftest "nullable number accepts number or nil" (assert-true (value-matches-type? 42 "number?")) (assert-true (value-matches-type? nil "number?")) (assert-false (value-matches-type? "x" "number?")))) ;; -------------------------------------------------------------------------- ;; Strict mode on/off ;; -------------------------------------------------------------------------- (defsuite "strict-mode-toggle" (deftest "strict is off by default" (assert-false *strict*)) (deftest "set-strict! enables and disables" ;; Verify by testing behavior: with strict on, bad types throw (set-strict! true) (set-prim-param-types! {"inc" {"positional" (list (list "n" "number")) "rest-type" nil}}) (let ((r (try-call (fn () (inc "x"))))) (assert-false (get r "ok"))) ;; Turn off: same call should succeed (JS coercion) (set-strict! false) (let ((r (try-call (fn () (inc "x"))))) (assert-true (get r "ok"))) (set-prim-param-types! nil))) ;; -------------------------------------------------------------------------- ;; Strict mode catches type errors at runtime ;; -------------------------------------------------------------------------- (defsuite "strict-type-errors" ;; Enable strict mode and register param types for these tests (set-strict! true) (set-prim-param-types! { "+" {"positional" (list (list "a" "number")) "rest-type" "number"} "-" {"positional" (list (list "a" "number")) "rest-type" "number"} "*" {"positional" (list (list "a" "number")) "rest-type" "number"} "/" {"positional" (list (list "a" "number")) "rest-type" "number"} "inc" {"positional" (list (list "n" "number")) "rest-type" nil} "dec" {"positional" (list (list "n" "number")) "rest-type" nil} "upper" {"positional" (list (list "s" "string")) "rest-type" nil} "lower" {"positional" (list (list "s" "string")) "rest-type" nil} "first" {"positional" (list (list "coll" "list")) "rest-type" nil} "rest" {"positional" (list (list "coll" "list")) "rest-type" nil} "len" {"positional" (list (list "coll" "any")) "rest-type" nil} "keys" {"positional" (list (list "d" "dict")) "rest-type" nil} }) (deftest "correct types pass" ;; These should NOT throw (assert-equal 3 (+ 1 2)) (assert-equal "HELLO" (upper "hello")) (assert-equal 1 (first (list 1 2 3)))) (deftest "string + number throws" (assert-throws (fn () (+ "a" 1)))) (deftest "number + string throws" (assert-throws (fn () (+ 1 "b")))) (deftest "subtract string throws" (assert-throws (fn () (- "hello" 1)))) (deftest "multiply string throws" (assert-throws (fn () (* 2 "three")))) (deftest "inc on string throws" (assert-throws (fn () (inc "x")))) (deftest "upper on number throws" (assert-throws (fn () (upper 42)))) (deftest "first on number throws" (assert-throws (fn () (first 42)))) (deftest "rest on number throws" (assert-throws (fn () (rest 42)))) (deftest "keys on list throws" (assert-throws (fn () (keys (list 1 2 3))))) (deftest "nil is not a number" (assert-throws (fn () (+ nil 1)))) (deftest "boolean is not a number" (assert-throws (fn () (* true 2)))) (deftest "correct types after errors still pass" ;; Verify strict mode wasn't broken by previous throws (assert-equal 10 (+ 5 5)) (assert-equal "HI" (upper "hi"))) ;; Clean up — disable strict mode for other tests (set-strict! false) (set-prim-param-types! nil))