From 1ea08e4f80983121101153d952e9c8315d1eca51 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 23 Jul 2026 15:42:03 -0500 Subject: [PATCH] Helper returns synthesize helpers_: the member slot is the helper name - The return shapeRef now seeds container 'helpers' with the helper name as the member, so an inline-record helper return tables helpers_ instead of helpers__return; parameter naming (helpers__) is unchanged - The contract fixture gains 'extent', a helper returning an inline record, pinning the synthesized name helpers_extent in the type table and the value-kind helper return with the arena bit - The anti-alphabetical suite asserts the new struct entry and helper row on both emissions; V13 byte-determinism stays green --- packages/compiler/src/library/sidecar.ts | 5 ++++- tests/harness/library-contract.test.ts | 13 ++++++++++++- tests/library-mode/contract/lib.ts | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/library/sidecar.ts b/packages/compiler/src/library/sidecar.ts index 02eb625f0..e98dd4a44 100644 --- a/packages/compiler/src/library/sidecar.ts +++ b/packages/compiler/src/library/sidecar.ts @@ -593,7 +593,10 @@ export function buildSidecar(input: SidecarBuildInput): SidecarBuildResult { if (fn.returns.k === "void") { throw new SidecarError(`helper '${fn.name}' returns void — a contract helper returns a value the host can read`, fn.loc); } - const returns = projector.shapeRef(fn.returns, `helpers_${fn.name}`, "return", fn.loc); + // Helper-return synthesized names are two-part like everything else: + // container 'helpers', member the helper's name — `helpers_`, + // never a '_return' suffix (the ratified spelling). + const returns = projector.shapeRef(fn.returns, "helpers", fn.name, fn.loc); if (helpers.some((h) => h.name === fn.name)) { throw new SidecarError(`helper '${fn.name}' is declared twice`, fn.loc); } diff --git a/tests/harness/library-contract.test.ts b/tests/harness/library-contract.test.ts index f8a3f267a..3e6a19c39 100644 --- a/tests/harness/library-contract.test.ts +++ b/tests/harness/library-contract.test.ts @@ -115,7 +115,7 @@ describe.each(EMISSIONS)("contract sidecar, %s emission", (emission) => { // The type table: declaration order everywhere, exactly the reachable // set, synthesized entries anchored at their containing declaration. - expect(doc.types.structs.map((s) => s.name)).toEqual(["Waypoint", "Shift", "Model", "Msg_blob_tag", "Msg_nudge"]); + expect(doc.types.structs.map((s) => s.name)).toEqual(["Waypoint", "Shift", "Model", "Msg_blob_tag", "Msg_nudge", "helpers_extent"]); expect(doc.types.enums.map((e) => e.name)).toEqual(["Zone"]); expect(doc.types.unions.map((u) => u.name)).toEqual(["Route"]); expect(doc.types.enums[0]!.members).toEqual(["west", "north", "east"]); @@ -135,6 +135,16 @@ describe.each(EMISSIONS)("contract sidecar, %s emission", (emission) => { { name: "status", type: { kind: "f64" } }, ], }); + // Helper-return synthesized names are the same two-part pattern: + // container 'helpers', member the helper name — no '_return' suffix. + expect(doc.types.structs[5]).toEqual({ + name: "helpers_extent", + synthesized: true, + fields: [ + { name: "span", type: { kind: "f64" } }, + { name: "first", type: { kind: "bytes" } }, + ], + }); expect(doc.types.unions[0]!.arms).toEqual([ { name: "warp", payload: { kind: "node", name: "Waypoint" } }, { name: "step", payload: { kind: "value", name: "Shift" } }, @@ -169,6 +179,7 @@ describe.each(EMISSIONS)("contract sidecar, %s emission", (emission) => { { name: "waypointsOf", params: [], returns: { kind: "slice", elem: { kind: "node", name: "Waypoint" } }, arena: true }, { name: "headline", params: [], returns: { kind: "bytes" }, arena: true }, { name: "waypointCount", params: [], returns: { kind: "f64" }, arena: false }, + { name: "extent", params: [], returns: { kind: "value", name: "helpers_extent" }, arena: true }, ]); // Helpers are bindable surface: the unbound list may name one. expect(doc.model_unbound).toEqual(["title", "waypointCount"]); diff --git a/tests/library-mode/contract/lib.ts b/tests/library-mode/contract/lib.ts index 7a4f16de0..7688d12bd 100644 --- a/tests/library-mode/contract/lib.ts +++ b/tests/library-mode/contract/lib.ts @@ -95,6 +95,13 @@ export function waypointCount(m: Model): number { return m.waypoints.length; } +// An inline-record return: the synthesized name is two-part like every +// other (`_`) — container `helpers`, member the helper +// name — so this tables `helpers_extent`, never a `_return` suffix. +export function extent(m: Model): { span: number; first: string } { + return { span: m.speed, first: m.title }; +} + // The ratified exported-const conventions (under an embedder's profile a // generated facade emits these; nothing here is host code). export const modelUnbound = ["title", "waypointCount"];