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

@@ -32,7 +32,7 @@
;; Each descriptor is a dict with "event" and "modifiers" keys.
(define parse-time
(fn (s)
(fn ((s :as string))
;; Parse time string: "2s" → 2000, "500ms" → 500
;; Uses nested if (not cond) because cond misclassifies 2-element
;; function calls like (nil? s) as scheme-style ((test body)) clauses.
@@ -43,7 +43,7 @@
(define parse-trigger-spec
(fn (spec)
(fn ((spec :as string))
;; Parse "click delay:500ms once,change" → list of trigger descriptors
(if (nil? spec)
nil
@@ -51,7 +51,7 @@
(filter
(fn (x) (not (nil? x)))
(map
(fn (part)
(fn ((part :as string))
(let ((tokens (split (trim part) " ")))
(if (empty? tokens)
nil
@@ -63,7 +63,7 @@
;; Normal trigger with optional modifiers
(let ((mods (dict)))
(for-each
(fn (tok)
(fn ((tok :as string))
(cond
(= tok "once")
(dict-set! mods "once" true)
@@ -81,7 +81,7 @@
(define default-trigger
(fn (tag-name)
(fn ((tag-name :as string))
;; Default trigger for element type
(cond
(= tag-name "FORM")
@@ -102,7 +102,7 @@
(fn (el)
;; Check element for sx-get, sx-post, etc. Returns (dict "method" "url") or nil.
(some
(fn (verb)
(fn ((verb :as string))
(let ((url (dom-get-attr el (str "sx-" verb))))
(if url
(dict "method" (upper verb) "url" url)
@@ -115,7 +115,7 @@
;; --------------------------------------------------------------------------
(define build-request-headers
(fn (el loaded-components css-hash)
(fn (el (loaded-components :as list) (css-hash :as string))
;; Build the SX request headers dict
(let ((headers (dict
"SX-Request" "true"
@@ -140,7 +140,7 @@
(let ((parsed (parse-header-value extra-h)))
(when parsed
(for-each
(fn (key) (dict-set! headers key (str (get parsed key))))
(fn ((key :as string)) (dict-set! headers key (str (get parsed key))))
(keys parsed))))))
headers)))
@@ -175,13 +175,13 @@
;; --------------------------------------------------------------------------
(define parse-swap-spec
(fn (raw-swap global-transitions?)
(fn ((raw-swap :as string) (global-transitions? :as boolean))
;; Parse "innerHTML transition:true" → dict with style + transition flag
(let ((parts (split (or raw-swap DEFAULT_SWAP) " "))
(style (first parts))
(use-transition global-transitions?))
(for-each
(fn (p)
(fn ((p :as string))
(cond
(= p "transition:true") (set! use-transition true)
(= p "transition:false") (set! use-transition false)))
@@ -194,7 +194,7 @@
;; --------------------------------------------------------------------------
(define parse-retry-spec
(fn (retry-attr)
(fn ((retry-attr :as string))
;; Parse "exponential:1000:30000" → spec dict or nil
(if (nil? retry-attr)
nil
@@ -206,7 +206,7 @@
(define next-retry-ms
(fn (current-ms cap-ms)
(fn ((current-ms :as number) (cap-ms :as number))
;; Exponential backoff: double current, cap at max
(min (* current-ms 2) cap-ms)))
@@ -216,7 +216,7 @@
;; --------------------------------------------------------------------------
(define filter-params
(fn (params-spec all-params)
(fn ((params-spec :as string) (all-params :as list))
;; Filter form parameters by sx-params spec.
;; all-params is a list of (key value) pairs.
;; Returns filtered list of (key value) pairs.
@@ -227,11 +227,11 @@
(if (starts-with? params-spec "not ")
(let ((excluded (map trim (split (slice params-spec 4) ","))))
(filter
(fn (p) (not (contains? excluded (first p))))
(fn ((p :as list)) (not (contains? excluded (first p))))
all-params))
(let ((allowed (map trim (split params-spec ","))))
(filter
(fn (p) (contains? allowed (first p)))
(fn ((p :as list)) (contains? allowed (first p)))
all-params))))))))
@@ -279,7 +279,7 @@
(define revert-optimistic
(fn (state)
(fn ((state :as dict))
;; Revert an optimistic update
(when state
(let ((target (get state "target"))
@@ -305,7 +305,7 @@
;; Returns list of (dict "element" el "swap-type" type "target-id" id).
(let ((results (list)))
(for-each
(fn (attr)
(fn ((attr :as string))
(let ((oob-els (dom-query-all container (str "[" attr "]"))))
(for-each
(fn (oob)
@@ -380,7 +380,7 @@
(reactive-attrs (if (empty? ra-str) (list) (split ra-str ","))))
;; Add/update attributes from new, skip reactive ones
(for-each
(fn (attr)
(fn ((attr :as list))
(let ((name (first attr))
(val (nth attr 1)))
(when (and (not (= (dom-get-attr old-el name) val))
@@ -389,7 +389,7 @@
(dom-attr-list new-el))
;; Remove attributes not in new, skip reactive + marker attrs
(for-each
(fn (attr)
(fn ((attr :as list))
(let ((aname (first attr)))
(when (and (not (dom-has-attr? new-el aname))
(not (contains? reactive-attrs aname))
@@ -406,7 +406,7 @@
(new-kids (dom-child-list new-parent))
;; Build ID map of old children for keyed matching
(old-by-id (reduce
(fn (acc kid)
(fn ((acc :as dict) kid)
(let ((id (dom-id kid)))
(if id (do (dict-set! acc id kid) acc) acc)))
(dict) old-kids))
@@ -447,7 +447,7 @@
;; Remove leftover old children
(for-each
(fn (i)
(fn ((i :as number))
(when (>= i oi)
(let ((leftover (nth old-kids i)))
(when (and (dom-is-child-of? leftover old-parent)
@@ -577,7 +577,7 @@
;; --------------------------------------------------------------------------
(define swap-dom-nodes
(fn (target new-nodes strategy)
(fn (target new-nodes (strategy :as string))
;; Execute a swap strategy on live DOM nodes.
;; new-nodes is typically a DocumentFragment or Element.
(case strategy
@@ -644,7 +644,7 @@
;; --------------------------------------------------------------------------
(define swap-html-string
(fn (target html strategy)
(fn (target (html :as string) (strategy :as string))
;; Execute a swap strategy using an HTML string (DOMParser pipeline).
(case strategy
"innerHTML"
@@ -675,7 +675,7 @@
;; --------------------------------------------------------------------------
(define handle-history
(fn (el url resp-headers)
(fn (el (url :as string) (resp-headers :as dict))
;; Process history push/replace based on element attrs and response headers
(let ((push-url (dom-get-attr el "sx-push-url"))
(replace-url (dom-get-attr el "sx-replace-url"))
@@ -701,7 +701,7 @@
(define PRELOAD_TTL 30000) ;; 30 seconds
(define preload-cache-get
(fn (cache url)
(fn ((cache :as dict) (url :as string))
;; Get and consume a cached preload response.
;; Returns (dict "text" ... "content-type" ...) or nil.
(let ((entry (dict-get cache url)))
@@ -713,7 +713,7 @@
(define preload-cache-set
(fn (cache url text content-type)
(fn ((cache :as dict) (url :as string) (text :as string) (content-type :as string))
;; Store a preloaded response
(dict-set! cache url
(dict "text" text "content-type" content-type "timestamp" (now-ms)))))
@@ -726,7 +726,7 @@
;; This is the logic; actual browser event binding is platform interface.
(define classify-trigger
(fn (trigger)
(fn ((trigger :as dict))
;; Classify a parsed trigger descriptor for binding.
;; Returns one of: "poll", "intersect", "load", "revealed", "event"
(let ((event (get trigger "event")))