Phase 3 cont. Adds:
* go-check-func-decl — binds the function in the outer ctx (recursive
self-reference will work once call-checking lands), extends the
body's ctx with each :field param group via go-ctx-extend-field
(the binding-group shape's *third* consumer in the type checker;
five total across parser+typer when counted with struct fields,
var-decls, const-decls, func params, method receivers).
* go-check-stmt — dispatches on :return / :assign / :var-decl /
:const-decl / :short-decl / :type-decl / :block; falls back to
go-synth for expression statements.
* go-check-block — threads ctx through stmts so that decls inside
the block extend the ctx for subsequent stmts.
* go-check-return-list — each return expr assignable to the
corresponding declared result type; mismatch counts are typed.
* go-check-assign / go-check-assign-pairs — RHS assignable to LHS
synthesised type, count mismatch typed.
* Helpers: go-decl-params-to-ty-list (flattens :field NAMES TYPE to
a flat list of N types), go-extend-with-params (folds extend-field
over a param-group list), go-repeat-ty.
Coverage tests:
func empty() {} → ok
func add(x, y int) int { return x + y } → ok
func bad() int { return "hi" } → typed error
func sig(x int) int → signature-only binds
func sumsq(x, y int) int { return x*x + y*y } → params visible
func two() int { var x int = 1; var y int = 2; → nested decl
return x + y }
func g() int { var x int; x = 5; return x } → assign verified
types 47/47, total 352/352.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 cont. Adds go-check-decl which dispatches on AST shape and
returns either the extended context or a :type-error:
:var-decl (:field NAMES TYPE-or-nil) EXPRS-or-nil
:const-decl (same shape; same logic in v0 — mutability later)
:short-decl LHS-LIST EXPRS (lhs is a list of :var nodes)
:type-decl NAME TYPE (type alias)
New helpers:
go-default-type — untyped-int → int, untyped-float → float64,
etc. Used when inferring var x = EXPR.
go-check-exprs-against — every expr assignable to the declared type.
go-bind-names-to-synth — pair names with default-typed synth of
corresponding exprs; extends ctx.
The canonical Go pitfall flows through end-to-end now:
(go-check-decl ctx (go-parse "var x float64 = 42 / 7"))
→ ctx + (x → float64)
Because: 42/7 synthesises to ty-untyped-int (binop result of two
untyped operands), then go-check-exprs-against uses go-type-assignable?
to check ty-untyped-int → ty-name "float64" — :ok via the
untyped-int-to-any-numeric assignability rule. The 6 (integer) result
gets float-converted on assignment, never floated mid-computation.
types 40/40, total 345/345.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 cont. Adds:
* go-classify-literal-string — heuristic detection of literal kind
from the value-string (parser strips lexer's kind tag; flagged for
follow-up to extend AST shape).
* go-synth-literal — :ty-untyped-int / -float / -imag / -string.
* go-synth-binop — arithmetic, bitwise, comparison, logical ops with
untyped-constant unification:
untyped-int + untyped-float → untyped-float
untyped + typed → typed
comparison ops → bool
logical ops → bool
* go-untyped? + go-type-assignable? — pluggable assignability that
swaps in where structural equality used to gate go-check. Untyped
int assignable to any numeric type; untyped float assignable to
float/complex; untyped string to string.
**Canonical Go pitfall handled correctly**: `var x float64 = 42 / 7`
parses to a binop, synth produces :ty-untyped-int (since BOTH operands
are untyped, the int division stays in the int domain), and check
against float64 returns :ok via assignability. Wrong implementations
that float-coerce eagerly would give 6.0; the right behaviour is
"compute 6 as int, then convert to float64 = 6.0".
Verified by test "binop: 42 / 7 assignable to float64 (canonical
pitfall)" and the type-only test "binop: 42 / 7 — untyped int".
Sister-plan static-types-bidirectional diary updated with the
**pluggable-assignable-predicate** kit-API proposal:
(check-with assignable? CTX EXPR EXPECTED)
Each consumer plugs in its own variance discipline (Go untyped-flow,
TS structural subtyping, Rust lifetime-aware identity) without
rewriting synth or the judgment skeleton.
types 28/28, total 333/333.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First slice of Phase 3 (bidirectional type checker).
lib/go/types.sx defines:
* go-ctx-empty / go-ctx-extend / go-ctx-lookup — context as a value.
* go-ctx-extend-field — consumes the (:field NAMES TYPE) shape from
the parser, binding every name to the shared type. This is the
cross-deliverable validation of the :field binding-group
observation made during Phase 2 func decls: parser produces it,
type checker consumes it, same shape end-to-end.
* go-predeclared — true / false / nil baked in. Full list expanded
on demand.
* go-synth — currently handles variable lookup; literals / calls /
binops follow in subsequent iterations.
* go-check — v0 defers to synth + structural type equality. Untyped-
constant flow and assignment-compatibility relations land later.
* Type errors carry first-class tags (:unbound, :mismatch,
:unsupported-synth) so consumers and tooling can dispatch.
Conformance.sh wired with new types suite. Scoreboard cleanup: drop
the "pending" types row since the suite is now real.
types 12/12, total 317/317. Phase 3 underway.
Sister-plan static-types-bidirectional diary updated with the
synth/check shape: judgment skeleton, error tag structure, and the
proposal that `check` should accept a `subtype?` predicate parameter
so each consumer (Go untyped-constants, TS variance, Rust lifetimes)
plugs in its own variance discipline without rewriting the judgment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>