From b99e69d1bba611e9cecaed0da3954eaeab8bfffd Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 11 Mar 2026 20:27:36 +0000 Subject: [PATCH] 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 --- shared/static/scripts/sx-browser.js | 50 ++++++------ shared/sx/ref/adapter-async.sx | 80 +++++++++---------- shared/sx/ref/adapter-dom.sx | 40 +++++----- shared/sx/ref/adapter-html.sx | 24 +++--- shared/sx/ref/adapter-sx.sx | 18 ++--- shared/sx/ref/boot.sx | 16 ++-- shared/sx/ref/bootstrap_py.py | 25 +++--- shared/sx/ref/continuations.sx | 4 +- shared/sx/ref/deps.sx | 72 +++++++++--------- shared/sx/ref/engine.sx | 54 ++++++------- shared/sx/ref/eval.sx | 101 ++++++++++++------------ shared/sx/ref/forms.sx | 22 +++--- shared/sx/ref/js.sx | 80 ++++++++++--------- shared/sx/ref/orchestration.sx | 100 ++++++++++++------------ shared/sx/ref/page-helpers.sx | 40 +++++----- shared/sx/ref/parser.sx | 8 +- shared/sx/ref/prove.sx | 60 +++++++-------- shared/sx/ref/py.sx | 114 +++++++++++++++------------- shared/sx/ref/render.sx | 20 ++--- shared/sx/ref/router.sx | 16 ++-- shared/sx/ref/signals.sx | 52 ++++++------- shared/sx/ref/sx_ref.py | 10 +-- shared/sx/ref/types.sx | 24 +++--- 23 files changed, 532 insertions(+), 498 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index c043acc..a2c42a5 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-11T17:38:00Z"; + var SX_VERSION = "2026-03-11T20:12:35Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -881,7 +881,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai var paramsExpr = first(args); var bodyExprs = rest(args); var body = (isSxTruthy((len(bodyExprs) == 1)) ? first(bodyExprs) : cons(makeSymbol("begin"), bodyExprs)); - var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p); }, paramsExpr); + var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : (isSxTruthy((isSxTruthy((typeOf(p) == "list")) && isSxTruthy((len(p) == 3)) && isSxTruthy((typeOf(nth(p, 1)) == "keyword")) && (keywordName(nth(p, 1)) == "as"))) ? symbolName(first(p)) : p)); }, paramsExpr); return makeLambda(paramNames, body, env); })(); }; @@ -1200,7 +1200,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai // process-bindings var processBindings = function(bindings, env) { return (function() { var local = envExtend(env); - { var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) { + { var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('pair'), Keyword('as'), Symbol('list')] = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) { (function() { var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair)))); return envSet(local, name, trampoline(evalExpr(nth(pair, 1), local))); @@ -2171,7 +2171,7 @@ return (function() { var tokens = split(trim(part), " "); return (isSxTruthy(isEmpty(tokens)) ? NIL : (isSxTruthy((isSxTruthy((first(tokens) == "every")) && (len(tokens) >= 2))) ? {["event"]: "every", ["modifiers"]: {["interval"]: parseTime(nth(tokens, 1))}} : (function() { var mods = {}; - { var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var tok = _c[_i]; (isSxTruthy((tok == "once")) ? dictSet(mods, "once", true) : (isSxTruthy((tok == "changed")) ? dictSet(mods, "changed", true) : (isSxTruthy(startsWith(tok, "delay:")) ? dictSet(mods, "delay", parseTime(slice(tok, 6))) : (isSxTruthy(startsWith(tok, "from:")) ? dictSet(mods, "from", slice(tok, 5)) : NIL)))); } } + { var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('tok'), Keyword('as'), Symbol('string')] = _c[_i]; (isSxTruthy((tok == "once")) ? dictSet(mods, "once", true) : (isSxTruthy((tok == "changed")) ? dictSet(mods, "changed", true) : (isSxTruthy(startsWith(tok, "delay:")) ? dictSet(mods, "delay", parseTime(slice(tok, 6))) : (isSxTruthy(startsWith(tok, "from:")) ? dictSet(mods, "from", slice(tok, 5)) : NIL)))); } } return {["event"]: first(tokens), ["modifiers"]: mods}; })())); })(); }, rawParts)); @@ -2217,7 +2217,7 @@ return (function() { var parts = split(sxOr(rawSwap, DEFAULT_SWAP), " "); var style = first(parts); var useTransition = globalTransitions_p; - { var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } } + { var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('p'), Keyword('as'), Symbol('string')] = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } } return {["style"]: style, ["transition"]: useTransition}; })(); }; @@ -2270,7 +2270,7 @@ return (function() { // find-oob-swaps var findOobSwaps = function(container) { return (function() { var results = []; - { var _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { + { var _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('attr'), Keyword('as'), Symbol('string')] = _c[_i]; (function() { var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]"))); return forEach(function(oob) { return (function() { var swapType = sxOr(domGetAttr(oob, attr), "outerHTML"); @@ -2289,7 +2289,7 @@ return (function() { var syncAttrs = function(oldEl, newEl) { return (function() { var raStr = sxOr(domGetAttr(oldEl, "data-sx-reactive-attrs"), ""); var reactiveAttrs = (isSxTruthy(isEmpty(raStr)) ? [] : split(raStr, ",")); - { var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { + { var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('attr'), Keyword('as'), Symbol('list')] = _c[_i]; (function() { var name = first(attr); var val = nth(attr, 1); return (isSxTruthy((isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) && !isSxTruthy(contains(reactiveAttrs, name)))) ? domSetAttr(oldEl, name, val) : NIL); @@ -2543,7 +2543,7 @@ return (function() { var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash); var csrf = csrfToken(); if (isSxTruthy(extraParams)) { - { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } } + { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; headers[k] = get(extraParams, k); } } } if (isSxTruthy(ct)) { headers["Content-Type"] = ct; @@ -2784,7 +2784,7 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\" var base = pageName; return (isSxTruthy(sxOr(isNil(params), isEmpty(keys(params)))) ? base : (function() { var parts = []; - { var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } } + { var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } } return (String(base) + String(":") + String(join("&", parts))); })()); })(); }; @@ -2799,7 +2799,7 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\" var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); }; // invalidate-page-cache - var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) { + var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) { _pageDataCache[k] = NIL; } } } swPostMessage({"type": "invalidate", "page": pageName}); @@ -3200,7 +3200,7 @@ return (function() { var comp = envGet(env, fullName); return (isSxTruthy(!isSxTruthy(isComponent(comp))) ? error((String("Unknown component: ") + String(fullName))) : (function() { var callExpr = [makeSymbol(fullName)]; - { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); + { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); callExpr.push(dictGet(kwargs, k)); } } return renderToDom(callExpr, env, NIL); })()); @@ -3280,7 +3280,7 @@ callExpr.push(dictGet(kwargs, k)); } } var kwargs = sxOr(first(sxParse(stateSx)), {}); var disposers = []; var local = envMerge(componentClosure(comp), env); - { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } + { var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('p'), Keyword('as'), Symbol('string')] = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } } return (function() { var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); domSetTextContent(el, ""); @@ -3358,7 +3358,7 @@ callExpr.push(dictGet(kwargs, k)); } } var componentsNeeded = function(pageSource, env) { return (function() { var direct = scanComponentsFromSource(pageSource); var allNeeded = []; - { var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) { + { var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('name'), Keyword('as'), Symbol('string')] = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) { allNeeded.push(name); } (function() { @@ -3378,11 +3378,11 @@ callExpr.push(dictGet(kwargs, k)); } } var pageCssClasses = function(pageSource, env) { return (function() { var needed = componentsNeeded(pageSource, env); var classes = []; - { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() { + { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('name'), Keyword('as'), Symbol('string')] = _c[_i]; (function() { var val = envGet(env, name); return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(cls) { return (isSxTruthy(!isSxTruthy(contains(classes, cls))) ? append_b(classes, cls) : NIL); }, componentCssClasses(val)) : NIL); })(); } } - { var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var cls = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) { + { var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('cls'), Keyword('as'), Symbol('string')] = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) { classes.push(cls); } } } return classes; @@ -3459,7 +3459,7 @@ callExpr.push(dictGet(kwargs, k)); } } var serverList = []; var clientList = []; var ioDeps = []; - { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() { + { var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('name'), Keyword('as'), Symbol('string')] = _c[_i]; (function() { var target = renderTarget(name, env, ioNames); compTargets[name] = target; return (isSxTruthy((target == "server")) ? (append_b(serverList, name), forEach(function(ioRef) { return (isSxTruthy(!isSxTruthy(contains(ioDeps, ioRef))) ? append_b(ioDeps, ioRef) : NIL); }, componentIoRefsCached(name, env, ioNames))) : append_b(clientList, name)); @@ -3484,7 +3484,7 @@ callExpr.push(dictGet(kwargs, k)); } } var result = {}; var items = slice(expr, 2); var n = len(items); - { var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var idx = _c[_i]; if (isSxTruthy((isSxTruthy(((idx + 1) < n)) && (typeOf(nth(items, idx)) == "keyword")))) { + { var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('idx'), Keyword('as'), Symbol('number')] = _c[_i]; if (isSxTruthy((isSxTruthy(((idx + 1) < n)) && (typeOf(nth(items, idx)) == "keyword")))) { (function() { var key = keywordName(nth(items, idx)); var val = nth(items, (idx + 1)); @@ -3524,7 +3524,7 @@ callExpr.push(dictGet(kwargs, k)); } } })()); }, items); }; // build-reference-data - var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/hypermedia/reference/events/", detailKeys, 2)}; if (_m == "js-api") return {"js-api-list": map(function(item) { return {"name": nth(item, 0), "desc": nth(item, 1)}; }, get(rawData, "js-api-list"))}; return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; })(); }; + var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/geography/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/geography/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/geography/hypermedia/reference/events/", detailKeys, 2)}; if (_m == "js-api") return {"js-api-list": map(function(item) { return {"name": nth(item, 0), "desc": nth(item, 1)}; }, get(rawData, "js-api-list"))}; return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3)}; })(); }; // build-attr-detail var buildAttrDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"attr-not-found": true} : {"attr-not-found": NIL, "attr-title": slug, "attr-description": get(detail, "description"), "attr-example": get(detail, "example"), "attr-handler": get(detail, "handler"), "attr-demo": get(detail, "demo"), "attr-wire-id": (isSxTruthy(dictHas(detail, "handler")) ? (String("ref-wire-") + String(replace_(replace_(slug, ":", "-"), "*", "star"))) : NIL)}); }; @@ -3555,7 +3555,7 @@ callExpr.push(dictGet(kwargs, k)); } } // build-bundle-analysis var buildBundleAnalysis = function(pagesRaw, componentsRaw, totalComponents, totalMacros, pureCount, ioCount) { return (function() { var pagesData = []; - { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { + { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('page'), Keyword('as'), Symbol('dict')] = _c[_i]; (function() { var neededNames = get(page, "needed-names"); var n = len(neededNames); var pct = (isSxTruthy((totalComponents > 0)) ? round(((n / totalComponents) * 100)) : 0); @@ -3564,7 +3564,7 @@ callExpr.push(dictGet(kwargs, k)); } } var ioInPage = 0; var pageIoRefs = []; var compDetails = []; - { var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var compName = _c[_i]; (function() { + { var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('compName'), Keyword('as'), Symbol('string')] = _c[_i]; (function() { var info = get(componentsRaw, compName); return (isSxTruthy(!isSxTruthy(isNil(info))) ? ((isSxTruthy(get(info, "is-pure")) ? (pureInPage = (pureInPage + 1)) : ((ioInPage = (ioInPage + 1)), forEach(function(ref) { return (isSxTruthy(!isSxTruthy(some(function(r) { return (r == ref); }, pageIoRefs))) ? append_b(pageIoRefs, ref) : NIL); }, sxOr(get(info, "io-refs"), [])))), append_b(compDetails, {"name": compName, "is-pure": get(info, "is-pure"), "affinity": get(info, "affinity"), "render-target": get(info, "render-target"), "io-refs": sxOr(get(info, "io-refs"), []), "deps": sxOr(get(info, "deps"), []), "source": get(info, "source")})) : NIL); })(); } } @@ -3578,7 +3578,7 @@ callExpr.push(dictGet(kwargs, k)); } } var pagesData = []; var clientCount = 0; var serverCount = 0; - { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() { + { var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('page'), Keyword('as'), Symbol('dict')] = _c[_i]; (function() { var hasData = get(page, "has-data"); var contentSrc = sxOr(get(page, "content-src"), ""); var mode = NIL; @@ -3649,7 +3649,7 @@ callExpr.push(dictGet(kwargs, k)); } } var findMatchingRoute = function(path, routes) { return (function() { var pathSegs = splitPathSegments(path); var result = NIL; - { var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var route = _c[_i]; if (isSxTruthy(isNil(result))) { + { var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('route'), Keyword('as'), Symbol('dict')] = _c[_i]; if (isSxTruthy(isNil(result))) { (function() { var params = matchRouteSegments(pathSegs, get(route, "parsed")); return (isSxTruthy(!isSxTruthy(isNil(params))) ? (function() { @@ -3697,7 +3697,7 @@ callExpr.push(dictGet(kwargs, k)); } } var deps = []; var computeCtx = NIL; return (function() { - var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } } + var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('dep'), Keyword('as'), Symbol('signal')] = _c[_i]; signalRemoveSub(dep, recompute); } } signalSetDeps(s, []); return (function() { var ctx = makeTrackingContext(recompute); @@ -3747,7 +3747,7 @@ return (function() { if (isSxTruthy(cleanupFn)) { invoke(cleanupFn); } -{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } } +{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('dep'), Keyword('as'), Symbol('signal')] = _c[_i]; signalRemoveSub(dep, runEffect); } } return (deps = []); }; registerInScope(disposeFn); return disposeFn; @@ -3771,7 +3771,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { return (function() { var seen = []; var pending = []; - { var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) { + { var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('s'), Keyword('as'), Symbol('signal')] = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('sub'), Keyword('as'), Symbol('lambda')] = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) { seen.push(sub); pending.push(sub); } } } } } diff --git a/shared/sx/ref/adapter-async.sx b/shared/sx/ref/adapter-async.sx index c2f093d..90f7bbd 100644 --- a/shared/sx/ref/adapter-async.sx +++ b/shared/sx/ref/adapter-async.sx @@ -41,7 +41,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render - (fn (expr env ctx) + (fn (expr (env :as dict) ctx) (case (type-of expr) "nil" "" "boolean" "" @@ -57,7 +57,7 @@ (define-async async-render-list - (fn (expr env ctx) + (fn (expr (env :as dict) ctx) (let ((head (first expr))) (if (not (= (type-of head) "symbol")) ;; Non-symbol head — data list, render each item @@ -139,7 +139,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-raw - (fn (args env ctx) + (fn ((args :as list) (env :as dict) ctx) (let ((parts (list))) (for-each (fn (arg) @@ -158,7 +158,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-element - (fn (tag args env ctx) + (fn ((tag :as string) (args :as list) (env :as dict) ctx) (let ((attrs (dict)) (children (list))) ;; Parse keyword attrs and children @@ -186,7 +186,7 @@ ;; compiles inline for-each lambdas as for loops (which can contain await). (define-async async-parse-element-args - (fn (args attrs children env ctx) + (fn ((args :as list) (attrs :as dict) (children :as list) (env :as dict) ctx) (let ((skip false) (i 0)) (for-each @@ -211,7 +211,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-component - (fn (comp args env ctx) + (fn ((comp :as component) (args :as list) (env :as dict) ctx) (let ((kwargs (dict)) (children (list))) ;; Parse keyword args and children @@ -233,7 +233,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-island - (fn (island args env ctx) + (fn (island (args :as list) (env :as dict) ctx) (let ((kwargs (dict)) (children (list))) (async-parse-kw-args args kwargs children env ctx) @@ -262,7 +262,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-lambda - (fn (f args env ctx) + (fn ((f :as lambda) (args :as list) (env :as dict) ctx) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed (fn (i p) (env-set! local p (nth args i))) @@ -275,7 +275,7 @@ ;; -------------------------------------------------------------------------- (define-async async-parse-kw-args - (fn (args kwargs children env ctx) + (fn ((args :as list) (kwargs :as dict) (children :as list) (env :as dict) ctx) (let ((skip false) (i 0)) (for-each @@ -301,7 +301,7 @@ ;; Bootstrapper emits this as: [await async_render(x, env, ctx) for x in exprs] (define-async async-map-render - (fn (exprs env ctx) + (fn ((exprs :as list) (env :as dict) ctx) (let ((results (list))) (for-each (fn (x) (append! results (async-render x env ctx))) @@ -319,7 +319,7 @@ "map" "map-indexed" "filter" "for-each")) (define async-render-form? - (fn (name) + (fn ((name :as string)) (contains? ASYNC_RENDER_FORMS name))) @@ -331,7 +331,7 @@ ;; and eval-cond from render.sx for correct scheme/clojure classification. (define-async dispatch-async-render-form - (fn (name expr env ctx) + (fn ((name :as string) expr (env :as dict) ctx) (cond ;; if (= name "if") @@ -407,7 +407,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-cond-scheme - (fn (clauses env ctx) + (fn ((clauses :as list) (env :as dict) ctx) (if (empty? clauses) "" (let ((clause (first clauses)) @@ -429,7 +429,7 @@ ;; -------------------------------------------------------------------------- (define-async async-render-cond-clojure - (fn (clauses env ctx) + (fn ((clauses :as list) (env :as dict) ctx) (if (< (len clauses) 2) "" (let ((test (first clauses)) @@ -449,7 +449,7 @@ ;; -------------------------------------------------------------------------- (define-async async-process-bindings - (fn (bindings env ctx) + (fn (bindings (env :as dict) ctx) ;; env-extend (not merge) — Env is not a dict subclass, so merge() ;; returns an empty dict, losing all parent scope bindings. (let ((local (env-extend env))) @@ -470,7 +470,7 @@ (define-async async-process-bindings-flat - (fn (bindings local ctx) + (fn ((bindings :as list) (local :as dict) ctx) (let ((skip false) (i 0)) (for-each @@ -495,7 +495,7 @@ ;; -------------------------------------------------------------------------- (define-async async-map-fn-render - (fn (f coll env ctx) + (fn (f (coll :as list) (env :as dict) ctx) (let ((results (list))) (for-each (fn (item) @@ -512,7 +512,7 @@ ;; -------------------------------------------------------------------------- (define-async async-map-indexed-fn-render - (fn (f coll env ctx) + (fn (f (coll :as list) (env :as dict) ctx) (let ((results (list)) (i 0)) (for-each @@ -543,7 +543,7 @@ ;; ========================================================================== (define-async async-aser - (fn (expr env ctx) + (fn (expr (env :as dict) ctx) (case (type-of expr) "number" expr "string" expr @@ -573,7 +573,7 @@ (define-async async-aser-dict - (fn (expr env ctx) + (fn ((expr :as dict) (env :as dict) ctx) (let ((result (dict))) (for-each (fn (key) @@ -587,7 +587,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-list - (fn (expr env ctx) + (fn (expr (env :as dict) ctx) (let ((head (first expr)) (args (rest expr))) (if (not (= (type-of head) "symbol")) @@ -666,7 +666,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-eval-call - (fn (head args env ctx) + (fn (head (args :as list) (env :as dict) ctx) (let ((f (async-eval head env ctx)) (evaled-args (async-eval-args args env ctx))) (cond @@ -694,7 +694,7 @@ ;; -------------------------------------------------------------------------- (define-async async-eval-args - (fn (args env ctx) + (fn ((args :as list) (env :as dict) ctx) (let ((results (list))) (for-each (fn (a) (append! results (async-eval a env ctx))) @@ -707,7 +707,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-map-list - (fn (exprs env ctx) + (fn ((exprs :as list) (env :as dict) ctx) (let ((results (list))) (for-each (fn (x) (append! results (async-aser x env ctx))) @@ -720,7 +720,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-fragment - (fn (children env ctx) + (fn ((children :as list) (env :as dict) ctx) (let ((parts (list))) (for-each (fn (c) @@ -744,7 +744,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-component - (fn (comp args env ctx) + (fn ((comp :as component) (args :as list) (env :as dict) ctx) (let ((kwargs (dict)) (children (list))) (async-parse-aser-kw-args args kwargs children env ctx) @@ -776,7 +776,7 @@ ;; -------------------------------------------------------------------------- (define-async async-parse-aser-kw-args - (fn (args kwargs children env ctx) + (fn ((args :as list) (kwargs :as dict) (children :as list) (env :as dict) ctx) (let ((skip false) (i 0)) (for-each @@ -801,7 +801,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-call - (fn (name args env ctx) + (fn ((name :as string) (args :as list) (env :as dict) ctx) (let ((token (if (or (= name "svg") (= name "math")) (svg-context-set! true) nil)) @@ -859,7 +859,7 @@ (list "map" "map-indexed" "filter" "for-each")) (define async-aser-form? - (fn (name) + (fn ((name :as string)) (or (contains? ASYNC_ASER_FORM_NAMES name) (contains? ASYNC_ASER_HO_NAMES name)))) @@ -871,7 +871,7 @@ ;; Uses cond-scheme? from eval.sx (the FIXED version with every? check). (define-async dispatch-async-aser-form - (fn (name expr env ctx) + (fn ((name :as string) expr (env :as dict) ctx) (let ((args (rest expr))) (cond ;; if @@ -1000,7 +1000,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-cond-scheme - (fn (clauses env ctx) + (fn ((clauses :as list) (env :as dict) ctx) (if (empty? clauses) nil (let ((clause (first clauses)) @@ -1022,7 +1022,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-cond-clojure - (fn (clauses env ctx) + (fn ((clauses :as list) (env :as dict) ctx) (if (< (len clauses) 2) nil (let ((test (first clauses)) @@ -1042,7 +1042,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-case-loop - (fn (match-val clauses env ctx) + (fn (match-val (clauses :as list) (env :as dict) ctx) (if (< (len clauses) 2) nil (let ((test (first clauses)) @@ -1062,7 +1062,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-thread-first - (fn (args env ctx) + (fn ((args :as list) (env :as dict) ctx) (let ((result (async-eval (first args) env ctx))) (for-each (fn (form) @@ -1082,7 +1082,7 @@ ;; -------------------------------------------------------------------------- (define-async async-invoke-or-lambda - (fn (f args env ctx) + (fn (f (args :as list) (env :as dict) ctx) (cond (and (callable? f) (not (lambda? f)) (not (component? f))) (let ((r (apply f args))) @@ -1104,7 +1104,7 @@ ;; -------------------------------------------------------------------------- (define-async async-aser-ho-map - (fn (args env ctx) + (fn ((args :as list) (env :as dict) ctx) (let ((f (async-eval (first args) env ctx)) (coll (async-eval (nth args 1) env ctx)) (results (list))) @@ -1120,7 +1120,7 @@ (define-async async-aser-ho-map-indexed - (fn (args env ctx) + (fn ((args :as list) (env :as dict) ctx) (let ((f (async-eval (first args) env ctx)) (coll (async-eval (nth args 1) env ctx)) (results (list)) @@ -1139,7 +1139,7 @@ (define-async async-aser-ho-for-each - (fn (args env ctx) + (fn ((args :as list) (env :as dict) ctx) (let ((f (async-eval (first args) env ctx)) (coll (async-eval (nth args 1) env ctx)) (results (list))) @@ -1170,7 +1170,7 @@ ;; (set-expand-components!) — enable component expansion context var (define-async async-eval-slot-inner - (fn (expr env ctx) + (fn (expr (env :as dict) ctx) ;; NOTE: Uses statement-form let + set! to avoid expression-context ;; let (IIFE lambdas) which can't contain await in Python. (let ((result nil)) @@ -1196,7 +1196,7 @@ (define-async async-maybe-expand-result - (fn (result env ctx) + (fn (result (env :as dict) ctx) ;; If the aser result is a component call string like "(~foo ...)", ;; re-parse and expand it. This handles indirect component references ;; (e.g. a let binding that evaluates to a component call). diff --git a/shared/sx/ref/adapter-dom.sx b/shared/sx/ref/adapter-dom.sx index e659b99..8eefa2f 100644 --- a/shared/sx/ref/adapter-dom.sx +++ b/shared/sx/ref/adapter-dom.sx @@ -19,7 +19,7 @@ ;; -------------------------------------------------------------------------- (define render-to-dom - (fn (expr env ns) + (fn (expr (env :as dict) ns) (set-render-active! true) (case (type-of expr) ;; nil / boolean false / boolean true → empty fragment @@ -67,7 +67,7 @@ ;; -------------------------------------------------------------------------- (define render-dom-list - (fn (expr env ns) + (fn (expr (env :as dict) ns) (let ((head (first expr))) (cond ;; Symbol head — dispatch on name @@ -166,7 +166,7 @@ ;; -------------------------------------------------------------------------- (define render-dom-element - (fn (tag args env ns) + (fn ((tag :as string) (args :as list) (env :as dict) ns) ;; Detect namespace from tag (let ((new-ns (cond (= tag "svg") SVG_NS (= tag "math") MATH_NS @@ -237,7 +237,7 @@ ;; -------------------------------------------------------------------------- (define render-dom-component - (fn (comp args env ns) + (fn ((comp :as component) (args :as list) (env :as dict) ns) ;; Parse kwargs and children, bind into component env, render body. (let ((kwargs (dict)) (children (list))) @@ -284,7 +284,7 @@ ;; -------------------------------------------------------------------------- (define render-dom-fragment - (fn (args env ns) + (fn ((args :as list) (env :as dict) ns) (let ((frag (create-fragment))) (for-each (fn (x) (dom-append frag (render-to-dom x env ns))) @@ -297,7 +297,7 @@ ;; -------------------------------------------------------------------------- (define render-dom-raw - (fn (args env) + (fn ((args :as list) (env :as dict)) (let ((frag (create-fragment))) (for-each (fn (arg) @@ -318,7 +318,7 @@ ;; -------------------------------------------------------------------------- (define render-dom-unknown-component - (fn (name) + (fn ((name :as string)) (error (str "Unknown component: " name)))) @@ -335,11 +335,11 @@ "error-boundary")) (define render-dom-form? - (fn (name) + (fn ((name :as string)) (contains? RENDER_DOM_FORMS name))) (define dispatch-render-form - (fn (name expr env ns) + (fn ((name :as string) expr (env :as dict) ns) (cond ;; if — reactive inside islands (re-renders when signal deps change) (= name "if") @@ -581,7 +581,7 @@ ;; -------------------------------------------------------------------------- (define render-lambda-dom - (fn (f args env ns) + (fn ((f :as lambda) (args :as list) (env :as dict) ns) ;; Bind lambda params and render body as DOM (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed @@ -605,7 +605,7 @@ ;; - Conditional fragments: (when (deref sig) ...) → reactive show/hide (define render-dom-island - (fn (island args env ns) + (fn (island (args :as list) (env :as dict) ns) ;; Parse kwargs and children (same as component) (let ((kwargs (dict)) (children (list))) @@ -679,7 +679,7 @@ ;; Supports :tag keyword to change wrapper element (default "div"). (define render-dom-lake - (fn (args env ns) + (fn ((args :as list) (env :as dict) ns) (let ((lake-id nil) (lake-tag "div") (children (list))) @@ -723,7 +723,7 @@ ;; Stores the island env and transform on the element for morph retrieval. (define render-dom-marsh - (fn (args env ns) + (fn ((args :as list) (env :as dict) ns) (let ((marsh-id nil) (marsh-tag "div") (marsh-transform nil) @@ -781,7 +781,7 @@ ;; Marks the attribute name on the element via data-sx-reactive-attrs so ;; the morph algorithm knows not to overwrite it with server content. (define reactive-attr - (fn (el attr-name compute-fn) + (fn (el (attr-name :as string) compute-fn) ;; Mark this attribute as reactively managed (let ((existing (or (dom-get-attr el "data-sx-reactive-attrs") "")) (updated (if (empty? existing) attr-name (str existing "," attr-name)))) @@ -802,7 +802,7 @@ ;; reactive-fragment — conditionally render a fragment based on a signal ;; Used for (when (deref sig) ...) or (if (deref sig) ...) inside an island. (define reactive-fragment - (fn (test-fn render-fn env ns) + (fn (test-fn render-fn (env :as dict) ns) (let ((marker (create-comment "island-fragment")) (current-nodes (list))) (effect (fn () @@ -824,13 +824,13 @@ ;; and reorderings touch the DOM. Without keys, falls back to clear+rerender. (define render-list-item - (fn (map-fn item env ns) + (fn (map-fn item (env :as dict) ns) (if (lambda? map-fn) (render-lambda-dom map-fn (list item) env ns) (render-to-dom (apply map-fn (list item)) env ns)))) (define extract-key - (fn (node index) + (fn (node (index :as number)) ;; Extract key from rendered node: :key attr, data-key, or index fallback (let ((k (dom-get-attr node "key"))) (if k @@ -839,7 +839,7 @@ (if dk (str dk) (str "__idx_" index))))))) (define reactive-list - (fn (map-fn items-sig env ns) + (fn (map-fn items-sig (env :as dict) ns) (let ((container (create-fragment)) (marker (create-comment "island-list")) (key-map (dict)) @@ -960,7 +960,7 @@ ;; teardown. (define render-dom-portal - (fn (args env ns) + (fn ((args :as list) (env :as dict) ns) (let ((selector (trampoline (eval-expr (first args) env))) (target (or (dom-query selector) (dom-ensure-element selector)))) @@ -1000,7 +1000,7 @@ ;; Calling (retry) re-renders the body, replacing the fallback. (define render-dom-error-boundary - (fn (args env ns) + (fn ((args :as list) (env :as dict) ns) (let ((fallback-expr (first args)) (body-exprs (rest args)) (container (dom-create-element "div" nil)) diff --git a/shared/sx/ref/adapter-html.sx b/shared/sx/ref/adapter-html.sx index f4719e2..306baaf 100644 --- a/shared/sx/ref/adapter-html.sx +++ b/shared/sx/ref/adapter-html.sx @@ -14,7 +14,7 @@ (define render-to-html - (fn (expr env) + (fn (expr (env :as dict)) (set-render-active! true) (case (type-of expr) ;; Literals — render directly @@ -34,7 +34,7 @@ :else (render-value-to-html (trampoline (eval-expr expr env)) env)))) (define render-value-to-html - (fn (val env) + (fn (val (env :as dict)) (case (type-of val) "nil" "" "string" (escape-html val) @@ -55,7 +55,7 @@ "map" "map-indexed" "filter" "for-each")) (define render-html-form? - (fn (name) + (fn ((name :as string)) (contains? RENDER_HTML_FORMS name))) @@ -64,7 +64,7 @@ ;; -------------------------------------------------------------------------- (define render-list-to-html - (fn (expr env) + (fn ((expr :as list) (env :as dict)) (if (empty? expr) "" (let ((head (first expr))) @@ -135,7 +135,7 @@ ;; -------------------------------------------------------------------------- (define dispatch-html-form - (fn (name expr env) + (fn ((name :as string) (expr :as list) (env :as dict)) (cond ;; if (= name "if") @@ -235,7 +235,7 @@ ;; -------------------------------------------------------------------------- (define render-lambda-html - (fn (f args env) + (fn ((f :as lambda) (args :as list) (env :as dict)) (let ((local (env-merge (lambda-closure f) env))) (for-each-indexed (fn (i p) @@ -249,7 +249,7 @@ ;; -------------------------------------------------------------------------- (define render-html-component - (fn (comp args env) + (fn ((comp :as component) (args :as list) (env :as dict)) ;; Expand component and render body through HTML adapter. ;; Component body contains rendering forms (HTML tags) that only the ;; adapter understands, so expansion must happen here, not in eval-expr. @@ -288,7 +288,7 @@ (define render-html-element - (fn (tag args env) + (fn ((tag :as string) (args :as list) (env :as dict)) (let ((parsed (parse-element-args args env)) (attrs (first parsed)) (children (nth parsed 1)) @@ -312,7 +312,7 @@ ;; content while preserving surrounding reactive DOM. (define render-html-lake - (fn (args env) + (fn ((args :as list) (env :as dict)) (let ((lake-id nil) (lake-tag "div") (children (list))) @@ -351,7 +351,7 @@ ;; the :transform is a client-only concern. (define render-html-marsh - (fn (args env) + (fn ((args :as list) (env :as dict)) (let ((marsh-id nil) (marsh-tag "div") (children (list))) @@ -394,7 +394,7 @@ ;; (swap! s f) → no-op (define render-html-island - (fn (island args env) + (fn ((island :as island) (args :as list) (env :as dict)) ;; Parse kwargs and children (same pattern as render-html-component) (let ((kwargs (dict)) (children (list))) @@ -452,7 +452,7 @@ ;; Handles all SX types natively: numbers, strings, booleans, nil, lists, dicts. (define serialize-island-state - (fn (kwargs) + (fn ((kwargs :as dict)) (if (empty-dict? kwargs) nil (sx-serialize kwargs)))) diff --git a/shared/sx/ref/adapter-sx.sx b/shared/sx/ref/adapter-sx.sx index 5bc388f..2979ecb 100644 --- a/shared/sx/ref/adapter-sx.sx +++ b/shared/sx/ref/adapter-sx.sx @@ -12,7 +12,7 @@ (define render-to-sx - (fn (expr env) + (fn (expr (env :as dict)) (let ((result (aser expr env))) ;; aser-call already returns serialized SX strings; ;; only serialize non-string values @@ -21,7 +21,7 @@ (serialize result))))) (define aser - (fn (expr env) + (fn (expr (env :as dict)) ;; Evaluate for SX wire format — serialize rendering forms, ;; evaluate control flow and function calls. (set-render-active! true) @@ -52,7 +52,7 @@ (define aser-list - (fn (expr env) + (fn ((expr :as list) (env :as dict)) (let ((head (first expr)) (args (rest expr))) (if (not (= (type-of head) "symbol")) @@ -104,7 +104,7 @@ (define aser-fragment - (fn (children env) + (fn ((children :as list) (env :as dict)) ;; Serialize (<> child1 child2 ...) to sx source string ;; Must flatten list results (e.g. from map/filter) to avoid nested parens (let ((parts (list))) @@ -126,7 +126,7 @@ (define aser-call - (fn (name args env) + (fn ((name :as string) (args :as list) (env :as dict)) ;; Serialize (name :key val child ...) — evaluate args but keep as sx ;; Uses for-each + mutable state (not reduce) so bootstrapper emits for-loops ;; that can contain nested for-each for list flattening. @@ -177,11 +177,11 @@ "some" "every?" "for-each")) (define special-form? - (fn (name) + (fn ((name :as string)) (contains? SPECIAL_FORM_NAMES name))) (define ho-form? - (fn (name) + (fn ((name :as string)) (contains? HO_FORM_NAMES name))) @@ -194,7 +194,7 @@ ;; Definition forms evaluate for side effects and return nil. (define aser-special - (fn (name expr env) + (fn ((name :as string) (expr :as list) (env :as dict)) (let ((args (rest expr))) (cond ;; if — evaluate condition, aser chosen branch @@ -314,7 +314,7 @@ ;; Helper: case dispatch for aser mode (define eval-case-aser - (fn (match-val clauses env) + (fn (match-val (clauses :as list) (env :as dict)) (if (< (len clauses) 2) nil (let ((test (first clauses)) diff --git a/shared/sx/ref/boot.sx b/shared/sx/ref/boot.sx index 30a2e63..494bbf4 100644 --- a/shared/sx/ref/boot.sx +++ b/shared/sx/ref/boot.sx @@ -72,7 +72,7 @@ ;; -------------------------------------------------------------------------- (define sx-mount - (fn (target source extra-env) + (fn (target (source :as string) (extra-env :as dict)) ;; Render SX source string into target element. ;; target: Element or CSS selector string ;; source: SX source string @@ -101,7 +101,7 @@ ;; new SX content, and replaces the wrapper's children. (define resolve-suspense - (fn (id sx) + (fn ((id :as string) (sx :as string)) ;; Process any new