Skip to content

Feat/runner array length#476

Closed
cukas wants to merge 3 commits into
mainfrom
feat/runner-array-length
Closed

Feat/runner array length#476
cukas wants to merge 3 commits into
mainfrom
feat/runner-array-length

Conversation

@cukas

@cukas cukas commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What

Why

How

Checklist

  • tsc -b passes
  • pnpm test passes
  • pnpm test:kern passes
  • pnpm lint passes
  • kern review packages/ --recursive checked

The discriminating oracle for the array `.length` read slice, authored BEFORE
the impl so the runner change is built to this spec. RED-at-base: the certify
cases abstain today (the `member` case admits only a caught-error `.message`);
they turn green only when the runner reads array length. The fences already pass
(abstaining is the correct fail-close at base).

- packages/core/tests/runner-array-length.test.ts (new) — direct ReferenceRunner
  spec: 6 certify cases (count / empty-0 / nested TOP-LEVEL count / arithmetic
  flow / scalar bind / parenthesized receiver) + 14 fail-close fences (string,
  ASTRAL string, number, boolean, rebound, array-literal receiver, index-position
  receiver, chained member, non-`length` member, optional, computed, unbound, and
  two direct evalPortableValue checks).
- packages/python/tests/kern-run-cli-differential.test.ts — 4 `.length` CERT
  entries (count / empty / nested top-count / arithmetic), every byte verified on
  real node + python3 via the emitter probe.
- packages/cli/tests/run.test.ts — CLI exit-code contract: `.length` happy
  (count / empty / arithmetic, exit 0) + abstain (string / astral / optional /
  computed / non-`length` member, exit 2). Module doc updated.

Only ARRAY `.length` certifies: a STRING receiver abstains because JS `.length`
counts UTF-16 code units while Python `len()` counts code points — `"😀".length`
is 2 in JS, 1 in Python (verified). Reference-only — the emitters already lower a
portable array `.length` to `len(...)` (Python) / `.length` (TS).

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
The ReferenceRunner — the executor `kern run` uses — now READS an array's
ELEMENT COUNT, so a program can ask how big an array is natively:

    let xs = [1,2,3]   ->   print xs.length   ->   3

This continues the array slices (2a literal binding + each, 2b literal index).
An array branch is added to the `member` case of `evalPortableValue`, BEFORE the
caught-error `.message` handling and behind the SAME fences: a `.length` read
certifies ONLY as a non-optional `.length` on a BARE IDENTIFIER bound to a
portable array, and returns the element count — a non-negative safe integer.
Everything else throws -> the runner ABSTAINS.

Only ARRAYS certify. A STRING `.length` must NOT: JS `.length` counts UTF-16
code units while Python `len()` counts code points, so `"😀".length` is 2 in JS
and 1 in Python (verified on real node + python3). `Array.isArray` excludes
strings, numbers, booleans, decimals, and caught errors, so every non-array
receiver falls through and abstains. Non-ident receivers (`[1,2,3].length`,
`xs[0].length`, `xs.length.foo`), optional (`xs?.length`), computed
(`xs['length']`), a non-`length` member (`xs.foo`), rebound, and unbound idents
all abstain too.

Reference-only change: an abstaining program is never certified and never
reaches the emitter differential. The emitters ALREADY lower a portable array
`.length` to `len(...)` (Python) / `.length` (TS), so no codegen change is
needed; the 3-leg differential passes as-authored. The length value is an
ordinary safe integer, so it flows through arithmetic / comparison / binding /
print with no further hazard. `evalPortableValue` is internal to the
ir/semantics runner layer (not re-exported), so `.length` extends only the
runner's scalar contexts (print / if / while / fmt / assign), consistently
fenced.

The slice-2a `.length access abstains (deferred)` fence is updated to assert a
STRING `.length` abstains (array `.length` now certifies) — mirroring how
slice-2b relaxed slice-2a's in-bounds index fence.

Gate: array-length spec 20/20 + array-values 13/13 + array-index 25/25 +
runner-entry-import-graph + ir-semantics neighborhood 164/164 + CLI run.test
52/52 + 3-leg differential 23/23 (0 skipped) + biome + docs:contracts:check.

Planned with agon brainstorm; built by codex via agon conquer against the
RED-at-base oracle; reviewed by the full agon roster.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…fence coverage

Full-roster agon review (4 engines clean; kimi 2 important + nits). Triaged:
- [important 0.90] Member case lacked a hasBinding check (unlike the sibling
  index case), so an UNBOUND receiver failed with the generic "outside portable
  scalar domain" instead of a precise "binding not found". Still abstained either
  way — diagnostics, not soundness — but now consistent with the index case.
- [important 0.60] Parenthesized-receiver test "may not match parser" — REFUTED:
  `parseExpression("(xs).length")` yields a member on a bare ident (verified) and
  the test is green; it documents real, sound certifying behavior. Kept.
- nits adopted: NULL and DECIMAL (tagged-object) receiver fences added, a
  nested-array TOP-LEVEL-count CLI happy test added, and the direct-helper test
  renamed from "abstains" to "throws" (abstain is runner-level terminology).

Gate: array-length 24/24 + array-values 13/13 + array-index 25/25 + CLI 53/53 +
biome. No behavior change to certified output; abstain outcomes unchanged.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
@cukas cukas force-pushed the feat/runner-array-length branch from 9702c00 to 4c039cb Compare June 25, 2026 15:27
@cukas cukas closed this Jun 25, 2026
@cukas cukas deleted the feat/runner-array-length branch June 25, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants