Issue #654: ref String: build the NK1261 warning, close the ref int gap in NK1257, migrate, then refuse

Open and answer this issue on GitHub.

ready · opened 2026-10-18 · updated 2026-11-03 · 7 comments

The owner asked whether ref is still used without a warning. Checked against today’s compiler (727bb97):

What is decided

What the compiler does

  1. NK1261 is not built: fn s(t: ref String) passes nikaia check, run and build without any warning. No code mentions NK1261.
  2. NK1257 misses the int alias: fn s(t: ref int) passes silently, while fn s(t: ref i64) is refused.
  3. Count of ref String, still written:

    Where Count
    tools/*.nika 1961
    examples/ 289
    Part I, Part II, Part III 36, 6, 19
    benches/ 8
    scripts/ 2
    guide/ 1

To build

  1. NK1261 as a warning on a parameter, a field, a result and a let, not inside extern "C" or a pub extern fn. Tests, including that the program’s meaning is unchanged.
  2. NK1257 on ref int, and on any alias that resolves to a word. Test.
  3. Migrate examples/, benches/, scripts/ and the specification’s code blocks: drop ref before String outside the C boundary. Regenerate tests/specification/EXPECTED.txt.
  4. Migrate tools/*.nika. package.rs must lower to the same Rust: &str stays where a view suffices (ADR-282 D2). Any difference is a defect to fix in the compiler first. Work one file at a time, with lower-std and the sysroot test.
  5. CLAUDE.md’s “Syntax gaps” and “Lowered types” lines that teach ref String are rewritten.
  6. When 3-5 leave none, NK1261 becomes a refusal (ADR-282 D2’s own condition), with a test, and Part III’s text for it changes.

Generated by Claude Code

Comments

keywan-ghadami, 2026-10-18

First round (7529675): benches/page and scripts/tokens.nika dropped ref. Both lower to byte-identical Rust.

Step 4 is blocked: ADR-282 D2 (“a String is a view wherever a view suffices”) is not built for most positions. Dropping ref changes behaviour in five places, checked by comparing the lowered Rust, the ledgers and nikaia test output before and after:

  1. An option with a default (; sep: String = " ", examples/tally) becomes an owned String, and every call writes String::from(..). The option path in emit/mod.rs (~3938) never asks lends_in_own.
  2. A DSL rule’s result (rule FIELD -> String = … { s }) is refused with NK1104. This hits access-log, config, json, k-nucleotide, report, inventory and 1brc.
  3. Struct fields:
    • one filled by a grammar action is NK1106 (Hit.path, Section.name, Entry.item, Member.key);
    • one filled with literals (benches/template) loses Row’s lifetimes and allocates once per row.
  4. A map key or Vec element (HashMap[ref String, …], benches/brc/naive): TrustedMap<&str> becomes TrustedMap<String>.
  5. Parameters and results:
    • the ledger drops views = ["x: borrowed"] (escaping, json, k-nucleotide);
    • k-nucleotide report_fragment(dna, fragment: String) becomes owned, and the generated Rust then fails rustc (&str: Borrow<String>);
    • inventory read(data: String) -> Vec[Entry] is NK2302;
    • fn path(ref self) -> String returning a view of self is NK1104 (examples/http, which already fails at HEAD).

Part I §6 (about lines 2476-2660) also states rules in terms of “a parameter written ref String” (NK2302). Its text changes with these fixes.

To build (revised order)

  1. Fix 1-5 in the compiler, each with a test: a String that only views reach is a view at every position. Done means the programs above, with ref dropped, lower to the same Rust as today.
  2. NK1261 warning (types_text_by_ref beside types_word_by_ref in types.nika, skipped for is_extern, plus Stmt::Let). This waits until tools/ is free.
  3. "int" in NK1257’s word list.
  4. The migration of examples, benches, spec blocks and Part I §6, then EXPECTED.txt.
  5. tools/*.nika.
  6. CLAUDE.md: done in 662f373, softened for the interim.
  7. NK1261 becomes a refusal.

Generated by Claude Code

keywan-ghadami, 2026-10-18

State after 0.0.1022 (c202263; commits 36d4ec9, 9634840, 7d51315, 7973244):

Built

Still to build

  1. Step 0 c1 — an option’s default (; sep: String = " "): options are not in the keeps walk. Adding them puts option names into every ledger’s keeps; build so that an option appears in the ledger only when it is kept, and cover the emitter and check_arguments.
  2. A lent String parameter handed on gets a & the ref version does not write (k-nucleotide: count(&dna, k), get(&fragment)). Same meaning, different text; bind a lent String parameter as lent where parameters are bound (check/mod.rs). Test: the k-nucleotide twin lowers byte-identical.
  3. examples/http has no test comparing the twins; add one.
  4. Step 1, NK1261 (warning on ref String outside extern "C"), with the test outputs and tests/specification/EXPECTED.txt it moves.
  5. Steps 3-5, 7 as listed above.

Needs the owner (status stays ready for 1-4; this one is asked separately)


Generated by Claude Code

keywan-ghadami, 2026-10-18

Where #654 stands after 0.0.1026 (455b697, 6f39ef0).

Built: step 0 c1. A String option the body only reads is lent the way a parameter is. Options are now part of the keeps analysis. An owned String passed to a ref String option is lent instead of refused with NK1106.

Open:


Generated by Claude Code

keywan-ghadami, 2026-10-19

Item 2 built in 5e503c1, version 0.0.1047 (6894a71).

Still open


Generated by Claude Code

keywan-ghadami, 2026-10-19

Step 1 (NK1261): built but not committed - it fires on plain String.

Why. parse_to_ast runs text_tiers::refine (parser/mod.rs ~886); where it decides a view, mark() (text_tiers.rs ~245) sets ty.is_view = true on a field, result or let written as plain String. The checker sees the AST after that, so struct User { name: String } and let nobody: String = "nobody" get NK1261 as if ref were written. Parameters are not rewritten, which is why the first tests missed it.

Direction taken (supervisor): refine already records what it decided on Parsed (parsed.text_tiers); types.nika skips the positions refine turned into views, so NK1261 fires only where the source wrote ref. Rejected: a flag on Type (changes PartialEq between a written ref String and a decided view, ~15 literals), and computing NK1261 before refine (a second walk over the same declarations).

Saved work: scratchpad nk1261-step1-v2.patch - types.nika re-applied, package.rs regenerated, the earlier test fixed (its run step failed on its own program: two text inputs and one view result is rustc’s “missing lifetime specifier”; split into join(..) -> String and first(a) -> ref String), and the findings helpers of 25 test files filtering NK1261. Still owed after the fix: tests/specification/EXPECTED.txt (cargo run -p nikaia --example specification), project:2247 and option_defaults:435 (the warning in their output), comptime_compiled:448. Part III’s code table already words NK1261. lower-std refuses only on errors, so warnings in tools/ do not block it.

Found on the way, develop red since 5e503c1: list_repeat::a_value_that_does_not_copy_is_refused ([name; 3] does not count as keeping name) and views_handed_back::following_nk1104s_help_is_a_program; a fix is being built.


Generated by Claude Code

keywan-ghadami, 2026-10-19

Step 1 built in c43c96b, version 0.0.1052 (1a6c53c): NK1261 is a warning on ref String written on a parameter, option, field, result or let outside extern "C" / pub extern fn. The tier pass records the positions it made a view (Parsed::text_views, keyed field T f, result f, result T::m, param f 0, let <byte>), and NK1261 skips them, so plain String never warns. Test helpers in 25 files filter NK1261; the repository-wide checks in typecheck and sequences no longer count warnings; EXPECTED.txt gains 5 NK1261 tags.

Also: develop was red from 5e503c1 - fixed in 7cca0ee (0.0.1050): [x; n] keeps x; and fn e(t: String) -> ref String { return t } is a program now (the parameter the result points into is lent, D5), so NK1104’s help test returns a local instead.

Warns now, for step 4/5: crates/nikaia-std/src/text.nika (parse_i64 …), examples/1brc.nika, examples/access-log and others; the specification’s own examples Part I #4, #9 (let mut maybe_string: ref String? = null), #78, #80 and Part III #25.


Generated by Claude Code

keywan-ghadami, 2026-10-19

Step 4 built: examples (0298543), benches (c6a95f1), the specification’s examples (99354b1, 0.0.1053). Every ref String whose removal lowers byte-identical is gone: fields, rule results, parameters, ref self results (also String?), Vec[ref String], options. escaping and benches/template Row now lower to &'static str (#659, same output).

Kept with ref, each a gap with its own issue:

Still open: Part I §6’s prose on NK2302 (“a parameter written ref String may not be kept past its call”) and Part III’s NK1106 text are about ref String semantics and need rewording when step 6 makes ref String a refusal; steps 4 (tools/*.nika, std .nika) and 6.


Generated by Claude Code

Back to Open Issues