Files
vercel-labs--zero-native/packages/core/test/conformance.test.ts
T
Chris Tate 41c4cdc47a feat(files): add delete file effects (#350)
* feat(files): add delete file effects

- Expose Cmd.deleteFile as the primary TypeScript API with checked routing and documentation.
- Add real, fake, permission, journal, and replay support through the shared file-effect engine.
- Cover compiled cores across macOS, Linux, and Windows, including reliable Windows append behavior.

* fix(files): await Windows append completion

* fix(files): preserve final symlink on delete
2026-08-13 21:00:48 -05:00

10379 lines
328 KiB
TypeScript

// Conformance corpus: the frontend's contract is that every subset rule
// teaches at check time with a named rule, and everything else checks
// clean. Each case is a small subset-legal module exercising a
// type-boundary combination (literal unions x comparisons x assignments
// x args/returns x elements x integer inference). A case either checks
// CLEAN — the external core compiler carries it from there (compile
// truth lives in the SDK's ts-core e2e batteries over real archives) —
// or is GATED by a named teaching rule at check time. Cases marked
// `formerEmitGate` were refused by the removed TS-to-Zig emitter's own
// re-derivations; they check clean now and ride the external compiler's
// semantics, diagnostics, and run-time traps.
import test from "node:test";
import assert from "node:assert/strict";
import { check, checkFiles } from "./helpers.ts";
interface Case {
readonly name: string;
/// Manifest-derived checker inputs needed by this fixture.
readonly checkOptions?: Parameters<typeof check>[1];
/// Expected teaching rule; omitted means the case must check clean.
readonly gate?: string;
/// The rule the REMOVED TS-to-Zig emitter used to teach at emission
/// (its layer-3 re-derivations and v1 deferrals). The frontend now
/// accepts these cases — the external core compiler carries them with
/// its own semantics, diagnostics, and run-time traps — so the case
/// must check clean; the field keeps the classification readable.
readonly formerEmitGate?: string;
readonly src: string;
}
const enumCases: Case[] = [
{
name: "cross-enum equality, superset filter over category",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export interface Expense { readonly id: number; readonly category: Category; }
export interface Model { readonly expenses: readonly Expense[]; readonly filter: CategoryFilter; }
export type Msg =
| { readonly kind: "set_filter"; readonly filter: CategoryFilter }
| { readonly kind: "clear" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "set_filter": return { ...model, filter: msg.filter };
case "clear": return { ...model, filter: "all" };
}
}
export function visible(model: Model): readonly Expense[] {
return model.expenses.filter((e) => model.filter === "all" || e.category === model.filter);
}
`,
},
{
name: "cross-enum inequality",
src: `
export type Small = "a" | "b";
export type Big = "a" | "b" | "c";
export function differs(s: Small, b: Big): boolean {
return s !== b;
}
`,
},
{
name: "cross-enum against an optional of the other union",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function matches(category: Category, filter: CategoryFilter | null): boolean {
return filter === null || category === filter;
}
`,
},
{
name: "cross-enum with both sides optional (null equals only null)",
src: `
export type Small = "a" | "b";
export type Big = "a" | "b" | "c";
export function same(s: Small | null, b: Big | null): boolean {
return s === b;
}
`,
},
{
name: "cross-enum equality with partial overlap",
src: `
export type Left = "x" | "y";
export type Right = "y" | "z";
export function overlap(l: Left, r: Right): boolean {
return l === r;
}
`,
},
{
name: "cross-enum assignment: subset value into superset field",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export interface Model { readonly filter: CategoryFilter; }
export type Msg =
| { readonly kind: "pick"; readonly category: Category }
| { readonly kind: "clear" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "pick": return { filter: msg.category };
case "clear": return { filter: "all" };
}
}
`,
},
{
name: "cross-enum function argument",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function isShown(filter: CategoryFilter): boolean {
return filter !== "all";
}
export function categoryShown(category: Category): boolean {
return isShown(category);
}
`,
},
{
name: "cross-enum function return",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function asFilter(category: Category): CategoryFilter {
return category;
}
`,
},
{
name: "cross-enum array literal element",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function withAll(category: Category): readonly CategoryFilter[] {
return [category, "all"];
}
`,
},
{
name: "cross-enum in ternary branches",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function pick(category: Category, everything: boolean): CategoryFilter {
const filter: CategoryFilter = everything ? "all" : category;
return filter;
}
`,
},
{
name: "reversed literal comparison (literal on the left)",
src: `
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function unfiltered(filter: CategoryFilter): boolean {
return "all" === filter;
}
`,
},
{
name: "reversed kind comparison (literal on the left)",
src: `
export type Msg = { readonly kind: "add" } | { readonly kind: "remove" };
export function isAdd(msg: Msg): boolean {
return "add" === msg.kind;
}
`,
},
{
name: "optional enum against its own literal",
src: `
export type Category = "food" | "travel" | "gear";
export function isFood(category: Category | null): boolean {
return category === "food";
}
`,
},
{
name: "same enum, optional against plain (regression)",
src: `
export type Category = "food" | "travel" | "gear";
export function sameCategory(a: Category | null, b: Category): boolean {
return a === b;
}
`,
},
{
name: "relational comparison on literal-union values is taught, not emitted",
gate: "NS1004",
src: `
export type Rank = "bronze" | "silver" | "gold";
export function before(a: Rank, b: Rank): boolean {
return a < b;
}
`,
},
{
name: "number-literal unions compare across aliases (shared integer repr)",
src: `
export type Bit = 0 | 1;
export type Level = 0 | 1 | 2;
export function matches(b: Bit, l: Level): boolean {
return b === l;
}
`,
},
{
name: "plain alias of an enum compares as the same enum",
src: `
export type Category = "food" | "travel" | "gear";
export type Chosen = Category;
export function same(a: Category, b: Chosen): boolean {
return a === b;
}
`,
},
{
name: "cross-enum equality under a logical chain in a filter callback",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export interface Item { readonly id: number; readonly category: Category; readonly active: boolean; }
export function shown(items: readonly Item[], filter: CategoryFilter): readonly Item[] {
return items.filter((i) => i.active && (filter === "all" || i.category === filter));
}
`,
},
{
name: "cross-enum comparison between two narrowed union payloads",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export type Ev =
| { readonly kind: "tag"; readonly category: Category }
| { readonly kind: "pick"; readonly filter: CategoryFilter };
export function samePick(a: Ev, b: Ev): boolean {
if (a.kind !== "tag") return false;
if (b.kind !== "pick") return false;
return a.category === b.filter;
}
`,
},
{
name: "cross-enum through a null-coalescing fallback",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export function effective(stored: CategoryFilter | null, category: Category): CategoryFilter {
return stored ?? category;
}
`,
},
];
const numberCases: Case[] = [
{
name: "payload demand reaches the model field it is assigned into",
src: `
export interface Model {
readonly durationSeconds: number;
readonly remainingSeconds: number;
readonly running: boolean;
}
export type Msg =
| { readonly kind: "tick" }
| { readonly kind: "set_duration"; readonly seconds: number };
export function initialModel(): Model {
return { durationSeconds: 300, remainingSeconds: 300, running: false };
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "tick": {
const remaining = model.remainingSeconds - 1;
if (remaining === 0) {
return { ...model, remainingSeconds: remaining, running: false };
}
return { ...model, remainingSeconds: remaining };
}
case "set_duration": {
if (msg.seconds <= 0) return model;
return { ...model, durationSeconds: msg.seconds, remainingSeconds: msg.seconds };
}
}
}
`,
},
{
name: "minimal demand-across-assignment edge",
src: `
export interface Model { readonly total: number; }
export type Msg =
| { readonly kind: "set"; readonly value: number }
| { readonly kind: "reset" };
export function update(model: Model, msg: Msg, bytes: Uint8Array): Model {
switch (msg.kind) {
case "set": {
const b = bytes[msg.value];
return { total: msg.value + b };
}
case "reset": return { total: 0 };
}
}
`,
},
{
name: "float taint flows backward: field and payload agree on f64",
src: `
export interface Model { readonly ratio: number; }
export type Msg =
| { readonly kind: "set"; readonly value: number }
| { readonly kind: "halve" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "set": return { ratio: msg.value };
case "halve": return { ratio: model.ratio * 0.5 };
}
}
`,
},
{
name: "fractional literal reaching an index slot is taught",
gate: "NS1016",
src: `
export function read(bytes: Uint8Array): number {
let i = 0;
i = 1.5;
return bytes[i];
}
`,
},
{
name: "payload both indexed and compared with a fraction is taught",
gate: "NS1016",
src: `
export type Msg = { readonly kind: "at"; readonly pos: number } | { readonly kind: "noop" };
export function read(msg: Msg, bytes: Uint8Array): number {
switch (msg.kind) {
case "at": {
if (msg.pos === 0.5) return 0;
return bytes[msg.pos];
}
case "noop": return 0;
}
}
`,
},
{
name: "float-valued field flowing into an index slot is taught",
gate: "NS1016",
src: `
export interface Model { readonly ratio: number; }
export function init(): Model { return { ratio: 0.5 }; }
export function sample(model: Model, bytes: Uint8Array): number {
const at = model.ratio;
return bytes[at];
}
`,
},
{
name: "length scaled by a fraction widens into the float site",
src: `
export function half(bytes: Uint8Array): number {
return bytes.length * 0.5;
}
`,
},
{
name: "float parameter compared against a length read",
src: `
export function pastEnd(cursor: number, bytes: Uint8Array): boolean {
return cursor * 1.5 > bytes.length;
}
`,
},
{
name: "returns mixing a length read with a fractional literal",
src: `
export function measure(bytes: Uint8Array, empty: boolean): number {
if (empty) return 0.5;
return bytes.length;
}
`,
},
{
name: "fractional literal used directly as an index is taught",
gate: "NS1016",
src: `
export function bad(bytes: Uint8Array): number {
return bytes[1.5];
}
`,
},
{
name: "optional payload with integer fallback feeding an index",
src: `
export interface Model { readonly cursor: number | null; }
export function at(model: Model, bytes: Uint8Array): number {
const i = model.cursor ?? 0;
return bytes[i];
}
`,
},
{
name: "demand travels through a chain of locals into the field",
src: `
export interface Model { readonly offset: number; }
export type Msg = { readonly kind: "move"; readonly by: number } | { readonly kind: "stay" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "move": {
const step = msg.by;
const next = step + model.offset;
return { offset: next };
}
case "stay": return model;
}
}
export function read(model: Model, bytes: Uint8Array): number {
return bytes[model.offset];
}
`,
},
{
name: "callee demand claims the caller's field argument",
src: `
export interface Model { readonly at: number; }
export function byteAt(bytes: Uint8Array, i: number): number {
return bytes[i];
}
export function sample(model: Model, bytes: Uint8Array): number {
return byteAt(bytes, model.at);
}
`,
},
{
name: "Math.max mixing an integer constant with a float demotes the constant",
src: `
export const FLOOR = 4;
export function clampScale(x: number): number {
return Math.max(FLOOR, x * 0.25);
}
`,
},
{
name: "Math.min mixing a float expression with a length read widens the read",
src: `
export function capped(x: number, bytes: Uint8Array): number {
return Math.min(x * 0.5, bytes.length);
}
`,
},
{
name: "guarded payload access through and/or chains and an else branch",
src: `
export type Msg = { readonly kind: "add"; readonly n: number } | { readonly kind: "zero" };
export function inRange(msg: Msg, bytes: Uint8Array): boolean {
return msg.kind === "add" && msg.n < bytes.length;
}
export function inRangeOrZero(msg: Msg, bytes: Uint8Array): boolean {
return msg.kind !== "add" || msg.n < bytes.length;
}
export function low(msg: Msg): number {
if (msg.kind !== "add") {
return 0;
} else {
return msg.n & 255;
}
}
`,
},
{
name: "template hole fed by a length read stays integer",
src: `
import { asciiBytes } from "@native-sdk/core";
export function label(items: Uint8Array): Uint8Array {
return asciiBytes(\`count \${items.length}\`);
}
`,
},
{
name: "demanded payload appended to a number array widens at the element",
src: `
export function record(samples: readonly number[], n: number, bytes: Uint8Array): readonly number[] {
const probe = bytes[n];
return [...samples, n + probe];
}
`,
},
{
name: "map over a number array keeps float element math",
src: `
export function doubled(samples: readonly number[]): readonly number[] {
return samples.map((x) => x * 2);
}
`,
},
{
name: "filter over a number array compares floats",
src: `
export function positive(samples: readonly number[]): readonly number[] {
return samples.filter((x) => x > 0);
}
`,
},
{
name: "indexing by a number-array element is taught",
gate: "NS1016",
src: `
export function gather(indices: readonly number[], bytes: Uint8Array): readonly number[] {
return indices.map((x) => bytes[x]);
}
`,
},
{
name: "uninitialized integer local assigned from payload and literal",
src: `
export type Msg = { readonly kind: "jump"; readonly to: number } | { readonly kind: "home" };
export function land(msg: Msg, bytes: Uint8Array): number {
let target: number;
switch (msg.kind) {
case "jump": {
target = msg.to;
break;
}
case "home": {
target = 0;
break;
}
}
return bytes[target];
}
`,
},
{
name: "compound assignment feeds demand back through the accumulator",
src: `
export type Msg = { readonly kind: "add"; readonly n: number } | { readonly kind: "zero" };
export function tally(msg: Msg, bytes: Uint8Array): number {
let acc = 0;
if (msg.kind === "add") acc += msg.n;
return bytes[acc];
}
`,
},
{
name: "float-bounded counting loop stays f64 end to end",
src: `
export function steps(limit: number): number {
let total = 0.0;
for (let i = 0; i < limit * 0.5; i++) {
total += 1;
}
return total;
}
`,
},
{
name: "integer counting loop over bytes (regression)",
src: `
export function spaces(bytes: Uint8Array): number {
let n = 0;
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] === 32) n += 1;
}
return n;
}
`,
},
{
name: "undemanded payload mixed with a fraction in a ternary goes f64",
src: `
export type Msg = { readonly kind: "set"; readonly value: number } | { readonly kind: "half" };
export function level(msg: Msg): number {
return msg.kind === "set" ? msg.value : 0.5;
}
`,
},
{
name: "demanded payload mixed with a fraction in a ternary is taught",
gate: "NS1016",
src: `
export type Msg = { readonly kind: "set"; readonly value: number } | { readonly kind: "half" };
export function pick(msg: Msg, flag: boolean, bytes: Uint8Array): number {
const v = flag && msg.kind === "set" ? msg.value : 0.5;
return bytes[v];
}
`,
},
{
name: "two undecided payloads compare as the same float class",
src: `
export type Msg =
| { readonly kind: "pair"; readonly a: number; readonly b: number }
| { readonly kind: "none" };
export function same(msg: Msg): boolean {
if (msg.kind !== "pair") return false;
return msg.a === msg.b;
}
`,
},
{
name: "comparison against a proven-integer side claims the payload",
src: `
export type Msg = { readonly kind: "check"; readonly n: number } | { readonly kind: "skip" };
export function atEnd(msg: Msg, bytes: Uint8Array): boolean {
if (msg.kind !== "check") return false;
return msg.n === bytes.length;
}
`,
},
{
name: "bitwise demand on a payload (R9 regression)",
src: `
export type Msg = { readonly kind: "mask"; readonly bits: number } | { readonly kind: "none" };
export function low(msg: Msg): number {
if (msg.kind !== "mask") return 0;
return msg.bits & 255;
}
`,
},
{
name: "optional float field takes fractions and payload values",
src: `
export interface Model { readonly hue: number | null; }
export type Msg = { readonly kind: "tint"; readonly hue: number } | { readonly kind: "clear" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "tint": return { hue: msg.hue * 0.1 };
case "clear": return { hue: null };
}
}
`,
},
{
name: "orelse fusion on an optional number result (R7 regression)",
src: `
export function find(xs: Uint8Array, want: number): number | null {
for (let i = 0; i < xs.length; i++) {
if (xs[i] === want) return i;
}
return null;
}
export function findOrZero(xs: Uint8Array, want: number): number {
const hit = find(xs, want);
if (hit === null) return 0;
return hit;
}
`,
},
{
name: "optional numeric equality in every expression position (null-safe, no unwrap)",
src: `
export function isZero(cls: number | null): boolean {
return cls === 0;
}
export function nonZero(cls: number | null): boolean {
return cls !== 0;
}
export function reversed(cls: number | null): boolean {
return 0 === cls;
}
export function inTernary(cls: number | null): number {
return cls === 0 ? 1 : cls !== 0 ? 2 : 3;
}
export function inChain(cls: number | null, flag: boolean): boolean {
return flag && cls === 1;
}
export function bothOptional(a: number | null, b: number | null): boolean {
return a === b;
}
export function againstValue(cls: number | null, n: number): boolean {
return cls === n;
}
`,
},
{
name: "division is float always; integer reads widen into the site",
src: `
export function ratio(a: number, b: number): number {
return a / b;
}
export function halfLen(bytes: Uint8Array): number {
return bytes.length / 2;
}
`,
},
{
name: "remainder on integer and float sites",
src: `
export function wrapAt(bytes: Uint8Array, at: number): number {
return bytes[at % bytes.length];
}
export function frac(x: number): number {
return x % 1;
}
`,
},
{
name: "bitwise beyond a provable 32-bit range takes the wrap helpers",
src: `
export function band(a: number, b: number): number {
return a & b;
}
export function bor(a: number, b: number): number {
return a | b;
}
export function bxor(a: number, b: number): number {
return a ^ b;
}
export function bmask(a: number): number {
return a & 255;
}
`,
},
{
name: "Math.round emits in value positions",
src: `
export function nearest(x: number): number {
return Math.round(x);
}
export interface Model { readonly scale: number; }
export function snapped(model: Model): number {
return Math.round(model.scale * 1.5);
}
`,
},
{
name: "Math.round feeding an index slot is taught, not emitted",
gate: "NS1016",
src: `
export function sample(bytes: Uint8Array, x: number): number {
return bytes[Math.round(x)];
}
`,
},
{
name: "union-typed local built by a selector ternary carries its annotation",
src: `
export type Ev =
| { readonly kind: "insert"; readonly text: Uint8Array }
| { readonly kind: "move"; readonly dx: number; readonly dy: number }
| { readonly kind: "clear" };
export function weigh(sel: number, payload: Uint8Array, dx: number, dy: number): number {
const ev: Ev = sel === 0 ? { kind: "insert", text: payload } : sel === 1 ? { kind: "move", dx: dx, dy: dy } : { kind: "clear" };
switch (ev.kind) {
case "insert": return ev.text.length;
case "move": return ev.dx + ev.dy;
case "clear": return 0;
}
}
`,
},
{
// R7d dual: `x === undefined ? A : <uses x>` narrows the ELSE branch —
// the find-miss ternary the guard-first spelling already covered, in
// the miss-first order (both operand orders).
name: "=== undefined / === null miss-first ternaries narrow the else branch",
src: `
export interface Task { readonly id: number; }
export interface Model { readonly tasks: readonly Task[]; readonly sel: number | null; }
export function firstOrMinus(model: Model, id: number): number {
const found = model.tasks.find((t) => t.id === id);
return found === undefined ? -1 : found.id + 1;
}
export function flipped(model: Model, id: number): number {
const found = model.tasks.find((t) => t.id === id);
return undefined === found ? -1 : found.id + 1;
}
export function selPlus(model: Model): number {
return model.sel === null ? 0 : model.sel + 1;
}
`,
},
{
// R7 dual in statement position: `if (x === null) { A } else { uses x }`
// narrows the else branch, and an exiting miss branch narrows the
// statements after the if too (else-if chains included).
name: "=== null if/else statement narrowing, else-if chains, and post-if reads",
src: `
export interface Task { readonly id: number; }
export interface Model { readonly tasks: readonly Task[]; }
export function branch(model: Model, id: number, bytes: Uint8Array): number {
const found = model.tasks.find((t) => t.id === id);
if (found === undefined) {
return 0;
} else {
return bytes[found.id];
}
}
export function chained(model: Model, id: number): number {
const found = model.tasks.find((t) => t.id === id);
if (found === undefined) {
return -1;
} else if (found.id > 5) {
return found.id * 2;
}
return found.id;
}
`,
},
{
// R7 fusion over a TERNARY initializer: `const x = c ? f(a) : g(a);
// if (x === null) <exit>` lowers to `(if (c) f else g) orelse <exit>`
// — the conditional must parenthesize, or Zig binds the orelse to the
// ELSE arm alone (the system-monitor-ts memory-sampler shape).
name: "orelse fusion over a ternary initializer parenthesizes the conditional",
src: `
export interface Sample { readonly used: number; }
function parseA(bytes: Uint8Array): Sample | null {
if (bytes.length === 0) return null;
return { used: bytes[0] };
}
function parseB(bytes: Uint8Array): Sample | null {
if (bytes.length < 2) return null;
return { used: bytes[1] };
}
export function pick(first: boolean, bytes: Uint8Array): number {
const sample = first ? parseA(bytes) : parseB(bytes);
if (sample === null) {
return -1;
}
return sample.used;
}
export function pickInline(first: boolean, bytes: Uint8Array): number {
const sample = first ? parseA(bytes) : parseB(bytes);
if (sample === null) return -1;
return sample.used;
}
`,
},
{
// Mixed-class optional equality: an optional slot the number tier
// classes i64 compared against a float value routes through the
// null-safe widening helper instead of emitting \`?i64 == f64\`.
name: "optional integer slot against a float value widens null-safely",
src: `
export interface Model { readonly sel: number | null; readonly frac: number; }
export function pickAt(model: Model, bytes: Uint8Array): number {
if (model.sel !== null) return bytes[model.sel];
return 0;
}
export function atHalf(model: Model): boolean {
const half = model.frac * 0.5;
return model.sel === half;
}
export function offHalf(model: Model): boolean {
const half = model.frac * 0.5;
return model.sel !== half;
}
export function atFractionLiteral(model: Model): boolean {
return model.sel === 0.5;
}
`,
},
];
const structuralCases: Case[] = [
{
// The host-event wiring channels together: the emitted module must carry
// compilable fns, arm consts, and the envMsgs tuple.
name: "the host-event wiring channels emit and compile",
src: `
export type ColorScheme = "light" | "dark";
export interface FrameEvent { readonly width: number; readonly height: number; readonly timestampMs: number; readonly intervalMs: number; }
export interface KeyEvent { readonly key: string; readonly shift: boolean; readonly control: boolean; readonly alt: boolean; readonly super: boolean; }
export interface FileDropPoint { readonly x: number; readonly y: number; }
export interface FileDropEvent { readonly windowId: number; readonly viewLabel: string; readonly point: FileDropPoint | null; readonly paths: readonly Uint8Array[]; }
export interface ChromeInsets { readonly top: number; readonly right: number; readonly bottom: number; readonly left: number; }
export interface ChromeButtons { readonly x: number; readonly y: number; readonly width: number; readonly height: number; }
export interface Model { readonly width: number; readonly dark: boolean; readonly chromeTop: number; readonly base: Uint8Array; }
export type Msg =
| { readonly kind: "resized"; readonly width: number }
| { readonly kind: "toggled" }
| { readonly kind: "appearance_changed"; readonly colorScheme: ColorScheme; readonly reduceMotion: boolean; readonly highContrast: boolean }
| { readonly kind: "chrome_changed"; readonly insets: ChromeInsets; readonly buttons: ChromeButtons; readonly tabsProjected: boolean }
| { readonly kind: "base_set"; readonly value: Uint8Array };
export function frameMsg(model: Model, frame: FrameEvent): Msg | null {
if (frame.width !== model.width) return { kind: "resized", width: frame.width };
return null;
}
export function keyMsg(key: KeyEvent): Msg | null {
if (key.control || key.alt || key.super || key.shift) return null;
if (key.key === "space") return { kind: "toggled" };
return null;
}
export function dropMsg(drop: FileDropEvent): Msg | null {
if (drop.paths.length > 0) return { kind: "base_set", value: drop.paths[0] };
return null;
}
export const appearanceMsg = "appearance_changed";
export const chromeMsg = "chrome_changed";
export const envMsgs = [{ env: "APP_BASE", msg: "base_set" }] as const;
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "resized": return { ...model, width: msg.width };
case "toggled": return model;
case "appearance_changed": return { ...model, dark: msg.colorScheme === "dark" };
case "chrome_changed": return { ...model, chromeTop: msg.insets.top };
case "base_set": return { ...model, base: msg.value };
}
}
`,
},
{
name: "record array update with spread, map, and filter (R15-R17 regression)",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export interface Model { readonly tasks: readonly Task[]; readonly nextId: number; }
export type Msg =
| { readonly kind: "add" }
| { readonly kind: "toggle"; readonly id: number }
| { readonly kind: "purge" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add":
return { ...model, tasks: [...model.tasks, { id: model.nextId, done: false }], nextId: model.nextId + 1 };
case "toggle":
return { ...model, tasks: model.tasks.map((t) => (t.id === msg.id ? { ...t, done: !t.done } : t)) };
case "purge":
return { ...model, tasks: model.tasks.filter((t) => !t.done) };
}
}
`,
},
{
name: "multi-field union arm mixing a cross-enum value and a demanded number",
src: `
export type Category = "food" | "travel" | "gear";
export type CategoryFilter = "all" | "food" | "travel" | "gear";
export type Msg =
| { readonly kind: "place"; readonly filter: CategoryFilter; readonly at: number }
| { readonly kind: "none" };
export function place(category: Category, at: number, bytes: Uint8Array): Msg {
const probe = bytes[at];
if (probe === 0) return { kind: "none" };
return { kind: "place", filter: category, at: at };
}
`,
},
{
name: "switch payload capture feeding integer math (R13 regression)",
src: `
export type Ev =
| { readonly kind: "insert"; readonly text: Uint8Array }
| { readonly kind: "move"; readonly dx: number; readonly dy: number }
| { readonly kind: "clear" };
export function weight(ev: Ev): number {
switch (ev.kind) {
case "insert": return ev.text.length;
case "move": return ev.dx + ev.dy;
case "clear": return 0;
}
}
export function at(ev: Ev, bytes: Uint8Array): number {
return bytes[weight(ev)];
}
`,
},
];
const cmdCases: Case[] = [
{
name: "Cmd pair-return with every v1 factory (effects channel regression)",
src: `
import { Cmd } from "@native-sdk/core";
export interface Model { readonly count: number; readonly saved: number; }
export type Msg =
| { readonly kind: "add" }
| { readonly kind: "save" }
| { readonly kind: "stamp" }
| { readonly kind: "tick"; readonly at: number };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "add": return { ...model, count: model.count + 1 };
case "save": return [{ ...model, saved: model.count }, Cmd.persist()];
case "stamp": return [model, Cmd.batch([Cmd.now("tick"), Cmd.host("beep", model.count)])];
case "tick": return { ...model, count: msg.at };
}
}
`,
},
{
name: "conditional command in the return slot",
src: `
import { Cmd } from "@native-sdk/core";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "add": return [{ count: model.count + 1 }, model.count > 10 ? Cmd.persist() : Cmd.none];
case "noop": return model;
}
}
`,
},
{
name: "Cmd escaping into a helper is taught, not emitted",
gate: "NS1017",
src: `
import { Cmd } from "@native-sdk/core";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
function saveCmd(): Cmd<Msg> {
return Cmd.persist();
}
export function update(model: Model, msg: Msg): Model { return model; }
`,
},
];
// The Cmd v2 surface: bytes/record payloads, keyed routed requests, cancel,
// the initialModel boot pair, and declarative subscriptions — each either
// emitting zig-compile-clean or gated by its teaching rule.
const cmdV2Cases: Case[] = [
{
name: "Cmd.host bytes payloads: model bytes, fresh bytes, empty bytes",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
export interface Model { readonly draft: Uint8Array; }
export type Msg = { readonly kind: "save" } | { readonly kind: "flush" } | { readonly kind: "zero" };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "save": return [model, Cmd.host("store.write", model.draft)];
case "flush": return [model, Cmd.host("store.write", asciiBytes(\`\${model.draft.length} bytes\`))];
case "zero": return [model, Cmd.host("store.write", new Uint8Array(0))];
}
}
`,
},
{
name: "Cmd.host record payload lowers through the derived encoder",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
export interface Model { readonly gain: number; readonly on: boolean; }
export type Msg = { readonly kind: "save" } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "save": return [model, Cmd.host("cfg.save", { gain: model.gain, on: model.on, tag: asciiBytes("v2") })];
case "noop": return model;
}
}
`,
},
{
name: "Cmd.request routes results by arm name, keyed and unkeyed; cancel drops a key",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
export interface Model { readonly data: Uint8Array; readonly errs: number; }
export type Msg =
| { readonly kind: "load" }
| { readonly kind: "probe" }
| { readonly kind: "stop" }
| { readonly kind: "loaded"; readonly body: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "load": return [model, Cmd.request("store.read", asciiBytes("notes.json"), { key: "load", ok: "loaded", err: "failed" })];
case "probe": return [model, Cmd.request("store.read", model.data, { ok: "loaded", err: "failed" })];
case "stop": return [model, Cmd.batch([Cmd.cancel("load"), Cmd.persist()])];
case "loaded": return { ...model, data: msg.body };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "initialModel boot pair: the init command runs once at install",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
export interface Model { readonly data: Uint8Array; }
export type Msg =
| { readonly kind: "loaded"; readonly body: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): [Model, Cmd<Msg>] {
return [{ data: new Uint8Array(0) }, Cmd.request("store.read", asciiBytes("boot.bin"), { key: "boot", ok: "loaded", err: "failed" })];
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "loaded": return { data: msg.body };
case "failed": return model;
}
}
`,
},
{
name: "subscriptions: timers derived from the model, batched, paused via Sub.none",
src: `
import { Sub } from "@native-sdk/core";
export interface Model { readonly running: boolean; readonly fast: boolean; readonly ticks: number; }
export type Msg =
| { readonly kind: "toggle" }
| { readonly kind: "tick"; readonly at: number }
| { readonly kind: "blink"; readonly at: number };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "toggle": return { ...model, running: !model.running };
case "tick": return { ...model, ticks: model.ticks + 1 };
case "blink": return model;
}
}
export function subscriptions(model: Model): Sub<Msg> {
if (!model.running) return Sub.none;
return Sub.batch([
Sub.timer("tick", model.fast ? 40 : 100, "tick"),
Sub.timer("blink", 250, "blink"),
]);
}
`,
},
{
name: "a payload mixed with extra arguments is taught",
formerEmitGate: "NS1026",
src: `
import { Cmd } from "@native-sdk/core";
export interface Model { readonly draft: Uint8Array; readonly count: number; }
export type Msg = { readonly kind: "save" } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "save": return [model, Cmd.host("store.write", model.draft as never, model.count as never)];
case "noop": return model;
}
}
`,
},
{
name: "a nested record payload field is taught (no wire encoding)",
formerEmitGate: "NS1026",
src: `
import { Cmd } from "@native-sdk/core";
export interface Inner { readonly a: number; }
export interface Model { readonly inner: Inner; }
export type Msg = { readonly kind: "save" } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "save": return [model, Cmd.host("cfg.save", { nested: model.inner as never })];
case "noop": return model;
}
}
`,
},
{
name: "a routing callback is taught, not run (routing is data)",
formerEmitGate: "NS1027",
src: `
import { Cmd, type BytesKind } from "@native-sdk/core";
export interface Model { readonly data: Uint8Array; }
export type Msg =
| { readonly kind: "load" }
| { readonly kind: "loaded"; readonly body: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "load": return [model, Cmd.request("store.read", model.data, {
ok: ((body: Uint8Array) => ({ kind: "loaded", body })) as unknown as BytesKind<Msg>,
err: "failed",
})];
case "loaded": return { ...model, data: msg.body };
case "failed": return model;
}
}
`,
},
{
name: "a non-literal routing arm is taught (decoders derive at build time)",
formerEmitGate: "NS1027",
src: `
import { Cmd } from "@native-sdk/core";
export interface Model { readonly data: Uint8Array; readonly alt: boolean; }
export type Msg =
| { readonly kind: "load" }
| { readonly kind: "loaded"; readonly body: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "load": return [model, Cmd.request("store.read", model.data, {
ok: model.alt ? "loaded" : "loaded",
err: "failed",
})];
case "loaded": return { ...model, data: msg.body };
case "failed": return model;
}
}
`,
},
{
name: "a routing arm without a bytes payload is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, type BytesKind } from "@native-sdk/core";
export interface Model { readonly data: Uint8Array; readonly ticks: number; }
export type Msg =
| { readonly kind: "load" }
| { readonly kind: "tick"; readonly at: number }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "load": return [model, Cmd.request("store.read", model.data, {
ok: "tick" as BytesKind<Msg>,
err: "failed",
})];
case "tick": return { ...model, ticks: model.ticks + 1 };
case "failed": return model;
}
}
`,
},
{
name: "a Sub in update's return is taught (subscriptions are declared, not issued)",
gate: "NS1025",
src: `
import { Sub } from "@native-sdk/core";
export interface Model { readonly ticks: number; }
export type Msg = { readonly kind: "go" } | { readonly kind: "tick"; readonly at: number };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "go": {
const sub = Sub.timer<Msg>("tick", 100, "tick");
return model;
}
case "tick": return { ...model, ticks: model.ticks + 1 };
}
}
`,
},
{
name: "a Sub stored in a local inside subscriptions is taught",
gate: "NS1025",
src: `
import { Sub } from "@native-sdk/core";
export interface Model { readonly ticks: number; }
export type Msg = { readonly kind: "noop" } | { readonly kind: "tick"; readonly at: number };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "noop": return model;
case "tick": return { ...model, ticks: model.ticks + 1 };
}
}
export function subscriptions(model: Model): Sub<Msg> {
const stored = Sub.timer<Msg>("tick", 100, "tick");
return stored;
}
`,
},
{
name: "a Sub-typed return outside subscriptions is taught",
gate: "NS1025",
src: `
import { Sub } from "@native-sdk/core";
export interface Model { readonly ticks: number; }
export type Msg = { readonly kind: "noop" } | { readonly kind: "tick"; readonly at: number };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "noop": return model;
case "tick": return { ...model, ticks: model.ticks + 1 };
}
}
function timers(model: Model): Sub<Msg> {
return Sub.timer("tick", 100, "tick");
}
`,
},
{
name: "a timer target without a single number payload is taught",
formerEmitGate: "NS1027",
src: `
import { Sub, type TimestampKind } from "@native-sdk/core";
export interface Model { readonly data: Uint8Array; }
export type Msg = { readonly kind: "noop" } | { readonly kind: "loaded"; readonly body: Uint8Array };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "noop": return model;
case "loaded": return { data: msg.body };
}
}
export function subscriptions(model: Model): Sub<Msg> {
return Sub.timer("load", 100, "loaded" as TimestampKind<Msg>);
}
`,
},
];
// A shared preamble for the named-op cases: every arm shape a routed op
// can target, plus one of each wrong shape to gate against.
const namedOpMsg = `
export interface Model { readonly code: number; readonly data: Uint8Array; readonly errs: number; }
export type Msg =
| { readonly kind: "go" }
| { readonly kind: "loaded"; readonly body: Uint8Array }
| { readonly kind: "wrote" }
| { readonly kind: "fetched"; readonly status: number; readonly body: Uint8Array }
| { readonly kind: "fired"; readonly at: number }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { code: -1, data: new Uint8Array(0), errs: 0 }; }
`;
// Slice C: the named buffered engine ops (readFile/writeFile/fetch/
// clipboard) and the one-shot delay — emit-clean in their exact shapes,
// gated with the taught rules everywhere else.
const namedOpCases: Case[] = [
{
name: "every named op emits in its documented shape",
checkOptions: { capabilities: ["credentials"], permissions: ["credentials"] },
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
export interface Model { readonly code: number; readonly data: Uint8Array; readonly saved: boolean; readonly errs: number; readonly at: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "loaded"; readonly body: Uint8Array }
| { readonly kind: "wrote" }
| { readonly kind: "fetched"; readonly status: number; readonly body: Uint8Array }
| { readonly kind: "fired"; readonly at: number }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { code: -1, data: new Uint8Array(0), saved: false, errs: 0, at: -1 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.readFile(asciiBytes("a.bin"), { key: "r", ok: "loaded", err: "failed" })];
if (msg.which === 1) return [model, Cmd.writeFile(asciiBytes("a.bin"), model.data, { key: "w", ok: "wrote", err: "failed" })];
if (msg.which === 14) return [model, Cmd.deleteFile(asciiBytes("a.bin"), { key: "x", ok: "wrote", err: "failed" })];
if (msg.which === 2) return [model, Cmd.fetch({ url: asciiBytes("https://a.test"), method: "PUT", headers: { accept: "text/plain" }, body: model.data, timeoutMs: 1000 }, { ok: "fetched", err: "failed" })];
if (msg.which === 3) return [model, Cmd.fetch({ url: model.data }, { key: "g", ok: "fetched", err: "failed" })];
if (msg.which === 4) return [model, Cmd.clipboardWrite(model.data)];
if (msg.which === 5) return [model, Cmd.clipboardRead({ key: "p", ok: "loaded", err: "failed" })];
if (msg.which === 6) return [model, Cmd.delay("d", model.at + 100, "fired")];
if (msg.which === 7) return [model, Cmd.showNotification({ title: model.data, subtitle: asciiBytes("native-sdk"), body: asciiBytes("Done") })];
if (msg.which === 8) return [model, Cmd.openExternalUrl(asciiBytes("https://native-sdk.dev/docs"))];
if (msg.which === 9) return [model, Cmd.revealPath(asciiBytes("/tmp/native-sdk.log"))];
if (msg.which === 10) return [model, Cmd.credentials.set("alice", model.data, { key: "credential-set", ok: "wrote", err: "failed" })];
if (msg.which === 11) return [model, Cmd.credentials.get("alice", { key: "credential-get", ok: "loaded", err: "failed" })];
if (msg.which === 12) return [model, Cmd.credentials.delete("alice", { key: "credential-delete", ok: "wrote", err: "failed" })];
if (msg.which === 13) return [model, Cmd.formatLocalTime(model.at, "datetime", { key: "local-time", ok: "loaded", err: "failed" })];
return [model, Cmd.batch([Cmd.delay("d", 250, "fired"), Cmd.cancel("d")])];
case "loaded": return { ...model, data: msg.body };
case "wrote": return { ...model, saved: true };
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return { ...model, at: msg.at };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a fetch ok arm that is not a {status, body} record is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type FetchedKind } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.fetch({ url: asciiBytes("https://a.test") }, { ok: "loaded" as FetchedKind<Msg>, err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a writeFile ok arm carrying a payload is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type EmptyKind } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.writeFile(asciiBytes("a.bin"), model.data, { ok: "loaded" as EmptyKind<Msg>, err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a delay target without a single number payload is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, type TimestampKind } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.delay("d", 100, "loaded" as TimestampKind<Msg>)];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a runtime bytes fetch header value emits and compiles (names stay compile-time)",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.fetch({ url: asciiBytes("https://a.test"), headers: { authorization: model.data, accept: "text/plain", "x-trace": asciiBytes("t-1") } }, { ok: "fetched", err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a smuggled string fetch header value is taught (values are literals or bytes)",
formerEmitGate: "NS1029",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.fetch({ url: asciiBytes("https://a.test"), headers: { etag: model.errs > 0 ? "a" : "b" } }, { ok: "fetched", err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a non-flat headers record is taught",
formerEmitGate: "NS1029",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.fetch({ url: asciiBytes("https://a.test"), headers: { nested: { deep: "x" } } as unknown as { readonly [name: string]: string } }, { ok: "fetched", err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a string path smuggled past the bytes rule is taught",
formerEmitGate: "NS1029",
src: `
import { Cmd } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.readFile("store.bin" as unknown as Uint8Array, { ok: "loaded", err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "an over-bound path literal stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.readFile(asciiBytes(${JSON.stringify("p".repeat(1025))}), { ok: "loaded", err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "more headers than the engine accepts stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.fetch({ url: asciiBytes("https://a.test"), headers: { a: "1", b: "2", c: "3", d: "4", e: "5", f: "6", g: "7", h: "8", i: "9" } }, { ok: "fetched", err: "failed" })];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "a literal delay outside the 1ms..one-year bound stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${namedOpMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.delay("d", 0, "fired")];
case "loaded": return { ...model, data: msg.body };
case "wrote": return model;
case "fetched": return { ...model, code: msg.status, data: msg.body };
case "fired": return model;
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
];
// The emit-contract hardening round: constructs that once transpiled into
// invalid Zig now either emit correctly (optional chains, string equality,
// the asciiBytes intrinsic) or stop with a taught rule (concatenation,
// parameter defaults, host-arg smuggling, chain null tests, Map/Set).
const contractCases: Case[] = [
{
name: "optional chains: ?? folds, value comparisons, boolean conditions",
src: `
export type Tone = "info" | "warn";
export interface Badge { readonly count: number; readonly label: Uint8Array; readonly urgent: boolean; readonly tone: Tone | null; }
export interface Panel { readonly badge: Badge | null; readonly width: number; }
export interface Model { readonly panel: Panel | null; readonly fallback: number; }
export function badgeCount(model: Model): number {
return model.panel?.badge?.count ?? 0;
}
export function labelLen(model: Model): number {
return model.panel?.badge?.label.length ?? -1;
}
export function isUrgent(model: Model): boolean {
return model.panel?.badge?.urgent ?? false;
}
export function urgentGate(model: Model, on: boolean): boolean {
if (model.panel?.badge?.urgent) return true;
return on && !model.panel?.badge?.urgent;
}
export function isWarn(model: Model): boolean {
return model.panel?.badge?.tone === "warn";
}
export function countIsFive(model: Model): boolean {
return model.panel?.badge?.count === 5;
}
export function widthOrFallback(model: Model): number {
const w = model.panel?.width ?? model.fallback;
return w * 2;
}
export function toneOrNull(model: Model): Tone | null {
return model.panel?.badge?.tone ?? null;
}
`,
},
{
name: "optional chain on a local holding an optional record",
src: `
export interface Sel { readonly at: number; readonly len: number; }
export interface Model { readonly sel: Sel | null; }
export function span(model: Model): number {
const sel = model.sel;
return (sel?.at ?? 0) + (sel?.len ?? 0);
}
`,
},
{
name: "string equality: the command-mapper idiom emits and compiles",
src: `
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "remove" };
export function commandMsg(name: string): Msg | null {
if (name === "app.add") return { kind: "add" };
if (name === "app.remove") return { kind: "remove" };
return null;
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": return { count: model.count + 1 };
case "remove": return { count: model.count - 1 };
}
}
`,
},
{
name: "string equality against optional strings keeps the null table",
src: `
export function pick(name: string | null): number {
if (name === "left") return 1;
if (name !== "right") return 2;
return 3;
}
export function same(a: string | null, b: string | null): boolean {
return a === b;
}
`,
},
{
name: "literal-union value equals a plain string by tag name",
src: `
export type Filter = "all" | "active" | "done";
export function matchesName(f: Filter, name: string): boolean {
return f === name;
}
`,
},
{
name: "SDK asciiBytes intrinsic: literal to rodata, template to arena bytes",
src: `
import { asciiBytes } from "@native-sdk/core";
export interface Model { readonly count: number; }
export function banner(): Uint8Array {
return asciiBytes("expenses");
}
export function summary(model: Model): Uint8Array {
return asciiBytes(\`\${model.count} items\`);
}
`,
},
{
name: "SDK utf8Bytes intrinsic: Unicode literal and template text",
src: `
import { utf8Bytes } from "@native-sdk/core";
export interface Model { readonly count: number; }
export function banner(): Uint8Array {
return utf8Bytes("Today · Loading… — café 😀");
}
export function summary(model: Model): Uint8Array {
return utf8Bytes(\`✓ \${model.count} ready\`);
}
`,
},
{
name: "string + concatenation is taught, not emitted",
gate: "NS1018",
src: `
export function shout(s: string): string {
return s + "!";
}
`,
},
{
name: "parameter defaults are taught, not silently dropped",
gate: "NS1019",
src: `
function step(n: number, by: number = 1): number {
return n + by;
}
export function next(n: number): number {
return step(n);
}
`,
},
{
name: "Cmd.host argument smuggled past the types is taught",
formerEmitGate: "NS1020",
src: `
import { Cmd } from "@native-sdk/core";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "go"; readonly path: string } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.host("open", msg.path as unknown as number)];
case "noop": return model;
}
}
`,
},
{
name: "null test on an optional chain is taught (undefined vs null would diverge)",
gate: "NS1021",
src: `
export interface Inner { readonly x: number | null; }
export interface Model { readonly inner: Inner | null; }
export function f(model: Model): boolean {
return model.inner?.x === null;
}
`,
},
{
name: "bare new Map() is the taught container rule, not an internal stop",
gate: "NS1011",
src: `
export function f(): number {
const m = new Map();
return m.size;
}
`,
},
{
name: "a relative import of a missing module is taught, before resolution matters",
gate: "NS1037",
src: `
// @ts-expect-error the sibling module deliberately does not exist: the rule
// must fire before resolution matters.
import { helper } from "./helper_mod.ts";
export function f(n: number): number { return helper(n); }
`,
},
];
// The iteration round: for...of over arrays and bytes, and the array-method
// lowerings (find/findIndex/some/every/reduce/slice/concat/indexOf/includes/
// join). Emitting cases must compile; the JS corners with no mapping are
// gated by their teaching rules.
const bindingSurfaceCases: Case[] = [
{
name: "exported model helpers forward as Model declarations and viewUnbound emits the opt-out",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export interface Model { readonly tasks: readonly Task[]; readonly nextId: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "chrome_changed"; readonly inset: number };
export const viewUnbound = ["nextId", "chrome_changed", "openTasks"] as const;
export function initialModel(): Model { return { tasks: [], nextId: 1 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": return { ...model, tasks: [...model.tasks, { id: model.nextId, done: false }], nextId: model.nextId + 1 };
case "chrome_changed": return model;
}
}
export function remainingCount(model: Model): number {
return model.tasks.filter((t) => !t.done).length;
}
export function openTasks(model: Model): readonly Task[] {
return model.tasks.filter((t) => !t.done);
}
export function total(model: Model): number { return model.nextId - 1; }
`,
},
{
name: "a helper colliding with a field's emitted name is gated",
gate: "NS1031",
src: `
interface Totals { readonly doneCount: number; }
export interface Model { readonly totals: Totals; readonly doneCount: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { totals: { doneCount: 0 }, doneCount: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return model; }
}
export function doneCount(model: Model): number { return model.totals.doneCount; }
`,
},
{
name: "helpers and fields with different spellings never collide (names emit verbatim)",
src: `
export interface Model { readonly done_count: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { done_count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return model; }
}
export function doneCount(model: Model): number { return model.done_count; }
`,
},
{
name: "a viewUnbound entry outside the model surface is gated",
gate: "NS1032",
src: `
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export const viewUnbound = ["typoedName"] as const;
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return model; }
}
`,
},
];
const stackedUnionCases: Case[] = [
{
name: "stacked union-switch labels sharing a body coalesce into one arm",
src: `
export type Ev = { readonly kind: "a"; readonly n: number } | { readonly kind: "b" } | { readonly kind: "c" };
export function pick(e: Ev): number {
switch (e.kind) {
case "a": return e.n;
case "b":
case "c": return -1;
}
}
`,
},
{
name: "stacked labels whose shared body reads a payload are gated",
formerEmitGate: "NS9001",
src: `
export type Ev = { readonly kind: "a"; readonly n: number } | { readonly kind: "b"; readonly n: number };
export function pick(e: Ev): number {
switch (e.kind) {
case "b":
case "a": return e.n;
}
}
`,
},
];
const iterationCases: Case[] = [
{
name: "for...of over a record array with break and continue",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function firstOpenId(tasks: readonly Task[]): number {
let hit = -1;
for (const t of tasks) {
if (t.done) continue;
hit = t.id;
break;
}
return hit;
}
`,
},
{
name: "for...of over bytes: integer widening, demand, and float use",
src: `
export function total(bytes: Uint8Array): number {
let n = 0;
for (const b of bytes) {
n += b;
}
return n;
}
export function probe(bytes: Uint8Array, table: Uint8Array): number {
let acc = 0;
for (const b of bytes) {
acc += table[b];
}
return acc;
}
export function scaled(bytes: Uint8Array): number {
let n = 0.0;
for (const b of bytes) {
n += b * 0.5;
}
return n;
}
`,
},
{
name: "for...of over a number array keeps float element math",
src: `
export function product(xs: readonly number[]): number {
let p = 1;
for (const x of xs) {
p = p * x;
}
return p;
}
`,
},
{
name: "for...of over a filtered array and an unused binding",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function doneTally(tasks: readonly Task[]): number {
let n = 0;
for (const t of tasks.filter((x) => x.done)) {
n += 1;
}
return n;
}
`,
},
{
name: "for...of over .entries() emits the indexed loop (the [i, x] pair form)",
src: `
export function sum(xs: readonly number[]): number {
let total = 0;
for (const [i, x] of xs.entries()) {
total += x + i;
}
return total;
}
`,
},
{
name: "for...of over .entries() beyond the [i, x] pair form is taught",
formerEmitGate: "NS9001",
src: `
export function sum(xs: readonly number[]): number {
let total = 0;
for (const [i] of xs.entries()) {
total += i;
}
return total;
}
`,
},
{
name: "for...of with a let binding is taught, not emitted",
formerEmitGate: "NS9001",
src: `
export function last(xs: readonly number[]): number {
let hit = 0;
for (let x of xs) {
hit = x;
}
return hit;
}
`,
},
{
name: "find consumed through the miss-test fusion and payload reads",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function firstDoneId(tasks: readonly Task[]): number {
const hit = tasks.find((t) => t.done);
if (hit === undefined) return -1;
return hit.id;
}
`,
},
{
name: "find consumed through ?? and an identifier guard block",
src: `
export function firstOver(xs: readonly number[], lim: number): number {
return xs.find((x) => x > lim) ?? -1;
}
export interface Task { readonly id: number; readonly done: boolean; }
export function doubledId(tasks: readonly Task[]): number {
const hit = tasks.find((t) => !t.done);
if (hit !== undefined) {
const twice = hit.id * 2;
return twice;
}
return 0;
}
`,
},
{
name: "findIndex, some, every, indexOf, and includes over scalar arrays",
src: `
export type Category = "food" | "travel" | "gear";
export interface Task { readonly id: number; readonly done: boolean; }
export function doneAt(tasks: readonly Task[]): number {
return tasks.findIndex((t) => t.done);
}
export function anyDone(tasks: readonly Task[]): boolean {
return tasks.some((t) => t.done);
}
export function allDone(tasks: readonly Task[]): boolean {
return tasks.every((t) => t.done);
}
export function whereIs(xs: readonly number[], v: number): number {
return xs.indexOf(v);
}
export function hasCategory(cats: readonly Category[], c: Category): boolean {
return cats.includes(c);
}
`,
},
{
name: "findIndex demand: the result indexes memory",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function markByte(tasks: readonly Task[], bytes: Uint8Array): number {
const at = tasks.findIndex((t) => t.done);
if (at === -1) return 0;
return bytes[at];
}
`,
},
{
name: "reduce with an initial value over numbers and records",
src: `
export interface Expense { readonly cents: number; }
export function total(xs: readonly number[]): number {
return xs.reduce((sum, x) => sum + x, 0);
}
export function centsTotal(expenses: readonly Expense[]): number {
return expenses.reduce((sum, e) => sum + e.cents, 0);
}
export function countBig(xs: readonly number[], lim: number): number {
return xs.reduce((n, x) => (x > lim ? n + 1 : n), 0);
}
`,
},
{
name: "reduce without an initial value is taught (empty-array throw)",
formerEmitGate: "NS1007",
src: `
export function total(xs: readonly number[]): number {
return xs.reduce((sum, x) => sum + x);
}
`,
},
{
name: "reduce result flowing into an index slot is taught (float-classed)",
gate: "NS1016",
src: `
export function pick(xs: readonly number[], bytes: Uint8Array): number {
const at = xs.reduce((sum, x) => sum + x, 0);
return bytes[at];
}
`,
},
{
name: "array slice and concat over records and numbers",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function firstTwo(tasks: readonly Task[]): readonly Task[] {
return tasks.slice(0, 2);
}
export function lastTwo(xs: readonly number[]): readonly number[] {
return xs.slice(-2);
}
export function trimmed(xs: readonly number[], from: number, to: number): readonly number[] {
return xs.slice(from, to);
}
export function stitched(a: readonly Task[], b: readonly Task[]): readonly Task[] {
return a.concat(b);
}
export function tripled(a: readonly number[], b: readonly number[], c: readonly number[]): readonly number[] {
return a.concat(b, c);
}
`,
},
{
name: "indexOf on a record array is taught (JS reference identity)",
formerEmitGate: "NS9001",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function has(tasks: readonly Task[], t: Task): number {
return tasks.indexOf(t);
}
`,
},
{
name: "join over bytes emits; join over a number array is taught",
src: `
export function csv(bytes: Uint8Array): string {
return bytes.join("-");
}
export function bare(bytes: Uint8Array): string {
return bytes.join("");
}
export function commas(bytes: Uint8Array): string {
return bytes.join();
}
`,
},
{
name: "join on a number array is taught (float elements)",
formerEmitGate: "NS9001",
src: `
export function csv(xs: readonly number[]): string {
return xs.join(",");
}
`,
},
{
name: "the wrong empty test is taught in both directions (R7c)",
formerEmitGate: "NS9001",
src: `
export function f(cursor: number | null): boolean {
return cursor === undefined;
}
`,
},
{
name: "=== null on a find result is taught (the miss is JS undefined)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): number {
const hit = xs.find((x) => x > 0);
if (hit === null) return -1;
return 0;
}
`,
},
];
// The building-and-sorting round: block-body callbacks in the array methods,
// the push-builder, prepend/multi-spread literals, toSorted, and the two
// comptime/clamping fixes. Emitting cases must compile; the shapes with no
// mapping are gated by their teaching rules.
const builderSortCases: Case[] = [
{
name: "block-body callbacks across map, filter, find, findIndex, some, every, reduce",
src: `
export interface Task { readonly id: number; readonly streak: number; readonly done: boolean; }
export interface Model { readonly tasks: readonly Task[]; }
export function capped(model: Model, lim: number): readonly Task[] {
return model.tasks.map((t) => {
if (t.streak > lim) return { ...t, streak: lim };
return t;
});
}
export function margins(model: Model, lim: number): readonly Task[] {
return model.tasks.filter((t) => {
if (t.done) return false;
const margin = lim - t.streak;
return margin > 0;
});
}
export function pickId(model: Model, want: number): number {
const hit = model.tasks.find((t) => {
if (t.id === want) return true;
return t.streak > want;
});
if (hit === undefined) return -1;
return hit.id;
}
export function whereDone(model: Model): number {
return model.tasks.findIndex((t) => {
if (t.streak === 0) return false;
return t.done;
});
}
export function anyOpen(model: Model): boolean {
return model.tasks.some((t) => {
const open = !t.done;
return open;
});
}
export function allSeeded(model: Model): boolean {
return model.tasks.every((t) => {
if (t.done) return true;
return t.streak > 0;
});
}
export function streakTotal(model: Model): number {
return model.tasks.reduce((sum, t) => {
if (t.done) return sum;
return sum + t.streak;
}, 0);
}
`,
},
{
name: "block-body callback with orelse fusion and a nested expression callback",
src: `
export interface Task { readonly id: number; readonly streak: number; readonly done: boolean; }
export function pairSum(tasks: readonly Task[], others: readonly Task[]): number {
return tasks.reduce((sum, t) => {
const twin = others.find((o) => o.id === t.id);
if (twin === undefined) return sum;
return sum + twin.streak;
}, 0);
}
`,
},
{
name: "push-builder: for...of, classic for, while, and pushes inside if/else",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function openIds(tasks: readonly Task[]): readonly number[] {
const out: number[] = [];
for (const t of tasks) {
if (t.done) {
out.push(-t.id);
} else {
out.push(t.id);
}
}
return out;
}
export function evens(bytes: Uint8Array): readonly number[] {
const out: number[] = [];
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] % 2 === 0) out.push(bytes[i]);
}
return out;
}
export function countdown(n: number): readonly number[] {
const out: number[] = [];
let left = n;
while (left > 0) {
out.push(left);
left -= 1;
}
return out;
}
`,
},
{
name: "push-builder of records escaping into the model (commit walkers see the prefix)",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export interface Model { readonly tasks: readonly Task[]; }
export type Msg = { readonly kind: "mark"; readonly id: number } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "mark": {
const next: Task[] = [];
for (const t of model.tasks) {
if (t.id === msg.id) next.push({ ...t, done: true });
else next.push(t);
}
return { tasks: next };
}
case "noop": return model;
}
}
`,
},
{
name: "builder length and element reads see the filled prefix",
src: `
export function firstBig(xs: readonly number[], lim: number): number {
const out: number[] = [];
for (const x of xs) {
if (x > lim) out.push(x);
}
if (out.length === 0) return -1;
return out[0];
}
`,
},
{
name: "prepend and multi-spread literals over numbers and records",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function pre(xs: readonly number[], x: number): readonly number[] {
return [x, ...xs];
}
export function preTask(tasks: readonly Task[], t: Task): readonly Task[] {
return [t, ...tasks];
}
export function stitch(a: readonly number[], b: readonly number[], x: number): readonly number[] {
return [...a, x, ...b];
}
export function wrap(xs: readonly number[], lo: number, hi: number): readonly number[] {
return [lo, ...xs, hi];
}
`,
},
{
name: "toSorted over records, numbers, and a block-body comparator",
src: `
export interface Task { readonly id: number; readonly streak: number; readonly done: boolean; }
export interface Model { readonly tasks: readonly Task[]; }
export function byStreak(model: Model): readonly Task[] {
return model.tasks.toSorted((a, b) => a.streak - b.streak);
}
export function descending(xs: readonly number[]): readonly number[] {
return xs.toSorted((a, b) => b - a);
}
export function doneLast(model: Model): readonly Task[] {
return model.tasks.toSorted((a, b) => {
if (a.done === b.done) return a.streak - b.streak;
if (a.done) return 1;
return -1;
});
}
`,
},
{
name: "bytes slice with out-of-range and negative bounds compiles to the clamped copy",
src: `
export function tail(bytes: Uint8Array, from: number): Uint8Array {
return bytes.slice(from);
}
export function window(bytes: Uint8Array, from: number, to: number): Uint8Array {
return bytes.slice(from, to);
}
export function lastTwo(bytes: Uint8Array): Uint8Array {
return bytes.slice(-2);
}
`,
},
{
name: "comptime division folds to the JS f64 value (0/0 once emitted an illegal divide)",
src: `
export const RATE = 1 / 0;
export function nanv(): number { return 0 / 0; }
export function half(): number { return 5 / 2; }
export function negZero(): number { return 0 / -1; }
`,
},
{
name: "number const from a ternary of literal branches carries its runtime type",
src: `
export function step(flag: boolean): number {
const delta = flag ? -1 : 1;
return delta;
}
export interface Habit { readonly id: number; readonly streak: number; readonly doneToday: boolean; }
export function toggled(habits: readonly Habit[], id: number): readonly Habit[] {
return habits.map((h) => {
if (h.id !== id) return h;
const bump = h.doneToday ? -1 : 1;
return { ...h, doneToday: !h.doneToday, streak: h.streak + bump };
});
}
`,
},
{
name: "toSorted without a comparator is taught (JS ToString ordering)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): readonly number[] {
return xs.toSorted();
}
`,
},
{
name: "in-place .sort() is taught toward .toSorted",
gate: "NS1022",
src: `
export function f(xs: number[]): number[] {
xs.sort((a, b) => a - b);
return xs;
}
`,
},
{
name: "push on a parameter array is the mutation rule",
gate: "NS1001",
src: `
export function f(xs: number[], x: number): number[] {
xs.push(x);
return xs;
}
`,
},
{
name: "push on a MIXED reassigned binding is taught (an alias assignment ends ownership)",
gate: "NS1001",
src: `
export function f(xs: number[], n: number): readonly number[] {
let out: number[] = [];
out.push(n);
out = xs;
return out;
}
`,
},
{
name: "R17m: a reassigned binding whose every assignment is a fresh construction mutates freely",
src: `
export function f(n: number): readonly number[] {
let out: number[] = [];
out.push(n);
out = [n, n];
out.push(n + 1);
return out;
}
`,
},
{
name: "push in value position is taught (the JS value is the new length)",
formerEmitGate: "NS9001",
src: `
export function f(n: number): number {
const out: number[] = [];
const len = out.push(n);
return len;
}
`,
},
{
name: "an un-annotated spread local is taught its array-type annotation (NS1052)",
gate: "NS1052",
src: `
export interface Turn { readonly id: number; readonly text: Uint8Array; }
export interface Model { readonly turns: readonly Turn[]; readonly nextId: number; }
export type Msg = { readonly kind: "add"; readonly text: Uint8Array } | { readonly kind: "clear" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": {
const turns = [...model.turns, { id: model.nextId, text: msg.text }];
return { ...model, turns: turns, nextId: model.nextId + 1 };
}
case "clear": return { ...model, turns: [] };
}
}
`,
},
{
name: "push with a spread argument is taught (one element per call)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): readonly number[] {
const out: number[] = [];
out.push(...xs);
return out;
}
`,
},
{
name: "pushing to the array a for...of iterates is taught, not silently snapshotted",
formerEmitGate: "NS9001",
src: `
export function f(n: number): readonly number[] {
const out: number[] = [];
out.push(n);
for (const x of out) {
if (x > 0) out.push(x - 1);
}
return out;
}
`,
},
{
name: "a callback path falling off the end is taught (implicit undefined)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): readonly number[] {
return xs.filter((x) => {
if (x > 0) return true;
});
}
`,
},
];
// The local-mutation round: mutating array methods are legal on arrays the
// function creates and still owns (the push-builder generalized to the full
// method set); the teaching rules fire only at the true semantic boundaries
// (shared inputs NS1001/NS1022, mutation after an escape NS1051).
const localMutationCases: Case[] = [
{
name: "R17m: recursion over borrowed readonly slices keeps the caller's ownership",
src: `
function sumFrom(xs: readonly number[], i: number): number {
if (i >= xs.length) return 0;
return xs[i] + sumFrom(xs, i + 1);
}
export function f(n: number): number {
const out: number[] = [n, n + 1];
const a = sumFrom(out, 0);
out.push(n + 2);
return a + sumFrom(out, 0);
}
`,
},
{
name: "NS1051: a readonly param handed onward into a MUTABLE position ends the borrow",
gate: "NS1051",
src: `
function scrub(xs: number[]): number { xs.pop(); return xs.length; }
function relay(xs: readonly number[]): number { return scrub(xs as number[]); }
export function f(): number { const out: number[] = [1, 2]; const t = relay(out); out.push(3); return t; }
`,
},
{
name: "NS1051: a readonly param the callee aliases through a cast is no borrow",
gate: "NS1051",
src: `
function sneak(xs: readonly number[]): number {
const grabbed = xs as number[];
return grabbed.length;
}
export function f(): number { const out: number[] = [1]; const t = sneak(out); out.push(2); return t; }
`,
},
{
name: "owned mutation: a parser stack (push/pop) over a byte scan",
src: `
export function maxDepth(bytes: Uint8Array): number {
const stack: number[] = [];
let max = 0;
for (const b of bytes) {
if (b === 40) stack.push(b);
if (b === 41) stack.pop();
if (stack.length > max) max = stack.length;
}
return max;
}
`,
},
{
name: "owned mutation: pop and shift values ride the find-miss optional machinery",
src: `
export function lastOr(xs: readonly number[], fallback: number): number {
const work = xs.slice();
const v = work.pop();
if (v === undefined) return fallback;
return v;
}
export function firstOr(xs: readonly number[], fallback: number): number {
const work = xs.slice();
return work.shift() ?? fallback;
}
`,
},
{
name: "owned mutation: the canonical in-place sort (slice copy, then sort)",
src: `
export interface Task { readonly id: number; readonly streak: number; }
export function byStreak(tasks: readonly Task[]): readonly Task[] {
const copy = tasks.slice();
copy.sort((a, b) => a.streak - b.streak);
return copy;
}
export function ordered(xs: readonly number[]): readonly number[] {
const copy = xs.slice();
copy.sort((a, b) => a - b);
return copy;
}
`,
},
{
name: "owned mutation: splice in value and statement position, unshift, reverse, fill",
src: `
export function surgery(xs: readonly number[]): readonly number[] {
const work = xs.slice();
const removed = work.splice(1, 2, 9, 8);
work.splice(0, 1);
work.unshift(-1, -2);
work.reverse();
work.fill(0, 1, 2);
return removed.concat(work);
}
`,
},
{
name: "owned mutation: non-empty starts (literal seed, map/filter/concat/toSorted copies)",
src: `
export function seeded(n: number): readonly number[] {
const st = [1, 2, 3];
for (let i = 0; i < n; i++) st.push(i);
return st;
}
export function doubledThenTrimmed(xs: readonly number[]): readonly number[] {
const work = xs.map((x) => x * 2);
work.pop();
return work;
}
export function keptThenGrown(xs: readonly number[], lim: number): readonly number[] {
const work = xs.filter((x) => x > lim);
work.push(lim);
return work;
}
export function joinedThenRotated(a: readonly number[], b: readonly number[]): readonly number[] {
const work = a.concat(b);
const head = work.shift();
if (head !== undefined) work.push(head);
return work;
}
export function sortedThenCapped(xs: readonly number[], cap: number): readonly number[] {
const work = xs.toSorted((a, b) => a - b);
work.fill(cap, -1);
return work;
}
`,
},
{
name: "owned mutation: indexed writes and interleaved reads on owned arrays",
src: `
export function swap(xs: readonly number[]): readonly number[] {
const work = xs.slice();
if (work.length >= 2) {
const tmp = work[0];
work[0] = work[work.length - 1];
work[work.length - 1] = tmp;
}
return work;
}
export function builderWrites(n: number): readonly number[] {
const out: number[] = [];
for (let i = 0; i < n; i++) out.push(i);
if (out.length > 0) out[0] = out[out.length - 1] + 1;
return out;
}
`,
},
{
name: "owned mutation: records pop into an optional record, splice moves them",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function dropLastDone(tasks: readonly Task[]): readonly Task[] {
const work = tasks.slice();
const last = work.pop();
if (last === undefined) return work;
if (!last.done) work.push(last);
return work;
}
export function promote(tasks: readonly Task[], at: number): readonly Task[] {
const work = tasks.slice();
const moved = work.splice(at, 1);
return moved.concat(work);
}
`,
},
{
name: "owned mutation: an early exit return is terminal, not an escape",
src: `
export function padded(xs: readonly number[], min: number): readonly number[] {
const work = xs.slice();
if (work.length >= min) return work;
while (work.length < min) work.push(0);
return work;
}
`,
},
{
name: "escape then mutate: push after the array was passed into a mutable position",
gate: "NS1051",
src: `
function total(xs: number[]): number { return xs.length; }
export function f(): number {
const out: number[] = [1];
const t = total(out);
out.push(2);
return t + out.length;
}
`,
},
{
name: "R17m: a readonly reader call is a borrow — mutation continues after it",
src: `
function total(xs: readonly number[]): number { return xs.length; }
export function f(): number {
const out: number[] = [1];
const t = total(out);
out.push(2);
return t + out.length;
}
`,
},
{
name: "escape then mutate: pop after the array was stored into the returned record",
gate: "NS1051",
src: `
export interface Pair { readonly xs: readonly number[]; readonly n: number; }
export function f(b: boolean): Pair {
const out: number[] = [1, 2];
const pair: Pair = { xs: out, n: 0 };
if (b) out.pop();
return pair;
}
`,
},
{
name: "escape then mutate: aliasing ends the original's ownership",
gate: "NS1051",
src: `
export function f(): number {
const a: number[] = [1];
const b = a;
a.push(2);
return b.length;
}
`,
},
{
name: "escape inside a loop gates the whole loop body (second iteration mutates after the pass)",
gate: "NS1051",
src: `
function probe(xs: number[]): number { return xs.length; }
export function f(n: number): number {
const out: number[] = [];
let t = 0;
for (let i = 0; i < n; i++) {
out.push(i);
t += probe(out);
}
return t;
}
`,
},
{
name: "never owned: mutating the alias of an owned array",
gate: "NS1001",
src: `
export function f(): number {
const a: number[] = [1];
const b = a;
b.push(2);
return a.length;
}
`,
},
{
name: "never owned: pop on a parameter",
gate: "NS1001",
src: `
export function f(xs: number[]): number {
return xs.pop() ?? -1;
}
`,
},
{
name: "never owned: splice on a model field through an as-cast",
gate: "NS1001",
src: `
export interface Model { readonly xs: readonly number[]; }
export function f(model: Model): readonly number[] {
(model.xs as number[]).splice(0, 1);
return model.xs;
}
`,
},
{
name: "never owned: push on a module-level table",
gate: "NS1001",
src: `
const TABLE: number[] = [1, 2];
export function f(): number {
TABLE.push(3);
return TABLE.length;
}
`,
},
{
name: "never owned: an array produced by a helper call (copy with .slice to own it)",
gate: "NS1001",
src: `
function make(): number[] { return [1, 2]; }
export function f(): readonly number[] {
const xs = make();
xs.push(3);
return xs;
}
`,
},
{
name: "never owned: indexed write through a parameter",
gate: "NS1001",
src: `
export function f(xs: number[]): number {
xs[0] = 1;
return xs[0];
}
`,
},
{
name: "in-place sort keeps NS1022 on shared arrays, naming the local-copy idiom",
gate: "NS1022",
src: `
export function f(xs: number[]): number[] {
xs.sort((a, b) => a - b);
return xs;
}
`,
},
{
name: "in-place sort without a comparator is taught (JS ToString ordering)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): readonly number[] {
const copy = xs.slice();
copy.sort();
return copy;
}
`,
},
{
name: "copyWithin stays out of v1 even on an owned array",
gate: "NS1001",
src: `
export function f(xs: readonly number[]): readonly number[] {
const copy = xs.slice();
copy.copyWithin(0, 1);
return copy;
}
`,
},
{
name: "sort/reverse in value position is taught (JS returns the same array)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): readonly number[] {
const copy = xs.slice();
return copy.sort((a, b) => a - b);
}
`,
},
{
name: "unshift in value position is taught (the JS value is the new length)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): number {
const copy = xs.slice();
const n = copy.unshift(0);
return n;
}
`,
},
{
name: "splice with a spread argument is taught",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[], ys: readonly number[]): readonly number[] {
const copy = xs.slice();
copy.splice(0, 0, ...ys);
return copy;
}
`,
},
{
name: "R17m: the appending write IS a push (the one growth shape)",
src: `
export function f(): readonly number[] {
const out = [1, 2];
out[out.length] = 3;
return out;
}
`,
},
{
name: "a compound appending write is taught (it reads the missing slot first)",
formerEmitGate: "NS9001",
src: `
export function f(): readonly number[] {
const out = [1, 2];
out[out.length] += 3;
return out;
}
`,
},
{
name: "length-changing mutation of the array a for...of iterates is taught",
formerEmitGate: "NS9001",
src: `
export function f(): number {
const out: number[] = [1, 2, 3];
let t = 0;
for (const x of out) {
t += x;
out.pop();
}
return t;
}
`,
},
{
name: "length-changing mutation from inside an iterating callback is taught",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): number {
const out: number[] = [1, 2, 3];
return out.reduce((acc, x) => {
out.pop();
return acc + x;
}, 0);
}
`,
},
{
name: "mutating a MIXED reassigned binding is taught (a shared array could ride in)",
gate: "NS1001",
src: `
export function f(xs: number[]): readonly number[] {
let work = xs.slice();
work = xs;
work.pop();
return work;
}
`,
},
{
name: "R17m: a reassigned binding of fresh copies stays owned (pop through the reassignment)",
src: `
export function f(xs: readonly number[]): readonly number[] {
let work = xs.slice();
work = xs.slice();
work.pop();
return work;
}
`,
},
];
const arrayStaticCases: Case[] = [
{
name: "NS1059: Array.from is taught toward the spread copy",
gate: "NS1059",
src: `export function f(xs: readonly number[]): number { const c = Array.from(xs); return c.length; }`,
},
{
name: "NS1059: Array.of is taught toward the literal",
gate: "NS1059",
src: `export function f(): number { const c = Array.of(1, 2, 3); return c.length; }`,
},
{
name: "NS1041: Array.isArray stays the runtime type test it is",
gate: "NS1041",
src: `export function f(x: unknown): boolean { return Array.isArray(x); }`,
},
];
const mathAndTableCases: Case[] = [
{
name: "Math batch: float sites, integer identities, and mixed min/max arities",
src: `
export function flo(x: number): number { return Math.floor(x); }
export function cei(x: number): number { return Math.ceil(x); }
export function tru(x: number): number { return Math.trunc(x); }
export function ab(x: number): number { return Math.abs(x); }
export function sg(x: number): number { return Math.sign(x); }
export function sq(x: number): number { return Math.sqrt(x); }
export function floInt(bytes: Uint8Array): number { return Math.floor(bytes.length); }
export function abInt(bytes: Uint8Array): number { return Math.abs(bytes.length - 10); }
export function sgInt(bytes: Uint8Array): number { return Math.sign(bytes.length - 2); }
export function sqInt(bytes: Uint8Array): number { return Math.sqrt(bytes.length); }
export function noArgs(): number { return Math.min() + Math.max(); }
export function oneArg(x: number): number { return Math.max(x); }
export function three(a: number, b: number, c: number): number { return Math.min(a, b, c); }
export function threeMixed(bytes: Uint8Array, x: number): number { return Math.max(bytes.length, x, 0); }
`,
},
{
name: "Math comptime folds: module consts and expression positions",
src: `
export const HALF = Math.floor(5 / 2);
export const DOWN = Math.floor(-5 / 2);
export const ROOT = Math.sqrt(2);
export const NANV = Math.sign(0 / 0);
export function pick(bytes: Uint8Array): number { return bytes[HALF]; }
export function low(): number { return Math.min(3, 2.5); }
export function ceilNegHalf(): number { return Math.ceil(-0.5); }
`,
},
{
name: "a float Math.floor result used as an index is the taught NS1016, not invalid Zig",
gate: "NS1016",
src: `
export function pick(bytes: Uint8Array, x: number): number {
return bytes[Math.floor(x / 2)];
}
`,
},
{
name: "Math methods outside the v1 set are taught by name",
formerEmitGate: "NS9001",
src: `
export function powed(x: number): number { return Math.pow(x, 2); }
`,
},
{
name: "Number classifiers compile on float and integer-classed arguments",
src: `
export function isInt(x: number): boolean { return Number.isInteger(x); }
export function isFin(x: number): boolean { return Number.isFinite(x); }
export function isNan(x: number): boolean { return Number.isNaN(x); }
export function intAlways(bytes: Uint8Array): boolean { return Number.isInteger(bytes.length); }
export function intFinite(bytes: Uint8Array): boolean { return Number.isFinite(bytes.length); }
export function intNever(bytes: Uint8Array): boolean { return Number.isNaN(bytes.length); }
export function guard(xs: readonly number[]): readonly number[] {
return xs.filter((x) => Number.isFinite(x) && !Number.isNaN(x));
}
`,
},
{
name: "Number methods outside the v1 classifiers are taught by name",
formerEmitGate: "NS9001",
src: `
export function parsed(): number { return Number.parseFloat("1.5"); }
`,
},
{
name: "the NaN and Infinity globals emit as f64 literals in every position",
src: `
export function nanv(): number { return NaN; }
export function inf(): number { return Infinity; }
export function negInf(): number { return -Infinity; }
export function isMax(x: number): boolean { return x === Infinity; }
export function clamped(x: number): number { return Math.min(x, Infinity); }
`,
},
{
name: "comptime % folds to the JS value (zero divisor, -0 dividend results, fractions)",
src: `
export const REM_NAN = 5 % 0;
export function nanv(): number { return 5 % 0; }
export function negZero(): number { return -5 % 5; }
export function frac(): number { return 5.5 % 2; }
export function pick(bytes: Uint8Array): number { return bytes[6 % 4]; }
`,
},
{
name: "module const tables: number/enum/string arrays, records, nested record arrays",
src: `
import { asciiBytes } from "@native-sdk/core";
export type Filter = "all" | "active" | "done";
export const WEEKDAYS = [3, 5, 2, 8];
export const FACTORS: readonly number[] = [0.5, 1, 2];
export const ORDER: readonly Filter[] = ["done", "active", "all"];
export const NAMES: readonly string[] = ["mon", "tue"];
export const SEED_NAME = asciiBytes("Stretch");
export const DEFAULT_FILTER: Filter = "active";
export interface Limits { readonly lo: number; readonly hi: number; }
export const LIMITS: Limits = { lo: 1, hi: 9 };
export interface Task { readonly id: number; readonly title: Uint8Array; readonly done: boolean; }
export interface Model { readonly tasks: readonly Task[]; }
export const SEEDS: readonly Task[] = [
{ id: 1, title: asciiBytes("Ship"), done: false },
{ id: 2, title: asciiBytes("Test"), done: true },
];
export function initialModel(): Model { return { tasks: SEEDS }; }
export function weekdayAt(i: number): number { return WEEKDAYS[i]; }
export function weekdayCount(): number { return WEEKDAYS.length; }
export function weekdayTotal(): number {
let t = 0;
for (const w of WEEKDAYS) t += w;
return t;
}
export function bigWeekdays(lim: number): readonly number[] { return WEEKDAYS.filter((w) => w > lim); }
export function orderAt(i: number): Filter { return ORDER[i]; }
export function nameMatches(n: string): boolean { return NAMES[0] === n; }
export function seedTitle(i: number): Uint8Array { return SEEDS[i].title; }
export function inRange(x: number): boolean { return x >= LIMITS.lo && x <= LIMITS.hi; }
`,
},
{
name: "an unannotated module const record is taught toward the interface annotation",
formerEmitGate: "NS9001",
src: `
export const LIMITS = { lo: 1, hi: 9 };
export function f(): number { return 1; }
`,
},
{
name: "a spread in a module const table is a taught stop (tables are comptime data)",
formerEmitGate: "NS9001",
src: `
export interface Limits { readonly lo: number; readonly hi: number; }
export const BASE: Limits = { lo: 1, hi: 9 };
export const WIDE: Limits = { ...BASE, hi: 99 };
export function f(): number { return 1; }
`,
},
{
name: "a table number that does not fold at compile time is a taught stop",
formerEmitGate: "NS9001",
src: `
function seed(): number { return 3; }
export const TABLE: readonly number[] = [1, seed()];
export function f(): number { return TABLE[0]; }
`,
},
{
name: "type-changing map: scalars, bytes, block bodies, optional results, chained reads",
src: `
export interface Task { readonly id: number; readonly title: Uint8Array; readonly streak: number; }
export function ids(tasks: readonly Task[]): readonly number[] { return tasks.map((t) => t.id); }
export function titles(tasks: readonly Task[]): readonly Uint8Array[] { return tasks.map((t) => t.title); }
export function doubled(tasks: readonly Task[]): readonly number[] { return tasks.map((t) => t.streak * 2); }
export function orNull(xs: readonly number[]): readonly (number | null)[] {
return xs.map((x) => (x > 0 ? x : null));
}
export function capped(tasks: readonly Task[], lim: number): readonly number[] {
return tasks.map((t) => {
if (t.streak > lim) return lim;
return t.streak;
});
}
export function mappedCount(tasks: readonly Task[]): number {
return tasks.map((t) => t.id).length;
}
export function firstTwoIds(tasks: readonly Task[]): readonly number[] {
const all = tasks.map((t) => t.id);
return all.slice(0, 2);
}
`,
},
{
name: "callback index parameters across map/filter/find/findIndex/some/every",
src: `
export interface Task { readonly id: number; readonly streak: number; }
export function weighted(xs: readonly number[]): readonly number[] { return xs.map((x, i) => x * i); }
export function evenSlots(xs: readonly number[]): readonly number[] { return xs.filter((x, i) => i % 2 === 0); }
export function lateBig(xs: readonly number[], at: number): number {
return xs.find((x, i) => i > at && x > 0) ?? -1;
}
export function lateAt(xs: readonly number[], at: number): number {
return xs.findIndex((x, i) => i >= at && x > 0);
}
export function anyLate(xs: readonly number[], at: number): boolean {
return xs.some((x, i) => i > at);
}
export function frontLoaded(tasks: readonly Task[]): boolean {
return tasks.every((t, i) => t.streak >= i);
}
`,
},
{
name: "a callback declaring the third (array) parameter is a taught stop",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): readonly number[] {
return xs.map((x, i, all) => x + all.length);
}
`,
},
{
name: "a reduce callback with an index parameter is a taught stop",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): number {
return xs.reduce((sum, x, i) => sum + x * i, 0);
}
`,
},
{
name: "bytes subarray with out-of-range and negative bounds compiles to the clamped view",
src: `
export function window(bytes: Uint8Array, from: number, to: number): Uint8Array {
return bytes.subarray(from, to);
}
export function tail(bytes: Uint8Array, from: number): Uint8Array {
return bytes.subarray(from);
}
export function whole(bytes: Uint8Array): Uint8Array {
return bytes.subarray();
}
export function chained(bytes: Uint8Array, from: number): Uint8Array {
return bytes.subarray(from).subarray(-2);
}
`,
},
{
name: "array-method calls lower directly inside if and else-if conditions",
src: `
export interface Task { readonly id: number; readonly done: boolean; }
export function grade(xs: readonly number[], flag: boolean): number {
if (flag) return -1;
else if (xs.some((x) => x > 3)) return 1;
else if (xs.every((x) => x < 0)) return 2;
return 0;
}
export function firstDone(tasks: readonly Task[]): number {
if (tasks.some((t) => t.done)) {
const hit = tasks.find((t) => t.done);
if (hit === undefined) return -1;
return hit.id;
}
return 0;
}
export function countIf(xs: readonly number[], lim: number): number {
if (xs.filter((x) => x > lim).length > 1) return 2;
return xs.findIndex((x) => x > lim) >= 0 ? 1 : 0;
}
`,
},
{
name: "a while condition still may not lower statements (re-evaluated per iteration)",
formerEmitGate: "NS9001",
src: `
export function f(xs: readonly number[]): number {
let n = 0;
while (xs.some((x) => x > n)) {
n += 1;
}
return n;
}
`,
},
{
name: "switch on a literal-union value: full coverage, shared labels, defaults, partial + break",
src: `
export type Filter = "all" | "active" | "done";
export type Bit = 0 | 1;
export function label(f: Filter): number {
switch (f) {
case "all": return 0;
case "active": return 1;
case "done": return 2;
}
}
export function sharedBody(f: Filter): number {
switch (f) {
case "active":
case "done":
return 1;
default:
return 0;
}
}
export function partial(f: Filter): number {
let n = 0;
switch (f) {
case "done":
n = 5;
break;
case "active": {
n = 2;
break;
}
}
return n;
}
export function flip(b: Bit): number {
switch (b) {
case 0: return 1;
case 1: return 0;
}
}
export function bitDefault(b: Bit): number {
switch (b) {
case 0: return 7;
default: return 9;
}
}
`,
},
{
name: "a value-switch default that is not the last clause is a taught stop",
formerEmitGate: "NS9001",
src: `
export type Filter = "all" | "active" | "done";
export function f(x: Filter): number {
switch (x) {
case "all": return 0;
default: return 9;
case "done": return 2;
}
return 1;
}
`,
},
{
name: "case labels falling through into a value-switch default are a taught stop",
formerEmitGate: "NS9001",
src: `
export type Filter = "all" | "active" | "done";
export function f(x: Filter): number {
switch (x) {
case "all":
default:
return 9;
}
}
`,
},
{
name: "switching on a plain number value emits the if/else chain",
src: `
export function f(x: number): number {
switch (x) {
case 1: return 1;
default: return 0;
}
}
`,
},
];
// The model tier (round 3): tagged unions as model fields, primitive arrays
// in the model, optional fields end to end, null-guarded `&&`/`||` chains,
// the -0 slot rule, and the NS1024 string-field gate.
const modelTierCases: Case[] = [
{
name: "a tag-discriminated union as a model field: construction, switch, kind guards",
src: `
export type View =
| { readonly kind: "list" }
| { readonly kind: "detail"; readonly id: number };
export interface Model { readonly view: View; readonly n: number; }
export type Msg = { readonly kind: "open"; readonly id: number } | { readonly kind: "back" };
export function initialModel(): Model { return { view: { kind: "list" }, n: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "open": return { ...model, view: { kind: "detail", id: msg.id } };
case "back": return { ...model, view: { kind: "list" } };
}
}
export function shownId(model: Model): number {
if (model.view.kind === "detail") return model.view.id;
return -1;
}
export function viewCode(model: Model): number {
switch (model.view.kind) {
case "list": return 0;
case "detail": return model.view.id;
}
}
`,
},
{
name: "model union arms with deep payloads: records, bytes, and primitive arrays",
src: `
import { asciiBytes } from "@native-sdk/core";
export interface Note { readonly id: number; readonly title: Uint8Array; }
export type View =
| { readonly kind: "list"; readonly scroll: number }
| { readonly kind: "detail"; readonly note: Note; readonly tags: readonly number[] }
| { readonly kind: "compose"; readonly draft: Uint8Array };
export interface Model { readonly view: View | null; readonly flags: readonly boolean[]; }
export type Msg = { readonly kind: "open"; readonly id: number } | { readonly kind: "close" };
export function initialModel(): Model { return { view: null, flags: [] }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "open":
return { ...model, view: { kind: "detail", note: { id: msg.id, title: asciiBytes("note") }, tags: [1, 2, 3] } };
case "close":
return { ...model, view: { kind: "list", scroll: 0 } };
}
}
export function weight(model: Model): number {
if (model.view !== null && model.view.kind === "detail") return model.view.note.id + model.view.tags.length;
return 0;
}
`,
},
{
name: "primitive arrays in the model: numbers, booleans, tags, nested and optional",
src: `
export type Grade = "low" | "high";
export interface Series { readonly points: readonly number[]; readonly name: Uint8Array; }
export interface Model {
readonly xs: readonly number[];
readonly flags: readonly boolean[];
readonly grades: readonly Grade[];
readonly series: readonly Series[];
readonly pending: readonly number[] | null;
}
export type Msg = { readonly kind: "push"; readonly v: number } | { readonly kind: "clear" };
export function initialModel(): Model {
return { xs: [], flags: [], grades: [], series: [], pending: null };
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "push":
return { ...model, xs: [...model.xs, msg.v], flags: [...model.flags, msg.v > 0], pending: model.xs };
case "clear":
return { ...model, xs: [], flags: [], pending: null };
}
}
export function total(model: Model): number {
return model.xs.reduce((sum, x) => sum + x, 0);
}
`,
},
{
name: "null-guarded && chains: statement, else-bearing, ternary, both orders, relational optionals",
src: `
export interface Sel { readonly items: readonly number[]; readonly at: number; }
export interface Model { readonly sel: Sel | null; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { sel: null }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return { sel: null }; }
}
export function hasItems(model: Model): boolean {
if (model.sel !== null && model.sel.items.length > 0) return true;
return false;
}
export function branchy(model: Model): number {
if (model.sel !== null && model.sel.at > 0) return model.sel.at;
else return -1;
}
export function ternary(model: Model): number {
return model.sel !== null && model.sel.at > 0 ? model.sel.at : -1;
}
export function flipped(model: Model): boolean {
return null !== model.sel && model.sel.at >= 0;
}
export function below(cls: number | null, lim: number): boolean {
return cls !== null && cls < lim;
}
`,
},
{
name: "null-guarded || chains: or-else shape, exit narrowing, ternary dual",
src: `
export interface Sel { readonly items: readonly number[]; readonly at: number; }
export function emptyish(sel: Sel | null): boolean {
return sel === null || sel.items.length === 0;
}
export function firstOrMinus(sel: Sel | null): number {
if (sel === null || sel.items.length === 0) return -1;
return sel.items[0];
}
export function clamp(cls: number | null): number {
if (cls === null || cls < 0) return 0;
return cls;
}
export function pick(sel: Sel | null): number {
return sel === null || sel.at < 0 ? -1 : sel.at;
}
`,
},
{
name: "a null guard in a while condition re-tests per iteration",
src: `
export interface Sel { readonly at: number; readonly len: number; }
export function walk(start: Sel | null): number {
let cur: Sel | null = start;
let hops = 0;
while (cur !== null && cur.len > 0) {
hops += cur.len;
cur = cur.len > 2 ? { at: cur.at, len: cur.len - 1 } : null;
}
return hops;
}
`,
},
{
name: "-0 literals and -0-producing constant arithmetic take float slots",
src: `
export function negZero(): number { return -0; }
export function prodNegZero(): number { return 0 * -1; }
export function viaConst(): number { const z = -0; return z; }
export function timesNeg(x: number): number { return x * -1; }
`,
},
{
name: "nested callbacks with colliding element and index names emit uniquely",
src: `
export function weights(xs: readonly number[]): number {
return xs.map((x, i) => xs.map((y, i2) => y * i2 + i).reduce((acc, v) => acc + v, 0) + x * i).reduce((acc, v) => acc + v, 0);
}
export function pairs(xs: readonly number[]): number {
return xs.map((x, i) => xs.map((x2, j) => x2 * x + j * i).reduce((a, v) => a + v, 0)).reduce((a, v) => a + v, 0);
}
`,
},
{
name: "a string-typed model field is a taught stop at the declaration",
gate: "NS1024",
src: `
export interface Model { readonly title: string; readonly n: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { title: "x", n: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return model; }
}
`,
},
{
name: "a string field nested in a model union arm is the same taught stop",
gate: "NS1024",
src: `
export type View =
| { readonly kind: "list" }
| { readonly kind: "detail"; readonly caption: string };
export interface Model { readonly view: View; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { view: { kind: "list" } }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return model; }
}
`,
},
{
name: "a string-array model field is the same taught stop",
gate: "NS1024",
src: `
export interface Model { readonly names: readonly string[]; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { names: [] }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "a": return model; case "b": return model; }
}
`,
},
{
name: "an array of unions in the model stays a loud stop (not in v1)",
formerEmitGate: "NS9001",
src: `
export type Item = { readonly kind: "a" } | { readonly kind: "b"; readonly n: number };
export interface Model { readonly items: readonly Item[]; }
export type Msg = { readonly kind: "x" } | { readonly kind: "y" };
export function initialModel(): Model { return { items: [] }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "x": return model; case "y": return model; }
}
`,
},
{
name: "an array of byte-strings in the model stays a loud stop (not in v1)",
formerEmitGate: "NS9001",
src: `
export interface Model { readonly rows: readonly Uint8Array[]; }
export type Msg = { readonly kind: "x" } | { readonly kind: "y" };
export function initialModel(): Model { return { rows: [] }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "x": return model; case "y": return model; }
}
`,
},
];
// A shared preamble for the streaming-op cases (slice D): every arm shape
// a spawn/audio stream can target, plus the audio event record.
const streamMsg = `
export type AudioState = "loaded" | "position" | "completed" | "failed" | "rejected" | "spectrum";
export interface Model { readonly lines: number; readonly out: Uint8Array; readonly code: number; readonly errs: number; readonly pos: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "line"; readonly text: Uint8Array }
| { readonly kind: "done"; readonly code: number }
| { readonly kind: "sampled"; readonly code: number; readonly output: Uint8Array }
| { readonly kind: "audio_evt"; readonly state: AudioState; readonly positionMs: number; readonly durationMs: number; readonly playing: boolean; readonly buffering: boolean; readonly bands: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { lines: 0, out: new Uint8Array(0), code: -1, errs: 0, pos: 0 }; }
`;
// The tail arms every streaming case's update must cover.
const streamTail = `
case "line": return { ...model, lines: model.lines + 1 };
case "done": return { ...model, code: msg.code };
case "sampled": return { ...model, code: msg.code, out: msg.output };
case "audio_evt": return { ...model, pos: msg.positionMs };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`;
// The image-load fixture: the fifteen-state union and the five-field
// result arm imageLoad routes (id echoes the requested ImageId).
const imageMsg = `
export type ImageState =
| "loaded" | "rejected" | "not_found" | "io_failed" | "connect_failed"
| "tls_failed" | "protocol_failed" | "timed_out" | "http_status"
| "cancelled" | "too_large" | "unsupported" | "decode_failed" | "registry_full"
| "alloc_failed";
export interface Model { readonly w: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "image_done"; readonly id: number; readonly state: ImageState; readonly width: number; readonly height: number; readonly status: number };
export function initialModel(): Model { return { w: 0, errs: 0 }; }
`;
const imageTail = `
case "image_done": return msg.state === "loaded" ? { ...model, w: msg.width } : { ...model, errs: model.errs + 1 };
}
}
`;
const channelMsg = `
export type ChannelState = "data" | "closed" | "rejected";
export interface Model { readonly seen: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "chan_event"; readonly key: number; readonly state: ChannelState; readonly bytes: Uint8Array; readonly droppedPending: number; readonly droppedTotal: number };
export function initialModel(): Model { return { seen: 0, errs: 0 }; }
`;
const channelTail = `
case "chan_event": return msg.state === "data" ? { ...model, seen: model.seen + 1 } : { ...model, errs: model.errs + 1 };
}
}
`;
const captureMsg = `
export type CaptureState = "started" | "data" | "failed" | "stopped" | "rejected";
export type CaptureSource = "microphone" | "system";
export interface Model { readonly seen: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "capture_event"; readonly key: number; readonly state: CaptureState; readonly source: CaptureSource; readonly sampleRate: number; readonly channels: number; readonly timestampMs: number; readonly frames: number; readonly pcm: Uint8Array; readonly droppedPending: number; readonly droppedTotal: number };
export function initialModel(): Model { return { seen: 0, errs: 0 }; }
`;
const captureTail = `
case "capture_event": return msg.state === "data" ? { ...model, seen: model.seen + msg.frames } : { ...model, errs: model.errs + (msg.state === "failed" ? 1 : 0) };
}
}
`;
// The video fixture: the five-state union and the seven-field event arm
// videoLoad routes.
const videoMsg = `
export type VideoState = "loaded" | "position" | "completed" | "failed" | "rejected";
export interface Model { readonly pos: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "video_evt"; readonly state: VideoState; readonly positionMs: number; readonly durationMs: number; readonly playing: boolean; readonly buffering: boolean; readonly width: number; readonly height: number }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { pos: 0, errs: 0 }; }
`;
const videoTail = `
case "video_evt": return { ...model, pos: msg.positionMs };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`;
const ptyMsg = `
export type PtyState = "output" | "exit";
export type PtyExitReason = "exited" | "signaled" | "cancelled" | "rejected" | "spawn_failed";
export interface Model { readonly chunks: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "pty_evt"; readonly key: Uint8Array; readonly state: PtyState; readonly bytes: Uint8Array; readonly code: number; readonly reason: PtyExitReason; readonly signal: number; readonly droppedWrites: number };
export function initialModel(): Model { return { chunks: 0, errs: 0 }; }
`;
const ptyTail = `
case "pty_evt": return msg.state === "output" ? { ...model, chunks: model.chunks + 1 } : { ...model, errs: model.errs + 1 };
}
}
`;
// Slice E: grammar-completeness round — the new statement/operator/
// declaration mappings in REALISTIC combinations (the minimal per-production
// pins live in grammar_matrix.test.ts), plus the new teaching gates.
const grammarCases: Case[] = [
{
name: "do-while inside update with model narrowing and a builder",
src: `
export interface Model { readonly xs: readonly number[]; readonly cursor: number | null; }
export type Msg = { readonly kind: "walk" } | { readonly kind: "noop" };
export function initialModel(): Model { return { xs: [3, 1, 2], cursor: null }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "walk": {
const out: number[] = [];
let i = 0;
do {
if (i < model.xs.length) out.push(model.xs[i] * 2);
i += 1;
} while (i < model.xs.length);
return { ...model, xs: out };
}
case "noop": return model;
}
}`,
},
{
name: "a do-while whose body always exits emits no unreachable trailing test",
src: `
export function firstByte(xs: Uint8Array): number {
do {
if (xs.length > 0) return xs[0];
return -1;
} while (true);
}`,
},
{
name: "labeled loops drive nested scans over record arrays",
src: `
export interface Cell { readonly row: number; readonly col: number; }
export function firstMatch(cells: readonly Cell[], rows: number, cols: number): number {
let hit = -1;
outer: for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
for (const cell of cells) {
if (cell.row === r && cell.col === c) { hit = r * 100 + c; break outer; }
}
}
}
return hit;
}`,
},
{
name: "a default arm coexists with payload captures on the named cases",
src: `
export interface Model { readonly n: number; }
export type Msg =
| { readonly kind: "set"; readonly v: number }
| { readonly kind: "bump"; readonly by: number }
| { readonly kind: "reset" }
| { readonly kind: "noop" };
export function initialModel(): Model { return { n: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "set": return { n: msg.v };
case "bump": return { n: model.n + msg.by };
default: return model;
}
}`,
},
{
name: "exponent, shifts, and the compound family flow through inference soundly",
src: `
export function checksum(xs: Uint8Array): number {
let h = 0;
for (const b of xs) {
h ^= b;
h <<= 1;
h >>>= 0;
h &= 0xffff;
}
return h;
}
export function decay(base: number, steps: Uint8Array): number {
let v = base;
for (const s of steps) {
v *= 0.5;
v += s;
v **= 1;
}
v /= 2;
return v;
}`,
},
{
name: "destructured const fields feed integer and float domains per field",
src: `
export interface Frame { readonly width: number; readonly height: number; readonly scale: number; }
export function area(frame: Frame, bytes: Uint8Array): number {
const { width, height, scale } = frame;
let cells = 0;
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] > 0) cells += 1;
}
return width * height * scale + cells;
}`,
},
{
name: "NS1039: the intrinsic SDK module is imported by name",
gate: "NS1039",
src: `
import * as sdk from "@native-sdk/core";
export function f(): number { return 1; }`,
},
{
name: "R15d: a const-bound local arrow hoists to a module-level function",
src: `export function f(): number { const g = (x: number): number => x + 1; return g(1); }`,
},
{
name: "NS1054: a capturing local function value is taught toward parameters",
gate: "NS1054",
src: `export function f(n: number): number { const g = (): number => n + 1; return g(); }`,
},
{
name: "export lists bind names over declarations: un-renamed exports pub, renames alias",
src: `function base(): number { return 2; }
const SCALE = 3;
export { base, SCALE as FACTOR };
export function f(): number { return base() * SCALE; }`,
},
{
name: "NS1047: re-exporting the SDK surface is not the core's to bind",
gate: "NS1047",
src: `import { Cmd } from "@native-sdk/core";
export { Cmd };
export function f(): number { return 1; }`,
},
{
name: "NS1047: renaming a generic template has no single value to bind",
gate: "NS1047",
src: `function pick<T>(a: T, b: T, first: boolean): T { return first ? a : b; }
export { pick as choose };
export function f(x: number, y: number): number { return pick(x, y, x < y); }`,
},
{
name: "NS1014: a renamed export cannot bind an entry-point name",
gate: "NS1014",
src: `
export interface Model { readonly n: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
function step(model: Model, msg: Msg): Model { return model; }
export { step as update };
export function initialModel(): Model { return { n: 0 }; }`,
},
{
name: "NS1048: loose equality is taught toward strict",
gate: "NS1048",
src: `export function f(a: number, b: number): boolean { return a != b; }`,
},
{
name: "NS1049: var in a for-init is taught toward let",
gate: "NS1049",
src: `export function f(n: number): number { let t = 0; for (var i = 0; i < n; i++) { t += i; } return t; }`,
},
{
name: "R15e: a generic helper monomorphizes per call site",
src: `export function pick<T>(a: T, b: T, first: boolean): T { return first ? a : b; }
export function f(x: number, y: number): number { return pick(x, y, x < y); }`,
},
{
name: "NS1050: a generic function VALUE is taught toward a module-level declaration",
gate: "NS1050",
src: `export function f(): number { const id = <T>(x: T): T => x; return id(1); }`,
},
];
// Slice D: the streaming multi-result ops — subprocess spawn and the audio
// event stream — emit-clean in their documented shapes, gated with the
// taught rules everywhere else.
const streamingCases: Case[] = [
{
name: "fetch streams route response lines and one terminal HTTP status",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.fetch(
{ url: asciiBytes("https://a.test/events"), method: "POST", headers: { accept: "text/event-stream" }, body: model.out, timeoutMs: 60000, maxLineBytes: 65536 },
{ key: "events", line: "line", ok: "done", err: "failed" },
)];
${streamTail}
`,
},
{
name: "the window verbs emit in their documented shapes",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.showWindow("player")];
if (msg.which === 1) return [model, Cmd.batch([Cmd.hideWindow("player"), Cmd.setDockPresence(false)])];
if (msg.which === 2) return [model, Cmd.launchAtLoginStatus({ key: "login-status", ok: "line", err: "failed" })];
if (msg.which === 3) return [model, Cmd.setLaunchAtLogin(true, { key: "login-set", ok: "line", err: "failed" })];
if (msg.which === 4) return [model, Cmd.batch([Cmd.showWindow("player"), Cmd.quitApp()])];
return [model, Cmd.quitApp()];
${streamTail}
`,
},
{
name: "a dynamic showWindow label is taught (window labels are declarations)",
formerEmitGate: "NS1027",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": {
const label = msg.which === 0 ? "player" : "settings";
return [model, Cmd.showWindow(label)];
}
${streamTail}
`,
},
{
// The wire's label length prefix counts BYTES: 100 CJK characters
// are 100 UTF-16 code units but 300 UTF-8 bytes, so the 255-byte
// teaching must fire on the byte count, not on \`.length\`.
name: "a showWindow label over 255 UTF-8 bytes is taught (100 CJK chars = 300 bytes)",
formerEmitGate: "NS9001",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
return [model, Cmd.showWindow("${"音".repeat(100)}")];
${streamTail}
`,
},
{
// The under-bound twin: a multibyte label whose BYTE length fits
// (250 bytes) emits and compiles — the byte gate must not over-refuse.
name: "a multibyte showWindow label under 255 bytes emits (250 UTF-8 bytes)",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
return [model, Cmd.showWindow("${"音".repeat(83)}x")];
${streamTail}
`,
},
{
name: "spawn and the audio verbs emit in their documented shapes",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.spawn([asciiBytes("/bin/ps"), asciiBytes("-axo")], { key: "ps", line: "line", exit: "done", err: "failed" })];
if (msg.which === 1) return [model, Cmd.spawn([asciiBytes("/usr/bin/pbcopy")], { key: "copy", stdin: model.out, exit: "done", err: "failed" })];
if (msg.which === 2) return [model, Cmd.spawn([asciiBytes("/usr/bin/vm_stat")], { key: "mem", collect: true, exit: "sampled", err: "failed" })];
if (msg.which === 3) return [model, Cmd.cancel("ps")];
if (msg.which === 4) return [model, Cmd.audioPlay("player", { path: asciiBytes("music/track.mp3") }, { event: "audio_evt" })];
if (msg.which === 5) return [model, Cmd.audioPlay("player", { url: asciiBytes("https://cdn.test/track.mp3"), cachePath: model.out, expectedBytes: 4096 }, { event: "audio_evt" })];
if (msg.which === 6) return [model, Cmd.batch([Cmd.audioPause("player"), Cmd.audioResume("player")])];
if (msg.which === 7) return [model, Cmd.audioSeek("player", 45000)];
if (msg.which === 8) return [model, Cmd.audioSetVolume("player", 0.5)];
return [model, Cmd.audioStop("player")];
${streamTail}
`,
},
{
name: "a line-mode spawn exit arm without a single number payload is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type TimestampKind } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([asciiBytes("/bin/ps")], { line: "line", exit: "line" as TimestampKind<Msg>, err: "failed" })];
${streamTail}
`,
},
{
name: "a collect spawn exit arm that is not a { code, output } record is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type FetchedKind } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([asciiBytes("/bin/ps")], { collect: true, exit: "done" as FetchedKind<Msg>, err: "failed" })];
${streamTail}
`,
},
{
name: "a line arm on a collect spawn is taught (collect has no line framing)",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type SpawnCollectRoute } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([asciiBytes("/bin/ps")], { collect: true, line: "line", exit: "sampled", err: "failed" } as SpawnCollectRoute<Msg>)];
${streamTail}
`,
},
{
name: "a dynamic argv value is taught (argv is an inline array literal)",
formerEmitGate: "NS1029",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
const PS: readonly Uint8Array[] = [asciiBytes("/bin/ps")];
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn(PS, { exit: "done", err: "failed" })];
${streamTail}
`,
},
{
name: "an empty argv stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([], { exit: "done", err: "failed" })];
${streamTail}
`,
},
{
name: "more argv elements than the engine accepts stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([${Array.from({ length: 17 }, (_, i) => `asciiBytes("a${i}")`).join(", ")}], { exit: "done", err: "failed" })];
${streamTail}
`,
},
{
name: "an argv block over the engine's byte bound stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([asciiBytes("/bin/echo"), asciiBytes(${JSON.stringify("a".repeat(2048))})], { exit: "done", err: "failed" })];
${streamTail}
`,
},
{
name: "an over-bound stdin literal stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.spawn([asciiBytes("/usr/bin/pbcopy")], { stdin: asciiBytes(${JSON.stringify("s".repeat(4097))}), exit: "done", err: "failed" })];
${streamTail}
`,
},
{
name: "an audio event arm whose state union misses a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type AudioEventKind } from "@native-sdk/core";
export type NarrowState = "loaded" | "position" | "completed" | "failed" | "rejected";
export interface Model { readonly pos: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go" }
| { readonly kind: "audio_evt"; readonly state: NarrowState; readonly positionMs: number; readonly durationMs: number; readonly playing: boolean; readonly buffering: boolean; readonly bands: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { pos: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioPlay("player", { path: asciiBytes("a.mp3") }, { event: "audio_evt" as AudioEventKind<Msg> })];
case "audio_evt": return { ...model, pos: msg.positionMs };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "an audio event arm with a wrong field shape is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type AudioEventKind } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioPlay("player", { path: asciiBytes("a.mp3") }, { event: "sampled" as AudioEventKind<Msg> })];
${streamTail}
`,
},
{
name: "an audio source without a path or url is taught (nothing could play)",
formerEmitGate: "NS1029",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioPlay("player", { cachePath: asciiBytes("cache/a.mp3") }, { event: "audio_evt" })];
${streamTail}
`,
},
{
name: "a dynamic audio key is taught (keys are compile-time routing data)",
formerEmitGate: "NS1027",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioPause(model.errs > 0 ? "a" : "b")];
${streamTail}
`,
},
{
name: "an audio volume literal outside 0..1 stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioSetVolume("player", 1.5)];
${streamTail}
`,
},
{
name: "a negative audio seek literal stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioSeek("player", -250)];
${streamTail}
`,
},
{
name: "imageLoad emits in its documented shapes (path, url+cache, model-expression ids)",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.imageLoad(7, { path: asciiBytes("art/cover.png") }, { event: "image_done" })];
if (msg.which === 1) return [model, Cmd.imageLoad(model.w + 1, { url: asciiBytes("https://cdn.test/a.png"), cachePath: asciiBytes("cache/a.png"), expectedBytes: 2048 }, { event: "image_done" })];
return [model, Cmd.imageLoad(9, { path: asciiBytes("art/b.png"), url: asciiBytes("https://cdn.test/b.png") }, { event: "image_done" })];
${imageTail}
`,
},
{
name: "an image result arm whose state union misses a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type ImageEventKind } from "@native-sdk/core";
export type NarrowState = "loaded" | "rejected" | "decode_failed";
export interface Model { readonly w: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "image_done"; readonly id: number; readonly state: NarrowState; readonly width: number; readonly height: number; readonly status: number };
export function initialModel(): Model { return { w: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { path: asciiBytes("a.png") }, { event: "image_done" as ImageEventKind<Msg> })];
case "image_done": return { ...model, w: msg.width };
}
}
`,
},
{
name: "an image result arm with a wrong field shape is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type ImageEventKind } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { path: asciiBytes("a.png") }, { event: "go" as ImageEventKind<Msg> })];
${imageTail}
`,
},
{
name: "an image source without a path or url is taught (nothing could load)",
formerEmitGate: "NS1029",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { cachePath: asciiBytes("cache/a.png") }, { event: "image_done" })];
${imageTail}
`,
},
{
name: "an image id literal the registry must refuse stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(0, { path: asciiBytes("a.png") }, { event: "image_done" })];
${imageTail}
`,
},
{
// The id bound is exclusive at 2^53: 2^53 - 1 is the last integer
// every tier carries exactly, so it builds.
name: "the top image id literal (2^53 - 1) builds",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(9007199254740991, { path: asciiBytes("a.png") }, { event: "image_done" })];
${imageTail}
`,
},
{
// 2^53 aliases 2^53 + 1 in f64 — the first id the wire cannot carry
// exactly, so the literal stops the build.
name: "an image id literal of 2^53 stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(9007199254740992, { path: asciiBytes("a.png") }, { event: "image_done" })];
${imageTail}
`,
},
{
// Byte counts are whole numbers: a fractional literal would
// truncate on the host into a size the app never declared, so the
// cache would verify every download against the wrong size and
// re-fetch on every launch. The literal stops the build.
name: "a fractional image expectedBytes literal stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { url: asciiBytes("https://cdn.test/a.png"), cachePath: asciiBytes("cache/a.png"), expectedBytes: 1.5 }, { event: "image_done" })];
${imageTail}
`,
},
{
name: "a whole-number image expectedBytes literal builds",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { url: asciiBytes("https://cdn.test/a.png"), cachePath: asciiBytes("cache/a.png"), expectedBytes: 4096 }, { event: "image_done" })];
${imageTail}
`,
},
{
name: "channelOpen and channelClose emit in their documented shapes (literal and model-expression keys)",
src: `
import { Cmd } from "@native-sdk/core";
${channelMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.channelOpen(41, { event: "chan_event" })];
if (msg.which === 1) return [model, Cmd.channelOpen(model.seen + 100, { event: "chan_event" })];
return [model, Cmd.channelClose(41)];
${channelTail}
`,
},
{
name: "audioCaptureStart and audioCaptureStop emit microphone and system capture records",
src: `
import { Cmd } from "@native-sdk/core";
${captureMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.audioCaptureStart(91, { source: "microphone" }, { event: "capture_event" })];
if (msg.which === 1) return [model, Cmd.audioCaptureStart(model.seen + 100, { source: "system", sampleRate: 16000, channels: 2 }, { event: "capture_event" })];
return [model, Cmd.audioCaptureStop(91)];
${captureTail}
`,
},
{
name: "an audio capture event arm whose state union misses a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, type AudioCaptureEventKind } from "@native-sdk/core";
export type NarrowState = "started" | "data" | "failed" | "stopped";
export type CaptureSource = "microphone" | "system";
export interface Model { readonly seen: number; }
export type Msg =
| { readonly kind: "go" }
| { readonly kind: "capture_event"; readonly key: number; readonly state: NarrowState; readonly source: CaptureSource; readonly sampleRate: number; readonly channels: number; readonly timestampMs: number; readonly frames: number; readonly pcm: Uint8Array; readonly droppedPending: number; readonly droppedTotal: number };
export function initialModel(): Model { return { seen: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioCaptureStart(91, { source: "microphone" }, { event: "capture_event" as AudioCaptureEventKind<Msg> })];
case "capture_event": return { seen: model.seen + 1 };
}
}
`,
},
{
name: "a channel event arm whose state union misses a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, type ChannelEventKind } from "@native-sdk/core";
export type NarrowState = "data" | "closed";
export interface Model { readonly seen: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "chan_event"; readonly key: number; readonly state: NarrowState; readonly bytes: Uint8Array; readonly droppedPending: number; readonly droppedTotal: number };
export function initialModel(): Model { return { seen: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.channelOpen(41, { event: "chan_event" as ChannelEventKind<Msg> })];
case "chan_event": return { ...model, seen: model.seen + 1 };
}
}
`,
},
{
name: "a channel event arm with a wrong field shape is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, type ChannelEventKind } from "@native-sdk/core";
${channelMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.channelOpen(41, { event: "go" as ChannelEventKind<Msg> })];
${channelTail}
`,
},
{
name: "a channel key literal the engine must refuse stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${channelMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.channelOpen(0, { event: "chan_event" })];
${channelTail}
`,
},
{
// 2^53 aliases 2^53 + 1 in f64 — the image id gate's bound, shared.
name: "a channel key literal of 2^53 stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${channelMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.channelClose(9007199254740992)];
${channelTail}
`,
},
{
name: "imageCancel emits: the numeric-id cancel with literal and model-expression ids",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.imageCancel(7)];
return [model, Cmd.imageCancel(model.w + 1)];
${imageTail}
`,
},
{
// The same literal gate as imageLoad: an id no load could ever park
// under has nothing to cancel.
name: "an imageCancel id literal the registry must refuse stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageCancel(0)];
${imageTail}
`,
},
{
name: "the top imageCancel id literal (2^53 - 1) builds",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageCancel(9007199254740991)];
${imageTail}
`,
},
{
name: "an imageCancel id literal of 2^53 stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageCancel(9007199254740992)];
${imageTail}
`,
},
{
name: "imageUnregister emits: the numeric-id registry release with literal and model-expression ids",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.imageUnregister(7)];
return [model, Cmd.imageUnregister(model.w + 1)];
${imageTail}
`,
},
{
// The same literal gate as imageLoad/imageCancel: an id no load
// could ever register under has nothing to unregister.
name: "an imageUnregister id literal the registry must refuse stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageUnregister(0)];
${imageTail}
`,
},
{
name: "the top imageUnregister id literal (2^53 - 1) builds",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageUnregister(9007199254740991)];
${imageTail}
`,
},
{
name: "an imageUnregister id literal of 2^53 stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageUnregister(9007199254740992)];
${imageTail}
`,
},
{
name: "the video verbs emit in their documented shapes",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.videoLoad("clip", { surface: 5, path: asciiBytes("media/clip.mp4") }, { event: "video_evt" })];
if (msg.which === 1) return [model, Cmd.videoLoad("clip", { surface: model.pos + 1, url: asciiBytes("https://cdn.test/clip.mp4"), autoplay: false, loop: true, muted: true }, { event: "video_evt" })];
if (msg.which === 2) return [model, Cmd.batch([Cmd.videoPlay("clip"), Cmd.videoPause("clip")])];
if (msg.which === 3) return [model, Cmd.videoSeek("clip", 45000)];
if (msg.which === 4) return [model, Cmd.videoSetVolume("clip", 0.5)];
if (msg.which === 5) return [model, Cmd.videoSetMuted("clip", model.errs > 0)];
if (msg.which === 6) return [model, Cmd.videoSetLoop("clip", model.errs === 0)];
return [model, Cmd.videoStop("clip")];
${videoTail}
`,
},
{
name: "a video event arm whose state union misses a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type VideoEventKind } from "@native-sdk/core";
export type NarrowState = "loaded" | "position" | "completed" | "failed";
export interface Model { readonly pos: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "video_evt"; readonly state: NarrowState; readonly positionMs: number; readonly durationMs: number; readonly playing: boolean; readonly buffering: boolean; readonly width: number; readonly height: number }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { pos: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { surface: 5, path: asciiBytes("clip.mp4") }, { event: "video_evt" as VideoEventKind<Msg> })];
case "video_evt": return { ...model, pos: msg.positionMs };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`,
},
{
name: "the pty verbs emit in their documented shapes (defaults, explicit grid, model-expression resize)",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go":
if (msg.which === 0) return [model, Cmd.ptySpawn([asciiBytes("/bin/zsh"), asciiBytes("-l")], { key: "shell", event: "pty_evt" })];
if (msg.which === 1) return [model, Cmd.ptySpawn([asciiBytes("/bin/sh")], { key: "shell", cols: 120, rows: 30, term: "xterm-256color", event: "pty_evt" })];
if (msg.which === 2) return [model, Cmd.ptyWrite("shell", asciiBytes("ls\\n"))];
if (msg.which === 3) return [model, Cmd.ptyResize("shell", model.chunks + 100, 40)];
return [model, Cmd.ptyKill("shell")];
${ptyTail}
`,
},
{
// The two-member state set leaves no room for a NARROWER union (a
// bare literal type is not a union), so the drift case is a
// misnamed member — the same NS1027 teaching.
name: "a pty event arm whose state union misnames a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type PtyEventKind } from "@native-sdk/core";
export type NarrowState = "output" | "done";
export type PtyExitReason = "exited" | "signaled" | "cancelled" | "rejected" | "spawn_failed";
export interface Model { readonly chunks: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "pty_evt"; readonly key: Uint8Array; readonly state: NarrowState; readonly bytes: Uint8Array; readonly code: number; readonly reason: PtyExitReason; readonly signal: number; readonly droppedWrites: number };
export function initialModel(): Model { return { chunks: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptySpawn([asciiBytes("/bin/sh")], { key: "shell", event: "pty_evt" as PtyEventKind<Msg> })];
case "pty_evt": return { ...model, chunks: model.chunks + 1 };
}
}
`,
},
{
name: "a video event arm with a wrong field shape is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type VideoEventKind } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { surface: 5, path: asciiBytes("clip.mp4") }, { event: "failed" as VideoEventKind<Msg> })];
${videoTail}
`,
},
{
name: "a video source without a path or url is taught (nothing could play)",
formerEmitGate: "NS1029",
src: `
import { Cmd } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { surface: 5 }, { event: "video_evt" })];
${videoTail}
`,
},
{
name: "a video source without a surface is taught (the frames need a texture channel)",
formerEmitGate: "NS1029",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { path: asciiBytes("clip.mp4") } as never, { event: "video_evt" })];
${videoTail}
`,
},
{
name: "a dynamic videoLoad option boolean is taught (the flags byte is build-time data)",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { surface: 5, path: asciiBytes("clip.mp4"), loop: model.errs > 0 }, { event: "video_evt" })];
${videoTail}
`,
},
{
name: "a dynamic video key is taught (keys are compile-time routing data)",
formerEmitGate: "NS1027",
src: `
import { Cmd } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoPause(model.errs > 0 ? "a" : "b")];
${videoTail}
`,
},
{
name: "a video volume literal outside 0..1 stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoSetVolume("clip", 1.5)];
${videoTail}
`,
},
{
name: "a negative video seek literal stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoSeek("clip", -250)];
${videoTail}
`,
},
{
name: "a pty event arm whose reason union misses a member is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type PtyEventKind } from "@native-sdk/core";
export type PtyState = "output" | "exit";
export type NarrowReason = "exited" | "cancelled";
export interface Model { readonly chunks: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "pty_evt"; readonly key: Uint8Array; readonly state: PtyState; readonly bytes: Uint8Array; readonly code: number; readonly reason: NarrowReason; readonly signal: number; readonly droppedWrites: number };
export function initialModel(): Model { return { chunks: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptySpawn([asciiBytes("/bin/sh")], { key: "shell", event: "pty_evt" as PtyEventKind<Msg> })];
case "pty_evt": return { ...model, chunks: model.chunks + 1 };
}
}
`,
},
{
name: "a pty event arm with a wrong field shape is taught",
formerEmitGate: "NS1027",
src: `
import { Cmd, asciiBytes, type PtyEventKind } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptySpawn([asciiBytes("/bin/sh")], { key: "shell", event: "go" as PtyEventKind<Msg> })];
${ptyTail}
`,
},
{
name: "a zero pty grid literal stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptySpawn([asciiBytes("/bin/sh")], { key: "shell", cols: 0, event: "pty_evt" })];
${ptyTail}
`,
},
{
// Grids are u16 at the transport: the first unrepresentable
// dimension stops the build the way the zero does.
name: "a pty resize dimension literal past the transport bound stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptyResize("shell", 65536, 40)];
${ptyTail}
`,
},
{
name: "an empty pty argv stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptySpawn([], { key: "shell", event: "pty_evt" })];
${ptyTail}
`,
},
{
name: "a pty TERM literal over the engine bound stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptySpawn([asciiBytes("/bin/sh")], { key: "shell", term: "${"x".repeat(33)}", event: "pty_evt" })];
${ptyTail}
`,
},
{
// Keystrokes and pastes, not bulk transfers: a compile-time-known
// payload over the engine's per-write bound stops the build.
name: "a pty write literal over the engine bound stops at compile time",
formerEmitGate: "NS1030",
src: `
import { Cmd, asciiBytes } from "@native-sdk/core";
${ptyMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.ptyWrite("shell", asciiBytes("${"y".repeat(4097)}"))];
${ptyTail}
`,
},
];
// The multi-file round: a core may split into modules under src/ (relative
// imports with real .ts filenames), and SDK library modules
// ("@native-sdk/core/text") transpile in when imported. Emit cases join the
// zig-compile corpus below; boundary mistakes are gated by NS1034-NS1038
// and the narrowed NS1014 (entry contract).
interface MultiFileCase {
readonly name: string;
readonly gate?: string;
readonly files: Record<string, string>;
}
const multiFileCases: MultiFileCase[] = [
{
name: "namespace imports alias in-graph modules: values, calls, qualified types, cross-class args",
files: {
"core.ts": `
import * as util from "./util.ts";
export interface Model { readonly n: number; }
export type Msg = { readonly kind: "bump" } | { readonly kind: "reset" };
export function initialModel(): Model { return { n: util.SEED }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "bump": {
const cfg: util.Cfg = { step: 2, scale: 1.5 };
return { n: util.grow(model.n, cfg) };
}
case "reset": return { n: util.SEED };
}
}`,
"util.ts": `
export interface Cfg { readonly step: number; readonly scale: number; }
export const SEED = 5;
export function grow(n: number, cfg: Cfg): number { return n + cfg.step * cfg.scale; }`,
},
},
{
name: "NS1039: a namespace alias used as a value is taught",
gate: "NS1039",
files: {
"core.ts": `
import * as util from "./util.ts";
const grabbed = util;
export function f(): number { return grabbed.SEED; }`,
"util.ts": `export const SEED = 5;`,
},
},
{
name: "NS1038: a type and a same-file exported value cannot share a name",
gate: "NS1038",
files: {
"core.ts": `
export interface Config { readonly n: number; }
export const Config = { n: 1 };
export function f(c: Config): number { return c.n; }`,
},
},
{
name: "value re-exports chain across modules and renames bind new names",
files: {
"core.ts": `
import { bump } from "./api.ts";
function tripled(n: number): number { return n * 3; }
export { tripled as thrice };
export { bump } from "./api.ts";
export function f(n: number): number { return bump(n) + tripled(n); }`,
"api.ts": `export { bump } from "./impl.ts";`,
"impl.ts": `export function bump(n: number): number { return n + 1; }`,
},
},
{
name: "NS1014: an imported module list-exporting an entry point is taught",
gate: "NS1014",
files: {
"core.ts": `
import { helper } from "./extra.ts";
export interface Model { readonly n: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { n: helper() }; }
export function update(model: Model, msg: Msg): Model { return model; }`,
"extra.ts": `
export function helper(): number { return 1; }
function subscriptions(): number { return 2; }
export { subscriptions };`,
},
},
{
name: "NS1014: the entry re-exporting an entry point from an imported module is taught",
gate: "NS1014",
files: {
"core.ts": `
export interface Model { readonly n: number; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export { update } from "./logic.ts";
export function initialModel(): Model { return { n: 0 }; }`,
"logic.ts": `
import type { Model, Msg } from "./core.ts";
export function update(model: Model, msg: Msg): Model { return model; }`,
},
},
{
name: "a renamed export-list helper joins the model binding surface under its exported name",
files: {
"core.ts": `
export interface Model { readonly tasks: readonly number[]; }
export type Msg = { readonly kind: "a" } | { readonly kind: "b" };
export function initialModel(): Model { return { tasks: [] }; }
export function update(model: Model, msg: Msg): Model { return model; }
function taskTotal(model: Model): number { return model.tasks.length; }
export { taskTotal as taskCount };
export const viewUnbound = ["taskCount"] as const;`,
},
},
{
name: "NS1038: a renamed export colliding with another module's exported value is taught",
gate: "NS1038",
files: {
"core.ts": `
import { pickOne } from "./util.ts";
function localPick(): number { return 2; }
export { localPick as pickOne };
export function f(): number { return pickOne() + localPick(); }`,
"util.ts": `export function pickOne(): number { return 1; }`,
},
},
{
name: "cross-file types, tables, consts, and helpers resolve and emit",
files: {
"core.ts": `
import { asciiBytes } from "@native-sdk/core";
import { LIMIT, SEEDS, capped, label, type Item, type Level } from "./tables.ts";
export interface Model { readonly items: readonly Item[]; readonly count: number; readonly level: Level; }
export type Msg = { readonly kind: "add" } | { readonly kind: "reset" };
export function initialModel(): Model {
return { items: SEEDS, count: 0, level: "low" };
}
export function title(model: Model): Uint8Array {
return label(model.level);
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": return { ...model, count: capped(model.count + 1), level: model.count + 1 >= LIMIT ? "high" : "low" };
case "reset": return { ...model, count: LIMIT - LIMIT, level: "low" };
}
}
`,
"tables.ts": `
import { asciiBytes } from "@native-sdk/core";
export type Level = "low" | "high";
export interface Item { readonly id: number; readonly name: Uint8Array; }
export const LIMIT = 3;
export const SEEDS: readonly Item[] = [
{ id: 1, name: asciiBytes("one") },
{ id: 2, name: asciiBytes("two") },
];
export function capped(n: number): number {
return n > LIMIT ? LIMIT : n;
}
export function label(level: Level): Uint8Array {
return level === "low" ? asciiBytes("Low") : asciiBytes("High");
}
`,
},
},
{
name: "cross-file discriminated union payloads and subdirectory modules",
files: {
"core.ts": `
import { emptyView, viewName, type View } from "./ui/views.ts";
export interface Model { readonly view: View; }
export type Msg = { readonly kind: "open"; readonly id: number } | { readonly kind: "close" };
export function initialModel(): Model { return { view: emptyView() }; }
export function currentView(model: Model): Uint8Array { return viewName(model.view); }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "open": return { view: { kind: "detail", id: msg.id } };
case "close": return { view: emptyView() };
}
}
`,
"ui/views.ts": `
import { asciiBytes } from "@native-sdk/core";
export type View = { readonly kind: "list" } | { readonly kind: "detail"; readonly id: number };
export function emptyView(): View { return { kind: "list" }; }
export function viewName(view: View): Uint8Array {
switch (view.kind) {
case "list": return asciiBytes("list");
case "detail": return asciiBytes("detail");
}
return asciiBytes("list");
}
`,
},
},
{
name: "the SDK text library transpiles in when imported",
files: {
"core.ts": `
import { asciiBytes } from "@native-sdk/core";
import { applyTextInputEvent, containsIgnoreCase, trimAsciiSpaces, type TextEditState, type TextInputEvent } from "@native-sdk/core/text";
export interface Model { readonly draft: Uint8Array; }
export type Msg = { readonly kind: "edit"; readonly edit: TextInputEvent } | { readonly kind: "clear" };
export function initialModel(): Model { return { draft: new Uint8Array(0) }; }
export function matchesHello(model: Model): boolean {
return containsIgnoreCase(trimAsciiSpaces(model.draft), asciiBytes("hello"));
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "edit": {
const state: TextEditState = { text: model.draft, selection: { anchor: model.draft.length, focus: model.draft.length }, composition: null };
const next = applyTextInputEvent(state, msg.edit, 64);
if (next === null) return model;
return { draft: next.text };
}
case "clear": return { draft: new Uint8Array(0) };
}
}
`,
},
},
{
name: "type-only back-edges are legal (helpers over the entry's Model)",
files: {
"core.ts": `
import { bump } from "./logic.ts";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": return bump(model);
case "noop": return model;
}
}
`,
"logic.ts": `
import type { Model } from "./core.ts";
export function bump(model: Model): Model {
return { ...model, count: model.count + 1 };
}
`,
},
},
{
name: "colliding PRIVATE helpers unique with a per-module prefix",
files: {
"core.ts": `
import { first } from "./a.ts";
import { second } from "./b.ts";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "go" } | { readonly kind: "noop" };
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "go": return { count: first(model.count) + second(model.count) };
case "noop": return model;
}
}
`,
"a.ts": `
function scale(n: number): number { return n * 2; }
export function first(n: number): number { return scale(n); }
`,
"b.ts": `
function scale(n: number): number { return n * 3; }
export function second(n: number): number { return scale(n); }
`,
},
},
{
name: "an import escaping src/ is taught (NS1034)",
gate: "NS1034",
files: {
"core.ts": `
// @ts-expect-error the escape is rejected before resolution matters.
import { helper } from "../outside.ts";
export const y = helper;
`,
},
},
{
name: "a bare npm runtime import is taught (NS1035)",
gate: "NS1035",
files: {
"core.ts": `
// @ts-expect-error the package deliberately does not resolve.
import { thing } from "lodash";
export const y = thing;
`,
},
},
{
name: "a runtime import cycle is taught (NS1036)",
gate: "NS1036",
files: {
"core.ts": `
import { a } from "./a.ts";
export const y = a;
`,
"a.ts": `
import { b } from "./b.ts";
export const a = b + 1;
`,
"b.ts": `
import { a } from "./a.ts";
export const b = a + 1;
`,
},
},
{
name: "a missing module file is taught (NS1037)",
gate: "NS1037",
files: {
"core.ts": `
// @ts-expect-error the module deliberately does not exist.
import { gone } from "./gone.ts";
export const y = gone;
`,
},
},
{
name: "an unknown @native-sdk module is taught (NS1037)",
gate: "NS1037",
files: {
"core.ts": `
// @ts-expect-error the SDK ships no such module.
import { nope } from "@native-sdk/core/nope";
export const y = nope;
`,
},
},
{
name: "a cross-file type-name collision is taught (NS1038)",
gate: "NS1038",
files: {
"core.ts": `
import { seed } from "./other.ts";
export interface Item { readonly id: number; }
export const first: Item = { id: 1 };
export const second = seed;
`,
"other.ts": `
export interface Item { readonly id: number; readonly extra: boolean; }
export function seed(): Item { return { id: 2, extra: true }; }
`,
},
},
{
name: "a cross-file EXPORTED value collision is taught (NS1038)",
gate: "NS1038",
files: {
"core.ts": `
import { helper } from "./other.ts";
export function scale(n: number): number { return n * 2; }
export const y = helper(scale(2));
`,
"other.ts": `
export function scale(n: number): number { return n * 3; }
export function helper(n: number): number { return scale(n); }
`,
},
},
{
name: "update exported from an imported module is taught (NS1014, the entry contract)",
gate: "NS1014",
files: {
"core.ts": `
import { seed } from "./logic.ts";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
export function initialModel(): Model { return seed(); }
`,
"logic.ts": `
import type { Model, Msg } from "./core.ts";
export function seed(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": return { count: model.count + 1 };
case "noop": return model;
}
}
`,
},
},
{
name: "viewUnbound declared in an imported module is taught (NS1014)",
gate: "NS1014",
files: {
"core.ts": `
import { seed } from "./logic.ts";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
export function initialModel(): Model { return seed(); }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "add": return { count: model.count + 1 };
case "noop": return model;
}
}
`,
"logic.ts": `
import type { Model } from "./core.ts";
export function seed(): Model { return { count: 0 }; }
export const viewUnbound = ["count"] as const;
`,
},
},
];
const completeLangTier2Cases: Case[] = [
{
name: "arrays of byte buffers follow array ownership, not the bytes legacy branch",
src: `
export function concatAll(parts: readonly Uint8Array[]): Uint8Array {
let total = 0;
for (const p of parts) total += p.length;
const out = new Uint8Array(total);
let off = 0;
for (const p of parts) { out.set(p, off); off += p.length; }
return out;
}
export function join(a: Uint8Array, b: Uint8Array): Uint8Array {
const parts: Uint8Array[] = [];
parts.push(a);
parts.push(b);
return concatAll(parts);
}
`,
},
{
name: "a shared array of byte buffers still teaches ownership on push",
gate: "NS1001",
src: `
export function collect(parts: Uint8Array[], extra: Uint8Array): number {
parts.push(extra);
return parts.length;
}
`,
},
{
name: "bare module-function references inline as array callbacks",
src: `
function double(n: number): number { return n * 2; }
function isPositive(n: number): boolean { return n > 0; }
function add(a: number, b: number): number { return a + b; }
function byAscending(a: number, b: number): number { return a - b; }
export function f(xs: readonly number[]): number {
return xs.map(double).filter(isPositive).toSorted(byAscending).reduce(add, 0);
}
`,
},
{
name: "a bare reference to a hoisted function-expression helper is a callback too",
src: `
export function f(xs: readonly number[]): readonly number[] {
const scale = function (x: number): number { return x * 3; };
return xs.map(scale);
}
`,
},
];
const dataClassCases: Case[] = [
{
name: "R19b: static methods and static readonly consts lower to module-level declarations",
src: `
import { asciiBytes } from "@native-sdk/core";
export class Task {
id: number;
done: boolean = false;
static readonly LIMIT = 9;
static readonly LABEL = asciiBytes("task");
constructor(id: number) { this.id = id; }
static clampId(n: number): number { return n > Task.LIMIT ? Task.LIMIT : n; }
static labelLength(): number { return Task.LABEL.length; }
bump(): void { this.id += 1; }
}
export function f(n: number): number {
const t = new Task(Task.clampId(n));
t.bump();
return t.id + Task.LIMIT + Task.labelLength();
}
`,
},
{
name: "R19b: private and protected keywords erase (tsc enforces them at the type level)",
src: `
export class Counter {
private n: number = 0;
protected step: number = 1;
private raise(): void { this.n += this.step; }
bump(): void { this.raise(); }
value(): number { return this.n; }
}
export function f(): number { const c = new Counter(); c.bump(); c.bump(); return c.value(); }
`,
},
{
name: "NS1010: a mutable static field is module state",
gate: "NS1010",
src: `
export class Config { static shared: number = 2; }
export function f(): number { return 1; }
`,
},
{
name: "NS1056: this inside a static member is taught toward the class name",
gate: "NS1056",
src: `
export class Config {
static readonly K = 1;
static bad(): number { return this.K; }
}
export function f(): number { return 1; }
`,
},
{
name: "data class: fields, constructor, mutating and reading methods",
src: `
export class Counter {
count: number = 0;
step: number;
constructor(step: number) { this.step = step; }
bump(): void { this.count += this.step; }
value(): number { return this.count; }
}
export function f(): number {
const c = new Counter(2);
c.bump();
c.bump();
return c.value();
}
`,
},
{
name: "data class: bytes and array fields, rebuild-style field assignment",
src: `
import { asciiBytes } from "@native-sdk/core";
export class Log {
title: Uint8Array;
entries: readonly number[] = [];
constructor(title: Uint8Array) { this.title = title; }
add(n: number): void { this.entries = [...this.entries, n]; }
total(): number {
let sum = 0;
for (const e of this.entries) sum += e;
return sum;
}
}
export function f(): number {
const log = new Log(asciiBytes("run"));
log.add(1.5);
log.add(2);
return log.total() + log.title.length;
}
`,
},
{
name: "class instances flow as record-shaped values between functions (read-only)",
src: `
export class Point {
x: number;
y: number;
constructor(x: number, y: number) { this.x = x; this.y = y; }
norm(): number { return this.x * this.x + this.y * this.y; }
}
function sumNorms(ps: readonly Point[]): number {
let total = 0;
for (const p of ps) total += p.norm();
return total;
}
export function f(): number {
const ps: readonly Point[] = [new Point(1, 2), new Point(3, 4)];
return sumNorms(ps);
}
`,
},
{
name: "extends teaches inheritance",
gate: "NS1055",
src: `
class Base { n: number = 0; }
export class Derived extends Base { m: number = 1; }
export function f(): number { return 1; }
`,
},
{
name: "accessors, statics, and #-privates teach the member surface",
gate: "NS1056",
src: `
export class Config {
#secret: number = 1;
static shared: number = 2;
get doubled(): number { return 4; }
}
export function f(): number { return 1; }
`,
},
{
name: "an unannotated class field teaches the annotation form",
gate: "NS1056",
src: `
export class Counter { n = 0; }
export function f(): number { return 1; }
`,
},
{
name: "`this` escaping as a value teaches",
gate: "NS1056",
src: `
export class Chain {
n: number = 0;
add(x: number): Chain { this.n += x; return this; }
}
export function f(): number { return 1; }
`,
},
{
name: "generic classes teach the concrete-struct rule",
gate: "NS1053",
src: `
export class Box<T> { v: T | null = null; }
export function f(): number { return 1; }
`,
},
{
name: "a mutating method on a parameter instance teaches ownership",
gate: "NS1001",
src: `
export class Counter {
n: number = 0;
bump(): void { this.n += 1; }
}
export function f(c: Counter): number { c.bump(); return c.n; }
`,
},
{
name: "a mutating method after the instance escaped teaches NS1051",
gate: "NS1051",
src: `
export class Counter {
n: number = 0;
bump(): void { this.n += 1; }
}
function observe(c: Counter): number { return c.n; }
export function f(): number {
const c = new Counter();
const seen = observe(c);
c.bump();
return seen;
}
`,
},
{
name: "instanceof on a class instance still teaches runtime type tests",
gate: "NS1041",
src: `
export class Counter { n: number = 0; }
export function f(v: Counter | null): number {
if (v instanceof Counter) return 1;
return 0;
}
`,
},
{
name: "class instances in the Model tree teach the record form (flagged follow-up)",
gate: "NS1056",
src: `
export class Task {
id: number;
constructor(id: number) { this.id = id; }
}
export interface Model { readonly tasks: readonly Task[]; }
export function initialModel(): Model { return { tasks: [] }; }
`,
},
{
name: "records built FROM class data keep the Model record-shaped",
src: `
export class Builder {
count: number = 0;
bump(): void { this.count += 1; }
}
export interface Model { readonly count: number; }
export type Msg =
| { readonly kind: "recount"; readonly upTo: number }
| { readonly kind: "noop" };
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "recount": {
const b = new Builder();
for (let i = 0; i < msg.upTo; i++) b.bump();
return { count: b.count };
}
case "noop": return model;
}
}
`,
},
];
const exceptionCases: Case[] = [
{
name: "heterogeneous throws merge into the thrown union; catch narrows by kind",
src: `
export interface ParseError { readonly kind: "parse"; readonly at: number; }
export interface IoError { readonly kind: "io"; readonly code: number; readonly hard: boolean; }
function go(n: number): number {
if (n < 0) throw { kind: "parse", at: n } as ParseError;
if (n > 100) throw { kind: "io", code: n, hard: n > 1000 } as IoError;
return n * 2;
}
export function f(n: number): number {
try { return go(n); } catch (e) {
if (e.kind === "parse") return e.at - 1000;
if (e.kind === "io") return e.hard ? -e.code : e.code;
return -1;
}
}
`,
},
{
name: "a declared union whose arms equal the thrown set carries the slot (as stays legal)",
src: `
export interface ParseError { readonly kind: "parse"; readonly at: number; }
export type AppError = { readonly kind: "parse"; readonly at: number } | { readonly kind: "empty" };
function go(n: number): number {
if (n < 0) throw { kind: "parse", at: n } as ParseError;
if (n === 0) throw { kind: "empty" } as AppError;
return n;
}
export function f(n: number): number {
try { return go(n); } catch (e) {
const err = e as AppError;
if (err.kind === "parse") return err.at;
return -2;
}
}
`,
},
{
name: "a member union's value rethrows through the thrown union (re-tagged arm by arm)",
src: `
export type Low = { readonly kind: "under"; readonly by: number } | { readonly kind: "zero" };
export interface High { readonly kind: "over"; readonly by: number; readonly hard: boolean; }
function check(n: number): number {
if (n < 0) { const e: Low = n === -1 ? { kind: "zero" } : { kind: "under", by: n }; throw e; }
if (n > 10) throw { kind: "over", by: n - 10, hard: n > 100 } as High;
return n;
}
export function f(n: number): number {
try { return check(n); } catch (e) {
switch (e.kind) {
case "under": return e.by - 100;
case "zero": return -1;
case "over": return e.hard ? e.by + 1000 : e.by;
default: return -999;
}
}
}
`,
},
{
name: "single-shape catch reads its fields directly, no as ceremony",
src: `
export interface Bad { readonly kind: "bad"; readonly at: number; }
function go(n: number): number { if (n < 0) throw { kind: "bad", at: n } as Bad; return n; }
export function f(n: number): number {
try { return go(n); } catch (e) { return e.at; }
}
`,
},
{
name: "NS1057: a shape without a kind tag cannot join a heterogeneous thrown set",
gate: "NS1057",
src: `
export interface ParseError { readonly kind: "parse"; readonly at: number; }
export function f(n: number): number {
if (n < 0) throw { kind: "parse", at: n } as ParseError;
if (n > 100) throw n;
return n;
}
`,
},
{
name: "NS1057: two thrown shapes sharing a kind with different payloads are ambiguous",
gate: "NS1057",
src: `
export interface AErr { readonly kind: "boom"; readonly at: number; }
export interface BErr { readonly kind: "boom"; readonly code: boolean; }
export function f(n: number): number {
if (n < 0) throw { kind: "boom", at: n } as AErr;
if (n > 100) throw { kind: "boom", code: true } as BErr;
return n;
}
`,
},
{
name: "NS1057: asserting one member shape in a heterogeneous core is taught toward kind tests",
gate: "NS1057",
src: `
export interface ParseError { readonly kind: "parse"; readonly at: number; }
export interface IoError { readonly kind: "io"; readonly code: number; }
function go(n: number): number {
if (n < 0) throw { kind: "parse", at: n } as ParseError;
if (n > 100) throw { kind: "io", code: n } as IoError;
return n;
}
export function f(n: number): number {
try { return go(n); } catch (e) {
const err = e as ParseError;
return err.at;
}
}
`,
},
{
name: "NS1057: the catch binding escaping into a call is taught toward narrowing",
gate: "NS1057",
src: `
export interface Bad { readonly kind: "bad"; readonly at: number; }
function width(x: Bad): number { return x.at; }
function go(n: number): number { if (n < 0) throw { kind: "bad", at: n } as Bad; return n; }
export function f(n: number): number {
try { return go(n); } catch (e) { return width(e); }
}
`,
},
{
name: "try/catch/finally: parser throws a tagged error, caller narrows it",
src: `
export type ParseError =
| { readonly kind: "bad_digit"; readonly at: number }
| { readonly kind: "empty" };
function parseDigits(bytes: Uint8Array): number {
if (bytes.length === 0) throw { kind: "empty" } as ParseError;
let n = 0;
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i];
if (b < 48 || b > 57) throw { kind: "bad_digit", at: i } as ParseError;
n = n * 10 + (b - 48);
}
return n;
}
export function safeParse(bytes: Uint8Array): number {
try {
return parseDigits(bytes);
} catch (e) {
const err = e as ParseError;
if (err.kind === "bad_digit") return -err.at - 1;
return -1000;
}
}
`,
},
{
name: "throw inside a map callback unwinds out of the lowered loop",
src: `
export type Bad = { readonly kind: "bad"; readonly at: number } | { readonly kind: "none" };
export function firstBad(xs: readonly number[]): number {
try {
const doubled = xs.map((x, i) => {
if (x < 0) throw { kind: "bad", at: i } as Bad;
return x * 2;
});
return doubled.length;
} catch (e) {
const err = e as Bad;
return err.kind === "bad" ? -err.at - 1 : -99;
}
}
`,
},
{
name: "rethrow and nested try; finally runs on both paths",
src: `
export type Fail = { readonly kind: "inner"; readonly n: number } | { readonly kind: "outer"; readonly n: number };
export function f(x: number): number {
let log = 0;
try {
try {
if (x > 0) throw { kind: "inner", n: x } as Fail;
log += 1;
} catch (e) {
const err = e as Fail;
if (err.kind === "inner") {
if (err.n > 5) throw { kind: "outer", n: err.n } as Fail;
}
throw e;
} finally {
log += 10;
}
} catch (e) {
const err = e as Fail;
const tag = err.kind === "outer" ? 2 : 1;
return log * 100 + tag;
}
return log;
}
`,
},
{
name: "an uncaught throw at an exported boundary compiles to a defined panic",
src: `
export type Boom = { readonly kind: "boom" } | { readonly kind: "other" };
export function mustBePositive(n: number): number {
if (n <= 0) throw { kind: "boom" } as Boom;
return n;
}
`,
},
{
name: "throw mid-mutation of an owned array: prior pushes persist into the catch",
src: `
export type Halt = { readonly kind: "halt" } | { readonly kind: "never" };
export function f(stop: number): number {
const xs: number[] = [];
try {
xs.push(1);
xs.push(2);
if (xs.length >= stop) throw { kind: "halt" } as Halt;
xs.push(3);
} catch {
return xs.length;
}
return xs.length * 10;
}
`,
},
{
name: "class methods can throw; the constructor can too",
src: `
export type RangeError2 = { readonly kind: "range"; readonly v: number } | { readonly kind: "none" };
export class Gauge {
v: number;
constructor(v: number) {
if (v < 0) throw { kind: "range", v } as RangeError2;
this.v = v;
}
add(d: number): void {
if (this.v + d > 100) throw { kind: "range", v: this.v + d } as RangeError2;
this.v += d;
}
}
export function f(start: number, d: number): number {
try {
const g = new Gauge(start);
g.add(d);
return g.v;
} catch (e) {
const err = e as RangeError2;
return err.kind === "range" ? -err.v : -1;
}
}
`,
},
{
name: "two different thrown shapes teach the one-shape rule",
gate: "NS1057",
src: `
export function f(x: number): number {
if (x < 0) throw 1;
if (x > 10) throw true;
return x;
}
`,
},
{
name: "a directly-used catch binding teaches the single-narrowing rule",
gate: "NS1057",
src: `
export function f(): number {
try { return 1; } catch (e) { return e === null ? 0 : 2; }
}
`,
},
{
name: "`throw new Error` teaches the subset-value rule",
gate: "NS1057",
src: `export function f(): number { throw new Error("x"); }`,
},
{
name: "`return` inside finally teaches no-unsafe-finally",
gate: "NS1058",
src: `export function f(): number { try { return 1; } finally { return 0; } }`,
},
];
// Shapes that first failed in RELEASE-mode app builds (wave-2 eval trials):
// Debug-lane-only compilation let them escape, which is why the compile test
// below runs the driver in both Debug and ReleaseFast.
const releaseModeCases: Case[] = [
{
name: "annotated enum local from a runtime ternary (pomodoro phase flip)",
src: `
export type Phase = "focus" | "rest";
export interface Model { readonly phase: Phase; readonly remaining: number; readonly completed: number; }
export type Msg = { readonly kind: "tick" } | { readonly kind: "reset" };
export function initialModel(): Model { return { phase: "focus", remaining: 1500, completed: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "tick": {
const nextPhase: Phase = model.phase === "focus" ? "rest" : "focus";
const nextDuration = nextPhase === "focus" ? 1500 : 300;
const nextCompleted = model.phase === "focus" ? model.completed + 1 : model.completed;
return { phase: nextPhase, remaining: nextDuration, completed: nextCompleted };
}
case "reset": return { ...model, phase: "focus" };
}
}
`,
},
{
name: "reassigned enum local anchors its enum type",
src: `
export type Phase = "focus" | "rest";
export function flip(count: number): Phase {
let phase: Phase = "focus";
if (count > 1) phase = "rest";
return phase;
}
`,
},
{
name: "bytes parameter written through set() and indexed stores (notes serializer)",
src: `
function writeChunk(out: Uint8Array, offset: number, chunk: Uint8Array): number {
out.set(chunk, offset);
out[offset] = 65;
return offset + chunk.length;
}
export function pack(chunks: readonly Uint8Array[]): Uint8Array {
let total = 0;
for (const c of chunks) total += c.length;
const out = new Uint8Array(total);
let offset = 0;
for (const c of chunks) offset = writeChunk(out, offset, c);
return out;
}
`,
},
];
// The byte-text method surface (R11t): the everyday string methods on core
// bytes, exercised through the type boundaries the per-method matrix
// (grammar_matrix.test.ts) does not cover — model integration, aliases,
// optional receivers, result classes feeding integer flows, split
// ownership — plus the taught edges that need FLOW (not literal) fixtures.
const textMethodCases: Case[] = [
{
name: "case-mapped search filter through a Bytes alias in the update path",
src: `
import { asciiBytes } from "@native-sdk/core";
export type Bytes = Uint8Array;
export interface Item { readonly id: number; readonly title: Bytes; }
export interface Model { readonly items: readonly Item[]; readonly query: Bytes; }
export type Msg =
| { readonly kind: "set_query"; readonly text: Bytes }
| { readonly kind: "clear" };
export function initialModel(): Model {
return { items: [{ id: 1, title: asciiBytes("Alpha") }], query: new Uint8Array(0) };
}
export function visible(model: Model): readonly Item[] {
const q = model.query.toLowerCase();
return model.items.filter((it) => it.title.toLowerCase().includes(q));
}
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "set_query": return { ...model, query: msg.text.trim() };
case "clear": return { ...model, query: new Uint8Array(0) };
}
}
`,
},
{
name: "indexOf result guards a subarray bound (integer-classed offset)",
src: `
export function afterColon(line: Uint8Array, sep: Uint8Array): Uint8Array {
const at = line.indexOf(sep);
if (at === -1) return line;
return line.subarray(at + sep.length);
}
export function tail(line: Uint8Array): number {
const last = line.lastIndexOf(32);
return last === -1 ? line.length : last;
}
`,
},
{
name: "split parts are locally owned (push after split) and iterate",
src: `
import { asciiBytes } from "@native-sdk/core";
export function fields(row: Uint8Array): number {
const parts = row.split(asciiBytes(","));
parts.push(asciiBytes("total"));
let bytes = 0;
for (const p of parts) bytes += p.trim().length;
return bytes + parts.length;
}
`,
},
{
name: "at() folds with ?? and tests === undefined (the one-empty rule)",
src: `
export function lastByte(s: Uint8Array): number {
const b = s.at(-1);
if (b === undefined) return -1;
return b;
}
export function firstOrZero(s: Uint8Array): number {
return s.at(0) ?? 0;
}
`,
},
{
name: "optional-chained text methods over a nullable receiver",
src: `
export interface Model { readonly note: Uint8Array | null; }
export function noteLen(model: Model): number {
return (model.note?.trim() ?? new Uint8Array(0)).length;
}
export function shout(model: Model): Uint8Array {
return model.note?.toUpperCase() ?? new Uint8Array(0);
}
`,
},
{
name: "pad targets and repeat counts ride integer flows into the helpers",
src: `
import { asciiBytes } from "@native-sdk/core";
export function gauge(width: number, used: number): Uint8Array {
const bar = asciiBytes("#").repeat(used);
return bar.padEnd(width, asciiBytes("."));
}
export function label(name: Uint8Array, col: number): Uint8Array {
return name.padStart(col);
}
`,
},
{
name: "dispatch by argument type: byte membership vs substring on one receiver",
src: `
export function hasBoth(s: Uint8Array, sub: Uint8Array, byte: number): boolean {
return s.includes(sub) && s.includes(byte);
}
`,
},
{
name: "repeat count fed by a float FLOW (not a literal) is the taught slot conflict",
gate: "NS1016",
src: `
export function f(s: Uint8Array, x: number): Uint8Array {
const n = x / 2;
return s.repeat(n);
}
`,
},
{
name: "padStart target fed by a fractional literal is the taught slot conflict",
gate: "NS1016",
src: `
export function f(s: Uint8Array): Uint8Array {
return s.padStart(4.5);
}
`,
},
{
name: "split parts cannot be pushed after the array escapes",
gate: "NS1051",
src: `
import { asciiBytes } from "@native-sdk/core";
function count(xs: readonly Uint8Array[]): number { return xs.length; }
export function f(row: Uint8Array, sink: Uint8Array[][]): number {
const parts = row.split(asciiBytes(","));
sink.push(parts);
parts.push(asciiBytes("late"));
return count(parts);
}
`,
},
];
// Ternaries whose arms lower STATEMENTS (spread object literals build their
// arena copy line by line). Those statements must scope to the branch that
// actually runs: hoisting them above the conditional both evaluates the
// untaken arm and reads a null-narrowing capture before it binds ("use of
// undeclared identifier"), and a skipped narrow path leaves optional reads
// un-unwrapped in the arm ("expected type 'f64', found '?f64'"). Every
// position a value ternary can sit in is pinned: declaration initializer,
// return, argument, object field, plus the orelse fusion and the
// switch-payload shapes that route the same lowering.
const ternarySpreadArmCases: Case[] = [
{
name: "null-narrowed ternary with a spread arm in return position",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function apply(q: Quote, parsed: number | null): Quote {
return parsed === null ? q : { ...q, state: "ok", price: parsed };
}
`,
},
{
name: "null-narrowed ternary with a spread arm as a declaration initializer",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function apply(q: Quote, parsed: number | null): Quote {
const updated: Quote = parsed === null ? q : { ...q, state: "ok", price: parsed };
return updated;
}
`,
},
{
name: "hit-test polarity (!== null) with spread arms on BOTH branches",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function apply(q: Quote, parsed: number | null): Quote {
return parsed !== null ? { ...q, state: "ok", price: parsed } : { ...q, state: "failed" };
}
`,
},
{
name: "nested ternary with spread arms (arm-within-arm) as an annotated declaration",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function apply(q: Quote, parsed: number | null): Quote {
const updated: Quote = parsed === null
? (q.state === "ok" ? q : { ...q, state: "failed" })
: { ...q, state: "ok", price: parsed };
return updated;
}
`,
},
{
name: "spread-arm ternary in argument position",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function cost(q: Quote): number { return q.price; }
export function probe(q: Quote, parsed: number | null): number {
return cost(parsed === null ? q : { ...q, state: "ok", price: parsed });
}
`,
},
{
name: "spread-arm ternary as an object-literal field value",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export interface Model { readonly quote: Quote; readonly n: number; }
export function apply(model: Model, parsed: number | null): Model {
return { ...model, quote: parsed === null ? model.quote : { ...model.quote, state: "ok", price: parsed } };
}
`,
},
{
name: "orelse-fusion shape whose miss arm is a spread literal",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): Quote {
return q === null ? { ...fallback, state: "idle" } : q;
}
`,
},
{
// The annotated declaration above supplies an expected type that masks
// the inference path: with NO annotation the ternary's own computed type
// is the local's type, and computing it from the raw optional arm types
// the local `?Quote` — its first non-optional use then fails Zig
// compilation ("expected type 'Quote', found '?Quote'"). The condition's
// null test narrows the arm that reuses the tested value, so the local
// must value as the non-optional Quote, exactly as tsc types it.
name: "INFERRED local from a miss-test spread-arm ternary values non-optional",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): Quote {
const picked = q === null ? { ...fallback, price: 0 } : q;
return picked;
}
`,
},
{
name: "INFERRED local, hit-test polarity (q !== null ? q : spread) values non-optional",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): Quote {
const picked = q !== null ? q : { ...fallback, price: 0 };
return picked;
}
`,
},
{
name: "TEA reducer: spread-arm ternary reads a switch payload through a local",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export interface Model { readonly quote: Quote; }
export type Msg = { readonly kind: "got"; readonly parsed: number | null } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "got": {
const parsed = msg.parsed;
const q = model.quote;
const updated: Quote = parsed === null
? (q.state === "ok" ? q : { ...q, state: "failed" })
: { ...q, state: "ok", price: parsed };
return { ...model, quote: updated };
}
case "noop": {
const q = model.quote;
const updated: Quote = q.state === "ok" ? q : { ...q, state: "failed" };
return { ...model, quote: updated };
}
}
}
`,
},
{
name: "TEA reducer: spread-arm ternary reads the optional switch payload directly",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export interface Model { readonly quote: Quote; }
export type Msg = { readonly kind: "got"; readonly parsed: number | null } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "got":
return { ...model, quote: msg.parsed === null ? model.quote : { ...model.quote, state: "ok", price: msg.parsed } };
case "noop":
return model;
}
}
`,
},
{
name: "optional switch payload stays optional through its capture (guarded use compiles)",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export interface Model { readonly quote: Quote; }
export type Msg = { readonly kind: "got"; readonly parsed: number | null } | { readonly kind: "noop" };
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "got": {
const parsed = msg.parsed;
if (parsed !== null) {
return { ...model, quote: { ...model.quote, state: "ok", price: parsed } };
}
return model;
}
case "noop": return model;
}
}
`,
},
{
// A parenthesized arm is still the tested target: `(q)` must value the
// inferred local as the non-optional Quote exactly like the bare `q`
// spelling — parens defeating the match typed the local `?Quote` and
// its field reads failed to compile.
name: "INFERRED local from a spread-arm ternary whose narrowed arm is parenthesized",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q === null ? { ...fallback, price: 0 } : (q);
return picked.price + picked.id;
}
`,
},
{
// The statement-free flavor routes through the orelse fusion instead of
// the temp lowering; the parenthesized arm must fuse the same way.
name: "orelse fusion still fires when the narrowed ternary arm is parenthesized",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q === null ? fallback : (q);
return picked.price + picked.id;
}
`,
},
{
// Doubly parenthesized: paren stripping must recurse, not peel one layer.
name: "a doubly-parenthesized narrowed ternary arm still matches the tested target",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q === null ? { ...fallback, price: 0 } : ((q));
return picked.price + picked.id;
}
`,
},
{
// The tested side of the condition can carry the parens too.
name: "a parenthesized tested target still narrows its ternary arm",
src: `
export type QuoteState = "idle" | "ok" | "failed";
export interface Quote { readonly id: number; readonly state: QuoteState; readonly price: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = (q) === null ? { ...fallback, price: 0 } : q;
return picked.price + picked.id;
}
`,
},
{
// A parenthesized null ARM is still a null arm: `(null)` must make the
// ternary's value optional exactly like the bare `null` spelling.
// Reading it as a non-null arm typed the local non-optional, the later
// guard's unwrap was skipped, and the field read hit the raw optional.
name: "a parenthesized null arm still values the ternary optional",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
const picked = q === null ? (null) : q;
if (picked === null) return -1;
return picked.v;
}
`,
},
{
name: "a parenthesized null arm on the OTHER side values the ternary optional too",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
const picked = q !== null ? q : (null);
if (picked === null) return -1;
return picked.v;
}
`,
},
{
// Doubly wrapped: the null-literal check must recurse through every
// paren layer, not peel one.
name: "a doubly-parenthesized null arm still values the ternary optional",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
const picked = q === null ? ((null)) : q;
if (picked === null) return -1;
return picked.v;
}
`,
},
{
// Emission erases non-null assertions exactly like parens, so the
// arm-identity comparison must see through them: `q!` IS the tested
// `q`, the ternary values non-optional, and the spread arm's temp
// types the struct — not `?Quote` with a field read Zig rejects.
name: "a non-null-asserted narrowed ternary arm still matches the tested target",
src: `
export interface Quote { readonly price: number; readonly qty: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q !== null ? q! : { ...fallback, price: 0 };
return picked.price + picked.qty;
}
`,
},
{
// The `as` spelling of the same wrapper erasure.
name: "an as-cast narrowed ternary arm still matches the tested target",
src: `
export interface Quote { readonly price: number; readonly qty: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q !== null ? (q as Quote) : { ...fallback, price: 0 };
return picked.price + picked.qty;
}
`,
},
{
// The `satisfies` spelling too — all three wrappers canonicalize in
// the narrowing key itself, so every consumer agrees at once.
name: "a satisfies-wrapped narrowed ternary arm still matches the tested target",
src: `
export interface Quote { readonly price: number; readonly qty: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q !== null ? (q satisfies Quote) : { ...fallback, price: 0 };
return picked.price + picked.qty;
}
`,
},
{
// A wrapper on the TESTED side narrows too: tsc sees through `q!` when
// narrowing `q`, so the capture form must fire exactly as for the bare
// spelling (the erased assertion emits the same `q != null` test).
name: "a non-null-asserted tested target still narrows its ternary arm",
src: `
export interface Quote { readonly price: number; readonly qty: number; }
export function pick(q: Quote | null, fallback: Quote): number {
const picked = q! !== null ? q! : { ...fallback, price: 0 };
return picked.price + picked.qty;
}
`,
},
{
// The orelse fold recognizes the wrapped arm as the tested value:
// `q === null ? A : q!` is `q orelse A`, never a lowered temp.
name: "a non-null-asserted miss-test arm still folds to orelse",
src: `
export function fold(q: number | null): number {
return q === null ? -1 : q!;
}
`,
},
{
// The statement-level orelse fusion sees through the wrapper on the
// guard's tested side too, and matches by declaration identity (tsc
// itself narrows through `!` — it is a reference candidate — so the
// follow-up read is tsc-clean).
name: "a non-null-asserted early-exit guard still fuses to orelse",
src: `
export interface Quote { readonly price: number; readonly qty: number; }
export function first(qs: readonly Quote[]): number {
const q = qs.find((x) => x.qty > 0);
if (q! === undefined) return -1;
return q.price;
}
`,
},
{
// tsc does NOT narrow through `as` in tested position, so the
// follow-up read carries its own assertion — and the canonicalized
// key routes it onto the fusion's capture all the same.
name: "an as-wrapped guard with an asserted follow-up read still fuses to orelse",
src: `
export interface Quote { readonly price: number; readonly qty: number; }
export function first(qs: readonly Quote[]): number {
const q = qs.find((x) => x.qty > 0);
if ((q as Quote | undefined) === undefined) return -1;
return q!.price;
}
`,
},
{
// The global `undefined` in arm position is an EMPTY exactly like the
// null keyword — its internal type is void, so a ZType-based arm check
// reads it as a non-empty value, types the local non-optional, skips
// the later guard's unwrap, and the field read hits the raw optional.
name: "an undefined alternate arm still values the ternary optional",
src: `
export interface P { readonly v: number; }
export function pick(q: P | null): number {
const picked = q === null ? undefined : q;
if (picked === undefined) return 0;
return picked.v;
}
`,
},
{
name: "an undefined arm on the OTHER side values the ternary optional too",
src: `
export interface P { readonly v: number; }
export function pick(q: P | null): number {
const picked = q !== null ? q : undefined;
if (picked === undefined) return 0;
return picked.v;
}
`,
},
{
// Wrapper canonicalization composes with the undefined-empty decision:
// an `as`-wrapped undefined arm paired with an `as`-wrapped tested
// value keeps the optional and fuses the guard exactly like the bare
// spelling.
name: "as-wrapped undefined and value arms still value the ternary optional",
src: `
export interface P { readonly v: number; }
export function pick(q: P | null): number {
const picked = q === null ? (undefined as P | undefined) : (q as P);
if (picked === undefined) return 0;
return picked.v;
}
`,
},
{
// The doubly-parenthesized undefined spelling, symmetric with the
// `((null))` case above: the empty check recurses through every layer.
name: "a doubly-parenthesized undefined arm still values the ternary optional",
src: `
export interface P { readonly v: number; }
export function pick(q: P | null): number {
const picked = q === null ? ((undefined)) : q;
if (picked === undefined) return 0;
return picked.v;
}
`,
},
{
// Ternary arms are ALTERNATIVES: probing/lowering one arm must not
// apply its flow effects to the sibling. The map callback here assigns
// the outer narrowed local — killing that narrow in shared flow state
// stripped the ELSE arm of its unwrap, and the sibling read the raw
// optional (invalid Zig). Each arm emits from the entry state; the
// kill joins only after both arms.
name: "a callback assignment in one ternary arm does not strip the sibling arm's narrow",
src: `
export interface P { readonly v: number; }
export function f(xs: number[], flag: boolean, seed: P | null): number {
let q: P | null = seed;
if (q === null) return 0;
const r = flag ? xs.map((x) => { q = null; return x * 2; }).length : q.v;
return r;
}
`,
},
{
// The capture-lowered (empty-test) ternary flavor of the same law: the
// narrowed arm's callback assigns a DIFFERENT outer narrowed local;
// the other arm still reads that local through its unwrap.
name: "a callback assignment in the narrowed arm does not strip the other arm's narrow",
src: `
export interface P { readonly v: number; }
export function g(xs: number[], q: P | null, seed: P | null): number {
let p: P | null = seed;
if (p === null) return 0;
const r = q !== null ? xs.map((x) => { p = null; return x + q.v; }).length : p.v;
return r;
}
`,
},
{
// Capture gates compare declaration-QUALIFIED keys, never source text:
// the map callback's parameter shadows the tested `box`, so its
// `box.q` is NOT a read of the outer target — a text match would bind
// a capture the declaration-keyed substitution never rewrites, a Zig
// unused-capture error. The arm reads only the shadow: no capture.
name: "a shadowed property spelling in a ternary arm binds no capture",
src: `
export interface B { readonly q: number | null; }
export function g(box: B, xs: readonly B[]): number {
return box.q === null ? 0 : xs.map((box) => box.q === null ? 0 : 1).length;
}
`,
},
{
// The dual: the arm reads the OUTER target too, so the capture binds
// and the outer read rewrites onto it while the shadowed read keeps
// its own declaration.
name: "a ternary arm reading both the outer property and a shadow binds a used capture",
src: `
export interface B { readonly q: number | null; }
export function g(box: B, xs: readonly B[]): number {
return box.q === null ? 0 : box.q + xs.map((box) => box.q === null ? 0 : 1).length;
}
`,
},
{
// The if-statement capture gate (branchReadsTarget) under the same
// shadowing: the then-branch reads only the shadow, so the plain
// comparison form emits, capture-free.
name: "a shadowed property spelling in a guarded branch binds no capture",
src: `
export interface B { readonly q: number | null; }
export function g(box: B, xs: readonly B[]): number {
if (box.q !== null) {
return xs.map((box) => box.q === null ? 0 : 1).length;
}
return 0;
}
`,
},
{
// The early-exit fusion gate (followingReadsTarget) under the same
// shadowing: nothing after the guard reads the outer target, so no
// orelse capture binds.
name: "a shadowed property spelling after an exit guard binds no orelse capture",
src: `
export interface B { readonly q: number | null; }
export function g(box: B, xs: readonly B[]): number {
if (box.q === null) return 0;
return xs.map((box) => box.q === null ? 0 : 1).length;
}
`,
},
{
// The switch payload-use gate under a same-named field: `box.v` is
// not a read of the payload field `v`, so no payload capture binds
// for the arm.
name: "a same-named field of another value binds no switch payload capture",
src: `
export type Msg = { readonly kind: "set"; readonly v: number } | { readonly kind: "clear" };
export interface Box { readonly v: number; }
export function upd(m: Msg, box: Box): number {
switch (m.kind) {
case "set": return box.v + 1;
case "clear": return 0;
}
}
`,
},
];
// Plain lexical blocks are NOT merge boundaries: tsc's narrowing is
// flow-based, so a guard inside a fall-through `{ ... }` narrows the
// statements AFTER the block too (and a kill inside it stays dead there).
// The emitter flattens such blocks into the enclosing list, which also
// keeps any unwrap capture lowered inside the block in scope for the
// post-block reads that rely on it. Merge contexts (if/else arms, loop
// bodies, labeled blocks) still bracket: narrows established inside them
// die at their exit.
const lexicalBlockFlowCases: Case[] = [
{
// The narrowing must survive the block's end: restoring entry state at
// the block exit re-optionalized `p` and the post-block read failed to
// compile.
name: "a guard inside a plain block narrows the statements after the block",
src: `
export interface P { readonly v: number; }
export function f(p: P | null): number {
{ if (p === null) return -1; }
return p.v;
}
`,
},
{
// The Zig-scope half: the guard's unwrap capture is lowered INSIDE the
// TS block, and the post-block reads go through it — flattening is what
// keeps that capture in the same Zig scope (the compile is the
// assertion).
name: "a narrow established in a block is readable after it through its capture",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
{
if (q === null) return -1;
if (q.v < 0) return -2;
}
return q.v + 1;
}
`,
},
{
// Containment control: a block inside an if ARM flows through to the
// arm's scope, but the arm itself still brackets — the post-arm
// re-check must read the LIVE optional, not a leaked capture.
name: "a block inside an if arm flows through to the arm; the arm still brackets",
src: `
export interface P { readonly v: number; }
export function f(p: P | null, c: boolean): number {
if (c) {
{ if (p === null) return -1; }
return p.v;
}
if (p === null) return 0;
return p.v + 1;
}
`,
},
{
name: "nested plain blocks flow through both boundaries",
src: `
export interface P { readonly v: number; }
export function f(p: P | null): number {
{
{
if (p === null) return -1;
}
if (p.v === 7) return 7;
}
return p.v;
}
`,
},
{
// Kills flow through a plain block exactly like narrows do: the killing
// assignment sits inside the block and the post-block re-check must
// read the live optional.
name: "a kill inside a plain block stays dead after the block",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) return -1;
{
if (flag) { p = null; }
}
if (p === null) return 0;
return p;
}
`,
},
{
// Flattening must not collide block-local declarations with later
// same-named ones: name uniquing is function-wide, so the second `a`
// takes a fresh spelling.
name: "a block-local const does not collide with a later same-named local",
src: `
export function f(flag: boolean): number {
{
const a = 1;
if (flag) return a;
}
const a = 2;
return a;
}
`,
},
{
// A block-scoped alias's guard fuses inside the flattened region; the
// outer value stays optional past the block, so the post-block re-check
// still reads the live slot.
name: "a block-local alias guard leaves the outer optional unnarrowed",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
{
const inner = q;
if (inner === null) return -1;
if (inner.v === 7) return 7;
}
if (q === null) return 0;
return q.v;
}
`,
},
];
// Flow-exit guard narrowing: tsc narrows after ANY statement that never
// falls through — return, break, continue, throw — so a loop's early-exit
// guard must narrow the remainder of the loop body exactly like an early
// return narrows the rest of the function. The dogfooding report behind
// these: `if (r === null) break;` inside a parse loop emitted Zig field
// access on the still-optional `r`. The scope cases pin the other half:
// the narrowing ENDS with the block the guard sits in (a break path may
// bypass it), so reads after the loop or branch see the unnarrowed value.
const exitGuardNarrowingCases: Case[] = [
{
name: "break guard narrows an optional for the rest of the loop body (the report's parse loop)",
src: `
export interface NumResult { readonly value: number; readonly next: number; }
export function parseNumber(body: Uint8Array, i: number): NumResult | null {
if (i >= body.length) return null;
return { value: body[i], next: i + 1 };
}
export function collect(body: Uint8Array): readonly number[] {
const out: number[] = [];
let i = 0;
while (i < body.length) {
const r = parseNumber(body, i);
if (r === null) break;
out.push(r.value);
i = r.next;
}
return out;
}
`,
},
{
name: "continue guard narrows an optional for the rest of the iteration",
src: `
export interface Hit { readonly value: number; }
export function lookup(i: number): Hit | null {
if (i % 2 === 0) return null;
return { value: i * 3 };
}
export function oddTotal(n: number): number {
let sum = 0;
for (let i = 0; i < n; i += 1) {
const r = lookup(i);
if (r === null) continue;
sum += r.value;
}
return sum;
}
`,
},
{
name: "kind guard with break narrows the union payload for the rest of the loop body",
src: `
export type Msg =
| { readonly kind: "num"; readonly value: number }
| { readonly kind: "stop" };
export function prefixTotal(msgs: readonly Msg[]): number {
let sum = 0;
for (const msg of msgs) {
if (msg.kind !== "num") break;
sum += msg.value;
}
return sum;
}
`,
},
{
name: "kind guard with continue skips non-matching arms and narrows the rest",
src: `
export type Msg =
| { readonly kind: "num"; readonly value: number }
| { readonly kind: "stop" };
export function numTotal(msgs: readonly Msg[]): number {
let sum = 0;
for (const msg of msgs) {
if (msg.kind !== "num") continue;
sum += msg.value;
}
return sum;
}
`,
},
{
name: "labeled break guard narrows through nested loops",
src: `
export interface Cell { readonly weight: number; }
export function probe(r: number, c: number): Cell | null {
if (r + c > 4) return null;
return { weight: r * 10 + c };
}
export function scan(rows: number, cols: number): number {
let sum = 0;
outer: for (let r = 0; r < rows; r += 1) {
for (let c = 0; c < cols; c += 1) {
const cell = probe(r, c);
if (cell === null) break outer;
sum += cell.weight;
}
}
return sum;
}
`,
},
{
name: "multi-statement break exit still narrows (block-form orelse)",
src: `
export interface P { readonly v: number; }
export function next(i: number): P | null {
if (i >= 3) return null;
return { v: i };
}
export function run(n: number): number {
let sum = 0;
let misses = 0;
for (let i = 0; i < n; i += 1) {
const p = next(i);
if (p === null) {
misses += 1;
break;
}
sum += p.v;
}
return sum + misses;
}
`,
},
{
name: "throw exit on a property target narrows the rest (block-form guard)",
src: `
export interface ParseError { readonly kind: "parse"; readonly at: number; }
export interface Sel { readonly value: number; }
export interface Model { readonly sel: Sel | null; }
function selValue(model: Model, at: number): number {
if (model.sel === null) throw { kind: "parse", at: at } as ParseError;
return model.sel.value;
}
export function readSel(model: Model, at: number): number {
try {
return selValue(model, at);
} catch (e) {
return e.at - 1;
}
}
`,
},
{
name: "present-test with a break else-arm narrows after the if",
src: `
export interface Tok { readonly v: number; readonly n: number; }
export function read(i: number): Tok | null {
if (i >= 5) return null;
return { v: i, n: i + 1 };
}
export function consume(limit: number): number {
let acc = 0;
let i = 0;
while (i < limit) {
const t = read(i);
if (t !== null) {
acc += t.v;
} else {
break;
}
i = t.n;
}
return acc;
}
`,
},
{
name: "present-test with a return else-arm narrows after the if (no read inside the hit arm)",
src: `
export interface R2 { readonly value: number; }
export function tally(r: R2 | null): number {
let t = 0;
if (r !== null) {
t += 1;
} else {
return 0;
}
return t + r.value;
}
`,
},
{
name: "the narrowing ends with the loop body: post-loop reads re-test the optional",
src: `
export interface Sel { readonly value: number; }
export interface Model { readonly sel: Sel | null; readonly count: number; }
export function drain(model: Model): number {
let total = 0;
let i = 0;
while (i < model.count) {
if (model.sel === null) break;
total += model.sel.value;
i += 1;
}
return model.sel === null ? total : total + model.sel.value;
}
`,
},
{
name: "the narrowing ends with the branch: a guard under a flag never leaks to the merge",
src: `
export interface Sel { readonly value: number; }
export interface Model { readonly sel: Sel | null; readonly count: number; }
export function pick(flag: boolean, model: Model): number {
let total = 0;
for (let i = 0; i < model.count; i += 1) {
if (flag) {
if (model.sel === null) break;
total += model.sel.value;
}
total += model.sel === null ? 0 : model.sel.value;
}
return total;
}
`,
},
{
name: "return guard regression pin: the long-supported early return still narrows",
src: `
export interface Parsed { readonly value: number; readonly rest: number; }
export function parseOne(body: Uint8Array, i: number): Parsed | null {
if (i >= body.length) return null;
return { value: body[i], rest: i + 1 };
}
export function first(body: Uint8Array): number {
const r = parseOne(body, 0);
if (r === null) return -1;
return r.value + r.rest;
}
`,
},
{
name: "a do-while body ending in break drops the (unreachable) trailing test",
src: `
export interface Sel { readonly value: number; }
export interface Model { readonly sel: Sel | null; }
export function once(model: Model): number {
let total = 0;
do {
if (model.sel !== null) {
total += model.sel.value;
}
break;
} while (total < 3);
return total;
}
`,
},
{
name: "break guard inside a do-while narrows the rest of its body",
src: `
export interface Sel { readonly value: number; }
export interface Model { readonly sel: Sel | null; }
export function drainDo(model: Model): number {
let total = 0;
do {
if (model.sel === null) break;
total += model.sel.value;
} while (total < 10);
return total;
}
`,
},
{
// Post-if narrowing must survive every exit path from the if emission:
// the else-if chain path returned before applying it, leaving the
// fall-through read on the still-optional value.
name: "an exiting null guard heading an else-if chain narrows after the statement",
src: `
export interface P { readonly v: number; }
export function pick(x: P | null, flag: boolean): number {
let n = 1;
if (x === null) {
return -1;
} else if (flag) {
n = 2;
}
return x.v + n;
}
`,
},
{
name: "an exiting null guard heading an else-if-else chain narrows after the statement",
src: `
export interface P { readonly v: number; }
export function pick(x: P | null, flag: boolean): number {
let n = 1;
if (x === null) {
return -1;
} else if (flag) {
n = 2;
} else {
n = 3;
}
return x.v + n;
}
`,
},
{
name: "an exiting null guard heading a chained else-if-else-if narrows after the statement",
src: `
export interface P { readonly v: number; }
export function pick(x: P | null, a: boolean, b: boolean): number {
let n = 1;
if (x === null) {
return -1;
} else if (a) {
n = 2;
} else if (b) {
n = 3;
}
return x.v + n;
}
`,
},
{
name: "a present test whose else-if chain always exits narrows after the statement (other polarity)",
src: `
export interface P { readonly v: number; }
export function pick(x: P | null, flag: boolean): number {
let n = 1;
if (x !== null) {
n = 2;
} else if (flag) {
return -1;
} else {
return -2;
}
return x.v + n;
}
`,
},
{
name: "a reassigned let with an adjacent continue guard stays assignable (no const fusion)",
src: `
export interface P { readonly v: number; readonly tag: number; }
export function next(i: number): P | null {
if (i % 2 === 0) return null;
return { v: i, tag: i };
}
export function total(n: number): number {
let sum = 0;
for (let i = 0; i < n; i += 1) {
let p = next(i);
if (p === null) continue;
p = { ...p, v: 10 };
sum += p.v;
}
return sum;
}
`,
},
{
name: "an early break out of a switch clause is gated (Zig break binds loops, not switches)",
formerEmitGate: "NS9001",
src: `
export type Msg =
| { readonly kind: "a"; readonly v: number }
| { readonly kind: "b" };
export function f(msgs: readonly Msg[]): number {
let sum = 0;
for (const m of msgs) {
switch (m.kind) {
case "a":
if (m.v > 10) break;
sum += m.v;
break;
case "b":
sum += 1;
break;
}
}
return sum;
}
`,
},
];
// Scoped kind-narrowing must restore EVERY map it mutates. narrowUnion
// writes memberSubst, narrowedUnion, and — for optional payload fields —
// stillOptionalSubst; a wrapper that snapshots only the first two leaves
// the optional marker overwritten on exit, so the payload capture reads as
// non-null afterward and null tests route around the narrowing lowerings.
const kindNarrowRestoreCases: Case[] = [
{
name: "a redundant kind guard inside a payload capture keeps the payload optional after it",
src: `
export type Msg =
| { readonly kind: "got"; readonly parsed: number | null }
| { readonly kind: "miss" };
export function score(msg: Msg): number {
switch (msg.kind) {
case "got": {
const marker = msg.kind === "got" ? 1 : 2;
return msg.parsed !== null ? msg.parsed + marker : 0;
}
case "miss":
return -1;
}
}
`,
},
{
name: "a nested kind guard STATEMENT inside the capture keeps the optional too",
src: `
export type Msg =
| { readonly kind: "got"; readonly parsed: number | null }
| { readonly kind: "miss" };
export function tally(msg: Msg): number {
switch (msg.kind) {
case "got": {
let bonus = 0;
if (msg.kind === "got") {
bonus = 5;
}
return msg.parsed === null ? bonus : msg.parsed + bonus;
}
case "miss":
return -1;
}
}
`,
},
{
// The narrowing maps key on the tested expression's TEXT, so a
// DIFFERENT variable spelling the same name collides the key: a
// callback parameter shadowing the switch subject. The kind guard over
// the shadow overwrites the subject's optional-payload marker under
// the shared "e.value" key, and only a full-map restore puts the
// subject's marker back — a delete-additions-only restore leaves the
// stale marker, the later null test reads the payload as already
// non-null, and the narrowing lowering is skipped (an optional lands
// in integer arithmetic).
name: "a kind guard over a SHADOWING callback parameter keeps the subject's payload optional",
src: `
export type Entry =
| { readonly kind: "score"; readonly value: number | null; readonly bonus: number }
| { readonly kind: "empty" };
export function total(e: Entry, others: readonly Entry[]): number {
switch (e.kind) {
case "score": {
const anyPositive = others.some((e) => (e.kind === "score" ? e.bonus > 0 : false));
const boost = anyPositive ? 1 : 0;
if (e.value !== null) {
return e.value + e.bonus + boost;
}
return e.bonus + boost;
}
case "empty":
return 0;
}
}
`,
},
{
// Statement spelling of the shadow: a block-scoped declaration of the
// same union-typed name inside the payload capture's scope, with a
// kind-guard STATEMENT over it, then a null test on the subject's
// payload after the block closes. The block boundary must hand back
// the subject's markers untouched by the shadow's narrowing.
name: "a kind guard over a block-scoped SHADOWING declaration keeps the subject's payload optional",
src: `
export type Entry =
| { readonly kind: "score"; readonly value: number | null; readonly bonus: number }
| { readonly kind: "empty" };
export function tally(e: Entry, other: Entry): number {
switch (e.kind) {
case "score": {
let acc = 0;
{
const e = other;
if (e.kind === "score") {
acc = e.bonus;
}
}
if (e.value !== null) {
return e.value + e.bonus + acc;
}
return e.bonus + acc;
}
case "empty":
return 0;
}
}
`,
},
{
// A redundant nested SWITCH on the same subject inside an already-
// narrowed arm overwrites the arm's capture entries with the inner
// capture name; the continuation after the inner switch must read the
// OUTER capture again (the inner one's block has closed in Zig).
name: "a redundant nested switch on the same subject hands back the outer capture",
src: `
export type Ev =
| { readonly kind: "hit"; readonly value: number }
| { readonly kind: "miss" };
export function score(e: Ev): number {
switch (e.kind) {
case "hit": {
let bonus = 0;
switch (e.kind) {
case "hit": {
bonus = e.value * 2;
break;
}
default:
break;
}
return e.value + bonus;
}
case "miss":
return 0;
}
}
`,
},
];
// Narrowing INVALIDATION must survive the merge: a branch that assigns a
// possibly-null value to a narrowed local kills its `.?` substitution at the
// assignment, and the block-exit snapshot restore (which exists for
// CONTAINMENT — narrowings added inside a branch must not leak out) must not
// resurrect the dead narrow. A resurrected narrow makes the post-merge
// re-check emit `p.? == null` — "comparison of 'f64' with null" in Zig. The
// merge is deliberately conservative: a kill on ANY path deletes the narrow
// at the merge point, so the author's post-merge re-check (which tsc demands
// anyway — the branch widened the type back) always compiles.
const narrowInvalidationMergeCases: Case[] = [
{
name: "a branch reassigning a narrowed local to null kills the narrow past the merge",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
name: "a branch reassigning from an optional-returning call kills the narrow past the merge",
src: `
export function pick(xs: readonly number[], i: number): number | null {
return i >= 0 && i < xs.length ? xs[i] : null;
}
export function f(q: number | null, xs: readonly number[], i: number): number {
let p: number | null = q;
if (p === null) { return -1; }
if (i > 0) {
p = pick(xs, i);
}
if (p === null) { return 0; }
return p;
}
`,
},
{
name: "an ELSE branch reassigning to null kills the narrow past the merge",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
// this path keeps the narrow; the merge must still drop it
} else {
p = null;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
name: "a nested inner branch's kill propagates through BOTH block exits to the merge",
src: `
export function f(q: number | null, a: boolean, b: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (a) {
if (b) {
p = null;
}
}
if (p === null) { return 0; }
return p;
}
`,
},
{
name: "a branch that reads the narrow and then kills it still drops it at the merge",
src: `
export function f(q: number | null): number {
let p: number | null = q;
if (p === null) { return -1; }
let acc: number = 0;
if (p > 2) {
acc = p;
p = null;
}
if (p === null) { return acc; }
return p;
}
`,
},
{
name: "a switch arm reassigning to null kills the narrow past the switch",
src: `
export type Msg = { readonly kind: "set"; readonly value: number } | { readonly kind: "clear" };
export function f(q: number | null, msg: Msg): number {
let p: number | null = q;
if (p === null) { return -1; }
switch (msg.kind) {
case "set": {
p = p + msg.value;
break;
}
case "clear": {
p = null;
break;
}
}
if (p === null) { return 0; }
return p;
}
`,
},
{
name: "a kind-guarded branch reassigning to null kills the narrow past the merge",
src: `
export type Msg = { readonly kind: "set"; readonly flag: boolean } | { readonly kind: "clear" };
export function g(q: number | null, msg: Msg): number {
let p: number | null = q;
if (p === null) { return -1; }
if (msg.kind === "set" && msg.flag) {
p = null;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// A compound null guard (`r !== null && r > 0`) routes its branch
// emission through the chain-substitution scope, whose own full-map
// restore runs AFTER the branch's kill re-apply. Without the kill-frame
// bracket on that scope, the kill of p's narrow resurrects at the
// restore and the post-merge re-check emits `p.? == null` —
// "comparison of 'f64' with null".
name: "a kill inside a compound-null-guarded branch stays dead past the merge",
src: `
export function f(q: number | null, r: number | null): number {
let p: number | null = q;
if (p === null) return -1;
if (r !== null && r > 0) {
p = null;
} else {}
if (p === null) return 0;
return p;
}
`,
},
{
name: "a kill in the ELSE of a compound-null-guarded if stays dead past the merge",
src: `
export function f(q: number | null, r: number | null): number {
let p: number | null = q;
if (p === null) return -1;
if (r !== null && r > 0) {
// the guarded path keeps the narrow; the merge must still drop it
} else {
p = null;
}
if (p === null) return 0;
return p;
}
`,
},
{
name: "a compound-guard kill nested inside a kind guard escapes both scopes",
src: `
export type Msg = { readonly kind: "set"; readonly flag: boolean } | { readonly kind: "clear" };
export function f(q: number | null, r: number | null, msg: Msg): number {
let p: number | null = q;
if (p === null) return -1;
if (msg.kind === "set" && msg.flag) {
if (r !== null && r > 0) {
p = null;
}
}
if (p === null) return 0;
return p;
}
`,
},
{
// The containment half must stay intact: a narrowing ESTABLISHED inside
// a branch still dies at the branch exit. If it leaked, the post-merge
// null test would read the narrowed spelling and emit the same
// comparison-with-null error the kill cases pin.
name: "containment pin: a narrow established inside a branch still does not leak out",
src: `
export function f(q: number | null, flag: boolean): number {
let acc: number = 0;
if (flag) {
if (q === null) { return -1; }
acc = q;
}
return acc + (q === null ? 0 : q);
}
`,
},
];
// The kill merge is flow-sensitive the way tsc is: a branch that kills a
// narrow and then always LEAVES the function cannot reach the merge, so its
// kill applies nowhere — tsc keeps the narrow on the surviving flow, and a
// read there depends on the substitution's unwrap spelling (a dropped
// spelling emits a field access or plain read on an optional, not a
// re-check). Kills on paths that resume inside the function still apply —
// and they apply where control resumes: a true fall-through merges; a
// branch whose only in-function routes are non-local edges stages its kills
// at each edge's destination instead (a caught throw at the post-try
// continuation, a break at its target construct's post-state, a continue at
// the loop's exit join, a lowered callback return after the value block) —
// never into flow the killing path cannot reach.
const killFallthroughCases: Case[] = [
{
name: "a branch that kills and returns leaves the surviving flow's capture narrow intact",
src: `
export interface P { readonly v: number; }
export function f(p0: P | null): number {
let p: P | null = p0;
if (p !== null) {
if (p.v < 0) { p = null; return -1; }
return p.v;
}
return 0;
}
`,
},
{
name: "a branch that kills and returns leaves the surviving flow's orelse narrow intact",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) { p = null; return -2; }
return p;
}
`,
},
{
name: "a branch that kills and throws (uncaught) leaves the surviving narrow intact",
src: `
export interface P { readonly v: number; }
export interface NegError { readonly kind: "neg"; readonly at: number; }
export function f(p0: P | null): number {
let p: P | null = p0;
if (p !== null) {
if (p.v < 0) { p = null; throw { kind: "neg", at: 0 } as NegError; }
return p.v;
}
return 0;
}
`,
},
{
name: "a kill two blocks deep on an always-returning path stays off the surviving reads",
src: `
export interface P { readonly v: number; }
export function f(p0: P | null, a: boolean, b: boolean): number {
let p: P | null = p0;
if (p !== null) {
if (a) {
if (b) { p = null; return -1; }
return p.v;
}
return p.v + 1;
}
return 0;
}
`,
},
{
// One arm exits and kills (its kill is unreachable at the merge); the
// other falls through and kills (its kill must survive). The post-merge
// re-check reads the LIVE slot, so a resurrected narrow here is the
// comparison-with-null compile error the invalidation cases pin.
name: "mixed arms: the exiting arm's kill drops, the fall-through arm's kill merges",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
return -2;
} else {
p = null;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// `break` leaves the loop, not the function: control resumes right
// after it, where the kill must hold for the re-check to compile.
name: "a break-guarded kill still reaches the code after the loop",
src: `
export function f(q: number | null, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) { return -1; }
for (const x of xs) {
if (x < 0) { p = null; break; }
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// `continue` resumes at the next iteration and the loop still falls
// through to the code after it, so the kill merges there too.
name: "a continue-guarded kill still reaches the code after the loop",
src: `
export function f(q: number | null, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) { return -1; }
let acc: number = 0;
for (const x of xs) {
if (x < 0) { p = null; continue; }
acc = acc + x;
}
if (p === null) { return acc; }
return p + acc;
}
`,
},
{
// Kills travel the same edges control does: a branch whose only
// in-function route is a caught throw resumes at the POST-try
// continuation, never at the try body's remaining statements — so the
// intra-try read keeps its narrow (tsc agrees) while the post-try
// re-check reads the live optional. Merging the kill intra-try
// stripped the surviving path's unwrap and the read failed to compile.
name: "a caught-throw kill skips the intra-try flow and lands after the try",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) return -1;
try {
if (flag) { p = null; throw { kind: "boom" } as Boom; }
return p.v;
} catch {}
if (p === null) return 0;
return p.v + 1;
}
`,
},
{
// Control: a branch with BOTH a caught-throw route and a normal
// fallthrough still merges intra-try — the fallthrough carries the
// kill into the try's remaining statements legitimately.
name: "a branch with a caught throw AND fallthrough still merges its kill intra-try",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
export function f(q: P | null, flag: boolean, deep: boolean): number {
let p: P | null = q;
if (p === null) return -1;
try {
if (flag) { p = null; if (deep) { throw { kind: "boom" } as Boom; } }
if (p === null) return -2;
return p.v;
} catch {}
if (p === null) return 0;
return p.v + 1;
}
`,
},
{
// Nested tries: a throw caught by the INNER catch that falls through
// resumes at the inner try's continuation — the kill lands THERE (and
// flows outward from that point), while the intra-inner-try read keeps
// its narrow.
name: "a caught-throw kill in a nested try lands at the inner continuation",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) return -1;
let acc: number = 0;
try {
try {
if (flag) { p = null; throw { kind: "boom" } as Boom; }
acc = p.v;
} catch {}
if (p === null) { acc = acc - 1; }
} catch {}
if (p === null) return acc;
return p.v + acc;
}
`,
},
{
// Control: a kill in the CATCH block itself merges at the post-try
// continuation exactly as before the exception-kills channel.
name: "a kill in the catch block still reaches the post-try re-check",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) return -1;
try {
if (flag) { throw { kind: "boom" } as Boom; }
} catch {
p = null;
}
if (p === null) return 0;
return p.v;
}
`,
},
{
// A throw under an enclosing try lands in the catch, not out of the
// function; control resumes after the try/catch, where p may be null.
name: "a kill before a locally-caught throw still reaches past the catch",
src: `
export interface NegError { readonly kind: "neg"; readonly at: number; }
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
try {
if (flag) { p = null; throw { kind: "neg", at: 0 } as NegError; }
} catch {
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// The try body's terminal `return` is not its only exit: the call
// before it can throw, land in the catch, and fall through to the
// re-check — which must then read the LIVE optional, not a stale
// unwrap (tsc types p as P | null here for the same reason).
name: "a kill before a throwing call in a try body reaches past a fall-through catch",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
function g(flag: boolean): void {
if (flag) { throw { kind: "boom" } as Boom; }
}
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) { return -1; }
try {
p = null;
g(flag);
return -2;
} catch {
}
if (p === null) { return 0; }
return p.v;
}
`,
},
{
// A catch that always leaves the function closes every exception route
// to the merge, so the body's kill drops on its terminal return and the
// surviving flow's read keeps its narrow (tsc agrees: no path through
// the try/catch reaches `return p.v`).
name: "a kill in a try body whose catch always returns stays off the surviving reads",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
function g(flag: boolean): void {
if (flag) { throw { kind: "boom" } as Boom; }
}
export function f(p0: P | null, flag: boolean): number {
let p: P | null = p0;
if (p !== null) {
if (p.v < 0) {
try { p = null; g(flag); return -1; } catch { return -2; }
}
return p.v;
}
return 0;
}
`,
},
{
// try/finally has no catch fallthrough: a mid-body throw propagates out
// of the function (after the finally), so the body's terminal return
// still drops its kills and the post-construct read keeps its narrow.
name: "a kill in a returning try/finally body stays off the code after it",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
let cleanup: number = 0;
if (flag) {
try {
p = null;
return -2;
} finally {
cleanup = 1;
}
}
return p + cleanup;
}
`,
},
{
// Control: a kill inside an exiting branch of a try body with NO local
// catch behaves exactly like one outside any try — the branch's kill
// drops, and the body's later read keeps the surviving narrow.
name: "an exiting-branch kill inside a catchless try body still drops",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
let acc: number = 0;
try {
if (flag) { p = null; return -2; }
acc = p;
} finally {
acc = acc + 1;
}
return acc;
}
`,
},
{
// The body's terminal return is not its only route out: the earlier
// break escapes the loop carrying the kill, so the post-loop re-check
// must read the LIVE optional (tsc types p as number | null there).
name: "a mid-list break escapes a loop body whose terminal statement returns",
src: `
export function f(q: number | null, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) { return -1; }
for (const x of xs) {
if (x < 0) { p = null; break; }
return -2;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// Control: a break bound to a loop WHOLLY inside the exiting branch
// resumes inside it and is no route out — the branch's kill still
// drops, and the surviving read keeps its capture narrow.
name: "a break bound to a loop wholly inside an exiting branch is no escape",
src: `
export interface P { readonly v: number; }
export function f(p0: P | null, xs: readonly number[]): number {
let p: P | null = p0;
if (p !== null) {
if (p.v < 0) {
p = null;
let acc: number = 0;
for (const x of xs) {
if (x < 0) { break; }
acc = acc + x;
}
return acc;
}
return p.v;
}
return 0;
}
`,
},
{
// The continue flavor of the mid-list escape: the loop resumes, then
// falls through to the re-check, which needs the live value.
name: "a mid-list continue escapes a loop body whose terminal statement returns",
src: `
export function f(q: number | null, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) { return -1; }
for (const x of xs) {
if (x < 0) { p = null; continue; }
return -2;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// A catch that ends in return still has a route back into the
// function when a call inside it can throw to an OUTER catch that
// falls through — the inner body's kill must reach the outer merge.
name: "a throwing call in an exiting catch escapes to an outer fall-through catch",
src: `
export interface Boom { readonly kind: "boom"; }
function g(flag: boolean): void {
if (flag) { throw { kind: "boom" } as Boom; }
}
export function f(q: number | null, a: boolean, b: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
try {
try {
p = null;
g(a);
return -2;
} catch {
g(b);
return -3;
}
} catch {
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// Control: when the OUTER catch also always leaves, every escape from
// the inner catch's throwing call still exits the function — the inner
// body's kill drops and the surviving read keeps its capture narrow.
name: "an exiting catch whose throws land in an exiting outer catch still closes the body",
src: `
export interface P { readonly v: number; }
export interface Boom { readonly kind: "boom"; }
function g(flag: boolean): void {
if (flag) { throw { kind: "boom" } as Boom; }
}
export function f(p0: P | null, a: boolean, b: boolean): number {
let p: P | null = p0;
if (p !== null) {
if (p.v < 0) {
try {
try { p = null; g(a); return -1; } catch { g(b); return -2; }
} catch {
return -3;
}
}
return p.v;
}
return 0;
}
`,
},
{
// A labeled break out of a labeled block resumes right after the
// block — inside the function — so its kill reaches the re-check even
// though the block's terminal statement returns.
name: "a labeled break out of a labeled block carries its kill past the block",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
outer: {
if (flag) { p = null; break outer; }
return -2;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// The do-while flavor of the mid-list escape.
name: "a mid-list break in a do-while body whose terminal statement returns",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
do {
if (flag) { p = null; break; }
return -2;
} while (flag);
if (p === null) { return 0; }
return p;
}
`,
},
{
// Control: clause-terminating breaks bind the switch inside the list,
// resume inside it, and are no routes out — the exiting branch's kill
// still drops past the terminal return.
name: "a switch-bound break inside an exiting branch is no escape",
src: `
export interface P { readonly v: number; }
export type Mode = "a" | "b";
export function f(p0: P | null, m: Mode): number {
let p: P | null = p0;
if (p !== null) {
if (p.v < 0) {
p = null;
let acc: number = 0;
switch (m) {
case "a": acc = 1; break;
case "b": acc = 2; break;
}
return acc;
}
return p.v;
}
return 0;
}
`,
},
{
// tsc treats `while (true)` without a break bound to it as never
// completing: the killing branch never reaches the merge, so the
// post-merge read keeps the pre-branch narrow. The kill must DROP —
// merging it resurrects the optional and the read emits optional
// arithmetic Zig rejects. (Zig agrees the loop is noreturn, so the
// branch body emits as-is.)
name: "a kill sealed behind while(true) never reaches the merge",
src: `
export function f(q: number | null, flag: boolean, bump: number): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
while (true) {}
}
return p + bump;
}
`,
},
{
// The classic-for spelling of the same seal. The subset's classic-for
// mapping requires init/cond/increment, so the literal-true condition
// is the emittable spelling of an infinite for loop (`for (;;)` stops
// at NS9001 before narrowing matters).
name: "a kill sealed behind a literal-true classic for never reaches the merge",
src: `
export function f(q: number | null, flag: boolean, bump: number): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
for (let i = 0; true; i += 1) {}
}
return p + bump;
}
`,
},
{
// The do-while spelling: the body runs before the first test, but a
// literal-true test with no bound break still never lets the loop
// complete.
name: "a kill sealed behind do-while(true) never reaches the merge",
src: `
export function f(q: number | null, flag: boolean, bump: number): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
do {} while (true);
}
return p + bump;
}
`,
},
{
// A break bound to the loop makes its end reachable: the killing
// branch DOES reach the merge, so the kill must still merge and the
// post-loop re-check reads the live slot.
name: "a break bound to an infinite loop carries the kill past it",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
while (true) {
if (flag) { break; }
}
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// A return inside the infinite loop leaves the function without
// completing the loop: the branch still counts as function-leaving,
// so its kill drops and the surviving flow keeps the narrow.
name: "a return nested in an infinite loop still makes the branch leave the function",
src: `
export function f(q: number | null, flag: boolean, n: number): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
while (true) {
if (n > 0) { return n; }
}
}
return p + 2;
}
`,
},
{
// Guard against over-recognition: a non-literal-true condition can be
// false on entry, the loop completes, and the kill reaches the merge —
// the re-check must read the live slot.
name: "a while over a non-literal condition is never terminal",
src: `
export function f(q: number | null, flag: boolean, n: number): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
let i: number = n;
while (i > 0) { i = i - 1; }
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// A throw caught by a fall-through catch resumes at the statements
// AFTER the try — here a terminal return — so every route through the
// branch (throw -> catch -> return; no-throw -> return) leaves the
// function. The kill must DROP: tsc keeps the narrow on the surviving
// flow, and merging would strip the unwrap off `return p.v`.
name: "a caught throw whose fall-through catch resumes into a returning tail still drops",
src: `
export interface NegError { readonly kind: "neg"; readonly at: number; }
export interface P { readonly v: number; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) { return -1; }
if (p.v < 0) {
try {
p = null;
if (flag) { throw { kind: "neg", at: 0 } as NegError; }
} catch {
}
return -2;
}
return p.v;
}
`,
},
{
// Control for the continuation logic: when the statements after the
// try do NOT all leave (a conditional return, then fallthrough), the
// caught throw's route reaches the merge and the kill must still
// MERGE — the post-branch re-check reads the live slot.
name: "a caught throw whose continuation can fall through still merges its kill",
src: `
export interface NegError { readonly kind: "neg"; readonly at: number; }
export interface P { readonly v: number; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) { return -1; }
if (p.v < 0) {
try {
p = null;
if (flag) { throw { kind: "neg", at: 0 } as NegError; }
} catch {
}
if (flag) { return -2; }
}
if (p === null) { return 0; }
return p.v;
}
`,
},
{
// Composition pin: a catch that itself returns closes the throw route
// without consulting the continuation, and the no-throw path leaves on
// the try's own terminal return — drop either way.
name: "a caught throw whose catch returns drops without consulting the continuation",
src: `
export interface NegError { readonly kind: "neg"; readonly at: number; }
export interface P { readonly v: number; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) { return -1; }
if (p.v < 0) {
try {
p = null;
if (flag) { throw { kind: "neg", at: 0 } as NegError; }
} catch {
return -2;
}
return -3;
}
return p.v;
}
`,
},
];
// Destination-specific kill staging: an arm whose only in-function routes
// are non-local edges (break, continue, a lowered callback return) applies
// its kills where those edges LAND — the target construct's post-state, the
// loop's exit join, the post-value-block continuation — never at the arm's
// own fall-through merge, a point the killing path cannot reach. tsc keeps
// the fall-through narrow there, and the reads depend on the unwrap
// spelling: a misrouted kill emits arithmetic or field access on a bare
// optional, which Zig rejects.
const edgeKillStagingCases: Case[] = [
{
// The core shape: the kill+break arm leaves the loop, so the
// fall-through read after it keeps the narrow (tsc agrees — the
// killing path cannot reach it), while the POST-loop state is where
// the break lands and must re-check.
name: "a kill+break arm leaves the loop-body fall-through narrow intact",
src: `
export function tally(vals: readonly number[], limit: number): number {
let p: number | null = 10;
if (p === null) return -1;
let sum: number = 0;
for (const v of vals) {
if (v > limit) { p = null; break; }
sum += p;
}
if (p === null) return -sum;
return sum + p;
}
`,
},
{
// An unlabeled break binds the INNER loop: its kill lands post-inner —
// the outer body's re-check — while the inner fall-through keeps the
// narrow.
name: "a nested inner-targeted break stages its kill at the inner loop only",
src: `
export function nested(grid: readonly number[], cols: number): number {
let p: number | null = 5;
if (p === null) return -1;
let sum: number = 0;
for (const r of grid) {
for (const c of grid) {
if (c > cols) { p = null; break; }
sum += p;
}
if (p === null) break;
sum += p;
}
return sum;
}
`,
},
{
// A labeled break out of both loops lands post-OUTER: the outer body's
// read after the inner loop keeps the narrow (the killing path skipped
// it), and only the post-outer state re-checks.
name: "a labeled break out of two loops stages its kill post-outer",
src: `
export function labeled(grid: readonly number[], cols: number): number {
let p: number | null = 5;
if (p === null) return -1;
let sum: number = 0;
outer: for (const r of grid) {
for (const c of grid) {
if (c > cols) { p = null; break outer; }
sum += p;
}
sum += p;
}
if (p === null) return -2;
return sum + p;
}
`,
},
{
// A continue-edge kill rides the back edge: the fall-through read
// after the arm keeps its per-iteration narrow (tsc agrees), the next
// iteration re-guards (tsc REQUIRES it — the loop-entry state widens
// for back-edge kills, so an unguarded read is TS18047 and never
// reaches emission), and the loop's exit join re-checks.
name: "a kill+continue arm leaves the same-iteration fall-through narrow intact",
src: `
export function contGood(vals: readonly number[], limit: number): number {
let p: number | null = 10;
let sum: number = 0;
for (const v of vals) {
if (p === null) break;
sum += p;
if (v > limit) { p = null; continue; }
sum += p;
}
if (p === null) return -sum;
return sum + p;
}
`,
},
{
// Mixed sibling arms route per class: the kill+break arm's kill stages
// post-loop (the join never sees it), so the fall-through read past
// BOTH arms keeps the narrow — only the loop's own exit re-checks.
name: "mixed arms: the kill+break sibling stays out of the join the pure arm feeds",
src: `
export function mixedArms(xs: readonly number[]): number {
let p: number | null = 4;
if (p === null) return -1;
let acc: number = 0;
for (const x of xs) {
if (x < 0) { p = null; break; }
else if (x === 0) { acc += 1; }
acc += p;
}
if (p === null) return acc;
return acc + p;
}
`,
},
{
// The do-while flavor: the kill+break arm leaves the body, the
// fall-through read keeps its narrow, and the trailing loop test plus
// the post-loop state see the staged kill.
name: "a kill+break arm in a do-while body leaves the fall-through narrow intact",
src: `
export function viaDoWhile(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) return -1;
let acc: number = 0;
do {
if (flag) { p = null; break; }
acc += p;
} while (acc < 10);
if (p === null) return acc;
return acc + p;
}
`,
},
{
// The R7d guarded-condition while: the loop's own chain unwraps q per
// iteration while the kill+break arm's kill on ANOTHER local stages
// post-loop, keeping the body's fall-through read narrowed.
name: "a kill+break under a null-guarded while condition stages past the loop",
src: `
export interface Q { readonly v: number; }
export function guarded(q0: Q | null, flag: boolean): number {
let p: number | null = 3;
if (p === null) return -1;
let q: Q | null = q0;
let acc: number = 0;
while (q !== null && q.v > 0) {
if (flag) { p = null; break; }
acc += p + q.v;
q = null;
}
if (p === null) return acc;
return acc + p;
}
`,
},
{
// A lowered callback return is an edge to the continuation AFTER the
// value block: the kill+return arm leaves the trailing in-callback
// read narrowed (tsc agrees), and the post-callback state re-checks.
name: "a kill+return arm in a lifted callback leaves the trailing read narrow intact",
src: `
export function viaCallback(vals: readonly number[], limit: number): number {
let p: number | null = 7;
const mapped = vals.map((v) => {
if (p === null) return 0;
if (v > limit) { p = null; return 0; }
return p + v;
});
let sum: number = 0;
for (const m of mapped) sum += m;
if (p === null) return -sum;
return sum + p;
}
`,
},
{
// Switch reconciliation: the clause-terminating break's kill rides the
// sibling JOIN to the post-switch state — applied once, dropped
// nowhere — while the LATER clause keeps its entry narrow (sibling
// isolation) and the post-switch read re-checks.
name: "a switch clause kill+break kills post-switch but not its later siblings",
src: `
export type Tag = "a" | "b" | "c";
export function viaSwitch(tag: Tag, limit: number): number {
let p: number | null = 3;
if (p === null) return -1;
let out: number = 0;
switch (tag) {
case "a": {
p = null;
break;
}
case "b": {
out = p + limit;
break;
}
case "c": {
out = p * 2;
break;
}
}
if (p === null) return -out;
return out + p;
}
`,
},
{
// A labeled continue targets the OUTER loop's back edge: the rest of
// the outer body keeps its (re-guarded) flow, and both the outer exit
// join and the post-loop state re-check. The in-body read after the
// inner loop re-guards because tsc widens the outer loop's entry for
// the back-edge kill.
name: "a labeled continue stages its kill at the outer loop's exit join",
src: `
export function labeledCont(xs: readonly number[]): number {
let p: number | null = 6;
let acc: number = 0;
outer: for (const a of xs) {
for (const b of xs) {
if (b < 0) { p = null; continue outer; }
}
if (p === null) return -acc;
acc += p + a;
}
if (p === null) return acc;
return acc + p;
}
`,
},
];
// A finally clause emits as a Zig defer whose TEXT precedes the try body's,
// but whose FLOW follows it: the finally runs at the construct's exits. Its
// kills must therefore stage until the construct's exit — never apply to the
// body emitted after the defer's text, where tsc keeps the narrow (the
// finally has not run at those program points). In the other temporal
// direction, tsc types the finally itself from the pre-try state minus every
// key the try or catch may assign null to, path-insensitively (an exception
// can enter the finally from between any two statements), so a finally read
// of such a key re-checks the LIVE optional — never a stale capture.
const finallyFlowOrderCases: Case[] = [
{
// The try body's read keeps its narrow even though the defer carrying
// the kill is emitted first: flow order, not emission order.
name: "a finally-clause kill stays off the try body's own reads",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
if (p === null) { return -1; }
try {
return p.v;
} finally {
p = null;
}
}
`,
},
{
// tsc widens the killed key inside the finally (the kill may or may not
// have run when an exception hands control over), so the finally's read
// re-checks and must test the live variable, not a pre-try capture.
name: "a finally clause re-checks a narrow the try body killed",
src: `
export function f(q: number | null, drop: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
let seen: number = 0;
try {
if (drop) { p = null; }
} finally {
if (p !== null) { seen = p + 1; }
}
return seen;
}
`,
},
{
// Kills in all three regions land where flow says: the try's kill is
// visible in the finally and after the construct; the catch's kill
// falls through to the post-try state; the finally's own kill applies
// at the construct's exit — and none of them touches the try body's
// reads before the killing statement.
name: "kills in try, catch, and finally each land at their flow destinations",
src: `
export interface Boom { readonly kind: "boom"; }
function mayThrow(flag: boolean): void {
if (flag) { throw { kind: "boom" } as Boom; }
}
export function f(q: number | null, r: number | null, s: number | null, flag: boolean): number {
let a: number | null = q;
let b: number | null = r;
let c: number | null = s;
if (a === null) { return -1; }
if (b === null) { return -2; }
if (c === null) { return -3; }
let out: number = 0;
try {
a = null;
mayThrow(flag);
out += b;
} catch {
b = null;
} finally {
c = null;
if (a !== null) { out += a; }
}
if (a !== null) { out += a; }
if (b !== null) { out += b; }
if (c !== null) { out += c; }
return out;
}
`,
},
];
// Kills that ride a throw edge into a catch are the catch's INPUT state,
// not a post-try constant: the catch body's reads see them, and their
// propagation OUT follows the catch's own routes. A catch that falls
// through carries them to the post-try continuation; a catch that
// breaks/continues stages them at that edge's landing point; a catch whose
// every route leaves the function drops them — the post-try continuation
// is then reached only by clean paths, where tsc keeps the narrow.
const catchRouteKillCases: Case[] = [
{
// The killed path is throw -> catch -> break -> post-loop; the code
// after the try inside the loop is reached only by the clean path, so
// its read keeps the narrow (tsc agrees) while the post-loop state
// re-checks.
name: "an exception kill follows the catch's break to the post-loop state",
src: `
export interface Boom { readonly kind: "boom"; }
export function f(q: number | null, fail: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
while (true) {
try {
if (fail) { p = null; throw { kind: "boom" } as Boom; }
} catch {
break;
}
return p + 1;
}
return -2;
}
`,
},
{
// A labeled break out of both loops lands post-OUTER: the reads on the
// clean path through both bodies keep the narrow, and only the
// post-outer state re-checks.
name: "an exception kill follows a labeled break out of nested loops",
src: `
export interface Boom { readonly kind: "boom"; }
export function f(q: number | null, limit: number, grid: readonly number[]): number {
let p: number | null = q;
if (p === null) { return -1; }
let sum: number = 0;
outer: for (const r of grid) {
for (const x of grid) {
try {
if (x > limit) { p = null; throw { kind: "boom" } as Boom; }
} catch {
break outer;
}
sum += p + x;
}
sum += p + r;
}
if (p === null) { return -sum; }
return sum + p;
}
`,
},
{
// A catch that re-kills the carried key and falls through merges it
// once at the post-try state — same destination, no double application.
name: "a catch that re-kills the carried key merges it once on fallthrough",
src: `
export interface Boom { readonly kind: "boom"; }
export function f(q: number | null, fail: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
try {
if (fail) { p = null; throw { kind: "boom" } as Boom; }
} catch {
p = null;
}
if (p === null) { return 0; }
return p;
}
`,
},
{
// A catch whose every route returns closes the exception path: the
// post-try continuation is reached only by the clean path and its read
// keeps the narrow (tsc agrees — no re-check is even legal to require).
name: "an exception kill drops with a catch that always returns",
src: `
export interface Boom { readonly kind: "boom"; }
export function f(q: number | null, fail: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
try {
if (fail) { p = null; throw { kind: "boom" } as Boom; }
} catch {
return -2;
}
return p + 1;
}
`,
},
];
// A lifted block-body callback whose only return is the trailing statement
// emits as straight-line statements plus that value. The statement prefix
// and the trailing expression are ONE flow in tsc — a guard in the prefix
// narrows the expression after it — so they must share one narrowing scope:
// a scope closed between them restores the maps before the expression they
// guard, emitting the read against the unnarrowed optional.
const callbackTrailingNarrowCases: Case[] = [
{
name: "a throw guard in a map callback narrows the trailing return expression",
src: `
export interface P { readonly v: number; }
export interface BadError { readonly kind: "bad"; readonly at: number; }
export function f(xs: readonly (P | null)[]): readonly number[] {
return xs.map((p) => {
if (p === null) { throw { kind: "bad", at: 0 } as BadError; }
return p.v;
});
}
`,
},
{
name: "a kind guard in a map callback narrows the trailing payload read",
src: `
export type Msg = { readonly kind: "num"; readonly value: number } | { readonly kind: "none" };
export interface BadError { readonly kind: "bad"; readonly at: number; }
export function f(xs: readonly Msg[]): readonly number[] {
return xs.map((m) => {
if (m.kind !== "num") { throw { kind: "bad", at: 0 } as BadError; }
return m.value;
});
}
`,
},
{
// The early-return spelling lifts as a labeled value block whose whole
// body already shares one scope; pinned so the two lifting paths keep
// agreeing on where a prefix guard's narrowing ends.
name: "an early-return guard in a map callback narrows the trailing return too",
src: `
export interface P { readonly v: number; }
export function f(xs: readonly (P | null)[]): readonly number[] {
return xs.map((p) => {
if (p === null) { return -1; }
return p.v;
});
}
`,
},
];
// tsc evaluates a do-while's condition AFTER the body and carries the
// body's flow state into it: a terminal guard in the body narrows the
// trailing test. The `while (true)` + trailing-exit-test lowering emits the
// body list and the lowered `if (!(cond)) break;` inside ONE narrowing
// scope, restored only at the loop boundary — a restore between them strips
// the narrow before the read it covers. The first-pass-flag form (a body
// binding `continue`) instead hoists the test ahead of the body, where the
// condition reads the live optional: the continue edge carries kills into
// the test, so tsc widens it there (the bare read is a TS18047 the checker
// already rejects — pinned in emitter.test.ts).
const doWhileTrailingTestCases: Case[] = [
{
// The regression repro: transpile reported success while the emitted
// trailing test read the raw `?P`.
name: "a terminal guard in a do-while body narrows the trailing test",
src: `
export interface P { readonly v: number; }
export function g(q: P | null): number {
let n = 0;
const p: P | null = q;
do {
if (p === null) return -1;
n += p.v;
n += 1;
} while (p.v > 0 && n < 10);
return n;
}
`,
},
{
// A capture that serves ONLY the trailing test: the follower walk must
// see the condition, or the guard binds no capture and the test reads
// the raw optional; and having bound one, the test must use it (an
// unused Zig const is a compile error).
name: "a do-while guard read only by the trailing test still binds its capture",
src: `
export interface P { readonly v: number; }
export function g(q: P | null): number {
let n = 0;
const p: P | null = q;
do {
if (p === null) return -1;
n += 1;
} while (p.v > n);
return n;
}
`,
},
{
// Back-edge semantics: the body-top read on the second iteration
// arrives via the back edge, which re-narrowed after the kill — tsc
// accepts the join of the pre-loop and end-of-body narrows, and the
// live `.?` read keeps the reassigned slot's value. The end-of-body
// guard's narrow must still reach the trailing test.
name: "a do-while body kill re-narrowed before the trailing test",
src: `
export interface P { readonly v: number; }
export function g(q: P | null, r: P | null): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
do {
n += p.v;
p = r;
if (p === null) return -1;
} while (p.v > 0 && n < 10);
return n;
}
`,
},
{
// Kill + continue: the continue edge jumps TO the test carrying the
// kill, so tsc widens the condition and the lowering must read the
// live optional in the hoisted first-pass head.
name: "a do-while continue-carried kill leaves the test on the live optional",
src: `
export interface P { readonly v: number; }
export function g(q: P | null, r: P | null): number {
let p: P | null = q;
let n = 0;
do {
if (p === null) return -1;
n += p.v;
if (n < 3) {
p = r;
continue;
}
n += 1;
} while (p !== null && p.v > 0 && n < 10);
return n;
}
`,
},
{
// Kill + break: the break edge SKIPS the test, so the fall-through
// narrow stays on the condition, and the kill lands only on the
// post-loop state (where the read must re-guard the live optional).
name: "a do-while kill+break arm keeps the test narrowed and widens post-loop",
src: `
export interface P { readonly v: number; }
export function g(q: P | null, r: P | null): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
do {
n += p.v;
if (n > 3) {
p = r;
break;
}
n += 1;
} while (p.v < 100);
return p === null ? -2 : p.v;
}
`,
},
{
// Nested do-whiles sharing one narrowed outer target: the inner body
// and inner test read the outer guard's capture, and the outer test
// still sees it after the inner loop's scope closes.
name: "nested do-whiles share a narrowed outer target through both tests",
src: `
export interface P { readonly v: number; }
export function g(q: P | null): number {
const p: P | null = q;
let n = 0;
do {
if (p === null) return -1;
let m = 0;
do {
n += p.v;
m += 1;
} while (p.v > 0 && m < 2);
} while (p.v > n);
return n;
}
`,
},
];
// Terminality must read constructs the way tsc's CFA does, not clause by
// clause or wrapper by wrapper: stacked case labels share the next
// statement-bearing clause's body (JS falls through empty clauses; the
// switch emitters coalesce the labels into one arm), and a label adds
// exactly one edge — `break label` resumes right after the labeled
// statement. Judging a stacked clause empty-handed or a labeled statement
// as never-terminal merges kills from branches that cannot reach the
// merge, stripping the surviving flow of a narrowing tsc keeps there —
// and the read emits raw optional arithmetic Zig rejects.
const terminalityGroupingCases: Case[] = [
{
// Stacked labels on a kind switch: every group exits, so the killing
// branch never reaches the merge and the surviving read keeps its
// unwrap. Judged clause by clause, the empty "inc" clause reads as
// non-exiting and the merged kill emits `p + 1` on a raw `?f64`.
name: "stacked case labels whose shared bodies all exit keep the branch terminal (kind switch)",
src: `
export type Msg =
| { readonly kind: "inc" }
| { readonly kind: "dec" }
| { readonly kind: "reset" };
export function f(q: number | null, flag: boolean, msg: Msg): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (msg.kind) {
case "inc":
case "dec":
return 10;
case "reset":
return 20;
}
}
return p + 1;
}
`,
},
{
// The defaulted value-switch spelling of the same shape (the else
// prong is the group tail for the trailing labels' semantics).
name: "stacked case labels whose shared bodies all exit keep the branch terminal (defaulted value switch)",
src: `
export type Mode = "a" | "b" | "c" | "d";
export function f(q: number | null, flag: boolean, mode: Mode): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (mode) {
case "a":
case "b":
return 10;
default:
return 20;
}
}
return p + 1;
}
`,
},
{
// Non-regression: a group whose shared body does NOT exit falls out of
// the switch, so the kill still merges and the post-branch re-check
// reads the live slot.
name: "a stacked group that falls out of the switch still merges its kill",
src: `
export type Msg =
| { readonly kind: "inc" }
| { readonly kind: "dec" }
| { readonly kind: "reset" };
export function f(q: number | null, flag: boolean, msg: Msg): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (msg.kind) {
case "inc":
case "dec":
return 10;
case "reset":
p = q;
}
}
if (p === null) { return 0; }
return p + 1;
}
`,
},
{
// A trailing run of empty clauses has no body at all: control falls
// out of the switch (JS no-op labels), the branch is nonterminal, and
// the kill merges into the re-check.
name: "trailing empty clauses make the switch nonterminal and merge the kill",
src: `
export type Msg =
| { readonly kind: "inc" }
| { readonly kind: "dec" }
| { readonly kind: "reset" };
export function f(q: number | null, flag: boolean, msg: Msg): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (msg.kind) {
case "inc":
return 10;
case "dec":
case "reset":
}
}
if (p === null) { return 0; }
return p + 1;
}
`,
},
{
// An empty clause before `default` shares the default's body in JS,
// but the emitters have no clean arm mapping for that shape and stop
// with the fall-into-default teaching.
name: "an empty case falling into default gates at emission",
formerEmitGate: "NS9001",
src: `
export type Mode = "a" | "b" | "c";
export function f(mode: Mode): number {
switch (mode) {
case "a":
return 1;
case "b":
default:
return 2;
}
}
`,
},
{
// A label is not a break: the constant-true loop under it still never
// completes, so the killing branch leaves the function and the
// surviving read keeps its unwrap. Judged as a bare LabeledStatement,
// the branch reads nonterminal and the merged kill emits `p + 1` on a
// raw `?f64`.
name: "a labeled constant-true loop with no break to the label stays terminal",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
spin: while (true) {}
}
return p + 1;
}
`,
},
{
// Non-regression: a break naming the label makes the loop's end
// reachable — the killing branch DOES reach the merge, so the
// post-branch re-check must read the live slot.
name: "a break to the label carries the kill past the labeled loop",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
spin: while (true) {
if (flag) { break spin; }
}
}
if (p === null) { return 0; }
return p + 1;
}
`,
},
{
// A labeled block whose statement list exits, with no break to the
// label, is as terminal as the bare block.
name: "a labeled terminal block with no break to the label stays terminal",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
blk: {
return 5;
}
}
return p + 1;
}
`,
},
{
// ... and one break to the label re-opens the block's end: the kill
// merges and the re-check reads the live slot.
name: "a break to the label re-opens a labeled block's end and merges the kill",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
blk: {
if (flag) { break blk; }
return 5;
}
}
if (p === null) { return 0; }
return p + 1;
}
`,
},
];
// A value switch without a `default` is still terminal when its case labels
// cover the scrutinee's literal-union type — tsc's own exhaustiveness
// judgment, which its CFA uses to keep a narrowing past a branch that ends
// in such a switch. Reading the defaultless switch as nonterminal merges
// kills from branches that cannot reach the merge and emits raw optional
// arithmetic Zig rejects. The consult stays on literal-union scrutinees
// (string/number/boolean literals) with literal case labels; anything wider
// keeps the conservative merge. The lowering already closes the
// never-reached fallthrough (string enums emit a Zig-exhaustive switch with
// no else; numeric aliases emit `else => unreachable`), so the terminality
// claim and the emitted shape agree.
const valueSwitchExhaustivenessCases: Case[] = [
{
// Every member has its own exiting case: the killing branch never
// reaches the merge, so the surviving read keeps its unwrap. Judged
// defaultless-therefore-nonterminal, the merged kill emits `p + 1` on
// a raw `?f64`.
name: "a defaultless value switch covering every union member keeps the branch terminal",
src: `
export type Mode = "a" | "b" | "c";
export function f(q: number | null, flag: boolean, mode: Mode): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (mode) {
case "a":
return 1;
case "b":
return 2;
case "c":
return 3;
}
}
return p + 1;
}
`,
},
{
// Coverage counts stacked labels through their shared body: a stacked
// group plus a separate case covers all three members.
name: "exhaustiveness coverage reads stacked labels alongside separate cases",
src: `
export type Mode = "a" | "b" | "c";
export function f(q: number | null, flag: boolean, mode: Mode): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (mode) {
case "a":
case "b":
return 10;
case "c":
return 20;
}
}
return p + 1;
}
`,
},
{
// Non-regression: one member uncovered means the switch may be skipped
// entirely — the kill merges and the re-check reads the live slot.
name: "a value switch leaving one member uncovered still merges its kill",
src: `
export type Mode = "a" | "b" | "c";
export function f(q: number | null, flag: boolean, mode: Mode): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (mode) {
case "a":
case "b":
return 10;
}
}
if (p === null) { return 0; }
return p + 1;
}
`,
},
{
// A plain string scrutinee has no enumerable members: the consult
// answers conservatively and the kill merges.
name: "a switch on a plain string stays conservative and merges the kill",
src: `
export function f(q: number | null, flag: boolean, s: string): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (s) {
case "x":
return 10;
case "y":
return 20;
}
}
if (p === null) { return 0; }
return p + 1;
}
`,
},
{
// Boolean literal unions are enumerable to the exhaustiveness consult
// (case true + case false covers `boolean`), but the v1 switch
// lowerings have no boolean-scrutinee mapping, so the shape gates at
// emission before terminality can matter end to end.
name: "a switch on a boolean scrutinee gates at emission",
formerEmitGate: "NS9001",
src: `
export function f(q: number | null, flag: boolean): number {
let p: number | null = q;
if (p === null) { return -1; }
switch (flag) {
case true:
return 1;
case false:
return 2;
}
}
`,
},
{
// Numeric literal unions enumerate the same way; the lowering closes
// the integer scrutinee's required else arm with `unreachable`.
name: "a defaultless numeric-literal-union switch covering every member keeps the branch terminal",
src: `
export type Level = 0 | 1 | 2;
export function f(q: number | null, flag: boolean, lvl: Level): number {
let p: number | null = q;
if (p === null) { return -1; }
if (flag) {
p = null;
switch (lvl) {
case 0:
case 1:
return 10;
case 2:
return 20;
}
}
return p + 1;
}
`,
},
{
// The emission/terminality agreement case: an exhaustive defaultless
// switch as a function's final statement must leave Zig no reachable
// missing-return path.
name: "an exhaustive defaultless value switch may end a value-returning function",
src: `
export type Mode = "a" | "b" | "c";
export function f(mode: Mode): number {
switch (mode) {
case "a":
case "b":
return 10;
case "c":
return 20;
}
}
export type Level = 0 | 1 | 2;
export function g(lvl: Level): number {
switch (lvl) {
case 0:
return 1;
case 1:
case 2:
return 2;
}
}
`,
},
];
// The exhaustiveness consult must never trust a flow type a callback
// assignment can falsify: tsc keeps `k` narrowed to "a" across
// `xs.map(() => { k = "b"; })` (it never widens locals for nested-function
// assignments), so judging coverage by the flow type claims terminality for
// a switch real execution skips — and the aligned lowering closes the
// skipped fallthrough with an `else => unreachable` execution then REACHES.
// The judgment reads the scrutinee's DECLARED union instead; a local never
// assigned inside any nested function may still keep the flow type (the
// straight-line CFA is exact for unaliased, uncaptured locals).
const flowTypeSoundnessCases: Case[] = [
{
// The callback-mutation repro: declared "a" | "b", flow type "a",
// cases cover only "a". The coverage claim must decline — the kill
// merges (the re-check below stays legal) and the lowered else
// completes instead of trapping.
name: "a callback-mutated scrutinee declines flow-type coverage and merges the kill",
src: `
export type AB = "a" | "b";
export function f(q: number | null, flag: boolean, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) return -1;
let k: AB = "a";
const ys = xs.map((x) => {
k = "b";
return x;
});
if (flag) {
p = null;
switch (k) {
case "a":
return 1;
}
}
if (p === null) return ys.length;
return p + 1;
}
`,
},
{
// The precise middle, pinned: flow narrowing on a local no nested
// function assigns still claims coverage — the guard's exclusion holds
// on every real path, the branch stays terminal, and the killing
// branch's kill never reaches the merge (the unguarded read compiles).
name: "flow-narrowed coverage still claims for a local never assigned in a callback",
src: `
export type Mode = "a" | "b" | "c";
export function f(q: number | null, flag: boolean, mode: Mode): number {
let p: number | null = q;
if (p === null) return -1;
if (mode === "c") return 30;
if (flag) {
p = null;
switch (mode) {
case "a":
return 1;
case "b":
return 2;
}
}
return p + 1;
}
`,
},
{
// A callback assignment elsewhere in the function demotes even a
// DECLARED-uncovered flow claim: with "c" excluded by the guard but k
// assignable by the callback, only the declared union may judge — it
// is not covered, so the kill merges and the re-check stays legal.
name: "a callback assignment demotes flow-narrowed coverage to the declared union",
src: `
export type Mode = "a" | "b" | "c";
export function f(q: number | null, flag: boolean, mode: Mode, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) return -1;
let k: Mode = mode;
const ys = xs.map((x) => {
k = "c";
return x;
});
if (k === "c") return 30;
if (flag) {
p = null;
switch (k) {
case "a":
return 1;
case "b":
return 2;
}
}
if (p === null) return ys.length;
return p + 1;
}
`,
},
{
// Declared-union coverage stays sound whatever any callback does: all
// members armed and exiting keeps the branch terminal and the merge
// kill dropped (the unguarded read compiles).
name: "declared-union coverage keeps terminality despite a callback assignment",
src: `
export type AB = "a" | "b";
export function f(q: number | null, flag: boolean, ab: AB, xs: readonly number[]): number {
let p: number | null = q;
if (p === null) return -1;
let k: AB = ab;
const ys = xs.map((x) => {
k = "b";
return x;
});
if (flag) {
p = null;
switch (k) {
case "a":
return 1 + ys.length;
case "b":
return 2;
}
}
return p + 1;
}
`,
},
];
// finallyEntryKills counts exactly the may-assigns tsc's CFA counts. Two
// pinned exclusions (probed against the checker provider): code under a
// keyword-literal constant condition tsc treats as unreachable, and nested
// function/callback bodies (tsc keeps the finally narrowed even when a
// callback defined in the try assigns the target). Counting either kills a
// narrow tsc typed the finally's reads with and emits member access on a
// raw optional — invalid Zig.
const tscExcludedRouteCases: Case[] = [
{
// The repro: the killing branch always returns, so tsc keeps p
// narrowed at the post-loop read; the `if (false) break` sits outside
// its CFA. The route walks must give the excluded arm no routes, or
// the dead break reads as a loop-escaping edge and stages a kill at
// the loop exit — the post-loop read then emits on a raw optional.
name: "a dead break inside an always-returning branch never kills the post-loop narrow",
src: `
export function f(es: number[], q: number | null): number {
let p: number | null = q;
if (p === null) return -1;
for (const e of es) {
if (e > 0) { p = null; if (false) break; return 1; }
}
return p;
}
`,
},
{
name: "a dead continue inside an always-returning branch never kills the post-loop narrow",
src: `
export function f(es: number[], q: number | null): number {
let p: number | null = q;
if (p === null) return -1;
for (const e of es) {
if (e > 0) { p = null; if (false) continue; return 1; }
}
return p;
}
`,
},
{
// Non-regression: a REAL break in the same shape is a loop-escaping
// edge, the kill stages at the loop exit, and the post-loop read must
// re-guard.
name: "a real break in the same shape still stages the loop-exit kill",
src: `
export function f(es: number[], q: number | null): number {
let p: number | null = q;
if (p === null) return -1;
for (const e of es) {
if (e > 0) { p = null; if (e > 1) break; return 1; }
}
return p === null ? -2 : p;
}
`,
},
{
// The ForStatement leg of tscExcludedArm at the branch-join layer:
// `for (; false;)` mirrors `while (false)` — the body's kills never
// reach the post-loop state.
name: "a for(;false;) body's kill is excluded at the join",
src: `
export function f(q: number | null): number {
let p: number | null = q;
if (p === null) return -1;
for (let i = 0; false; i += 1) p = null;
return p;
}
`,
},
{
// ... and at the finally entry scan: tsc types the finally read with
// the narrow intact, so the emitted read must keep its substitution.
name: "a for(;false;) body in a try never kills the finally's narrow",
src: `
export function f(q: number | null): number {
let p: number | null = q;
if (p === null) return -1;
let t = 0;
try {
t = t + 1;
for (let i = 0; false; i += 1) p = null;
} finally {
t = t + p;
}
return t;
}
`,
},
{
// Non-regression: a real classic for's kills still count.
name: "a real for-loop kill still widens the post-loop read",
src: `
export function f(n: number, q: number | null): number {
let p: number | null = q;
if (p === null) return -1;
for (let i = 0; i < n; i += 1) p = null;
return p === null ? -2 : p;
}
`,
},
];
const plainSwitchTerminalityCases: Case[] = [
{
// An INFERRED literal union (1 | 2) passes the sound coverage
// judgment, so the switch is claimed terminal — but the emitter-level
// type is plain number and the lowering is the if/else chain. In a
// callback value block the chain must produce on every path: the
// claimed-terminal chain closes with an unreachable else.
name: "inferred-union plain switch as the tail of a callback value block",
src: `
export function m(xs: number[]): number[] {
return xs.map((x) => {
const k = x > 0 ? 1 : 2;
switch (k) {
case 1: return 10;
case 2: return 20;
}
});
}
`,
},
{
name: "inferred-union plain switch as a returning function's last statement",
src: `
export function s(x: number): number {
const k = x > 0 ? 1 : 2;
switch (k) {
case 1: return 10;
case 2: return 20;
}
}
`,
},
{
// A non-exhaustive inferred union declines the claim and completes
// normally — no unreachable, and the trailing return still emits.
name: "non-exhaustive inferred-union plain switch completes normally",
src: `
export function t(x: number): number {
const k = x > 0 ? 1 : 2;
switch (k) {
case 1: return 10;
}
return 5;
}
`,
},
];
const finallyStaticExclusionCases: Case[] = [
{
// The repro: `if (false) p = null` is unreachable to tsc, so the
// finally still types `p.v` against the narrow — the emitted read must
// keep its substitution.
name: "an if-false assignment in a try never kills the finally's narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
try {
if (false) p = null;
n += 1;
} finally {
n += p.v;
}
return n;
}
`,
},
{
// The else of a constant-true if is equally unreachable to tsc.
name: "the else of an if-true in a try never kills the finally's narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
try {
if (true) {
n += 1;
} else {
p = null;
}
} finally {
n += p.v;
}
return n;
}
`,
},
{
// A while-false body never runs under tsc's CFA either.
name: "a while-false body in a try never kills the finally's narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
try {
while (false) {
p = null;
}
n += 1;
} finally {
n += p.v;
}
return n;
}
`,
},
{
// A callback defined in the try assigning the target does not widen
// tsc's finally typing (probed — called or not), so the scan must not
// descend into nested function bodies.
name: "a callback assignment inside a try never kills the finally's narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, xs: readonly number[]): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
try {
const ys = xs.map((x) => {
p = null;
return x;
});
n += ys.length;
} finally {
n += p.v;
}
return n;
}
`,
},
{
// Non-regression: a genuinely conditional assignment still widens the
// finally (tsc's path-insensitive may-assign), so the finally must
// re-guard its read — the kill must still land.
name: "a flag-conditional assignment in a try still kills the finally's narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p === null) return -1;
let n = 0;
try {
if (flag) p = null;
n += 1;
} finally {
if (p !== null) {
n += p.v;
}
}
return n;
}
`,
},
];
// A guarded capture serves READS only: the bare target of a plain `=`
// assignment writes the raw slot, never the capture, so an arm that only
// writes the guarded local must decline the capture form (a Zig capture
// nothing consumes is an unused-capture error). Compound assignments and
// `++`/`--` read the slot and still gate the capture on.
const writeOnlyArmCaptureCases: Case[] = [
{
// The repro: the hit arm's only occurrence of `p` is a callback's
// plain assignment — no read, so no capture; the plain `!= null`
// comparison keeps the ternary valid.
name: "a write-only ternary hit arm declines the capture",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, xs: readonly number[]): number {
let p: P | null = q;
return p !== null ? xs.map((x) => { p = null; return x; }).length : 0;
}
`,
},
{
// The `=== null` dual: TS narrows the ELSE arm, so the same read-aware
// judgment gates that side's capture.
name: "a write-only ternary miss-arm dual declines the capture",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, xs: readonly number[]): number {
let p: P | null = q;
return p === null ? 0 : xs.map((x) => { p = null; return x; }).length;
}
`,
},
{
// Non-regression: an arm that READS the target still binds the capture
// (and the unwrapped read consumes it).
name: "a reading ternary arm still binds the capture",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
return q !== null ? q.v : 0;
}
`,
},
{
// A guarded while-chain whose body only writes the target takes the
// same judgment: no read, no capture.
name: "a write-only guarded while body declines the capture",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
let n = 0;
while (p !== null && n < 3) {
p = null;
n += 1;
}
return n;
}
`,
},
];
// Branch arms tsc's CFA excludes (tscExcludedArm: bare keyword-literal
// conditions only) contribute NO kills to the branch join — the same
// judgment the finally-entry scan applies. The arm still emits (Zig
// compiles `if (false)` fine); only the flow bookkeeping skips it. A kill
// from such an arm would strip a narrow tsc typed the fall-through reads
// with, emitting member access on a raw optional — invalid Zig.
const staticBranchJoinCases: Case[] = [
{
// The repro: tsc keeps `p` narrowed past `if (false) p = null`, so the
// fall-through read must keep its unwrap.
name: "an if-false arm's kill never reaches the branch join",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number): number {
let p: P | null = make(a);
if (p === null) return -1;
if (false) p = null;
return p.v;
}
`,
},
{
// The else of a constant-true if is equally excluded.
name: "an if-true else arm's kill never reaches the branch join",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number): number {
let p: P | null = make(a);
if (p === null) return -1;
let n = 0;
if (true) {
n += 1;
} else {
p = null;
}
return n + p.v;
}
`,
},
{
// A while-false body never runs under tsc's CFA.
name: "a while-false body's kill never reaches the post-loop state",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number): number {
let p: P | null = make(a);
if (p === null) return -1;
while (false) {
p = null;
}
return p.v;
}
`,
},
{
// The distinction: a do-while(false) body runs ONCE (the test follows
// the body), so its kill DOES count — tsc widens, the source re-guards,
// and the emitted read re-checks. (The kill stays conditional: a
// definite `p = null` would leave tsc typing the fall-through read
// against `never`.)
name: "a do-while-false body's kill still counts (the body runs once)",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number): number {
let p: P | null = make(a);
if (p === null) return -1;
do {
if (a > 2) p = null;
} while (false);
if (p === null) return -2;
return p.v;
}
`,
},
{
// Non-regression: a genuinely conditional kill still joins (tsc's
// path-insensitive may-assign), so the fall-through read re-guards.
name: "a flag-conditional kill still reaches the branch join",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number, flag: boolean): number {
let p: P | null = make(a);
if (p === null) return -1;
if (flag) p = null;
if (p === null) return -2;
return p.v;
}
`,
},
];
// The scrutinee flow-trust scan is position-aware: an arrow or function
// expression DEFINED after the switch does not exist as a value before it,
// so its capture-site assignment cannot have run — the flow type stays
// trustworthy for the exhaustiveness claim. Hoisted function declarations
// (and shared enclosing loops, whose back edge re-enters the switch) still
// decline; those directions pin in emitter.test.ts and the runfidelity
// corpus.
const scrutineePositionCases: Case[] = [
{
// The repro: a filter callback after the switch assigns `k`. tsc keeps
// the switch exhaustive over the flow type, so the guard branch never
// completes and the fall-through `p.v` is typed against the narrow —
// declining trust here left `p` optional and the read raw.
name: "a callback after the switch keeps the scrutinee's flow trust",
src: `
export type Mode = "a" | "b" | "c";
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number, xs: readonly number[]): number {
let k: Mode = a > 10 ? "a" : "b";
const p = make(a);
if (p === null) {
switch (k) {
case "a": return -1;
case "b": return -2;
}
}
return p.v + xs.filter((x) => {
k = "c";
return x > 0;
}).length;
}
`,
},
{
// Non-regression: a callback BEFORE the switch still declines trust —
// the declared union is consulted and the defaultless switch stays
// non-exhaustive (it completes; the fall-through return emits).
name: "a callback before the switch still declines the scrutinee's flow trust",
src: `
export type Mode = "a" | "b" | "c";
export function f(xs: readonly number[]): number {
let k: Mode = "a";
const ys = xs.map((x) => {
k = "c";
return x;
});
switch (k) {
case "a": return 1;
}
return 3 + ys.length;
}
`,
},
];
// Labels are function-scoped in JS: a nested helper's `break outer` binds
// the HELPER's own `outer` label, never the enclosing function's loop. All
// break/continue-binding walks stop at function boundaries (the
// breaksToLabel rule), so an enclosing constant-true loop stays terminal
// and its label drops when nothing in THIS function consumes it.
const labelFunctionScopeCases: Case[] = [
{
// The repro: the helper reuses `outer`; the enclosing infinite loop
// must stay terminal, so the guarded branch's kill drops and the
// fall-through read keeps its unwrap. Binding the helper's break here
// read the loop as fallible and merged the kill — a raw optional read.
name: "a nested helper's labeled break never binds the enclosing loop",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number, flag: boolean): number {
let p: P | null = make(a);
if (p === null) return -1;
if (flag) {
outer: while (true) {
const helper = (): number => {
outer: while (true) {
break outer;
}
return 1;
};
if (a > 100) {
p = null;
continue outer;
}
return helper();
}
}
return p.v;
}
`,
},
{
// With no reference left in THIS function, the enclosing label must
// drop entirely (Zig rejects an unused loop label); the helper keeps
// its own.
name: "a label only a nested helper reuses drops from the enclosing loop",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number, flag: boolean): number {
let p: P | null = make(a);
if (p === null) return -1;
if (flag) {
outer: while (true) {
const helper = (): number => {
outer: while (true) {
break outer;
}
return 1;
};
p = null;
return helper();
}
}
return p.v;
}
`,
},
{
// Non-regression: a REAL labeled break in the same function still
// makes the loop fallible, so the branch kill merges and the
// fall-through re-guards.
name: "a real labeled break in the same function still merges its kill",
src: `
export interface P { readonly v: number; }
function make(a: number): P | null {
if (a < 0) return null;
return { v: a };
}
export function f(a: number, flag: boolean): number {
let p: P | null = make(a);
if (p === null) return -1;
if (flag) {
outer: while (true) {
p = null;
break outer;
}
}
if (p === null) return -2;
return p.v;
}
`,
},
];
// Multi-alternative constructs (if/else arms, else-if chains, switch
// clauses) JOIN their assignment kills: every arm is an alternative from
// the construct's ENTRY state — tsc types each one as if no sibling ran —
// so one arm's kill must not strip a sibling of a narrowing tsc keeps
// there. The kills apply to the surviving flow only after the last arm.
const branchKillJoinCases: Case[] = [
{
// The then arm kills; the else arm is a SIBLING typed from the entry
// state (p non-null after the guard), so its read must still unwrap.
// A sequential merge deletes the narrow before the else arm emits and
// the read hits the bare optional — invalid Zig.
name: "a kill in the then arm leaves the else arm's entry narrow intact",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, flag: boolean): number {
let p: P | null = a;
if (p === null) return -1;
if (flag) {
p = null;
} else {
return p.v;
}
return 0;
}
`,
},
{
// Same join across an else-if chain: the middle arm kills, the last
// arm reads — every arm of the chain is a sibling of every other.
name: "a kill in a middle else-if arm leaves the last arm's narrow intact",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, sel: number): number {
let p: P | null = a;
if (p === null) return -1;
if (sel === 1) {
return 1;
} else if (sel === 2) {
p = null;
} else if (sel === 3) {
return p.v;
}
if (p === null) return 0;
return p.v;
}
`,
},
{
// Switch clauses are pure siblings (tsc's noFallthroughCasesInSwitch
// keeps a non-empty clause from falling into the next): a kill in an
// earlier clause must not reach a later clause's reads, and the joined
// kill still reaches the post-switch re-check.
name: "a kill in one switch clause leaves a later clause's narrow intact",
src: `
export type Msg =
| { readonly kind: "kill" }
| { readonly kind: "use" }
| { readonly kind: "skip" };
export interface P { readonly v: number; }
export function f(a: P | null, msg: Msg): number {
let p: P | null = a;
if (p === null) return -1;
switch (msg.kind) {
case "kill":
p = null;
break;
case "use":
return p.v;
case "skip":
break;
}
if (p === null) return 0;
return p.v;
}
`,
},
{
// Kills travel the edges control does: an always-throwing arm's kill
// rides the throw into the catch, so the catch body types from a state
// where the narrow is dead (tsc kills anything the try assigns before
// a possible throw) and its re-check reads the live slot. The
// intra-try flow after the arm keeps the entry narrow.
name: "a kill on an always-throwing path is dead inside the catch arm",
src: `
export interface P { readonly v: number; }
export interface BoomError { readonly kind: "boom"; readonly at: number; }
export function f(a: P | null, flag: boolean): number {
let p: P | null = a;
if (p === null) return -1;
let n = 0;
try {
if (flag) { p = null; throw { kind: "boom", at: 0 } as BoomError; }
n += p.v;
} catch (err) {
if (p === null) {
n += 1;
} else {
n += p.v;
}
}
if (p === null) return n;
return n + p.v;
}
`,
},
];
// tsc's POST-construct narrow is the guard-implied narrow MINUS the arms'
// kills: an exiting empty-test arm narrows the code after the statement
// only on the paths that kept the value non-null. A surviving arm that
// re-assigned null buries the narrow — the post-construct install must
// subtract the joined kill set or it resurrects a `.?` spelling against a
// slot that can be null again (a comparison-with-null compile error at the
// re-check, a wrong unwrap past it).
const postNarrowKillSubtractionCases: Case[] = [
{
// The head arm exits (guard-implied narrow); the else-if arm kills.
// The post-if state is unnarrowed, so the re-check reads the plain
// optional and the read past it unwraps through the re-check.
name: "an else-if arm's kill subtracts from the exiting guard's post-if narrow",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, flag: boolean): number {
let p: P | null = a;
if (p === null) return -1;
else if (flag) {
p = null;
}
if (p === null) return 0;
return p.v;
}
`,
},
{
// Same subtraction on the generic if/else tail: the else arm never
// reads the target, so the statement routes through the generic
// lowering and its post-if narrow application.
name: "a kill in a non-reading else arm subtracts from the post-if narrow",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, flag: boolean): number {
let p: P | null = a;
let n = 0;
if (p === null) {
return -1;
} else {
if (flag) { p = null; }
n += 1;
}
if (p === null) return n;
return n + p.v;
}
`,
},
{
// The reading else arm takes the capture form; its conditional kill
// still subtracts from the miss-exit's post-if narrow.
name: "a kill in a reading else arm subtracts from the capture form's post-if narrow",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, flag: boolean): number {
let p: P | null = a;
let n = 0;
if (p === null) {
return -1;
} else {
n += p.v;
if (flag) { p = null; }
}
if (p === null) return n;
return n + p.v;
}
`,
},
{
// The present-test mirror: the then arm reads (capture form), the
// else arm exits, and the then arm's kill subtracts the same way.
name: "a kill in the present-test's then arm subtracts from the post-if narrow",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, flag: boolean): number {
let p: P | null = a;
let n = 0;
if (p !== null) {
n += p.v;
if (flag) { p = null; }
} else {
return -1;
}
if (p === null) return n;
return n + p.v;
}
`,
},
{
// No kills anywhere: the post-if narrow still installs, and the read
// after the statement unwraps without a re-check (non-regression for
// the subtraction — an empty kill set must subtract nothing).
name: "the post-if narrow still installs when no arm kills the target",
src: `
export interface P { readonly v: number; }
export function f(a: P | null): number {
let n = 0;
const p = a;
n += 1;
if (p === null) {
return -1;
} else {
n += 2;
}
return n + p.v;
}
`,
},
];
// Narrowing keys are DECLARATION-qualified, so two same-named declarations
// can never collide in the narrowing maps. Emission flattens plain lexical
// blocks (a fall-through block is not a merge boundary), which leaves no
// restore point at the block's end — with raw text keys, a block-local
// shadow's entries stayed live for the OUTER name after the block, binding
// its null checks and reads to the inner declaration's capture. Callback
// parameters share the enclosing maps the same way.
const shadowedDeclarationCases: Case[] = [
{
// A shadow in a flattened block: the inner guard's capture must not
// rewrite the outer q's re-check after the block (with text keys it
// emitted a null test against the inner non-optional capture).
name: "a block-local shadow's narrow dies with the block despite flattening",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, b: P | null): number {
let q: P | null = a;
let n = 0;
{
const q = b;
n += 1;
if (q === null) return -2;
n += q.v;
}
if (q === null) return -1;
return q.v + n;
}
`,
},
{
// A shadow in an if-arm while the OUTER name's capture narrow is
// active: the inner declaration's guard must bind the inner value,
// not the outer capture.
name: "an arm-local shadow narrows independently of the outer capture",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, b: P | null, flag: boolean): number {
const q = a;
let n = 0;
n += 1;
if (q === null) return -1;
if (flag) {
const q = b;
if (q === null) return -2;
return q.v + n;
}
return q.v + n;
}
`,
},
{
// A callback parameter named like an actively narrowed outer local:
// the parameter's reads must bind the loop capture, never the outer
// one (with text keys the filter tested the outer value — code that
// compiled and ran wrong; the run-fidelity corpus pins the behavior).
name: "a callback parameter shadowing a narrowed outer local reads its own capture",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, xs: readonly P[]): number {
const q = a;
let n = 0;
n += 1;
if (q === null) return -1;
const found = xs.filter((q) => q.v > 0);
let m = 0;
for (const g of found) m += g.v;
return q.v + m + n;
}
`,
},
{
// Two shadows deep inside nested flattened blocks: every level keys
// its own declaration, and the outer re-check still reads the outer
// slot.
name: "nested block shadows two deep keep all three narrows separate",
src: `
export interface P { readonly v: number; }
export function f(a: P | null, b: P | null, c: P | null): number {
let q: P | null = a;
let n = 0;
{
const q = b;
n += 1;
if (q === null) return -2;
n += q.v;
{
const q = c;
n += 1;
if (q === null) return -3;
n += q.v;
}
}
if (q === null) return -1;
return q.v + n;
}
`,
},
];
const elementAccessKeyCases: Case[] = [
{
// Element-access narrows key like property chains: base declaration
// plus canonical index. With text keys, the inner shadow's guard
// capture stayed live past the flattened block and rewrote the outer
// read (compiled clean and returned the inner element's value; the
// run-fidelity corpus pins the values).
name: "a shadowed declaration's element-access narrow dies with its block",
src: `
export interface BoxOpt { readonly b: number | null; }
export function f(): number {
const xs: BoxOpt[] = [{ b: 10 }];
let total = 0;
{
const xs: BoxOpt[] = [{ b: 3 }];
if (xs[0].b === null) return -2;
total += xs[0].b;
}
if (xs[0].b === null) return -1;
total += xs[0].b;
return total;
}
`,
},
{
// The chain composes in both directions: a property base under the
// element access (`a.rows[0]`) and a property read off the element
// (`...[0].b`) — the shadowed base must key its own declaration
// through both hops.
name: "element-of-property narrows key the base declaration through the chain",
src: `
export interface Row { readonly b: number | null; }
export interface Grid { readonly rows: readonly Row[]; }
export function f(a: Grid, c: Grid): number {
let total = 0;
{
const a = c;
if (a.rows[0].b === null) return -2;
total += a.rows[0].b;
}
if (a.rows[0].b === null) return -1;
total += a.rows[0].b;
return total;
}
`,
},
{
// An identifier index keys the index's OWN declaration: a shadowed
// `i` selects a different element, so the inner narrow must not
// serve the outer read.
name: "an identifier index keys its declaration, so a shadowed index narrows apart",
src: `
export interface BoxOpt { readonly b: number | null; }
export function f(): number {
const xs: BoxOpt[] = [{ b: 10 }, { b: 3 }];
const i = 0;
let total = 0;
{
const i = 1;
if (xs[i].b === null) return -2;
total += xs[i].b;
}
if (xs[i].b === null) return -1;
total += xs[i].b;
return total;
}
`,
},
{
// A computed index is not a stable reference to one element, so it
// has no narrowing key: the guard stays a plain test and the read
// stays a live optional (tsc does not narrow it either).
name: "a computed element index declines narrowing; reads stay live optionals",
src: `
export interface BoxOpt { readonly b: number | null; }
export function f(): number {
const xs: BoxOpt[] = [{ b: 10 }, { b: 3 }];
let total = 0;
if (xs[0 + 1].b !== null) {
const v = xs[0 + 1].b;
if (v !== null) total += v;
}
return total;
}
`,
},
];
const nonNullReassignCases: Case[] = [
{
// tsc keeps the target narrowed through a provably non-null
// reassignment. A capture would bind unused (every read follows the
// assignment), so the branch takes the plain-test form and reads go
// through the live slot.
name: "a non-null reassignment inside the guarded branch keeps the narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
if (p !== null) {
p = { v: 2 };
return p.v;
}
return 0;
}
`,
},
{
// Reads on both sides of the assignment: the early read consumes the
// capture, and the substitution transitions to the live-slot spelling
// at the assignment so the later read sees the reassigned value.
name: "reads before and after a non-null reassignment both stay narrowed",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
let total = 0;
if (p !== null) {
total += p.v;
p = { v: total + 5 };
total += p.v;
}
return total;
}
`,
},
{
// All reads follow the assignment (several statements' worth): no
// capture may bind, and every read goes through the live slot.
name: "a branch whose reads all follow the reassignment binds no capture",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
let total = 0;
if (p !== null) {
p = { v: 7 };
total += p.v;
total += p.v;
}
return total;
}
`,
},
{
// A reassignment nested in an inner if: tsc keeps the target
// non-null on both paths out of the inner statement (this case's
// clean tsc gate pins that), so the read after it must too — the
// live-slot spelling is the one form valid on both paths.
name: "a conditional non-null reassignment keeps the read after it narrowed",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, flag: boolean): number {
let p: P | null = q;
if (p !== null) {
if (flag) {
p = { v: p.v + 10 };
}
return p.v;
}
return -1;
}
`,
},
{
// The dual arm shape: the else branch holds the narrow and reassigns.
name: "a non-null reassignment in the narrowed else arm keeps its reads",
src: `
export interface P { readonly v: number; }
export function f(q: P | null): number {
let p: P | null = q;
if (p === null) {
return 0;
} else {
p = { v: 2 };
return p.v;
}
}
`,
},
{
// A loop body that reads and then reassigns: the back edge hands the
// reassigned value to the next iteration's read, so the branch takes
// the live-slot form for every read.
name: "a loop that reassigns the narrowed target reads the live slot",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, n: number): number {
let p: P | null = q;
let total = 0;
if (p !== null) {
for (let k = 0; k < n; k++) {
total += p.v;
p = { v: total };
}
}
return total;
}
`,
},
{
// Reassignment to a possibly-null value still kills the narrow —
// the read must re-guard, and the re-guard binds its own capture.
name: "a possibly-null reassignment still kills the narrow",
src: `
export interface P { readonly v: number; }
export function f(q: P | null, r: P | null): number {
let p: P | null = q;
if (p !== null) {
p = r;
if (p !== null) {
return p.v;
}
}
return 0;
}
`,
},
];
const corpus: Case[] = [
...branchKillJoinCases,
...postNarrowKillSubtractionCases,
...shadowedDeclarationCases,
...elementAccessKeyCases,
...nonNullReassignCases,
...exitGuardNarrowingCases,
...kindNarrowRestoreCases,
...narrowInvalidationMergeCases,
...killFallthroughCases,
...edgeKillStagingCases,
...finallyFlowOrderCases,
...catchRouteKillCases,
...callbackTrailingNarrowCases,
...doWhileTrailingTestCases,
...terminalityGroupingCases,
...valueSwitchExhaustivenessCases,
...flowTypeSoundnessCases,
...finallyStaticExclusionCases,
...tscExcludedRouteCases,
...plainSwitchTerminalityCases,
...writeOnlyArmCaptureCases,
...staticBranchJoinCases,
...scrutineePositionCases,
...labelFunctionScopeCases,
...textMethodCases,
...releaseModeCases,
...completeLangTier2Cases,
...dataClassCases,
...exceptionCases,
...enumCases,
...numberCases,
...structuralCases,
...cmdCases,
...cmdV2Cases,
...namedOpCases,
...contractCases,
...bindingSurfaceCases,
...stackedUnionCases,
...iterationCases,
...builderSortCases,
...localMutationCases,
...mathAndTableCases,
...arrayStaticCases,
...modelTierCases,
...streamingCases,
...grammarCases,
...ternarySpreadArmCases,
...lexicalBlockFlowCases,
];
test("corpus: gated cases teach at check time, accepted cases check clean", () => {
const mismatches: string[] = [];
for (const c of corpus) {
const result = check(c.src, c.checkOptions);
assert.equal(result.typeErrors.length, 0, `${c.name}: tsc errors\n${result.typeErrors.join("\n")}`);
if (c.gate) {
if (result.ok) {
mismatches.push(`${c.name}: expected ${c.gate}, but the check succeeded`);
continue;
}
const ids = result.diagnostics.map((d) => d.id);
if (!ids.includes(c.gate)) mismatches.push(`${c.name}: expected ${c.gate}, got ${ids.join(", ") || "none"}`);
} else if (!result.ok) {
const details = result.diagnostics.map((d) => `${d.id} ${d.message}`).join("\n");
mismatches.push(`${c.name}: check failed\n${details}`);
}
}
assert.deepEqual(mismatches, [], mismatches.join("\n\n"));
});
test("multi-file corpus: gated cases teach, emit cases transpile clean", () => {
for (const c of multiFileCases) {
const result = checkFiles(c.files);
if (c.gate) {
assert.equal(result.ok, false, `${c.name}: expected ${c.gate}, but transpile succeeded`);
const ids = result.diagnostics.map((d) => d.id);
assert.ok(ids.includes(c.gate), `${c.name}: expected ${c.gate}, got ${ids.join(", ") || "none"}`);
} else {
assert.equal(result.typeErrors.length, 0, `${c.name}: tsc errors\n${result.typeErrors.join("\n")}`);
const details = result.diagnostics.map((d) => `${d.id} ${d.message}`).join("\n");
assert.equal(result.ok, true, `${c.name}: transpile failed\n${details}`);
// The staleness contract: every module of the graph is an input.
assert.ok(result.inputs.length >= Object.keys(c.files).length, `${c.name}: inputs missing (${result.inputs.length})`);
}
}
});
test("NS1028: Cmd.persist compiles but requires the manifest capability", () => {
const result = check(`
import { Cmd } from "@native-sdk/core";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "add": return [{ count: model.count + 1 }, Cmd.persist()];
case "noop": return model;
}
}
`);
// The warning never gates: the op compiles, emits its wire record, and
// the notice rides the separate channel.
assert.equal(result.ok, true, "persist must keep compiling");
assert.equal(result.diagnostics.length, 0);
const w = result.warnings.find((d) => d.id === "NS1028");
assert.ok(w, "reports NS1028 as a warning");
assert.ok(w.message.includes("persist` capability"), "points at the capability declaration");
const declared = check(`
import { Cmd } from "@native-sdk/core";
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "add" } | { readonly kind: "noop" };
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "add": return [{ count: model.count + 1 }, Cmd.persist()];
case "noop": return model;
}
}
`, { capabilities: ["persist"] });
assert.equal(declared.warnings.some((d) => d.id === "NS1028"), false);
});
test("NS1028: an unused persist capability is an inverse warning", () => {
const result = check(`
export interface Model { readonly count: number; }
export type Msg = { readonly kind: "noop" };
export function initialModel(): Model { return { count: 0 }; }
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) { case "noop": return model; }
}
`, { capabilities: ["persist"] });
const warning = result.warnings.find((d) => d.id === "NS1028");
assert.ok(warning);
assert.ok(warning.message.includes("no `Cmd.persist()` call"));
});
test("NS1016 speaks in rule, fix, and why", () => {
const result = check(`
export function read(bytes: Uint8Array): number {
let i = 0;
i = 1.5;
return bytes[i];
}
`);
assert.equal(result.ok, false);
const d = result.diagnostics.find((x) => x.id === "NS1016");
assert.ok(d, "reports NS1016");
assert.ok(d.message.includes("must be an integer"), "names the conflict");
assert.ok(d.message.toLowerCase().includes("machine type"), "says why");
});
// The EventKind validators hold their documented exact-union `state` rule in
// STOCK tsc, not just in the transpiler's own shape check: `M extends Msgish
// & ImageEventArm` alone lets a NARROWER state union through (every narrower
// literal extends the full union), so the validators carry a tuple-wrapped
// reverse check — [ImageState] extends [M["state"]] — and a missing-member
// arm resolves to `never`, refusing the un-cast route at type-check time.
test("a narrower image state union fails ImageEventKind in tsc itself", () => {
const result = check(`
import { Cmd, asciiBytes } from "@native-sdk/core";
export type NarrowState = "loaded" | "rejected" | "decode_failed";
export interface Model { readonly w: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go"; readonly which: number }
| { readonly kind: "image_done"; readonly id: number; readonly state: NarrowState; readonly width: number; readonly height: number; readonly status: number };
export function initialModel(): Model { return { w: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { path: asciiBytes("a.png") }, { event: "image_done" })];
case "image_done": return { ...model, w: msg.width };
}
}
`);
assert.ok(result.typeErrors.length > 0, "tsc must refuse the narrower state union (ImageEventKind resolves it to never)");
assert.ok(result.typeErrors.some((e) => e.includes("never")), `the refusal is the never-resolution\n${result.typeErrors.join("\n")}`);
});
test("the exact fifteen-member image state union still satisfies ImageEventKind in tsc", () => {
const result = check(`
import { Cmd, asciiBytes } from "@native-sdk/core";
${imageMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.imageLoad(7, { path: asciiBytes("a.png") }, { event: "image_done" })];
${imageTail}
`);
assert.equal(result.typeErrors.length, 0, `tsc errors\n${result.typeErrors.join("\n")}`);
assert.equal(result.ok, true, "the exact union must keep transpiling");
});
test("a narrower audio state union fails AudioEventKind in tsc itself", () => {
const result = check(`
import { Cmd, asciiBytes } from "@native-sdk/core";
export type NarrowState = "loaded" | "position" | "completed" | "failed" | "rejected";
export interface Model { readonly pos: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go" }
| { readonly kind: "audio_evt"; readonly state: NarrowState; readonly positionMs: number; readonly durationMs: number; readonly playing: boolean; readonly buffering: boolean; readonly bands: Uint8Array }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { pos: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioPlay("player", { path: asciiBytes("a.mp3") }, { event: "audio_evt" })];
case "audio_evt": return { ...model, pos: msg.positionMs };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`);
assert.ok(result.typeErrors.length > 0, "tsc must refuse the narrower state union (AudioEventKind resolves it to never)");
assert.ok(result.typeErrors.some((e) => e.includes("never")), `the refusal is the never-resolution\n${result.typeErrors.join("\n")}`);
});
test("the exact six-member audio state union still satisfies AudioEventKind in tsc", () => {
const result = check(`
import { Cmd, asciiBytes } from "@native-sdk/core";
${streamMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.audioPlay("player", { path: asciiBytes("a.mp3") }, { event: "audio_evt" })];
${streamTail}
`);
assert.equal(result.typeErrors.length, 0, `tsc errors\n${result.typeErrors.join("\n")}`);
assert.equal(result.ok, true, "the exact union must keep transpiling");
});
test("a narrower video state union fails VideoEventKind in tsc itself", () => {
const result = check(`
import { Cmd, asciiBytes } from "@native-sdk/core";
export type NarrowState = "loaded" | "position" | "completed" | "failed";
export interface Model { readonly pos: number; readonly errs: number; }
export type Msg =
| { readonly kind: "go" }
| { readonly kind: "video_evt"; readonly state: NarrowState; readonly positionMs: number; readonly durationMs: number; readonly playing: boolean; readonly buffering: boolean; readonly width: number; readonly height: number }
| { readonly kind: "failed"; readonly why: Uint8Array };
export function initialModel(): Model { return { pos: 0, errs: 0 }; }
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { surface: 5, path: asciiBytes("clip.mp4") }, { event: "video_evt" })];
case "video_evt": return { ...model, pos: msg.positionMs };
case "failed": return { ...model, errs: model.errs + 1 };
}
}
`);
assert.ok(result.typeErrors.length > 0, "tsc must refuse the narrower state union (VideoEventKind resolves it to never)");
assert.ok(result.typeErrors.some((e) => e.includes("never")), `the refusal is the never-resolution\n${result.typeErrors.join("\n")}`);
});
test("the exact five-member video state union still satisfies VideoEventKind in tsc", () => {
const result = check(`
import { Cmd, asciiBytes } from "@native-sdk/core";
${videoMsg}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "go": return [model, Cmd.videoLoad("clip", { surface: 5, path: asciiBytes("clip.mp4") }, { event: "video_evt" })];
${videoTail}
`);
assert.equal(result.typeErrors.length, 0, `tsc errors\n${result.typeErrors.join("\n")}`);
assert.equal(result.ok, true, "the exact union must keep transpiling");
});