Add strict typing mode + 139 new tests: 749/749 passing
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>
This commit is contained in:
147
spec/tests/test-strict.sx
Normal file
147
spec/tests/test-strict.sx
Normal file
@@ -0,0 +1,147 @@
|
||||
;; ==========================================================================
|
||||
;; 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))
|
||||
Reference in New Issue
Block a user