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
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:
@@ -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-
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user