Corpus stdlib redo (Buffer/StringBuf as CkLang), with/with mut + Size u16 locks #29

Manually merged
tvidal merged 5 commits from feat/029-corpus-stdlib-redo-buffer-stringbuf-as-cklang into master 2026-02-05 09:30:00 +01:00
Owner

Corpus stdlib redo (Buffer/StringBuf as CkLang), with/with mut + Size u16 locks

Along the way:

  • corpus(stdlib): lift deferred op surfaces into real code
  • design+corpus: lock with/with mut, redo Buffer as CkLang over the floor
  • corpus(stdlib): redo StringBuf as CkLang; read size as the record field
  • chore: gitignore machine-local .mcp.json
  • types: widen Size to u16, resolve the u8/usize width flags, lock it

Touches 17 files: .gitignore, docs/actors.md, docs/impl/memory-model/capabilities.md, docs/impl/type-system/value-parameters.md, docs/records.md, docs/syntax.md and 11 more.

Corpus stdlib redo (Buffer/StringBuf as CkLang), with/with mut + Size u16 locks Along the way: - corpus(stdlib): lift deferred op surfaces into real code - design+corpus: lock `with`/`with mut`, redo Buffer as CkLang over the floor - corpus(stdlib): redo StringBuf as CkLang; read `size` as the record field - chore: gitignore machine-local .mcp.json - types: widen Size to u16, resolve the u8/usize width flags, lock it Touches 17 files: `.gitignore`, `docs/actors.md`, `docs/impl/memory-model/capabilities.md`, `docs/impl/type-system/value-parameters.md`, `docs/records.md`, `docs/syntax.md` and 11 more.
Author
Owner

Split. The decision in freeze is a pure function now, with its own tests in tests/corpus/stdlib/Buffer.ck.

Split. The decision in `freeze` is a pure function now, with its own tests in `tests/corpus/stdlib/Buffer.ck`.
tvidal approved these changes 2026-02-05 09:25:00 +01:00
tvidal left a comment

Yes. The commit messages carried me through all 17 files, which is not always true.

