diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index cf26b945..4aaa3cab 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,11 +1,11 @@ { "language": "go", - "total_pass": 333, - "total": 333, + "total_pass": 345, + "total": 345, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"parse","pass":176,"total":176,"status":"ok"}, - {"name":"types","pass":28,"total":28,"status":"ok"}, + {"name":"types","pass":40,"total":40,"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 d69b6519..385464cd 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,12 +1,12 @@ # Go-on-SX Scoreboard -**Total: 333 / 333 tests passing** +**Total: 345 / 345 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | -| ✅ | types | 28 | 28 | +| ✅ | types | 40 | 40 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | diff --git a/lib/go/tests/types.sx b/lib/go/tests/types.sx index fa291b66..896ad0c9 100644 --- a/lib/go/tests/types.sx +++ b/lib/go/tests/types.sx @@ -191,6 +191,79 @@ (gtchk go-ctx-empty "\"hi\"" (list :ty-name "string")) :ok) +(go-types-test + "decl: var x int (no init) — binds x to int" + (go-ctx-lookup (go-check-decl go-ctx-empty (go-parse "var x int")) "x") + (list :ty-name "int")) + +(go-types-test + "decl: var x int = 5 — checks 5 vs int, binds" + (go-ctx-lookup (go-check-decl go-ctx-empty (go-parse "var x int = 5")) "x") + (list :ty-name "int")) + +(go-types-test + "decl: var x = 5 — inferred, default-typed to int" + (go-ctx-lookup (go-check-decl go-ctx-empty (go-parse "var x = 5")) "x") + (list :ty-name "int")) + +(go-types-test + "decl: var x = 3.14 — inferred, default-typed to float64" + (go-ctx-lookup (go-check-decl go-ctx-empty (go-parse "var x = 3.14")) "x") + (list :ty-name "float64")) + +(go-types-test + "decl: var x float64 = 42 / 7 — canonical pitfall" + (go-ctx-lookup + (go-check-decl go-ctx-empty (go-parse "var x float64 = 42 / 7")) + "x") + (list :ty-name "float64")) + +(go-types-test + "decl: var x string = 42 — type-error" + (go-check-decl go-ctx-empty (go-parse "var x string = 42")) + (list + :type-error :mismatch + (list :ty-name "string") + (list :ty-untyped-int))) + +(go-types-test + "decl: var x, y int — binds both" + (let + ((ctx (go-check-decl go-ctx-empty (go-parse "var x, y int")))) + (list (go-ctx-lookup ctx "x") (go-ctx-lookup ctx "y"))) + (list (list :ty-name "int") (list :ty-name "int"))) + +(go-types-test + "decl: const Pi = 3.14 — binds Pi to float64" + (go-ctx-lookup + (go-check-decl go-ctx-empty (go-parse "const Pi = 3.14")) + "Pi") + (list :ty-name "float64")) + +(go-types-test + "decl: const C int = 42 — typed const" + (go-ctx-lookup + (go-check-decl go-ctx-empty (go-parse "const C int = 42")) + "C") + (list :ty-name "int")) + +(go-types-test + "decl: type T int — binds T to int alias" + (go-ctx-lookup (go-check-decl go-ctx-empty (go-parse "type T int")) "T") + (list :ty-name "int")) + +(go-types-test + "decl: short-decl x := 5 — binds x to int" + (go-ctx-lookup (go-check-decl go-ctx-empty (go-parse "x := 5")) "x") + (list :ty-name "int")) + +(go-types-test + "decl: short-decl a, b := 1, 2 — binds both" + (let + ((ctx (go-check-decl go-ctx-empty (go-parse "a, b := 1, 2")))) + (list (go-ctx-lookup ctx "a") (go-ctx-lookup ctx "b"))) + (list (list :ty-name "int") (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 54a814fd..98002a2f 100644 --- a/lib/go/types.sx +++ b/lib/go/types.sx @@ -311,3 +311,108 @@ (go-type-assignable? got expected) :ok :else (list :type-error :mismatch expected got))))) + +;; ── default types ──────────────────────────────────────────────── +;; Go spec § Constants: the *default type* of an untyped constant +;; is what it becomes when assigned to a sloppily-typed slot +;; (e.g., `var x = 42` makes x an int). + +(define + go-default-type + (fn (t) + (cond + (not (list? t)) t + (= (first t) :ty-untyped-int) (list :ty-name "int") + (= (first t) :ty-untyped-float) (list :ty-name "float64") + (= (first t) :ty-untyped-imag) (list :ty-name "complex128") + (= (first t) :ty-untyped-string) (list :ty-name "string") + (= (first t) :ty-untyped-rune) (list :ty-name "int32") + :else t))) + +;; ── declaration checking ──────────────────────────────────────── +;; Returns either: +;; the extended context (success) +;; (list :type-error TAG ...) (failure) + +(define + go-check-exprs-against + ;; Check every EXPR in EXPRS is assignable to EXPECTED. Returns the + ;; first :type-error encountered, or :ok. + (fn (ctx exprs expected) + (cond + (or (= exprs nil) (= (len exprs) 0)) :ok + :else + (let ((r (go-check ctx (first exprs) expected))) + (cond + (go-type-error? r) r + :else (go-check-exprs-against ctx (rest exprs) expected)))))) + +(define + go-bind-names-to-synth + ;; Pair each NAME with the synthesised default-typed type of the + ;; corresponding EXPR; extend CTX with all pairs. NAMES and EXPRS + ;; may have different lengths (multi-return funcs aren't here yet); + ;; for now we zip the shorter of the two. + (fn (ctx names exprs) + (cond + (or (= (len names) 0) (= (len exprs) 0)) ctx + :else + (let ((t (go-synth ctx (first exprs)))) + (cond + (go-type-error? t) t + :else + (let ((ctx2 (go-ctx-extend ctx (first names) + (go-default-type t)))) + (go-bind-names-to-synth ctx2 (rest names) (rest exprs)))))))) + +(define + go-check-var-decl + ;; Shape: (:var-decl (:field NAMES TYPE-or-nil) EXPRS-or-nil) + ;; or (:const-decl (:field NAMES TYPE-or-nil) EXPRS). + ;; Logic is the same for v0; const-vs-var distinction matters for + ;; mutability checks which arrive later. + (fn (ctx decl) + (let ((field (nth decl 1)) (exprs (nth decl 2))) + (let ((names (nth field 1)) (ann-ty (nth field 2))) + (cond + ;; var x T (no init) → bind names to T + (or (= exprs nil) (= (len exprs) 0)) + (cond + (= ann-ty nil) (list :type-error :missing-type-or-init names) + :else (go-ctx-extend-field ctx field)) + ;; Annotated: var x T = expr — check each expr against T + (not (= ann-ty nil)) + (let ((err (go-check-exprs-against ctx exprs ann-ty))) + (cond + (go-type-error? err) err + :else (go-ctx-extend-field ctx field))) + ;; Inferred: var x = expr — bind names to default(synth(expr)) + :else (go-bind-names-to-synth ctx names exprs)))))) + +(define + go-check-short-decl + ;; Shape: (:short-decl LHS-LIST EXPRS). LHS is a list of (:var NAME). + ;; Extracts the names and falls through to bind-names-to-synth. + (fn (ctx decl) + (let ((lhs-list (nth decl 1)) (exprs (nth decl 2))) + (let ((names (map (fn (lhs) + (cond + (and (list? lhs) (= (first lhs) :var)) + (nth lhs 1) + :else :unknown)) + lhs-list))) + (go-bind-names-to-synth ctx names exprs))))) + +(define + go-check-decl + ;; Top-level dispatcher: accepts any decl AST shape, returns extended + ;; context or :type-error. + (fn (ctx decl) + (cond + (and (list? decl) (= (first decl) :var-decl)) (go-check-var-decl ctx decl) + (and (list? decl) (= (first decl) :const-decl)) (go-check-var-decl ctx decl) + (and (list? decl) (= (first decl) :short-decl)) (go-check-short-decl ctx decl) + (and (list? decl) (= (first decl) :type-decl)) + (let ((name (nth decl 1)) (ty (nth decl 2))) + (go-ctx-extend ctx name ty)) + :else ctx))) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index efbeb8af..fdae1796 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -228,8 +228,12 @@ Progress-log line → push `origin/loops/go`. successfully against `float64`. Untyped int + untyped float unifies to untyped float. Typed-var + untyped-int propagates the var's type. Comparison/logical ops produce `bool`. -- [ ] Var/const declaration checking (`var x T = expr`, `var x = expr`, - `const Pi = 3.14`). +- [x] Var/const declaration checking (`var x T = expr`, `var x = expr`, + `var x T`, `const Pi = 3.14`, `type T int`, `var x, y int`, plus + short-decl `x := 5` and `a, b := 1, 2`). `go-check-decl` returns + the extended context or a `:type-error`. Untyped synthesized types + get their default-type (`untyped-int → int`, `untyped-float → + float64`, etc.) when bound in inferred-type decls. - [ ] Function declaration: extend ctx with params via `:field` group, check body, verify return-list types match signature. - [ ] Call type-checking (synth callee, check args against param types, @@ -239,7 +243,7 @@ Progress-log line → push `origin/loops/go`. - [ ] 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: 28/28. Chisel note +- **Acceptance:** types/ suite at 60+ tests. Current: 40/40. Chisel note `shapes-static-types-bidirectional` — sister-plan design diary is the cross-language record. @@ -540,6 +544,18 @@ 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.: declaration checking — `var`/`const`/`type` + + short-decl `:=`. `go-check-decl` returns the extended context (or a + `:type-error`). New helpers: `go-default-type` (untyped-int → int, + untyped-float → float64, etc.), `go-check-exprs-against`, + `go-bind-names-to-synth`. Annotated decls check each init expression + is assignable to the declared type; inferred decls bind names to the + default-typed synthesis of the init. **`var x float64 = 42 / 7` and + `const C int = 42` both bind x to float64 / C to int correctly via + the assignability relation from the previous commit.** +12 tests, + types 40/40, total 345/345. `[nothing]` — the kit-relevant insights + (synth/check + assignable predicate) already in the diary; this is + pure Go-side composition on top. - 2026-05-27 — Phase 3 cont.: literal synth + binop synth + assignability. Heuristic `go-classify-literal-string` decodes the parser's untagged literal values back into `:int`/`:float`/`:imag`/`:string` kinds