go: types.sx — literal synth + binop + assignability; canonical pitfall handled + 16 tests [shapes-static-types-bidirectional]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s

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>
This commit is contained in:
2026-05-27 20:46:03 +00:00
parent 6c3b7d1cf9
commit 86ddaf255c
6 changed files with 348 additions and 24 deletions

View File

@@ -217,11 +217,17 @@ Progress-log line → push `origin/loops/go`.
- [x] Scaffold: `go-synth` / `go-check` skeletons; context-as-value
(`go-ctx-empty` / `-extend` / `-lookup` / `-extend-field`);
predeclared `true`/`false`/`nil`; structural type equality.
- [ ] Literal kinds in AST (parser change: `(:literal KIND VALUE)`)
+ literal synth (`:ty-untyped-int`/`-float`/`-string`/`-rune`).
- [ ] Binary-op synth with untyped-constant flow (canonical pitfall:
`var x float64 = 42 / 7` must compute as untyped int / int = 6,
then convert to float64).
- [/] Literal synth: heuristic kind detection from value strings
(`go-classify-literal-string`) → `:ty-untyped-int`/`-float`/
`-imag`/`-string` (`-rune` deferred — value-shape ambiguous with
single-char string). Parser-shape change to `(:literal KIND VALUE)`
flagged as future work; the heuristic stopgap avoids breaking 66
existing parse tests.
- [x] Binary-op synth with untyped-constant flow. **Canonical pitfall
handled**: `42 / 7` synthesises to `:ty-untyped-int`, then checks
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`).
- [ ] Function declaration: extend ctx with params via `:field` group,
@@ -233,7 +239,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: 12/12. Chisel note
- **Acceptance:** types/ suite at 60+ tests. Current: 28/28. Chisel note
`shapes-static-types-bidirectional` — sister-plan design diary is the
cross-language record.
@@ -534,6 +540,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.: literal synth + binop synth + assignability.
Heuristic `go-classify-literal-string` decodes the parser's untagged
literal values back into `:int`/`:float`/`:imag`/`:string` kinds
(rune defers); these become `:ty-untyped-*` types. `go-synth-binop`
handles arithmetic / bitwise / comparison / logical operators with
untyped-constant unification: untyped int + untyped float → untyped
float; untyped + typed → typed. **Canonical Go pitfall now handled**:
`42 / 7` synthesises to `:ty-untyped-int`, then `go-check` against
`float64` returns `:ok` via `go-type-assignable?`. +16 tests, types
28/28, total 333/333. `[shapes-static-types-bidirectional]` — sister
plan diary updated with the assignable-relation insight (kit's
`check` should accept a `subtype?`/`assignable?` predicate parameter).
- 2026-05-27 — **Phase 3 scaffold.** First `lib/go/types.sx` cut: context
as an association list (`go-ctx-empty` + `-extend` + `-lookup`), a
load-bearing `go-ctx-extend-field` that consumes the `:field` binding-

View File

@@ -282,6 +282,39 @@ The kits compose; design accordingly.
_Newest first. Append one dated entry per milestone landed._
- 2026-05-27 — Follow-up from Phase 3 scaffold: **assignability** has
landed as a separate relation from structural equality. Go's
untyped-constant flow (`var x float64 = 42 / 7` — 42/7 stays untyped
int, then converts to float64) is one instance of a broader pattern:
the value's "natural" type isn't quite the slot's type, but they're
compatible under a per-language relation.
**Design insight for the kit**: `check` should *not* call `equal?`
on the synthesised vs expected types. It should call a pluggable
`assignable?` predicate that each consumer supplies:
```
(check CTX EXPR EXPECTED) →
let GOT = (synth CTX EXPR)
if (assignable? GOT EXPECTED) :ok else (:mismatch EXPECTED GOT)
```
Go's `assignable?` handles untyped constants → numeric-type
conversion. TS would supply structural subtyping (`{a: number, b:
string}` assignable to `{a: number}`). Rust supplies lifetime-aware
type identity with implicit `&T -> &U` where `T: Deref<U>`. None of
the consumers need to rewrite synth or the judgment skeleton — only
swap in their variance discipline.
Concretely the kit interface looks like:
```
(check-with assignable? CTX EXPR EXPECTED) — kit primitive
```
Source: Go-on-SX commit landing `go-type-assignable?` in
`lib/go/types.sx`.
- 2026-05-27 — From Go-on-SX Phase 3 scaffold (`lib/go/types.sx` first
cut): the **independent synth/check shape** has landed. Two judgments,
both consuming a context-as-value: