go: Phase 7 foundation — generics syntax through parser/typer/eval [shapes-static-types-bidirectional]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s

gp-parse-type-params consumes the optional [NAMES CONSTRAINT, ...]
clause after a func name. AST stays backward-compatible: 5-slot
func-decl when no [...] is present, 6-slot when it is.

Typer binds each type-param name as (:ty-param NAME CONSTRAINT) so
body's (:ty-name "T") references resolve. Eval is type-erasing —
ignores type info, dispatches by name + arity.

10 new tests: parse (3), types (5), eval (2). Total 527/527.

Shape: the field binding-group from the canonical kit now feeds
6 consumers (struct fields, var-decls, const-decls, params,
receivers, type-params). Confirms it as a TRUE cross-deliverable
shape — sister-plan diary documents the 5 roles binding-groups
take and why the kit should expose ONE parser + pluggable validators.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 00:31:28 +00:00
parent c50f5d5155
commit 459427512d
9 changed files with 264 additions and 34 deletions

View File

@@ -694,6 +694,42 @@
(when (> depth 0) (gp-block-loop)))
:else (do (gp-advance!) (gp-block-loop)))))
(gp-block-loop))))
(define
gp-parse-type-params
;; Optional [...] preceding a func/type decl's regular params.
;; Each group is `NAMES constraint-type` (re-uses the regular
;; param-group parser). Returns a list of (:field NAMES TY)
;; records, or nil if no `[` is present. Type-set constraints
;; (`T int | float64`) deferred.
(fn ()
(cond
(not (and (= (gp-tok-type) "op") (= (gp-tok-value) "[")))
nil
:else
(do
(gp-advance!)
(let ((groups (list)))
(define
gp-tp-loop
(fn ()
(cond
(and (= (gp-tok-type) "op") (= (gp-tok-value) "]"))
(gp-advance!)
:else
(let ((group (gp-parse-decl-param-group)))
(cond
(= group nil)
(do (gp-advance!) (gp-tp-loop))
:else
(do
(append! groups group)
(cond
(and (= (gp-tok-type) "op")
(= (gp-tok-value) ","))
(do (gp-advance!) (gp-tp-loop))
:else (gp-tp-loop))))))))
(gp-tp-loop)
groups)))))
(define
gp-parse-func-decl
;; Caller has consumed 'func'.
@@ -715,18 +751,31 @@
(= (gp-tok-type) "ident")
(let ((name (gp-tok-value)))
(gp-advance!)
(let ((params (gp-parse-func-decl-params)))
(let ((results (gp-parse-func-type-results)))
(let ((body nil))
(when (and (= (gp-tok-type) "op")
(= (gp-tok-value) "{"))
(gp-advance!)
(set! body (gp-parse-block-body)))
(cond
(= recv nil)
(list :func-decl name params results body)
:else
(list :method-decl recv name params results body))))))
;; Type parameters: [T any] / [T, U any] / [T any, U comparable].
;; Same shape as a regular param group — (:field NAMES TY).
;; Type-set constraints (T int | float64) deferred.
(let ((type-params (gp-parse-type-params)))
(let ((params (gp-parse-func-decl-params)))
(let ((results (gp-parse-func-type-results)))
(let ((body nil))
(when (and (= (gp-tok-type) "op")
(= (gp-tok-value) "{"))
(gp-advance!)
(set! body (gp-parse-block-body)))
;; Keep the legacy 5-slot shape when there are
;; no type params so existing AST consumers
;; (parse tests, types/eval pattern matchers)
;; stay compatible. Only add the 6th slot when
;; a `[...]` clause was actually present.
(cond
(and (= recv nil) (= type-params nil))
(list :func-decl name params results body)
(= recv nil)
(list :func-decl name params results body type-params)
(= type-params nil)
(list :method-decl recv name params results body)
:else
(list :method-decl recv name params results body type-params)))))))
:else nil))))
(define
gp-parse-case-body

View File

@@ -1,12 +1,12 @@
{
"language": "go",
"total_pass": 517,
"total": 517,
"total_pass": 527,
"total": 527,
"suites": [
{"name":"lex","pass":129,"total":129,"status":"ok"},
{"name":"parse","pass":176,"total":176,"status":"ok"},
{"name":"types","pass":72,"total":72,"status":"ok"},
{"name":"eval","pass":100,"total":100,"status":"ok"},
{"name":"parse","pass":179,"total":179,"status":"ok"},
{"name":"types","pass":77,"total":77,"status":"ok"},
{"name":"eval","pass":102,"total":102,"status":"ok"},
{"name":"runtime","pass":40,"total":40,"status":"ok"},
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
{"name":"e2e","pass":0,"total":0,"status":"pending"}

View File

@@ -1,13 +1,13 @@
# Go-on-SX Scoreboard
**Total: 517 / 517 tests passing**
**Total: 527 / 527 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | lex | 129 | 129 |
| ✅ | parse | 176 | 176 |
| ✅ | types | 72 | 72 |
| ✅ | eval | 100 | 100 |
| ✅ | parse | 179 | 179 |
| ✅ | types | 77 | 77 |
| ✅ | eval | 102 | 102 |
| ✅ | runtime | 40 | 40 |
| ⬜ | stdlib | 0 | 0 |
| ⬜ | e2e | 0 | 0 |

View File

@@ -614,6 +614,20 @@
(go-env-lookup env "got"))
5)
(go-eval-test
"generic: identity Id[T any](x) returns x at runtime"
(let
((env (go-eval-program go-env-builtins (list (go-parse "func Id[T any](x T) T { return x }") (go-parse "r := Id(42)")))))
(go-env-lookup env "r"))
42)
(go-eval-test
"generic: Id works with strings (type erasure)"
(let
((env (go-eval-program go-env-builtins (list (go-parse "func Id[T any](x T) T { return x }") (go-parse "r := Id(\"hi\")")))))
(go-env-lookup env "r"))
"hi")
(define
go-eval-test-summary
(str "eval " go-eval-test-pass "/" go-eval-test-count))

View File

@@ -793,6 +793,38 @@
(list
(ast-app (ast-var "+") (list (ast-var "x") (ast-var "y")))))))))
(go-parse-test
"fdecl: generic identity func with one type param [T any]"
(go-parse "func Id[T any](x T) T { return x }")
(list
:func-decl "Id"
(list (list :field (list "x") (list :ty-name "T")))
(list (list :ty-name "T"))
(list :block (list (list :return (list (list :var "x")))))
(list (list :field (list "T") (list :ty-name "any")))))
(go-parse-test
"fdecl: generic with two type params [T, U any]"
(go-parse "func Map[T, U any](x T) U { return x }")
(list
:func-decl "Map"
(list (list :field (list "x") (list :ty-name "T")))
(list (list :ty-name "U"))
(list :block (list (list :return (list (list :var "x")))))
(list (list :field (list "T" "U") (list :ty-name "any")))))
(go-parse-test
"fdecl: generic with multi-group type params"
(go-parse "func F[T any, U comparable]() {}")
(list
:func-decl "F"
(list)
(list)
(list :block (list))
(list
(list :field (list "T") (list :ty-name "any"))
(list :field (list "U") (list :ty-name "comparable")))))
(go-parse-test
"fdecl: func with multi-group params"
(go-parse "func mix(x int, y string) {}")
@@ -830,8 +862,8 @@
"String"
(list)
(list (list :ty-name "string"))
(list :block
(list (list :return (list (list :select (ast-var "p") "x")))))))
(list
:block (list (list :return (list (list :select (ast-var "p") "x")))))))
(go-parse-test
"mdecl: method on value receiver"
@@ -846,7 +878,10 @@
(go-parse-test
"fdecl: body with return"
(go-parse "func ret() { return 42 }")
(list :func-decl "ret" (list) (list)
(list
:func-decl "ret"
(list)
(list)
(list :block (list (list :return (list (ast-literal "42")))))))
(go-parse-test

View File

@@ -563,6 +563,41 @@
(list :method "Close" (list) (list (list :ty-name "error")))))))
false)
(go-types-test
"generic: identity func [T any] checks (body uses x of type T)"
(let
((ctx (go-check-decl go-ctx-empty (go-parse "func Id[T any](x T) T { return x }"))))
(go-type-error? ctx))
false)
(go-types-test
"generic: two type params [T, U any] checks"
(let
((ctx (go-check-decl go-ctx-empty (go-parse "func Pair[T, U any](x T, y U) T { return x }"))))
(go-type-error? ctx))
false)
(go-types-test
"generic: multi-group type params [T any, U comparable] checks"
(let
((ctx (go-check-decl go-ctx-empty (go-parse "func F[T any, U comparable](x T, y U) T { return x }"))))
(go-type-error? ctx))
false)
(go-types-test
"generic: empty body with type params still checks"
(let
((ctx (go-check-decl go-ctx-empty (go-parse "func Noop[T any]() {}"))))
(go-type-error? ctx))
false)
(go-types-test
"generic: multiple uses of same type param check (x T, y T)"
(let
((ctx (go-check-decl go-ctx-empty (go-parse "func H[T any](x T, y T) T { return x }"))))
(go-type-error? ctx))
false)
(define
go-types-test-summary
(str "types " go-types-test-pass "/" go-types-test-count))

