Step 7d complete: exhaustive match checking + evaluator cleanup

Match exhaustiveness analysis:
- check-match-exhaustiveness function in evaluator.sx
- lint-node in tree-tools.sx checks match forms during format-check
- Warns on: no wildcard/catch-all, boolean missing true/false case
- (match x (true "yes")) → "match may be non-exhaustive"

Evaluator cleanup:
- Added missing step-sf-callcc definition (was in old transpiled output)
- Added missing step-sf-case definition (was in old transpiled output)
- Removed protocol functions from bootstrap skip set (they transpile fine)
- Retranspiled VM (bootstrap_vm.py) for compatibility

2650 tests pass (+5 from new features).

All Step 7 features complete:
  7a: ->> |> as-> pipe operators
  7b: Dict patterns, &rest, let-match destructuring
  7c: define-protocol, implement, satisfies?
  7d: Exhaustive match checking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 15:43:57 +00:00
parent 653be79c8d
commit efd0d9168f
6 changed files with 213 additions and 80 deletions

View File

@@ -1351,13 +1351,13 @@
kont))))
(define
step-sf-case
step-sf-callcc
(fn
(args env kont)
(make-cek-state
(first args)
env
(kont-push (make-case-frame nil (rest args) env) kont))))
(kont-push (make-callcc-frame env) kont))))
;; ═══════════════════════════════════════════════════════════════
;; R7RS syntax-rules / define-syntax
@@ -1371,6 +1371,17 @@
;; Match a syntax-rules pattern against a form.
;; Returns a dict of bindings on success, nil on failure.
;; literals is a list of symbol name strings that must match exactly.
(define
step-sf-case
(fn
(args env kont)
(make-cek-state
(first args)
env
(kont-push (make-case-frame nil (rest args) env) kont))))
;; Match a list pattern against a form list, handling ellipsis at any position.
;; pi = pattern index, fi = form index.
(define
step-sf-let-match
(fn
@@ -1384,8 +1395,8 @@
env
kont))))
;; Match a list pattern against a form list, handling ellipsis at any position.
;; pi = pattern index, fi = form index.
;; Find which pattern variable in a template drives an ellipsis.
;; Returns the variable name (string) whose binding is a list, or nil.
(define
step-eval-list
(fn
@@ -1550,8 +1561,8 @@
:else (step-eval-call head args env kont)))))
(step-eval-call head args env kont))))))
;; Find which pattern variable in a template drives an ellipsis.
;; Returns the variable name (string) whose binding is a list, or nil.
;; Find ALL ellipsis-bound pattern variables in a template.
;; Returns a list of variable name strings.
(define
step-sf-parameterize
(fn
@@ -1570,8 +1581,8 @@
(make-parameterize-frame bindings nil (list) body env)
kont)))))))
;; Find ALL ellipsis-bound pattern variables in a template.
;; Returns a list of variable name strings.
;; Instantiate a template with pattern variable bindings.
;; Handles ellipsis repetition and recursive substitution.
(define
syntax-rules-match
(fn
@@ -1592,8 +1603,9 @@
(syntax-rules-match-list pattern 0 form 0 literals)
:else (if (= pattern form) (dict) nil))))
;; Instantiate a template with pattern variable bindings.
;; Handles ellipsis repetition and recursive substitution.
;; Walk a template list, handling ellipsis at any position.
;; When element at i is followed by ... at i+1, expand the element
;; for each value of its ellipsis variables (all cycled in parallel).
(define
syntax-rules-match-list
(fn
@@ -1676,9 +1688,10 @@
(keys sub-result))
rest-result)))))))))
;; Walk a template list, handling ellipsis at any position.
;; When element at i is followed by ... at i+1, expand the element
;; for each value of its ellipsis variables (all cycled in parallel).
;; Try each syntax-rules clause against a form.
;; Returns the instantiated template for the first matching rule, or errors.
;; form is the raw args (without macro name). We prepend a dummy _ symbol
;; because syntax-rules patterns include the keyword as the first element.
(define
syntax-rules-find-var
(fn
@@ -1698,10 +1711,6 @@
template)
:else nil)))
;; Try each syntax-rules clause against a form.
;; Returns the instantiated template for the first matching rule, or errors.
;; form is the raw args (without macro name). We prepend a dummy _ symbol
;; because syntax-rules patterns include the keyword as the first element.
(define
syntax-rules-find-all-vars
(fn
@@ -1719,6 +1728,10 @@
template)
:else (list))))
;; Special form: (syntax-rules (literal ...) (pattern template) ...)
;; Creates a Macro with rules/literals stored in closure env.
;; Body is a marker symbol; expand-macro detects it and calls
;; the pattern matcher directly.
(define
syntax-rules-instantiate
(fn
@@ -1732,10 +1745,6 @@
template
:else (syntax-rules-instantiate-list template 0 bindings))))
;; Special form: (syntax-rules (literal ...) (pattern template) ...)
;; Creates a Macro with rules/literals stored in closure env.
;; Body is a marker symbol; expand-macro detects it and calls
;; the pattern matcher directly.
(define
syntax-rules-instantiate-list
(fn
@@ -1785,14 +1794,6 @@
(syntax-rules-instantiate elem bindings)
(syntax-rules-instantiate-list template (+ i 1) bindings)))))))
(define
syntax-rules-expand
(fn
(literals rules form)
(let
((full-form (cons (make-symbol "_") form)))
(syntax-rules-try-rules literals rules full-form))))
;; R7RS records (SRFI-9)
;;
;; (define-record-type <point>
@@ -1804,6 +1805,15 @@
;; Creates: constructor, predicate, accessors, optional mutators.
;; Opaque — only accessible through generated functions.
;; Generative — each call creates a unique type.
(define
syntax-rules-expand
(fn
(literals rules form)
(let
((full-form (cons (make-symbol "_") form)))
(syntax-rules-try-rules literals rules full-form))))
;; Delimited continuations
(define
syntax-rules-try-rules
(fn
@@ -1823,7 +1833,6 @@
(syntax-rules-instantiate template bindings)
(syntax-rules-try-rules literals (rest rules) full-form)))))))
;; Delimited continuations
(define
sf-syntax-rules
(fn
@@ -1842,6 +1851,7 @@
closure
"syntax-rules")))))
;; Signal dereferencing with reactive dependency tracking
(define
step-sf-define-library
(fn
@@ -1886,7 +1896,13 @@
(register-library lib-spec export-dict)
(make-cek-value nil env kont))))))
;; Signal dereferencing with reactive dependency tracking
;; ═══════════════════════════════════════════════════════════════
;; Part 8: Call Dispatch
;;
;; cek-call: invoke a function from native code (runs a nested
;; trampoline). step-eval-call: CEK-native call dispatch for
;; lambda, component, native fn, and continuations.
;; ═══════════════════════════════════════════════════════════════
(define
bind-import-set
(fn
@@ -1918,13 +1934,7 @@
(fn (key) (env-bind! env key (get exports key)))
(keys exports))))))))
;; ═══════════════════════════════════════════════════════════════
;; Part 8: Call Dispatch
;;
;; cek-call: invoke a function from native code (runs a nested
;; trampoline). step-eval-call: CEK-native call dispatch for
;; lambda, component, native fn, and continuations.
;; ═══════════════════════════════════════════════════════════════
;; Reactive signal tracking — captures dependency continuation for re-render
(define
step-sf-import
(fn
@@ -1949,7 +1959,6 @@
env
(kont-push (make-import-frame import-set rest-sets env) kont))))))))
;; Reactive signal tracking — captures dependency continuation for re-render
(define
step-sf-perform
(fn
@@ -1962,8 +1971,6 @@
env
(kont-push (make-perform-frame env) kont)))))
(define *protocol-registry* (dict))
;; ═══════════════════════════════════════════════════════════════
;; Part 9: Higher-Order Form Machinery
;;
@@ -1971,6 +1978,8 @@
;; ho-swap-args auto-detects argument order. HoSetupFrame stages
;; argument evaluation, then dispatches to the appropriate step-ho-*.
;; ═══════════════════════════════════════════════════════════════
(define *protocol-registry* (dict))
(define
sf-define-record-type
(fn
@@ -2062,6 +2071,67 @@
method-specs)
nil)))
(define
check-match-exhaustiveness
(fn
(clauses)
(let
((warnings (list))
(patterns (map first clauses))
(has-wildcard
(some
(fn
(p)
(or
(= p (quote _))
(and
(symbol? p)
(not (= p (quote true)))
(not (= p (quote false))))))
patterns))
(has-else (some (fn (p) (= p :else)) patterns))
(has-true (some (fn (p) (= p true)) patterns))
(has-false (some (fn (p) (= p false)) patterns))
(has-nil (some (fn (p) (= p nil)) patterns))
(has-predicate
(some
(fn (p) (and (list? p) (= (first p) (quote ?))))
patterns)))
(when
(and (not has-wildcard) (not has-else))
(set!
warnings
(append
warnings
(list "match may be non-exhaustive (no wildcard or :else pattern)"))))
(when
(and
(or has-true has-false)
(not (and has-true has-false))
(not has-wildcard)
(not has-else))
(set!
warnings
(append
warnings
(list
(if
has-true
"match on boolean missing false case"
"match on boolean missing true case")))))
(when
(and
has-nil
(not has-wildcard)
(not has-else)
(= (len patterns) 1))
(set!
warnings
(append
warnings
(list "match checks nil but has no non-nil pattern"))))
warnings)))
(define
sf-implement
(fn
@@ -2124,13 +2194,44 @@
(not (nil? (get (get proto "impls") (type-of value)))))))))
(define
step-sf-callcc
check-match-exhaustiveness
(fn
(args env kont)
(make-cek-state
(first args)
env
(kont-push (make-callcc-frame env) kont))))
(clauses)
(let
((warnings (list))
(patterns (map (fn (c) (first c)) clauses))
(has-wildcard
(some
(fn
(p)
(and (symbol? p) (not (= p true)) (not (= p false))))
patterns))
(has-else (some (fn (p) (= p :else)) patterns))
(has-true (some (fn (p) (= p true)) patterns))
(has-false (some (fn (p) (= p false)) patterns)))
(when
(and (not has-wildcard) (not has-else))
(set!
warnings
(append
warnings
(list "match may be non-exhaustive (no wildcard or :else pattern)"))))
(when
(and
(or has-true has-false)
(not (and has-true has-false))
(not has-wildcard)
(not has-else))
(set!
warnings
(append
warnings
(list
(if
has-true
"match on boolean missing false case"
"match on boolean missing true case")))))
warnings)))
(define
match-find-clause
@@ -2234,6 +2335,14 @@
env
(kont-push (make-handler-frame handlers (rest body) env) kont))))))
;; ═══════════════════════════════════════════════════════════════
;; Part 10: Continue Phase — Frame Dispatch
;;
;; When phase="continue", pop the top frame and process the value.
;; Each frame type has its own handling: if frames check truthiness,
;; let frames bind the value, arg frames accumulate it, etc.
;; continue-with-call handles the final function/component dispatch.
;; ═══════════════════════════════════════════════════════════════
(define
step-sf-restart-case
(fn
@@ -2258,6 +2367,9 @@
env
(kont-push (make-restart-frame restarts (list) env) kont)))))
;; Final call dispatch from arg frame — all args evaluated, invoke function.
;; Handles: lambda (bind params + TCO), component (keyword args + TCO),
;; native fn (direct call), continuation (resume), callcc continuation (escape).
(define
step-sf-signal
(fn
@@ -2275,14 +2387,6 @@
(list condition)
(kont-push (make-signal-return-frame env kont) kont))))))
;; ═══════════════════════════════════════════════════════════════
;; Part 10: Continue Phase — Frame Dispatch
;;
;; When phase="continue", pop the top frame and process the value.
;; Each frame type has its own handling: if frames check truthiness,
;; let frames bind the value, arg frames accumulate it, etc.
;; continue-with-call handles the final function/component dispatch.
;; ═══════════════════════════════════════════════════════════════
(define
step-sf-invoke-restart
(fn
@@ -2311,9 +2415,13 @@
(env-bind! restart-env (first params) restart-arg))
(make-cek-state body restart-env rest-kont)))))))
;; Final call dispatch from arg frame — all args evaluated, invoke function.
;; Handles: lambda (bind params + TCO), component (keyword args + TCO),
;; native fn (direct call), continuation (resume), callcc continuation (escape).
;; ═══════════════════════════════════════════════════════════════
;; Part 11: Entry Points
;;
;; eval-expr-cek / trampoline-cek: CEK evaluation entry points.
;; eval-expr / trampoline: top-level bindings that override the
;; forward declarations from Part 5.
;; ═══════════════════════════════════════════════════════════════
(define
step-sf-if
(fn
@@ -2337,13 +2445,6 @@
env
(kont-push (make-when-frame (rest args) env) kont))))
;; ═══════════════════════════════════════════════════════════════
;; Part 11: Entry Points
;;
;; eval-expr-cek / trampoline-cek: CEK evaluation entry points.
;; eval-expr / trampoline: top-level bindings that override the
;; forward declarations from Part 5.
;; ═══════════════════════════════════════════════════════════════
(define
step-sf-begin
(fn