agent: builder model: opus max_turns: 120 budget_usd: 30 —

Brief: #859 steps 1 and 4, field.ty (with the parser form expr.name::member) and match on a kind

Score: owner’s direction (2026-10-31): #859 first; #1019 (ADR-grammar-2), json::write[T], the schema check and yaml/toml wait on it. Step 3 is built (78897e3, b859a): T::kind, T::name, T::kind == meta::Type::X for the kinds that carry no type. Step 2 (a type as an argument, schema(User)) is the next run’s: do not use fn f[T](T) in tests.

Problem (probed at 79b5ce2)

struct User { name: String, age: i64 }
fn show[T: Struct](v: T) {
    for field in T::fields {
        println(f"{field.name}: {field.ty::name}")   // parser: "Expected `}` here, but found `:`"
    }
}
fn main() { show(User { name: "a", age: 1 }) }

let k = field.ty is NK1180 (“A field from T::fields has no ty”). And match T::kind { … } with arms that carry types does not exist.

Decision (nothing new; accepted records, no owner decision needed)

Locations

To build

  1. Step 1. Parser form and field.ty. Test tests/compiler/tests/reflected_field_ty.nika + .stdout: the probe above, plus field.ty::kind == meta::Type::Text printed per field. Commit parser, check: a reflected field's .ty is its type; expr.name::member (ADR-330 D5, #859 step 1), Part of #859.
  2. Step 4. Tests: match_on_a_kind.nika + .stdout (a generic fn describe[T](v: T) -> String whose match T::kind names Text, I64, List(it), Maybe(a), Struct(s) with for field in s::fields, called with a String, an i64, a Vec[i64], an i64? and a struct holding a list); match_on_a_kind_arm_checked.nika + .stderr (an arm whose body is wrong for its kind, e.g. Text => it::name with it unbound, refused even when no call takes that arm). Commit check, emit: a match on a kind checks each arm under its kind and emits the arm the type takes (ADR-330 D4, #859 step 4), Part of #859.

If step 4 is too large for this run, commit step 1 and as much of step 4 as is verified (tests green), and say what is left under Needs:.

Done when

The new tests pass through nikaia test --project tests/compiler; each failed once against the compiler without your change (say so under Tests:); lower-std fixpoint clean; cargo build --profile fast -p nikaia; the gate of .claude/agents/builder.md; pushed. Changelog: one line per step.

Stop and report if

An edit is needed outside crates/nikaia/src/parser/mod.rs, crates/nikaia-std/src/tools/check_*.nika, emit_*.nika, crates/nikaia/src/build_time.rs, their lowered files and the new tests; a form beyond ADR-330 is needed; an error in another agent’s file.

Not yours, never stage: every file git status shows modified or untracked at your start, and every docs/briefs/* file except your report.

Rules (CLAUDE.md; this block is pasted into every brief, also a fix-forward)

Report (at most 15 lines, to exactly /home/user/Nikaia/docs/briefs/b859b-field-ty-kind-match.md.report.md)

Fields of docs/architect.md, Briefs and reports: Result: done|noop|partial|blocked (the bare word alone), Files, Commit, Body (optional), Fixes, Pushed, Tests, Gate (green|red), Deviation, Found, Needs, Changelog, Owner, Disk freed, Rs2nika (none). none where nothing, as the bare word. Final answer, one line: done <sha>, report <path> (or BLOCKED: ... / PUSH REJECTED ...).

Attribution: Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01UVxJDvLe1uS47UcqA9y1U2

This site is open source. Improve this page.