Files
rose-ash/spec/tests/test-adt.sx
giles 0dc7e1599c spec: match special form — ADT constructor pattern matching (20 tests)
Extends match-pattern in spec/evaluator.sx with an ADT case: when the
pattern is (CtorName var...) and the value is an ADT dict (:_adt true),
check :_ctor matches, arity matches, then recursively bind field patterns.
Supports nested patterns, wildcard _, variable binding, and zero-arg ctors.

Changes step-sf-match to route no-clause errors through raise-eval-frame
instead of direct error, allowing guard to catch non-exhaustive matches.

40/40 ADT tests pass (20 define-type + 20 match). Zero regressions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:16:16 +00:00

279 lines
9.2 KiB
Plaintext

(defsuite
"algebraic-data-types"
(deftest
"constructor creates dict with adt marker"
(do
(define-type Maybe (Just value) (Nothing))
(assert= true (get (Just 42) :_adt))))
(deftest
"constructor stores type name"
(do
(define-type Shape (Circle radius) (Square side))
(assert= "Shape" (get (Circle 5) :_type))
(assert= "Shape" (get (Square 3) :_type))))
(deftest
"constructor stores constructor name"
(do
(define-type Opt (Some val) (None))
(assert= "Some" (get (Some 1) :_ctor))
(assert= "None" (get (None) :_ctor))))
(deftest
"constructor stores fields as list"
(do
(define-type Pair (Pair-of fst snd))
(assert-equal
(list 1 2)
(get (Pair-of 1 2) :_fields))))
(deftest
"zero-arg constructor has empty fields"
(do
(define-type Flag (Set) (Unset))
(assert-equal (list) (get (Set) :_fields))
(assert-equal (list) (get (Unset) :_fields))))
(deftest
"type predicate true for all constructors"
(do
(define-type Expr (Num n) (Add left right) (Neg e))
(assert= true (Expr? (Num 5)))
(assert= true (Expr? (Add (Num 1) (Num 2))))
(assert= true (Expr? (Neg (Num 3))))))
(deftest
"type predicate false for non-adt values"
(do
(define-type Box (Box-of x))
(assert= false (Box? 42))
(assert= false (Box? "hello"))
(assert= false (Box? nil))
(assert= false (Box? (list 1 2)))
(assert= false (Box? {}))))
(deftest
"type predicate false for wrong adt type"
(do
(define-type AT (AV x))
(define-type BT (BV x))
(assert= false (AT? (BV 1)))
(assert= false (BT? (AV 1)))))
(deftest
"constructor predicate true for matching constructor"
(do
(define-type Result (Ok value) (Err msg))
(assert= true (Ok? (Ok 42)))
(assert= true (Err? (Err "bad")))))
(deftest
"constructor predicate false for wrong constructor"
(do
(define-type Coin (Heads) (Tails))
(assert= false (Heads? (Tails)))
(assert= false (Tails? (Heads)))))
(deftest
"constructor predicate false for non-adt"
(do
(define-type Wrap (Wrapped x))
(assert= false (Wrapped? 42))
(assert= false (Wrapped? nil))
(assert= false (Wrapped? "str"))))
(deftest
"single-field accessor returns field value"
(do
(define-type Holder (Held content))
(assert= 99 (Held-content (Held 99)))
(assert= "hello" (Held-content (Held "hello")))))
(deftest
"multi-field accessors return correct fields"
(do
(define-type Triple (Triple-of a b c))
(let
((t (Triple-of 10 20 30)))
(assert= 10 (Triple-of-a t))
(assert= 20 (Triple-of-b t))
(assert= 30 (Triple-of-c t)))))
(deftest
"tree constructors and accessors"
(do
(define-type Tree (Leaf) (Node left val right))
(let
((t (Node (Leaf) 5 (Node (Leaf) 3 (Leaf)))))
(assert= true (Node? t))
(assert= 5 (Node-val t))
(assert= true (Leaf? (Node-left t)))
(assert= true (Node? (Node-right t)))
(assert= 3 (Node-val (Node-right t))))))
(deftest
"arity error on too few args"
(do
(define-type Pair2 (Pair2-of a b))
(let
((ok false))
(guard (exn (else (set! ok true))) (Pair2-of 1))
(assert ok))))
(deftest
"arity error on too many args"
(do
(define-type Single (Single-of x))
(let
((ok false))
(guard
(exn (else (set! ok true)))
(Single-of 1 2))
(assert ok))))
(deftest
"multiple types are independent"
(do
(define-type Color2 (Red2) (Green2) (Blue2))
(define-type Suit (Hearts) (Diamonds) (Clubs) (Spades))
(assert= false (Color2? (Hearts)))
(assert= false (Suit? (Red2)))
(assert= true (Color2? (Blue2)))
(assert= true (Suit? (Spades)))))
(deftest
"adt fields can hold any value"
(do
(define-type Container (Hold x))
(assert-equal
(list 1 2 3)
(Hold-x (Hold (list 1 2 3))))
(assert-equal {:a 1} (Hold-x (Hold {:a 1})))))
(deftest
"adt-registry tracks type constructor names"
(do
(define-type Days (Mon) (Tue) (Wed) (Thu) (Fri))
(assert-equal
(list "Mon" "Tue" "Wed" "Thu" "Fri")
(get *adt-registry* "Days"))))
(deftest
"constructors with same field name in different types are independent"
(do
(define-type P1 (P1-ctor value))
(define-type P2 (P2-ctor value))
(assert= 10 (P1-ctor-value (P1-ctor 10)))
(assert= 20 (P2-ctor-value (P2-ctor 20)))))
(deftest
"match dispatches on first matching constructor"
(do
(define-type Color (Red) (Green) (Blue))
(assert= "red" (match (Red) ((Red) "red") ((Green) "green") ((Blue) "blue")))
(assert= "green" (match (Green) ((Red) "red") ((Green) "green") ((Blue) "blue")))
(assert= "blue" (match (Blue) ((Red) "red") ((Green) "green") ((Blue) "blue")))))
(deftest
"match binds field to variable"
(do
(define-type Wrapper (Wrap val))
(assert= 42 (match (Wrap 42) ((Wrap v) v)))
(assert= "hi" (match (Wrap "hi") ((Wrap v) v)))))
(deftest
"match zero-arg constructor"
(do
(define-type Signal (On) (Off))
(assert= "on" (match (On) ((On) "on") ((Off) "off")))
(assert= "off" (match (Off) ((On) "on") ((Off) "off")))))
(deftest
"match multi-field constructor binds all fields"
(do
(define-type Vec2 (V2 x y))
(let ((v (V2 3 4)))
(assert= 7 (match v ((V2 a b) (+ a b)))))))
(deftest
"match with else clause"
(do
(define-type Opt2 (Some2 val) (None2))
(assert= 10 (match (Some2 10) ((Some2 v) v) (else 0)))
(assert= 0 (match (None2) ((Some2 v) v) (else 0)))))
(deftest
"match else catches non-adt values"
(do
(assert= "other" (match 42 ((else) "other") (else "other")))
(assert= "other" (match "str" (else "other")))))
(deftest
"match returns body expression value"
(do
(define-type Num (Num-of n))
(assert= 100 (match (Num-of 10) ((Num-of n) (* n n))))))
(deftest
"match second arm fires when first does not match"
(do
(define-type Either (Left val) (Right val))
(assert= "left-1" (match (Left 1) ((Left v) (str "left-" v)) ((Right v) (str "right-" v))))
(assert= "right-2" (match (Right 2) ((Left v) (str "left-" v)) ((Right v) (str "right-" v))))))
(deftest
"match wildcard _ in constructor pattern"
(do
(define-type Pair3 (Pair3-of a b))
(assert= 5 (match (Pair3-of 5 99) ((Pair3-of x _) x)))
(assert= 99 (match (Pair3-of 5 99) ((Pair3-of _ y) y)))))
(deftest
"match nested adt constructor pattern"
(do
(define-type Tree2 (Leaf2) (Node2 left val right))
(let ((t (Node2 (Leaf2) 7 (Leaf2))))
(assert= 7 (match t ((Node2 _ v _) v)))
(assert= true (match t ((Node2 (Leaf2) _ _) true) (else false))))))
(deftest
"match literal pattern"
(do
(assert= "zero" (match 0 (0 "zero") (else "nonzero")))
(assert= "hello" (match "hello" ("hello" "hello") (else "other")))))
(deftest
"match symbol binding pattern"
(do
(assert= 42 (match 42 (x x)))))
(deftest
"match no matching clause raises error"
(do
(define-type AB (A-val) (B-val))
(let ((ok false))
(guard (exn (else (set! ok true)))
(match (A-val) ((B-val) "b")))
(assert ok))))
(deftest
"match result used in further computation"
(do
(define-type Num2 (N v))
(assert= 30
(+
(match (N 10) ((N v) v))
(match (N 20) ((N v) v))))))
(deftest
"match with define"
(do
(define-type Tag (Tagged label value))
(define get-label (fn (t) (match t ((Tagged lbl _) lbl))))
(define get-value (fn (t) (match t ((Tagged _ val) val))))
(let ((t (Tagged "name" 99)))
(assert= "name" (get-label t))
(assert= 99 (get-value t)))))
(deftest
"match three-field constructor"
(do
(define-type Triple2 (T3 a b c))
(assert= 6 (match (T3 1 2 3) ((T3 a b c) (+ a b c))))))
(deftest
"match clauses tried in order"
(do
(define-type Expr2 (Lit n) (Add l r) (Mul l r))
(define eval-expr2 (fn (e)
(match e
((Lit n) n)
((Add l r) (+ (eval-expr2 l) (eval-expr2 r)))
((Mul l r) (* (eval-expr2 l) (eval-expr2 r))))))
(assert= 7 (eval-expr2 (Add (Lit 3) (Lit 4))))
(assert= 12 (eval-expr2 (Mul (Lit 3) (Lit 4))))
(assert= 11 (eval-expr2 (Add (Lit 2) (Mul (Lit 3) (Lit 3)))))))
(deftest
"match else binding captures value"
(do
(define-type Coin2 (Heads2) (Tails2))
(assert= "Tails2" (match (Tails2) ((Heads2) "Heads2") (x (get x :_ctor))))))
(deftest
"match on adt with string field"
(do
(define-type Msg (Hello name) (Bye name))
(assert= "Hello, Alice" (match (Hello "Alice") ((Hello n) (str "Hello, " n)) ((Bye n) (str "Bye, " n))))
(assert= "Bye, Bob" (match (Bye "Bob") ((Hello n) (str "Hello, " n)) ((Bye n) (str "Bye, " n))))))
(deftest
"match nested pattern with variable binding"
(do
(define-type Box2 (Box2-of v))
(define-type Inner (Inner-of n))
(assert= 5 (match (Box2-of (Inner-of 5)) ((Box2-of (Inner-of n)) n)))))
)