feat(function-calls): FC-a — records cross the fn boundary by value (plan slice 5, first half) #65

Manually merged
mdoorn merged 7 commits from feat/065-feat-function-calls-fc-a-records-cross-the-fn into master 2026-03-24 11:55:54 +01:00
Owner

feat(function-calls): FC-a — records cross the fn boundary by value (plan slice 5, first half)

Along the way:

  • feat(function-calls): FC-b — mixed unions cross the fn boundary by value (plan slice 5, second half)
  • feat(values): V4 — String joins the one nominal law; Ty::String/Ty::EmptyString DELETED (debt #3 closed)
  • docs: session handoff — function-calls slice (FC-a+FC-b) and values slice (V1..V4) COMPLETE; debt #3 closed; §1b two-turbofish escalation OPEN; alloc slice next (unblocked); debt register at 21 live entries
  • docs(alloc): positional Region(a,b) was a spelling error (owner ruling 2026-07-17) — Alloc.ck corrected to brace construction, Floor:: call spelling, explicit :: pins; slice_alloc.md dependency (a) restated; Floor.ck header quotes updated
  • docs(values): §1b clarified per owner rulings 2026-07-17 — value-receiver rule (:: vs <type|module>::, resolved at resolution), alias-carries-its-bound coercion targets (String IS String<64>; List spells its args), populated re-bound is a one-way fit-gated coercion
  • fix(lower/codegen): L1 — the record lowering law becomes TOTAL over the type-checked expression grammar (root causes A, B, E; debt #18 CLOSED)

Touches 122 files: Alloc.ck, handoff.md, plan_values_02.md, slice_alloc.md, src/rust/crates/ck_codegen/src/call.rs, src/rust/crates/ck_codegen/src/indirect.rs and 116 more.

feat(function-calls): FC-a — records cross the fn boundary by value (plan slice 5, first half) Along the way: - feat(function-calls): FC-b — mixed unions cross the fn boundary by value (plan slice 5, second half) - feat(values): V4 — String<N> joins the one nominal law; Ty::String/Ty::EmptyString DELETED (debt #3 closed) - docs: session handoff — function-calls slice (FC-a+FC-b) and values slice (V1..V4) COMPLETE; debt #3 closed; §1b two-turbofish escalation OPEN; alloc slice next (unblocked); debt register at 21 live entries - docs(alloc): positional Region(a,b) was a spelling error (owner ruling 2026-07-17) — Alloc.ck corrected to brace construction, Floor:: call spelling, explicit ::<T> pins; slice_alloc.md dependency (a) restated; Floor.ck header quotes updated - docs(values): §1b clarified per owner rulings 2026-07-17 — value-receiver rule (<value>::<type> vs <type|module>::<member>, resolved at resolution), alias-carries-its-bound coercion targets (String IS String<64>; List spells its args), populated re-bound is a one-way fit-gated coercion - fix(lower/codegen): L1 — the record lowering law becomes TOTAL over the type-checked expression grammar (root causes A, B, E; debt #18 CLOSED) Touches 122 files: `Alloc.ck`, `handoff.md`, `plan_values_02.md`, `slice_alloc.md`, `src/rust/crates/ck_codegen/src/call.rs`, `src/rust/crates/ck_codegen/src/indirect.rs` and 116 more.
Author
Owner

Split. The decision in nm_all is a pure function now, with its own tests in src/rust/crates/ck_driver/tests/record_totality_run.rs.

Split. The decision in `nm_all` is a pure function now, with its own tests in `src/rust/crates/ck_driver/tests/record_totality_run.rs`.
Author
Owner

Named them all. A new variant is a compile error in src/rust/crates/ck_types/src/lift.rs now, which is where we want to find out.

Named them all. A new variant is a compile error in `src/rust/crates/ck_types/src/lift.rs` now, which is where we want to find out.
mdoorn requested changes 2026-03-24 08:30:00 +01:00
Dismissed
mdoorn left a comment

I do not want to merge while the error path in src/rust/crates/ck_driver/tests/record_totality_run.rs is silent. The rest is fine.

I do not want to merge while the error path in `src/rust/crates/ck_driver/tests/record_totality_run.rs` is silent. The rest is fine.
@ -0,0 +122,4 @@
}
/// `nm <binary>` output (all symbols; scanned for forbidden libc/`ck_rt` names).
fn nm_all(binary: &Path) -> String {
Owner

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

`nm_all` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
@ -189,0 +216,4 @@
let id = match head {
"String" => interner.string_id()?,
"EmptyString" => interner.empty_string_id()?,
_ => return None,
Owner

The catch-all arm means a variant added later compiles and lands here without a word. Can the match on _ name every variant so the compiler tells us?, at src/rust/crates/ck_types/src/lift.rs:219.

The catch-all arm means a variant added later compiles and lands here without a word. Can the match on `_` name every variant so the compiler tells us?, at src/rust/crates/ck_types/src/lift.rs:219.
Author
Owner

Updated. The point about src/rust/crates/ck_driver/tests/record_totality_run.rs turned out to be a real bug, so thank you.

Updated. The point about `src/rust/crates/ck_driver/tests/record_totality_run.rs` turned out to be a real bug, so thank you.
tvidal approved these changes 2026-03-24 10:02:43 +01:00
tvidal left a comment

Looked through src/rust/crates/ck_driver/tests/record_totality_run.rs as well. Fine by me.

Looked through `src/rust/crates/ck_driver/tests/record_totality_run.rs` as well. Fine by me.
mdoorn approved these changes 2026-03-24 10:59:19 +01:00
mdoorn left a comment

Both answered, and src/rust/crates/ck_driver/tests/record_totality_run.rs reads right now. Merging.

Both answered, and `src/rust/crates/ck_driver/tests/record_totality_run.rs` reads right now. Merging.
ADD (one generic law, TypeId-keyed, zero name arms): recursive
Ty::Nominal -> RecordLayout resolver (compile/record_abi.rs) snapshotted as
RecordLayouts and threaded like LeafReprs; ValueRepr::Record(Arc<RecordLayout>);
abi_types total over both snapshots; caller flatten (emit_call_arg) /
callee repack (bind_params fresh frame) / symmetric multi-word returns
(ReturnMode::ValueWords, emit_user_call_value repack); record_seed.rs
pre-registers record-typed locals. record_plan now delegates to the one
resolver (narrow Int/String-only copy deleted).

KILL (grep-proven): abi_types Nominal|Unknown => vec![pointer] blanket +
other => Err("...S1/FS4 frame...") arm + both defensive frame comments;
user_fns.rs multi-word-return refusal + is_scalar_return (whole dead subtree).

Gates: unit record_crosses_as_its_flattened_ordered_word_sequence (flat +
nested); corpus record_fn_cross (swap round-trip, exit 42, freestanding,
in-container); arch-test R5 (no type-name string literal in the ABI path),
red-proven twice (sub-agent + orchestrator, independently); LoC caps HELD
(ck_codegen 594/72, ck_lower 552/71 — not raised). fmt 0 / clippy 0 /
workspace exit 0 (147 test binaries), verified by the orchestrator.

Net: 40 files, +1044/-272 (production ~+679/-272 — additive by design per the
revised runbook: the word-list machinery did not exist; the 2026-07-16 runbook
revision records the measured-false original premise).

Owned debt (recorded): nested-record CROSSING is an honest hard error today
(codegen: unsupported Floor IR form) because construction keeps a nested
record as one frame-pointer word while the ABI flattens fully — closes when
construction inlines nested records; owner: function-calls follow-up.
union_copy_run untouched and green; union paths (lower_union_return,
copy_union_frame) stay alive for FC-b, next commit.
ADD (reuses FC-a's machinery whole — no second mechanism): a mixed-union
frame IS a tagged record (data is sugar), so build_union_layouts
(compile/maps.rs) represents each union_size frame as a word-granular
RecordLayout keyed by the union parent and every variant TypeId, merged into
the ONE RecordLayouts snapshot by the shared build_abi_layouts
(record_abi.rs) — both entry paths (collect_user_fns AND fn_main) route
through it so caller and callee agree on every frame. abi_types Ty::Union
arm: mixed -> frame words, all-bare -> one tag word (unchanged);
abi_layout_of generalizes record_layout_of by shape.

KILL (grep-proven, whole dead subtree): lower_union_return + its entry.rs
call (the arena-copy return), copy_union_frame, copy_union_words,
union_width_of. arena_alloc_local verified still live (str/list lowering).

Tests rewritten to the settled model (asserted the deleted arena copy):
union_arg_crosses_a_user_call_by_frame_copy -> ..._by_value (one ctor frame,
no Alloc, one arg word); union_return_copies_into_the_arena ->
union_return_crosses_by_value_no_arena_copy.

Gates: union_copy_run passes UNCHANGED (exit 42 — the by-value proof; fixture
untouched); record_fn_cross_run still green; arch-test R6 (banned union
arena-copy symbols) red-proven twice (sub-agent + orchestrator,
independently); LoC caps HELD (594/72, 552/71, 815/80 — no constant changed).
fmt 0 / clippy 0 / workspace exit 0 (147 test binaries), orchestrator-run.

Net production Rust: +144/-141 across 9 files (+229/-170 incl. tests).
plan.md slice 5 (function calls) is now COMPLETE: FC-a acd3f07 + this commit.
Values-V4 (String<N> collapse) unblocks next.
KILL (grep-proven, 0 production hits): the Ty::String / Ty::EmptyString
variants (ck_types/src/ty.rs) and builtin_ty's variant-fabricating rows.
String is now ordinary Ty::Nominal{String,[Value N]}; EmptyString
Ty::Nominal{EmptyString,[]}. Recognition everywhere is by well-known TypeId
(Interner::string_id/empty_string_id, registered once at resolve from the
DECLARED corpus name — the sanctioned binding that dies with the literals
slice), threaded through ck_lower/ck_codegen like FC-a's RecordLayouts.
ValueRepr::Str survives as the two-word handle shape. No name-string probe
in any ABI/lowering path (arch-test V3 enforces).

ADD: live corpus tests/corpus/stdlib/Types.ck grown to the base roster —
newtype String<size: Size> = (| position: usize, len: usize |) (V3c rep),
the LOCKED same-name alias type String = String<64> (V3b arity law),
record EmptyString {}, List/Map/Set; composes trimmed (owned divergence,
traits slice). Global well-known resolution (well_known_value_type) so every
module agrees on one String TypeId. The pS1b coercion turbofish wired for
string literals via the existing generic value_turbofish path:
"world"::<String> / ::<String<64>>, fit-gated by exact byte measure (§1.4),
""::<String<N>> per §2a, E0715/E0739. "hello" -> String<8> by the 2^k
bucket; "" -> EmptyString.

Arch-test V3 (plan_parameters §8.6): a parameterized stdlib value type
reaching machine code through a type-name literal in the ABI decision path
is a build failure — error-level, red-proven twice (sub-agent + orchestrator
independently, Edit-reversal probes). Architecture suite 14/14.

Corpus/goldens: ~20 call sites coerced ::<String> to meet the strict
String<64> boundary (§1a: the fn boundary never coerces — the law's own
accepted verbosity); cktest goldens re-specified from real output; untrapped
ratchet back at exactly 112 (cap unchanged); greeter .meta regenerated
(291 bytes, only the 20-byte span shifts).

Owned debt (comments in place): (1) transitional +-as-concat result =
String<64> — operators/traits slice; (2) Panic::raise hint accepts any
String<N> — traits slice; (3) §1b letter-form coercion (x::Name<...>)
unimplemented language-wide, corpus spelled ::<T> meanwhile — ESCALATED to
owner (doc-vs-code conflict, likely entangled with path syntax); (4) stale
doc-comment references to the deleted variants (narrow.rs, mock.rs) — Haiku
doc sweep. Debt #17 not absorbed.

Gates (orchestrator-run): fmt 0 / clippy -D warnings 0 / cargo test
--workspace exit 0 (147 test binaries; soft-assert stdout noise only).
Net: 89 files +1039/-295 (production +818/-204 — honest report: the
id-registry + threading is the price of String-as-nominal, FC-a precedent).

The values slice (V1..V4) and the function-calls slice are now COMPLETE.
Records are brace-constructed by design; no positional record constructor
exists in the language. The authored Alloc.ck (2026-07-11 draft) and the
runbook's measured-additions bullet carried the erroneous spelling.
Debt #21's four open questions ruled; the §1b surface slice is now
dispatchable closed. Grammar disambiguation is resolve-time by what the
head names; no case convention is enforced.
A — layout law: value_repr's _ => None becomes a recorded-type fallback
(ty_repr + a Record(row) classification, TypeId-keyed); any shape-less
node (call, transitional record, record-typed projection) now classifies.

Q1.2 — one recursive record materializer (new ck_codegen/record_value.rs):
record literal -> fresh frame; record-returning call -> repacked base;
record-typed load/projection -> in-place sub-address; nested records lay
out INLINE (store_record_flat). Extracted from value.rs (net -66 there);
arch caps R3/R4 held without raising (worst files unchanged/shrunk).

B — cross-module fns no longer lower against EMPTY ABI tables:
lower_cross_module_fns builds the real build_abi_layouts once and threads
it through; provider bodies build their variant facts. Pure threading.

E — record-returning '= expr' callee fell out of A (binder classifies
from recorded type); record_seed.rs unchanged.

Also: let-bound list locals index generically (list_locals, the list twin
of record_locals) — the list_call_elem probe's second half.

Contract: 11/14 measured rows green (orchestrator re-ran every probe);
record_totality + record_cross_module corpus stones land the coverage
(discriminating exits, registered in RUNTIME_TESTED). Debt #18
(nested-record crossing) CLOSED — probed both directions.

Remaining, precisely bounded (-> L4 slice): floor-declared record types
(Region) resolve to Unknown/inconsistent TypeId in fn-body typecheck, so
probe_roff/probe_rproj still refuse — a resolve/signature-lifting defect,
NOT the lowering law. probe_ab's impl-half is a fixture defect (undefined
type in the probe), its header-half passes.

Gates (orchestrator-run): fmt 0, clippy -D warnings 0, workspace exit 0,
architecture 14/14.
mdoorn manually merged commit 47afac2072 into master 2026-03-24 11:55:54 +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!65
No description provided.