Emit trait and policy module split
Platform spec ADR
Emit trait and policy module split
Spec standingStandard
0 revisions (git unavailable at build; counts may be empty)
No commits recorded for this path.
| Section id | Required | Found |
|---|---|---|
what-this-feature-specifies | yes | yes |
implementation-anchors | yes | yes |
Full tree: run pnpm verify:platform-spec-layout (writes src/generated/platform-spec-layout-report.json).
Context
Section titled “Context”The reference formatter under compiler/crates/beskid_analysis/src/format/ currently splits responsibilities across several files:
emit.rs—Emittrait,EmitCtx(context),Emitter(driver),EmitError, and theformat_programentry point.policy.rs— three small free functions that encode blank-line / between-member / between-block-items policy.items/,expressions_emit.rs,statements_emit.rs,types_emit.rs— per-constructEmitimplementations.
Two anti-patterns would make this hard to evolve safely:
- Scattered layout policy — per-construct emitters writing raw
\nor four-space runs make a policy change (for example, “double blank between top-level items”) a multi-file refactor. - God-trait
Emit— putting blank-line policy insideEmit::emitparameters would force the AST-side modules to know about higher-level layout intent.
This ADR records the chosen split.
Decision
Section titled “Decision”The formatter must preserve the following module decomposition:
Emittrait informat/emit.rsdefines a single methodemit(&self, w, cx). Per-construct modules must implementEmitfor the AST node they own.EmitCtxinformat/emit.rsis the only sanctioned place to mutate indent depth, write newlines, write indents, or open/close braces. AST-side modules must call its helpers (write_indent,nl,ln,space,token,open_brace,close_brace) rather than writing raw layout characters.policy.rsis the only place that owns inter-declaration, inter-member, and inter-statement blank-line decisions. AST-side modules callcx.between_top_level_declarations(...),cx.between_members(...), andcx.between_block_items(...); they do not duplicate the policy.mod.rsstays as exports-only. New construct emitters are added as siblings underitems/(or as new files when the construct does not fit an existing role).
A change that violates rule (2) or (3) must be revised before merging; the parent Verification and traceability article enumerates the file anchors.
Consequences
Section titled “Consequences”Pros:
- A new layout rule lands as one edit in
policy.rsand an optional method onEmitCtx; downstream emitters don’t need to know. - The formatter is easier to audit: anyone reviewing layout output knows to read
policy.rsfirst. - The per-construct emitters stay readable — each function reads as “what does this node look like in source”, not “how do I implement blank-line policy”.
Cons / accepted trade-offs:
- AST-side modules cannot work around policy by writing raw characters; this is intentional and is enforced by code review.
EmitCtxaccrues helpers over time. We accept this as the lesser evil compared to scattered policy.
Verification anchors
Section titled “Verification anchors”compiler/crates/beskid_analysis/src/format/emit.rs— single owner ofEmit,EmitCtx,Emitter,EmitError, andformat_program.compiler/crates/beskid_analysis/src/format/policy.rs— single owner of the three policy helpers.compiler/crates/beskid_analysis/src/format/mod.rs—moddeclarations andpub usere-exports only; no business logic.