Strict mode (spec/eval.sx):
- *strict* flag, set-strict!, set-prim-param-types!
- value-matches-type? checks values against declared types
- strict-check-args validates primitive call args at runtime
- Injected into eval-call before apply — zero cost when off
- Supports positional params, rest-type, nullable ("string?")
New test files:
- test-strict.sx (25): value-matches-type?, toggle, 12 type error cases
- test-errors.sx (74): undefined symbols, arity, permissive coercion,
strict type mismatches, nil/empty edge cases, number edge cases,
string edge cases, recursion patterns
- test-advanced.sx (39): nested special forms, higher-order patterns,
define patterns, quasiquote advanced, thread-first, letrec, case/cond
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
148 lines
5.3 KiB
Plaintext
148 lines
5.3 KiB
Plaintext
;; ==========================================================================
|
|
;; 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))
|