Yes. The commit messages carried me through all 17 files, which is not always true.
@ -74,0 +132,4 @@
// (buf dead after) is enforced by the ownership pass (M4), not by a copy.
// `List<T, size>` is NOT split into empty/non-empty (the deliberate non-
// uniformity with String) — an empty buffer freezes to `[]`.
fn freeze<T, size: Size>(buf: Buffer<T, size>) -> List<T, size> {
Owner

freeze reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?

`freeze` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
dferreira approved these changes 2026-02-05 09:25:00 +01:00
dferreira left a comment

Read it too. No objection to tests/corpus/stdlib/Buffer.ck.

Read it too. No objection to `tests/corpus/stdlib/Buffer.ck`.
The "DEFERRED — parser can't" comments were largely stale; the grammar
has since grown. Verified each claim empirically, then wrote the locked
surfaces as code:

- Error.ck: real `trait Error { hint, site, stacktrace, cause }` (trait
  fields parse since slice-8; the E0040 claim was false). Resolves clean.
- Buffer/StringBuf/Bytes/String: full op surfaces as `fn … { intrinsic }`,
  mirroring the Memory.ck floor — opaque-newtype ops are intrinsic, their
  storage is unspeakable in CkLang.
- Arena.ck: restored the OOM bound check as the tail `match` (success in
  the `false` arm); its cited E0062/E0050 blockers were stale, the real
  one is statement-position `match` (E0036).
- Span.ck: corrected stale grammar rationale to the true design blockers.
- trap.md: rewritten, strict-exhaustive reconcile green. Every new trap is
  the same ratchet class (`intrinsic` → E0301), retiring together when
  codegen learns to bind `intrinsic`.

Still deferred on design, not grammar: Assert::any (function type),
String::slice/StrSpan (span fork), Span get/to_owned (undesigned
OutOfBounds + bound fork), Panic test mock.
The mutation model. CkLang gets ONE mutation operator and no bare `=`:
- `let x` binds `imm`, `let mut x` binds `uniq` (the capability reaching the
  surface without spelling the full lattice);
- `x with { f: v }` copies (any binding); `x with mut { f: v }` mutates in
  place (uniq only, returns Unit); `x with mut <expr>` replaces the whole
  value. `with mut` on a non-mut binding is a compile error (M4 enforces).
The split puts copy-vs-mutate cost in the source, greppable as `with mut` —
never an optimizer's invisible choice.

Docs:
- syntax.md: new canonical "Mutation: `with` / `with mut`" section.
- actors.md: fold the bare-`=` actor-state carve-out into `with mut`; state
  is just a `uniq` binding, not special syntax.
- records.md: `with mut` documented as the in-place sibling of `with`.
- capabilities.md: note the locked surface (let/let mut/with/with mut); the
  lattice semantics + checker stay the proposed part.

Buffer redone as real CkLang (the first worked "stdlib is CkLang" example):
- `newtype Buffer` -> `record Buffer { region, len }`. Rule that falls out:
  `newtype` is only for the irreducible floor (Region/Handle); anything
  implementable in CkLang is a `record` over it, opacity via field privacy.
- new/push/push_all/len/clear are CkLang over the Memory floor + `with mut`;
  only `freeze` (the uniq->imm transition, minting an opaque List) stays
  `intrinsic`, and reduces to a record move once List lands.
- trap.md rewritten; reconcile green. New transient gaps trapped and labelled:
  cross-module floor refs, `size` in value position (evidence for Size=usize),
  and the not-yet-parsed `with mut`.
StringBuf becomes the Buffer twin: `record StringBuf { region, len }` over the
Memory floor; new/push/push_str/len/clear are real CkLang with `with mut`.
Two ops stay `intrinsic`: `freeze` (the uniq->imm transition, reduces to a
record move once String lands) and `encode_utf8` (UTF-8 encode is mask/shift
bit math and CkLang has no bitwise operators yet, so the encode is below
CkLang — reduces to ordinary CkLang once they land).

Buffer + StringBuf both read the bound as the `buf.size` FIELD (the lowercase
type param `size: Size` is itself an immutable field — five-keyword carve-up),
which resolves clean; only `new` still needs the bare type-level value to size
the allocation (the lone const-generic-in-value gap, ties to Size=usize,
deferred). `size` is never mutated — only `len` is `with mut`-patched.

trap.md reconciled green; intro rewritten — `intrinsic` is now per-reason
(floor / transition / no-bitwise-ops / unlanded builtin), not "opaque newtype".
Claude Code's MCP server config hard-codes an absolute home path and a
per-session socket UUID, so it is local state, never shareable repo content —
the same class as .claude/settings.local.json.
`Size` is the type every bounded-collection / String size argument is
written in (`String<256>`, `List<Int, 12>`, `Buffer<T, size>`). It was
aliased to `u8` (cap 255) but the stdlib and docs already used `<256>`
bounds — `String<256>`, `List<String<256>, 8>` — which a `u8` cannot hold.

Two competing flags sat in the tree: Primitives.ck flagged toward `u16`
(the `<256>` problem); Buffer.ck/trap.md flagged toward `usize` (`new`
uses the bound arithmetically). Both resolve toward `u16`: `Size` is a
type-level identity, so the compact two-byte slot wins and Buffer's few
arithmetic sites widen `Size`->`usize` explicitly rather than `Size`
itself growing to eight bytes.

- corpus: Bounded.ck (canonical decl + rationale), Primitives.ck
  (FLAG -> dated WIDTH lock), Buffer.ck (usize note rewritten),
  Aliases.ck (stale `size: u8` comment)
- docs: types.md, traits.md, value-parameters.md (u8 -> u16, and stale
  `String<size: u8>` -> `String<size: Size>`)
- trap.md: ratchet Buffer.ck coords +2 (the note grew two lines)
tvidal manually merged commit 2826fe553d into master 2026-02-05 09:30:00 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!29
No description provided.