ADR-186: A Parse Keeps The Text Its Record Views

Status: Accepted Date: September 22, 2026 Closes: #140 (a grammar’s entry does not say what it keeps) Related: ADR-296 D24 (an entry rule is a ledger entry), ADR-140 D3 (the entry is reached by name), ADR-094 D2 (keeps), ADR-283 D2, D5 (the states a view is in), ADR-296 D35 (an action may not pause), ADR-010 D1 (absence read fail-closed)

1. Context

ADR-140 D3 makes a grammar entry a call by name, so its contract is a ledger entry like any other and its four columns have to be answered. sync is ADR-296 D35, asserted rather than inferred; touches and locks are derived over the action blocks, with the grammar as the unit (ADR-292 D3’s over-approximation). That leaves keeps.

Leaving it absent is not neutral. keeps_its reads an absent keeps on a present entry as keeps nothing — the same convention std.contracts uses for sync. An absent column on a parse would answer lends, in the open direction: read, whose body is one Stock::file(data), would lose keeps = ["data"], though the parse it calls hands back Entry, which holds a ref String into the very text data names.

No new mechanism is needed. The walk exists and the predicate exists; what this adds is the item kind.

2. Decisions

D1 — A parse keeps its input exactly when its declared result may hold a view into it.

A grammar entry takes one parameter, the text (ADR-296 D24). A parse hands back views into that text — a ref String field of a record it built is a view of the bytes it was given — and a value that outlives the call is what keeps means (ADR-094 D2). So Stock::file -> Vec[Entry] keeps its input where Entry holds a ref String, and Calc::expr -> i64 keeps nothing.

An empty column means asked and no, never nobody derived it — the two are written the same way, so an underived column would be an answer rather than a silence.

D2 — The rule’s declared result decides it, and not the action blocks.

touches walks the actions, because what a parse does is in them. What a parse can hand back is not: it is the rule’s declared result type, and an action that built something else would not type-check. The input is not a name an action block can reach, so the only way the text leaves the call is through the result.

That is also why this needs no fixpoint over the grammar the way touches does. One rule, one declared type, one answer.

D3 — A result this walk cannot see into keeps the text.

ADR-094’s polarity, unchanged: a use this walk cannot account for counts as keeping, because the two wrong answers are not symmetric. Saying kept of a text only read costs a caller an owned argument, which is where every caller already is. Saying lent of a text a parse holds views into is a & on a value that outlives the call, and that is rustc’s words about a file nobody wrote (Part III C.1).

So the walk answers no view only where it can account for every name in the result: a type this package declares that holds none, or one Part I 2.2 offers, or a container whose arguments it has already read. Anything else is a declaration it did not read.

D4 — The same fact writes the views column, and the state is Borrowed.

An entry whose result may view gets input: borrowed and <result>: borrowed (ADR-283 D5). The buffer is the caller’s text, which outlives the call — the same sentence tether::of already reaches for a function with a view among its parameters, read off the shape of a grammar instead of off a signature.

One predicate for both columns, so the two cannot drift: a parse that keeps its text is a parse whose result views it, and there is no third answer for one of them to hold.

3. Consequences

4. What this does not decide

This site is open source. Improve this page.