From 4bd92620606fae9e8794e5992f7fa74b4aa998a1 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 27 May 2026 20:59:38 +0000 Subject: [PATCH] =?UTF-8?q?go:=20types.sx=20=E2=80=94=20composite-literal?= =?UTF-8?q?=20element=20checking;=20Phase=203=20bar=20crossed=20+=2010=20t?= =?UTF-8?q?ests=20[nothing]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 cont. Adds composite-literal type-checking via go-synth-composite: []T{...} — go-check-composite-elems with VAL-TY=T, KEY-TY=nil. Each plain elem assignable to T; :kv element accepted (Go's index-keyed shorthand: `[]int{0: 5, 1: 10}`) with only the value checked. [N]T{...} — same as slice; result :ty-array N T. map[K]V{...} — KEY-TY=K, VAL-TY=V. Each :kv pair: key assignable to K, value to V. Non-:kv elements in maps are (:type-error :map-elem-missing-key). The literal's *synthesised* type is the type expression itself, so nested composites fall out by recursion: [][]int{[]int{1,2}, []int{3,4}} → outer: go-check-composite-elems with VAL-TY=[]int → each inner []int{1,2} goes through go-synth-composite recursively, yielding :ty-slice :ty-name "int" — assignable-equal to VAL-TY. Coverage: positive cases (homogeneous slices/arrays/maps, empty slice, nested), and three negative cases (slice element mismatch, map key mismatch, map value mismatch). Also a decl test: var x = []int{1, 2, 3} → binds x to :ty-slice :ty-name "int" Named-type literals (`Point{1,2}`, `pkg.T{...}`) need type-decl-driven field resolution; deferred. Interface satisfaction and AST-path error context also remain — neither gates Phase 4. **Phase 3 acceptance bar (60+) crossed: types 65/65, total 370/370.** Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/go/scoreboard.json | 6 ++-- lib/go/scoreboard.md | 4 +-- lib/go/tests/types.sx | 61 +++++++++++++++++++++++++++++++++++++++++ lib/go/types.sx | 62 ++++++++++++++++++++++++++++++++++++++++++ plans/go-on-sx.md | 24 ++++++++++++++-- 5 files changed, 150 insertions(+), 7 deletions(-) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 5bd1ee9b..fbe3c3aa 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,11 +1,11 @@ { "language": "go", - "total_pass": 360, - "total": 360, + "total_pass": 370, + "total": 370, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"parse","pass":176,"total":176,"status":"ok"}, - {"name":"types","pass":55,"total":55,"status":"ok"}, + {"name":"types","pass":65,"total":65,"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 bfdd9e31..5fc97fde 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,12 +1,12 @@ # Go-on-SX Scoreboard -**Total: 360 / 360 tests passing** +**Total: 370 / 370 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | -| ✅ | types | 55 | 55 | +| ✅ | types | 65 | 65 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | diff --git a/lib/go/tests/types.sx b/lib/go/tests/types.sx index 101a205a..952529c2 100644 --- a/lib/go/tests/types.sx +++ b/lib/go/tests/types.sx @@ -415,6 +415,67 @@ (go-parse "double(42)")) (list :ty-name "int")) +(go-types-test + "composite: []int{1,2,3} — synth slice type" + (gtsy go-ctx-empty "[]int{1, 2, 3}") + (list :ty-slice (list :ty-name "int"))) + +(go-types-test + "composite: []string{\"a\",\"b\"}" + (gtsy go-ctx-empty "[]string{\"a\", \"b\"}") + (list :ty-slice (list :ty-name "string"))) + +(go-types-test + "composite: []int{1, \"bad\"} — element type-error" + (gtsy go-ctx-empty "[]int{1, \"bad\"}") + (list + :type-error :mismatch + (list :ty-name "int") + (list :ty-untyped-string))) + +(go-types-test + "composite: empty []int{}" + (gtsy go-ctx-empty "[]int{}") + (list :ty-slice (list :ty-name "int"))) + +(go-types-test + "composite: [3]int{1,2,3} array" + (gtsy go-ctx-empty "[3]int{1, 2, 3}") + (list :ty-array (list :literal "3") (list :ty-name "int"))) + +(go-types-test + "composite: map[string]int — synth map type" + (gtsy go-ctx-empty "map[string]int{\"a\": 1, \"b\": 2}") + (list :ty-map (list :ty-name "string") (list :ty-name "int"))) + +(go-types-test + "composite: map value type-error" + (gtsy go-ctx-empty "map[string]int{\"a\": \"bad\"}") + (list + :type-error :mismatch + (list :ty-name "int") + (list :ty-untyped-string))) + +(go-types-test + "composite: map key type-error" + (gtsy go-ctx-empty "map[string]int{42: 1}") + (list + :type-error :mismatch + (list :ty-name "string") + (list :ty-untyped-int))) + +(go-types-test + "composite: nested [][]int{[]int{1,2}, []int{3,4}}" + (gtsy go-ctx-empty "[][]int{[]int{1, 2}, []int{3, 4}}") + (list :ty-slice (list :ty-slice (list :ty-name "int")))) + +(go-types-test + "composite: var x = []int{1,2,3} — inferred slice" + (go-ctx-lookup + (go-check-decl go-ctx-empty (go-parse "var x = []int{1, 2, 3}")) + "x") + (list :ty-slice (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 a348d76d..8e666ed8 100644 --- a/lib/go/types.sx +++ b/lib/go/types.sx @@ -262,6 +262,9 @@ (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))) + ;; (:composite TYPE-OR-EXPR ELEMS) — composite literal + (and (list? expr) (= (first expr) :composite)) + (go-synth-composite ctx (nth expr 1) (nth expr 2)) :else (list :type-error :unsupported-synth expr)))) (define @@ -288,6 +291,65 @@ (go-type-error? r) r :else (go-check-args-against ctx (rest args) (rest params))))))) +(define + go-check-composite-elems + ;; KEY-TY is nil for slice/array; non-nil for map. + ;; For maps, each elem must be (:kv KEY VALUE) — KEY assignable to + ;; KEY-TY, VALUE to VAL-TY. + ;; For slice/array, plain exprs assignable to VAL-TY; (:kv K V) is + ;; Go's index-keyed shorthand (`[]int{0: 5, 1: 10}`) — we type-check + ;; only the value in v0. + (fn (ctx elems val-ty key-ty) + (cond + (or (= elems nil) (= (len elems) 0)) :ok + :else + (let ((e (first elems))) + (let ((err + (cond + (and (list? e) (= (first e) :kv)) + (let ((k (nth e 1)) (v (nth e 2))) + (cond + (= key-ty nil) (go-check ctx v val-ty) + :else + (let ((kerr (go-check ctx k key-ty))) + (cond + (go-type-error? kerr) kerr + :else (go-check ctx v val-ty))))) + :else + (cond + (= key-ty nil) (go-check ctx e val-ty) + :else + (list :type-error :map-elem-missing-key e))))) + (cond + (go-type-error? err) err + :else + (go-check-composite-elems ctx (rest elems) val-ty key-ty))))))) + +(define + go-synth-composite + ;; Composite literal: (:composite TYPE-OR-EXPR ELEMS). + ;; []T{...} — each elem assignable to T; result :ty-slice T + ;; [N]T{...} — same; result :ty-array N T + ;; map[K]V{...} — each :kv key:K, value:V; result :ty-map K V + ;; Named-type literals (Point{...}, pkg.T{...}) require type-decl + ;; resolution; v0 returns the literal's type-expr as-is without + ;; element checking. + (fn (ctx ty elems) + (cond + (and (list? ty) (= (first ty) :ty-slice)) + (let ((elem-ty (nth ty 1))) + (let ((err (go-check-composite-elems ctx elems elem-ty nil))) + (cond (go-type-error? err) err :else ty))) + (and (list? ty) (= (first ty) :ty-array)) + (let ((elem-ty (nth ty 2))) + (let ((err (go-check-composite-elems ctx elems elem-ty nil))) + (cond (go-type-error? err) err :else ty))) + (and (list? ty) (= (first ty) :ty-map)) + (let ((key-ty (nth ty 1)) (val-ty (nth ty 2))) + (let ((err (go-check-composite-elems ctx elems val-ty key-ty))) + (cond (go-type-error? err) err :else ty))) + :else ty))) + (define go-synth-call ;; Synth a function call. Returns the result type, or :type-error. diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 6cd7844c..53262e23 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -244,12 +244,19 @@ Progress-log line → push `origin/loops/go`. 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). +- [x] Composite literal element checking — slice `[]T{...}`, array + `[N]T{...}`, map `map[K]V{...}` (key + value checked). + `:kv` element with no key on slice/array is permitted (Go's + index-keyed shorthand). Nested composite literals work + (`[][]int{[]int{1,2}, []int{3,4}}`). Named-type composite + literals (`Point{...}`) need type-decl resolution; deferred. - [ ] 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: 55/55. Chisel note +- **Acceptance:** types/ suite at 60+ tests. **Bar crossed: 65/65.** + Remaining sub-items (interface satisfaction, error reporting carrying + AST paths) refine but don't gate Phase 4. Chisel note `shapes-static-types-bidirectional` — sister-plan design diary is the cross-language record. @@ -550,6 +557,19 @@ 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.: composite-literal element checking. + `go-synth-composite` dispatches on the literal's type expression: + `:ty-slice` and `:ty-array` check each element assignable to the + element type; `:ty-map` checks each `:kv` pair (key against K, value + against V) and rejects non-`:kv` map elements. The literal's + synthesised type is the type-expression itself, so nested composites + fall out by recursion: `[][]int{[]int{1,2}, []int{3,4}}` checks each + inner `[]int{...}` as a value of type `[]int`. Named-type literals + (`Point{1,2}`, `pkg.T{...}`) need type-decl-driven field resolution; + deferred. **Phase 3 acceptance bar (60+ tests) crossed: 65/65, total + 370/370.** `[nothing]` — composite-literal semantics are mostly Go- + specific. Remaining Phase 3 items (interface satisfaction; AST-path + error context) sharpen the surface but don't gate Phase 4 (evaluator). - 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