Why it is shaped this way
The report holds each lens
Status: accepted, 2026-10-09. Extends The report is what a dependent target's compilation would read. Serves The compiler decides. Reopen if a consumer needs the lens's checks (satisfied, fieldErrors) or its connection surface spelled out too, or if the lenses make the report too large to review as a diff.
Status: accepted, 2026-10-09. Extends
The report is what a dependent target's compilation would read.
Serves The compiler decides. Reopen
if a consumer needs the lens's checks (satisfied, fieldErrors) or its
connection surface spelled out too, or if the lenses make the report too
large to review as a diff.
Context#
The report listed what a target compiled but not what its code reads as. A
lens's accessors are the target's surface: their names, which response key
each reads, whether each reads absent, a @catch result, a throwing read
or a list. That surface is decided once, in the decide pass, and each
emitter only prints it. Two readers needed it as data: a reviewer, for whom
a renamed accessor or a field that starts throwing is a contract change the
report did not show, and the harness for Relay's reader tests
(spec/relay/), which generates one read per response path and must spell
each step as the accessor reads, with ?., try or a result's get(),
without parsing generated code.
Decision#
generate --report adds to every operation its data's lens and to every
fragment its lens. A lens is its GraphQL type and its accessors in order;
an accessor is its name as the target's language spells it unescaped, the
response key it reads (none for a spread or a type condition, which read
their parent's object), what it reads (scalar, linked, spread,
aliased, condition), and its shape: optional when it may read absent,
caught with whether the result's value may be absent, throws, list
with whether an element may be absent, the fragment a spread reads, the
types a condition reads for, and the nested lens. The shape is derived from
the decided read forms by the rules both emitters print, so the report and
the code cannot disagree on what the program decided. A report written from
the plan alone, before the decide pass, has no lenses.
Evidence#
BatonTests.report.json, the golden over the Swift test target, holds the lenses of every document the tests compile.- The report's tests: an accessor's key under an alias, the shapes of
@required(action: THROW),@catch,@catch(to: NULL), a caught link, a list of links and an@include, a type condition's types, a spread's fragment. scripts/relay-harvest/translate.pygenerates the Relay reader cases' reads from the report; the Swift test target compiles every one.
Not chosen#
- Parsing the generated Swift for its accessors: the generated code is printed for a compiler, not read by one, and Kotlin would need a second parser.
- A separate
--lensesoutput: the lenses are the target's contract as much as the operations are, and the report is where the contract is read.
Baton