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

@@ -15,6 +15,15 @@
;; :doc "description"
;; :body (reference-implementation ...))
;;
;; Typed params use (name :as type) syntax:
;; (define-primitive "+"
;; :params (&rest (args :as number))
;; :returns "number"
;; :doc "Sum all arguments.")
;;
;; Untyped params default to `any`. Typed params enable the gradual
;; type checker (types.sx) to catch mistyped primitive calls.
;;
;; The :body is optional — when provided, it gives a reference
;; implementation in SX that bootstrap compilers MAY use for testing
;; or as a fallback. Most targets will implement natively for performance.
@@ -32,89 +41,89 @@
(define-module :core.arithmetic)
(define-primitive "+"
:params (&rest args)
:params (&rest (args :as number))
:returns "number"
:doc "Sum all arguments."
:body (reduce (fn (a b) (native-add a b)) 0 args))
(define-primitive "-"
:params (a &rest b)
:params ((a :as number) &rest (b :as number))
:returns "number"
:doc "Subtract. Unary: negate. Binary: a - b."
:body (if (empty? b) (native-neg a) (native-sub a (first b))))
(define-primitive "*"
:params (&rest args)
:params (&rest (args :as number))
:returns "number"
:doc "Multiply all arguments."
:body (reduce (fn (a b) (native-mul a b)) 1 args))
(define-primitive "/"
:params (a b)
:params ((a :as number) (b :as number))
:returns "number"
:doc "Divide a by b."
:body (native-div a b))
(define-primitive "mod"
:params (a b)
:params ((a :as number) (b :as number))
:returns "number"
:doc "Modulo a % b."
:body (native-mod a b))
(define-primitive "sqrt"
:params (x)
:params ((x :as number))
:returns "number"
:doc "Square root.")
(define-primitive "pow"
:params (x n)
:params ((x :as number) (n :as number))
:returns "number"
:doc "x raised to power n.")
(define-primitive "abs"
:params (x)
:params ((x :as number))
:returns "number"
:doc "Absolute value.")
(define-primitive "floor"
:params (x)
:params ((x :as number))
:returns "number"
:doc "Floor to integer.")
(define-primitive "ceil"
:params (x)
:params ((x :as number))
:returns "number"
:doc "Ceiling to integer.")
(define-primitive "round"
:params (x &rest ndigits)
:params ((x :as number) &rest (ndigits :as number))
:returns "number"
:doc "Round to ndigits decimal places (default 0).")
(define-primitive "min"
:params (&rest args)
:params (&rest (args :as number))
:returns "number"
:doc "Minimum. Single list arg or variadic.")
(define-primitive "max"
:params (&rest args)
:params (&rest (args :as number))
:returns "number"
:doc "Maximum. Single list arg or variadic.")
(define-primitive "clamp"
:params (x lo hi)
:params ((x :as number) (lo :as number) (hi :as number))
:returns "number"
:doc "Clamp x to range [lo, hi]."
:body (max lo (min hi x)))
(define-primitive "inc"
:params (n)
:params ((n :as number))
:returns "number"
:doc "Increment by 1."
:body (+ n 1))
(define-primitive "dec"
:params (n)
:params ((n :as number))
:returns "number"
:doc "Decrement by 1."
:body (- n 1))
@@ -159,22 +168,22 @@
Same semantics as = but explicit Scheme name.")
(define-primitive "<"
:params (a b)
:params ((a :as number) (b :as number))
:returns "boolean"
:doc "Less than.")
(define-primitive ">"
:params (a b)
:params ((a :as number) (b :as number))
:returns "boolean"
:doc "Greater than.")
(define-primitive "<="
:params (a b)
:params ((a :as number) (b :as number))
:returns "boolean"
:doc "Less than or equal.")
(define-primitive ">="
:params (a b)
:params ((a :as number) (b :as number))
:returns "boolean"
:doc "Greater than or equal.")
@@ -186,19 +195,19 @@
(define-module :core.predicates)
(define-primitive "odd?"
:params (n)
:params ((n :as number))
:returns "boolean"
:doc "True if n is odd."
:body (= (mod n 2) 1))
(define-primitive "even?"
:params (n)
:params ((n :as number))
:returns "boolean"
:doc "True if n is even."
:body (= (mod n 2) 0))
(define-primitive "zero?"
:params (n)
:params ((n :as number))
:returns "boolean"
:doc "True if n is zero."
:body (= n 0))
@@ -274,82 +283,82 @@
:doc "Concatenate all args as strings. nil → empty string, bool → true/false.")
(define-primitive "concat"
:params (&rest colls)
:params (&rest (colls :as list))
:returns "list"
:doc "Concatenate multiple lists into one. Skips nil values.")
(define-primitive "upper"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Uppercase string.")
(define-primitive "upcase"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Alias for upper. Uppercase string.")
(define-primitive "lower"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Lowercase string.")
(define-primitive "downcase"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Alias for lower. Lowercase string.")
(define-primitive "string-length"
:params (s)
:params ((s :as string))
:returns "number"
:doc "Length of string in characters.")
(define-primitive "substring"
:params (s start end)
:params ((s :as string) (start :as number) (end :as number))
:returns "string"
:doc "Extract substring from start (inclusive) to end (exclusive).")
(define-primitive "string-contains?"
:params (s needle)
:params ((s :as string) (needle :as string))
:returns "boolean"
:doc "True if string s contains substring needle.")
(define-primitive "trim"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Strip leading/trailing whitespace.")
(define-primitive "split"
:params (s &rest sep)
:params ((s :as string) &rest (sep :as string))
:returns "list"
:doc "Split string by separator (default space).")
(define-primitive "join"
:params (sep coll)
:params ((sep :as string) (coll :as list))
:returns "string"
:doc "Join collection items with separator string.")
(define-primitive "replace"
:params (s old new)
:params ((s :as string) (old :as string) (new :as string))
:returns "string"
:doc "Replace all occurrences of old with new in s.")
(define-primitive "slice"
:params (coll start &rest end)
:params (coll (start :as number) &rest (end :as number))
:returns "any"
:doc "Slice a string or list from start to end (exclusive). End is optional.")
(define-primitive "index-of"
:params (s needle &rest from)
:params ((s :as string) (needle :as string) &rest (from :as number))
:returns "number"
:doc "Index of first occurrence of needle in s, or -1 if not found. Optional start index.")
(define-primitive "starts-with?"
:params (s prefix)
:params ((s :as string) (prefix :as string))
:returns "boolean"
:doc "True if string s starts with prefix.")
(define-primitive "ends-with?"
:params (s suffix)
:params ((s :as string) (suffix :as string))
:returns "boolean"
:doc "True if string s ends with suffix.")
@@ -371,7 +380,7 @@
:doc "Create a dict from key/value pairs: (dict :a 1 :b 2).")
(define-primitive "range"
:params (start end &rest step)
:params ((start :as number) (end :as number) &rest (step :as number))
:returns "list"
:doc "Integer range [start, end) with optional step.")
@@ -386,57 +395,57 @@
:doc "Length of string, list, or dict.")
(define-primitive "first"
:params (coll)
:params ((coll :as list))
:returns "any"
:doc "First element, or nil if empty.")
(define-primitive "last"
:params (coll)
:params ((coll :as list))
:returns "any"
:doc "Last element, or nil if empty.")
(define-primitive "rest"
:params (coll)
:params ((coll :as list))
:returns "list"
:doc "All elements except the first.")
(define-primitive "nth"
:params (coll n)
:params ((coll :as list) (n :as number))
:returns "any"
:doc "Element at index n, or nil if out of bounds.")
(define-primitive "cons"
:params (x coll)
:params (x (coll :as list))
:returns "list"
:doc "Prepend x to coll.")
(define-primitive "append"
:params (coll x)
:params ((coll :as list) x)
:returns "list"
:doc "If x is a list, concatenate. Otherwise append x as single element.")
(define-primitive "append!"
:params (coll x)
:params ((coll :as list) x)
:returns "list"
:doc "Mutate coll by appending x in-place. Returns coll.")
(define-primitive "reverse"
:params (coll)
:params ((coll :as list))
:returns "list"
:doc "Return coll in reverse order.")
(define-primitive "flatten"
:params (coll)
:params ((coll :as list))
:returns "list"
:doc "Flatten one level of nesting. Nested lists become top-level elements.")
(define-primitive "chunk-every"
:params (coll n)
:params ((coll :as list) (n :as number))
:returns "list"
:doc "Split coll into sub-lists of size n.")
(define-primitive "zip-pairs"
:params (coll)
:params ((coll :as list))
:returns "list"
:doc "Consecutive pairs: (1 2 3 4) → ((1 2) (2 3) (3 4)).")
@@ -448,37 +457,37 @@
(define-module :core.dict)
(define-primitive "keys"
:params (d)
:params ((d :as dict))
:returns "list"
:doc "List of dict keys.")
(define-primitive "vals"
:params (d)
:params ((d :as dict))
:returns "list"
:doc "List of dict values.")
(define-primitive "merge"
:params (&rest dicts)
:params (&rest (dicts :as dict))
:returns "dict"
:doc "Merge dicts left to right. Later keys win. Skips nil.")
(define-primitive "has-key?"
:params (d key)
:params ((d :as dict) key)
:returns "boolean"
:doc "True if dict d contains key.")
(define-primitive "assoc"
:params (d &rest pairs)
:params ((d :as dict) &rest pairs)
:returns "dict"
:doc "Return new dict with key/value pairs added/overwritten.")
(define-primitive "dissoc"
:params (d &rest keys)
:params ((d :as dict) &rest keys)
:returns "dict"
:doc "Return new dict with keys removed.")
(define-primitive "dict-set!"
:params (d key val)
:params ((d :as dict) key val)
:returns "any"
:doc "Mutate dict d by setting key to val in-place. Returns val.")
@@ -495,12 +504,12 @@
(define-module :stdlib.format)
(define-primitive "format-date"
:params (date-str fmt)
:params ((date-str :as string) (fmt :as string))
:returns "string"
:doc "Parse ISO date string and format with strftime-style format.")
(define-primitive "format-decimal"
:params (val &rest places)
:params ((val :as number) &rest (places :as number))
:returns "string"
:doc "Format number with fixed decimal places (default 2).")
@@ -510,7 +519,7 @@
:doc "Parse string to integer with optional default on failure.")
(define-primitive "parse-datetime"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Parse datetime string — identity passthrough (returns string or nil).")
@@ -522,17 +531,17 @@
(define-module :stdlib.text)
(define-primitive "pluralize"
:params (count &rest forms)
:params ((count :as number) &rest (forms :as string))
:returns "string"
:doc "Pluralize: (pluralize 1) → \"\", (pluralize 2) → \"s\". Or (pluralize n \"item\" \"items\").")
(define-primitive "escape"
:params (s)
:params ((s :as string))
:returns "string"
:doc "HTML-escape a string (&, <, >, \", ').")
(define-primitive "strip-tags"
:params (s)
:params ((s :as string))
:returns "string"
:doc "Remove HTML tags from string.")
@@ -567,16 +576,16 @@
:doc "Return type name: number, string, boolean, nil, symbol, keyword, list, dict, lambda, component, island, macro.")
(define-primitive "symbol-name"
:params (sym)
:params ((sym :as symbol))
:returns "string"
:doc "Return the name string of a symbol.")
(define-primitive "keyword-name"
:params (kw)
:params ((kw :as keyword))
:returns "string"
:doc "Return the name string of a keyword.")
(define-primitive "sx-parse"
:params (source)
:params ((source :as string))
:returns "list"
:doc "Parse SX source string into a list of AST expressions.")