Add (param :as type) annotations to all fn/lambda params across SX spec

Extend the type annotation system from defcomp-only to fn/lambda params:
- Infrastructure: sf-lambda, py/js-collect-params-loop, and bootstrap_py.py
  now recognize (name :as type) in param lists, extracting just the name
- bootstrap_py.py: add _extract_param_name() helper, fix _emit_for_each_stmt
- 521 type annotations across 22 .sx spec files (eval, types, adapters,
  transpilers, engine, orchestration, deps, signals, router, prove, etc.)
- Zero behavioral change: annotations are metadata for static analysis only
- All bootstrappers (Python, JS, G1) pass, 81/81 spec tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 20:27:36 +00:00
parent c82941d93c
commit b99e69d1bb
23 changed files with 532 additions and 498 deletions

View File

@@ -72,7 +72,7 @@
;; --------------------------------------------------------------------------
(define reset!
(fn (s value)
(fn ((s :as signal) value)
(when (signal? s)
(let ((old (signal-value s)))
(when (not (identical? old value))
@@ -85,7 +85,7 @@
;; --------------------------------------------------------------------------
(define swap!
(fn (s f &rest args)
(fn ((s :as signal) (f :as lambda) &rest args)
(when (signal? s)
(let ((old (signal-value s))
(new-val (apply f (cons old args))))
@@ -103,7 +103,7 @@
;; by tracking deref calls during evaluation.
(define computed
(fn (compute-fn)
(fn ((compute-fn :as lambda))
(let ((s (make-signal nil))
(deps (list))
(compute-ctx nil))
@@ -113,7 +113,7 @@
(fn ()
;; Unsubscribe from old deps
(for-each
(fn (dep) (signal-remove-sub! dep recompute))
(fn ((dep :as signal)) (signal-remove-sub! dep recompute))
(signal-deps s))
(signal-set-deps! s (list))
@@ -146,7 +146,7 @@
;; function that tears down the effect.
(define effect
(fn (effect-fn)
(fn ((effect-fn :as lambda))
(let ((deps (list))
(disposed false)
(cleanup-fn nil))
@@ -159,7 +159,7 @@
;; Unsubscribe from old deps
(for-each
(fn (dep) (signal-remove-sub! dep run-effect))
(fn ((dep :as signal)) (signal-remove-sub! dep run-effect))
deps)
(set! deps (list))
@@ -183,7 +183,7 @@
(set! disposed true)
(when cleanup-fn (invoke cleanup-fn))
(for-each
(fn (dep) (signal-remove-sub! dep run-effect))
(fn ((dep :as signal)) (signal-remove-sub! dep run-effect))
deps)
(set! deps (list)))))
;; Auto-register with island scope so disposal happens on swap
@@ -202,7 +202,7 @@
(define *batch-queue* (list))
(define batch
(fn (thunk)
(fn ((thunk :as lambda))
(set! *batch-depth* (+ *batch-depth* 1))
(invoke thunk)
(set! *batch-depth* (- *batch-depth* 1))
@@ -214,15 +214,15 @@
(let ((seen (list))
(pending (list)))
(for-each
(fn (s)
(fn ((s :as signal))
(for-each
(fn (sub)
(fn ((sub :as lambda))
(when (not (contains? seen sub))
(append! seen sub)
(append! pending sub)))
(signal-subscribers s)))
queue)
(for-each (fn (sub) (sub)) pending))))))
(for-each (fn ((sub :as lambda)) (sub)) pending))))))
;; --------------------------------------------------------------------------
@@ -232,16 +232,16 @@
;; If inside a batch, queues the signal. Otherwise, notifies immediately.
(define notify-subscribers
(fn (s)
(fn ((s :as signal))
(if (> *batch-depth* 0)
(when (not (contains? *batch-queue* s))
(append! *batch-queue* s))
(flush-subscribers s))))
(define flush-subscribers
(fn (s)
(fn ((s :as signal))
(for-each
(fn (sub) (sub))
(fn ((sub :as lambda)) (sub))
(signal-subscribers s))))
@@ -269,10 +269,10 @@
;; For effects, the dispose function is returned by effect itself.
(define dispose-computed
(fn (s)
(fn ((s :as signal))
(when (signal? s)
(for-each
(fn (dep) (signal-remove-sub! dep nil))
(fn ((dep :as signal)) (signal-remove-sub! dep nil))
(signal-deps s))
(signal-set-deps! s (list)))))
@@ -288,7 +288,7 @@
(define *island-scope* nil)
(define with-island-scope
(fn (scope-fn body-fn)
(fn ((scope-fn :as lambda) (body-fn :as lambda))
(let ((prev *island-scope*))
(set! *island-scope* scope-fn)
(let ((result (body-fn)))
@@ -300,7 +300,7 @@
;; *island-scope* is non-nil.
(define register-in-scope
(fn (disposable)
(fn ((disposable :as lambda))
(when *island-scope*
(*island-scope* disposable))))
@@ -323,7 +323,7 @@
;; (dom-get-data el key) → any — retrieve stored value
(define with-marsh-scope
(fn (marsh-el body-fn)
(fn (marsh-el (body-fn :as lambda))
;; Execute body-fn collecting all disposables into a marsh-local list.
;; Nested under the current island scope — if the island is disposed,
;; the marsh is disposed too (because island scope collected the marsh's
@@ -341,7 +341,7 @@
;; Parent island scope and sibling marshes are unaffected.
(let ((disposers (dom-get-data marsh-el "sx-marsh-disposers")))
(when disposers
(for-each (fn (d) (invoke d)) disposers)
(for-each (fn ((d :as lambda)) (invoke d)) disposers)
(dom-set-data marsh-el "sx-marsh-disposers" nil)))))
@@ -359,7 +359,7 @@
(define *store-registry* (dict))
(define def-store
(fn (name init-fn)
(fn ((name :as string) (init-fn :as lambda))
(let ((registry *store-registry*))
;; Only create the store once — subsequent calls return existing
(when (not (has-key? registry name))
@@ -367,7 +367,7 @@
(get *store-registry* name))))
(define use-store
(fn (name)
(fn ((name :as string))
(if (has-key? *store-registry* name)
(get *store-registry* name)
(error (str "Store not found: " name
@@ -402,11 +402,11 @@
;; These are platform primitives because they require browser DOM APIs.
(define emit-event
(fn (el event-name detail)
(fn (el (event-name :as string) detail)
(dom-dispatch el event-name detail)))
(define on-event
(fn (el event-name handler)
(fn (el (event-name :as string) (handler :as lambda))
(dom-listen el event-name handler)))
;; Convenience: create an effect that listens for a DOM event on an
@@ -416,7 +416,7 @@
;; removed automatically via the cleanup return.
(define bridge-event
(fn (el event-name target-signal transform-fn)
(fn (el (event-name :as string) (target-signal :as signal) transform-fn)
(effect (fn ()
(let ((remove (dom-listen el event-name
(fn (e)
@@ -450,7 +450,7 @@
;; (promise-then promise on-resolve on-reject) → void
(define resource
(fn (fetch-fn)
(fn ((fetch-fn :as lambda))
(let ((state (signal (dict "loading" true "data" nil "error" nil))))
;; Kick off the async operation
(promise-then (invoke fetch-fn)