View File

@@ -749,11 +749,19 @@
(define
go-check-func-decl
;; Bind the function in the outer ctx (so recursion works), extend
;; ctx with params, check the body. Returns the outer ctx with the
;; function bound, or :type-error.
;; ctx with type params + value params, check the body. Returns the
;; outer ctx with the function bound, or :type-error.
;;
;; Type parameters become opaque type variables in the body's ctx:
;; each name `T` is bound as a type alias to (:ty-param "T") so the
;; checker treats references to T as "this type", not "unknown".
;; Constraint enforcement (T satisfies `comparable` etc.) is a
;; later refinement; v0 just allows any operation that's polymorphic
;; under the constraint `any`.
(fn (ctx decl)
(let ((name (nth decl 1)) (params (nth decl 2))
(results (nth decl 3)) (body (nth decl 4)))
(results (nth decl 3)) (body (nth decl 4))
(type-params (cond (> (len decl) 5) (nth decl 5) :else nil)))
(let ((fn-ty
(list :ty-func
(go-decl-params-to-ty-list params) results)))
@@ -762,10 +770,40 @@
(= body nil) ctx-with-fn
(and (list? body) (= (first body) :block))
(let ((body-ctx
(go-extend-with-params ctx-with-fn params)))
(go-extend-with-type-params
(go-extend-with-params ctx-with-fn params)
type-params)))
(let ((err
(go-check-block body-ctx (nth body 1) results)))
(cond
(go-type-error? err) err
:else ctx-with-fn)))
:else ctx-with-fn))))))
(define
go-extend-with-type-params
;; Each (:field NAMES CONSTRAINT) field contributes opaque type
;; vars: bind each NAME as a type alias to (:ty-param NAME). The
;; constraint type is stored alongside so future "constraint
;; satisfaction" checks can find it; for v0 it's informational.
(fn (ctx type-params)
(cond
(or (= type-params nil) (= (len type-params) 0)) ctx
:else
(let ((field (first type-params)))
(let ((names (nth field 1)) (constraint (nth field 2)))
(go-extend-with-type-params
(go-extend-with-type-param-names ctx names constraint)
(rest type-params)))))))
(define
go-extend-with-type-param-names
(fn (ctx names constraint)
(cond
(= (len names) 0) ctx
:else
(let ((nm (first names)))
(go-extend-with-type-param-names
(go-ctx-extend ctx nm
(list :ty-param nm constraint))
(rest names) constraint)))))