Add typed params to 67 primitives, implement check-primitive-call

Annotate all primitives in primitives.sx with (:as type) param types
where meaningful (67/80 — 13 polymorphic ops stay untyped). Add
parse_primitive_param_types() to boundary_parser.py for extraction.
Implement check-primitive-call in types.sx with full positional + rest
param validation, thread prim-param-types through check-body-walk,
check-component, and check-all. 10 new tests (438 total, all pass).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 18:39:20 +00:00
parent 8a530569a2
commit 5d5512e74a
5 changed files with 398 additions and 81 deletions

View File

@@ -793,8 +793,95 @@ def _load_types(env):
}
env["test-prim-types"] = _test_prim_types
# test-prim-param-types: param type signatures for primitive call checking
def _test_prim_param_types():
# Each entry: {"positional": [["name", "type"|None], ...], "rest-type": "type"|None}
return {
"+": {"positional": [], "rest-type": "number"},
"-": {"positional": [["a", "number"]], "rest-type": "number"},
"*": {"positional": [], "rest-type": "number"},
"/": {"positional": [["a", "number"], ["b", "number"]], "rest-type": None},
"mod": {"positional": [["a", "number"], ["b", "number"]], "rest-type": None},
"sqrt": {"positional": [["x", "number"]], "rest-type": None},
"pow": {"positional": [["x", "number"], ["n", "number"]], "rest-type": None},
"abs": {"positional": [["x", "number"]], "rest-type": None},
"floor": {"positional": [["x", "number"]], "rest-type": None},
"ceil": {"positional": [["x", "number"]], "rest-type": None},
"round": {"positional": [["x", "number"]], "rest-type": "number"},
"min": {"positional": [], "rest-type": "number"},
"max": {"positional": [], "rest-type": "number"},
"clamp": {"positional": [["x", "number"], ["lo", "number"], ["hi", "number"]], "rest-type": None},
"inc": {"positional": [["n", "number"]], "rest-type": None},
"dec": {"positional": [["n", "number"]], "rest-type": None},
"<": {"positional": [["a", "number"], ["b", "number"]], "rest-type": None},
">": {"positional": [["a", "number"], ["b", "number"]], "rest-type": None},
"<=": {"positional": [["a", "number"], ["b", "number"]], "rest-type": None},
">=": {"positional": [["a", "number"], ["b", "number"]], "rest-type": None},
"odd?": {"positional": [["n", "number"]], "rest-type": None},
"even?": {"positional": [["n", "number"]], "rest-type": None},
"zero?": {"positional": [["n", "number"]], "rest-type": None},
"upper": {"positional": [["s", "string"]], "rest-type": None},
"upcase": {"positional": [["s", "string"]], "rest-type": None},
"lower": {"positional": [["s", "string"]], "rest-type": None},
"downcase": {"positional": [["s", "string"]], "rest-type": None},
"string-length": {"positional": [["s", "string"]], "rest-type": None},
"substring": {"positional": [["s", "string"], ["start", "number"], ["end", "number"]], "rest-type": None},
"string-contains?": {"positional": [["s", "string"], ["needle", "string"]], "rest-type": None},
"trim": {"positional": [["s", "string"]], "rest-type": None},
"split": {"positional": [["s", "string"]], "rest-type": "string"},
"join": {"positional": [["sep", "string"], ["coll", "list"]], "rest-type": None},
"replace": {"positional": [["s", "string"], ["old", "string"], ["new", "string"]], "rest-type": None},
"index-of": {"positional": [["s", "string"], ["needle", "string"]], "rest-type": "number"},
"starts-with?": {"positional": [["s", "string"], ["prefix", "string"]], "rest-type": None},
"ends-with?": {"positional": [["s", "string"], ["suffix", "string"]], "rest-type": None},
"concat": {"positional": [], "rest-type": "list"},
"range": {"positional": [["start", "number"], ["end", "number"]], "rest-type": "number"},
"first": {"positional": [["coll", "list"]], "rest-type": None},
"last": {"positional": [["coll", "list"]], "rest-type": None},
"rest": {"positional": [["coll", "list"]], "rest-type": None},
"nth": {"positional": [["coll", "list"], ["n", "number"]], "rest-type": None},
"cons": {"positional": [["x", None], ["coll", "list"]], "rest-type": None},
"append": {"positional": [["coll", "list"]], "rest-type": None},
"append!": {"positional": [["coll", "list"]], "rest-type": None},
"reverse": {"positional": [["coll", "list"]], "rest-type": None},
"flatten": {"positional": [["coll", "list"]], "rest-type": None},
"chunk-every": {"positional": [["coll", "list"], ["n", "number"]], "rest-type": None},
"zip-pairs": {"positional": [["coll", "list"]], "rest-type": None},
"keys": {"positional": [["d", "dict"]], "rest-type": None},
"vals": {"positional": [["d", "dict"]], "rest-type": None},
"merge": {"positional": [], "rest-type": "dict"},
"has-key?": {"positional": [["d", "dict"]], "rest-type": None},
"assoc": {"positional": [["d", "dict"]], "rest-type": None},
"dissoc": {"positional": [["d", "dict"]], "rest-type": None},
"dict-set!": {"positional": [["d", "dict"]], "rest-type": None},
"format-date": {"positional": [["date-str", "string"], ["fmt", "string"]], "rest-type": None},
"format-decimal": {"positional": [["val", "number"]], "rest-type": "number"},
"parse-datetime": {"positional": [["s", "string"]], "rest-type": None},
"pluralize": {"positional": [["count", "number"]], "rest-type": "string"},
"escape": {"positional": [["s", "string"]], "rest-type": None},
"strip-tags": {"positional": [["s", "string"]], "rest-type": None},
"symbol-name": {"positional": [["sym", "symbol"]], "rest-type": None},
"keyword-name": {"positional": [["kw", "keyword"]], "rest-type": None},
"sx-parse": {"positional": [["source", "string"]], "rest-type": None},
}
env["test-prim-param-types"] = _test_prim_param_types
env["test-env"] = lambda: env
# Platform functions needed by types.sx check-body-walk
if "env-get" not in env:
env["env-get"] = lambda e, k: e.get(k) if hasattr(e, 'get') else None
if "env-has?" not in env:
env["env-has?"] = lambda e, k: k in e
if "dict-has?" not in env:
env["dict-has?"] = lambda d, k: k in d if isinstance(d, dict) else False
if "dict-get" not in env:
env["dict-get"] = lambda d, k, *default: d.get(k, default[0] if default else None) if isinstance(d, dict) else (default[0] if default else None)
# types.sx uses component-has-children (no ?), test runner has component-has-children?
if "component-has-children" not in env:
env["component-has-children"] = lambda c: getattr(c, 'has_children', False)
# Try bootstrapped types first, fall back to eval
try:
from shared.sx.ref.sx_ref import (