From 5b4a8be689a4c0889498d8eb59b6f69d2ee9ae15 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 27 May 2026 20:56:10 +0000 Subject: [PATCH] =?UTF-8?q?go:=20types.sx=20=E2=80=94=20call=20type-checki?= =?UTF-8?q?ng=20+=208=20tests;=20recursive=20funcs=20now=20type=20[nothing?= =?UTF-8?q?]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 cont. The expression-synth :app dispatch is now bifurcated: * go-is-binop-call? — head is :var with an operator name AND 2 args AND the operator is in one of the binop tables. Short-circuits to go-synth-binop as before. * Everything else routes to go-synth-call. go-synth-call: 1. Synth the callee. Must produce a (list :ty-func PARAMS RESULTS). Otherwise → (:type-error :not-callable TYPE). 2. Arity-check args vs params. Mismatch → (:type-error :arity-mismatch). 3. go-check-args-against: each arg assignable to corresponding param (untyped-constant flow works — `f(42)` accepts the untyped int into an int param). 4. Result by count: 0 results → (list :ty-void) 1 result → that result directly N results → (list :ty-tuple TYPES) for multi-return The recursive case lights up: go-check-func-decl binds the function in its own body's ctx before checking. So: func fib(n int) int { return fib(n) + fib(n) } now type-checks because `fib` resolves inside the body, synth-call sees its `:ty-func` and verifies the recursive call. Multi-return functions destructure into `:ty-tuple` which short-decl will need to consume next iteration. types 55/55, total 360/360. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/go/scoreboard.json | 6 +-- lib/go/scoreboard.md | 4 +- lib/go/tests/types.sx | 89 ++++++++++++++++++++++++++++++++++++++++++ lib/go/types.sx | 66 +++++++++++++++++++++++++++---- plans/go-on-sx.md | 23 +++++++++-- 5 files changed, 173 insertions(+), 15 deletions(-) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index f8361de8..5bd1ee9b 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,11 +1,11 @@ { "language": "go", - "total_pass": 352, - "total": 352, + "total_pass": 360, + "total": 360, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"parse","pass":176,"total":176,"status":"ok"}, - {"name":"types","pass":47,"total":47,"status":"ok"}, + {"name":"types","pass":55,"total":55,"status":"ok"}, {"name":"eval","pass":0,"total":0,"status":"pending"}, {"name":"runtime","pass":0,"total":0,"status":"pending"}, {"name":"stdlib","pass":0,"total":0,"status":"pending"}, diff --git a/lib/go/scoreboard.md b/lib/go/scoreboard.md index 5c69aeb4..bfdd9e31 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,12 +1,12 @@ # Go-on-SX Scoreboard -**Total: 352 / 352 tests passing** +**Total: 360 / 360 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | -| ✅ | types | 47 | 47 | +| ✅ | types | 55 | 55 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | diff --git a/lib/go/tests/types.sx b/lib/go/tests/types.sx index 7496673f..101a205a 100644 --- a/lib/go/tests/types.sx +++ b/lib/go/tests/types.sx @@ -326,6 +326,95 @@ "g") (list :ty-func (list) (list (list :ty-name "int")))) +(go-types-test + "call: synth result of typed func" + (go-synth + (go-ctx-extend + go-ctx-empty + "double" + (list + :ty-func (list (list :ty-name "int")) + (list (list :ty-name "int")))) + (go-parse "double(5)")) + (list :ty-name "int")) + +(go-types-test + "call: arg-count mismatch" + (go-synth + (go-ctx-extend + go-ctx-empty + "double" + (list + :ty-func (list (list :ty-name "int")) + (list (list :ty-name "int")))) + (go-parse "double(1, 2)")) + (list :type-error :arity-mismatch 1 2)) + +(go-types-test + "call: arg-type mismatch" + (go-synth + (go-ctx-extend + go-ctx-empty + "f" + (list + :ty-func (list (list :ty-name "int")) + (list (list :ty-name "int")))) + (go-parse "f(\"hi\")")) + (list + :type-error :mismatch + (list :ty-name "int") + (list :ty-untyped-string))) + +(go-types-test + "call: not callable (calling an int)" + (go-synth + (go-ctx-extend go-ctx-empty "x" (list :ty-name "int")) + (go-parse "x(1)")) + (list :type-error :not-callable (list :ty-name "int"))) + +(go-types-test + "call: no-result func (void) call" + (go-synth + (go-ctx-extend + go-ctx-empty + "log" + (list :ty-func (list (list :ty-name "string")) (list))) + (go-parse "log(\"hi\")")) + (list :ty-void)) + +(go-types-test + "call: multi-return → :ty-tuple" + (go-synth + (go-ctx-extend + go-ctx-empty + "divmod" + (list + :ty-func (list (list :ty-name "int") (list :ty-name "int")) + (list (list :ty-name "int") (list :ty-name "int")))) + (go-parse "divmod(10, 3)")) + (list :ty-tuple (list (list :ty-name "int") (list :ty-name "int")))) + +(go-types-test + "call: recursive func works (fib)" + (go-ctx-lookup + (go-check-decl + go-ctx-empty + (go-parse "func fib(n int) int { return fib(n) + fib(n) }")) + "fib") + (list :ty-func (list (list :ty-name "int")) (list (list :ty-name "int")))) + +(go-types-test + "call: untyped-int arg accepted into int param" + (go-synth + (go-ctx-extend + go-ctx-empty + "double" + (list + :ty-func (list (list :ty-name "int")) + (list (list :ty-name "int")))) + (go-parse "double(42)")) + (list :ty-name "int")) + (define go-types-test-summary (str "types " go-types-test-pass "/" go-types-test-count)) diff --git a/lib/go/types.sx b/lib/go/types.sx index cfa6d7ce..a348d76d 100644 --- a/lib/go/types.sx +++ b/lib/go/types.sx @@ -253,15 +253,67 @@ (cond (= t nil) (list :type-error :unbound name) :else t))))) - ;; (:app (:var OP) [LHS RHS]) — binary operator - (and (list? expr) (= (first expr) :app) - (list? (nth expr 1)) (= (first (nth expr 1)) :var) - (= (len (nth expr 2)) 2)) - (let ((op (nth (nth expr 1) 1)) - (args (nth expr 2))) - (go-synth-binop ctx op (first args) (nth args 1))) + ;; (:app HEAD ARGS) — function application: + ;; binop if HEAD is :var with an operator name + 2 args + ;; else: general function call + (and (list? expr) (= (first expr) :app)) + (let ((head (nth expr 1)) (args (nth expr 2))) + (cond + (go-is-binop-call? head args) + (go-synth-binop ctx (nth head 1) (first args) (nth args 1)) + :else (go-synth-call ctx head args))) :else (list :type-error :unsupported-synth expr)))) +(define + go-is-binop-call? + (fn (head args) + (and (list? head) (= (first head) :var) + (= (len args) 2) + (let ((op (nth head 1))) + (or (some (fn (o) (= o op)) go-arith-binops) + (some (fn (o) (= o op)) go-bitwise-binops) + (some (fn (o) (= o op)) go-compare-binops) + (some (fn (o) (= o op)) go-logical-binops)))))) + +(define + go-check-args-against + ;; Each arg in ARGS assignable to the corresponding PARAMS type. + ;; Caller already verified arities match. + (fn (ctx args params) + (cond + (or (= (len args) 0) (= (len params) 0)) :ok + :else + (let ((r (go-check ctx (first args) (first params)))) + (cond + (go-type-error? r) r + :else (go-check-args-against ctx (rest args) (rest params))))))) + +(define + go-synth-call + ;; Synth a function call. Returns the result type, or :type-error. + ;; 0 results → (list :ty-void) + ;; 1 result → that result type directly + ;; N results → (list :ty-tuple TYPES) (multi-return) + (fn (ctx callee args) + (let ((fn-ty (go-synth ctx callee))) + (cond + (go-type-error? fn-ty) fn-ty + (not (and (list? fn-ty) (= (first fn-ty) :ty-func))) + (list :type-error :not-callable fn-ty) + :else + (let ((params (nth fn-ty 1)) (results (nth fn-ty 2))) + (cond + (not (= (len args) (len params))) + (list :type-error :arity-mismatch + (len params) (len args)) + :else + (let ((err (go-check-args-against ctx args params))) + (cond + (go-type-error? err) err + (= (len results) 0) (list :ty-void) + (= (len results) 1) (first results) + :else (list :ty-tuple results))))))))) + (define go-synth-binop (fn (ctx op lhs rhs) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index b7077179..6cd7844c 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -239,14 +239,17 @@ Progress-log line → push `origin/loops/go`. signature, assignments verify RHS assignable to LHS). The function itself is bound in the body's ctx so recursion will work once call-checking lands. Signature-only (no body) just binds. -- [ ] Call type-checking (synth callee, check args against param types, - synth result). +- [x] Call type-checking. `go-synth-call`: synth callee → expect + `:ty-func`, arity-check, check each arg assignable to param, + then return type by result count (0 → `:ty-void`, 1 → that type, + N → `:ty-tuple`). Recursive calls now type-check because the func + is bound in the body's ctx. Untyped-constant args flow through. - [ ] Composite type element checking (slice / map / chan). - [ ] Interface satisfaction (structural match against method sets). - [ ] Short variable declaration `:=` (synth RHS into LHS bindings). - Defer: generics (Phase 7), full conversion rules, type assertions, type switches. -- **Acceptance:** types/ suite at 60+ tests. Current: 47/47. Chisel note +- **Acceptance:** types/ suite at 60+ tests. Current: 55/55. Chisel note `shapes-static-types-bidirectional` — sister-plan design diary is the cross-language record. @@ -547,6 +550,20 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`. _Newest first. Append one dated entry per commit._ +- 2026-05-27 — Phase 3 cont.: call type-checking. `go-synth-call` + synthesises the callee's type, asserts it's a `:ty-func`, arity- + checks args, then `go-check-args-against` runs each arg through + `go-check` against the corresponding param type (untyped-constant + flow works). Result: `:ty-void` for 0-result funcs, the result type + for 1-result, `(list :ty-tuple TYPES)` for multi-return. The + `:app` dispatch in `go-synth` now routes via `go-is-binop-call?` + (operator name + 2 args + op in the binop tables) — binops short- + circuit; everything else goes through the call path. **Recursive + functions now type-check** because the func is bound in its own + body's ctx by `go-check-func-decl`. +8 tests, types 55/55, total + 360/360. `[nothing]` — Go-side composition on top of established + primitives; no new kit-relevant shapes (call semantics are uniform + across statically-typed guests). - 2026-05-27 — Phase 3 cont.: function-declaration checking + statement-level dispatch. `go-check-func-decl` binds the function in the outer ctx (so the body can see itself), extends the body's ctx