Skip to content

P6.x candidate: cutover slicing — emitter.rs/emitter/lower.rs from bynk_syntax::ast to bynk-emit::ir #1187

Description

@accuser

Summary

What this issue is, and is not

Per #1137's own convention (the IrItem::Agent/Service/Capability/Provider scoping issues —
#1169, #1171, #1173, #1174 — each a placeholder settled before its own P6.x implementation issue),
this is scoping, not an increment proposal itself. No code changes here. Each numbered slice below
becomes its own Closes #<n> proposal against this doc once its predecessors have landed.

Grounded findings

emit_project (emitter.rs:261) already splits per CommonsItem kind, one loop each
Type/Event (→ emit_type, emit.rs:19), free Fn (→ emit_free_fn, emitter.rs:504), then a
combined loop at emitter.rs:357-376 dispatching Capability/Provider/Service/Agent to
emit_capability/emit_provider/emit_service/emit_agent (emit.rs:1083/1118/1247/2500). This
maps close to one-to-one onto IrItem's own variants — the natural axis to slice on. The complication:
~8-10 smaller exhaustive-match helpers scattered through emitter.rs (collect_external_references
and friends, emitter.rs:1718-2148; import collection, 882-999/1571-1793; agent-registry reset,
383-397) each need their per-kind arm touched by whichever slice converts that kind — small, but not
zero, per-slice overhead.

emitter/lower.rs's dispatch (lower_expr:926, emit_statement:523) is separable by expression
kind
— most arms already call a dedicated lower_* helper independently swappable to read IrExpr
instead of Expr. Every lower_* function keeps its existing Lowered { pre, expr } return shape
(T2.1/R6.2, untouched) — only the argument type and internal dispatch change, per the-ir.md §3.7's own
"cutover changes what it reads, not what it returns" framing. LowerCtx is threaded through nearly
every call, so a slice converting one expression kind still needs an adapter at sibling call sites that
haven't converted yet — workable, not a hard blocker.

The R6.5 defect names a concrete stake for the Agent slice. Production block_writes_state
(emitter.rs:727) recognises a store write only via a bare-identifier name match
(ExprKind::Ident(id) at the receiver, emitter.rs:730-731) — a locally-shadowed name or non-identifier
receiver shape is silently miscategorised (missed write → implicit commit wrapper skipped; live data
loss). The IR's body_writes_state (ir/lower.rs:1040) already classifies correctly via the checker's
resolved Callee::Store, with regression coverage pinning exactly the shadowing case
(commit_shape_read_only_for_a_locally_shadowed_store_field_name, ir/lower.rs:5413) that
block_writes_state cannot avoid. Converting Agent/Service handler-commit-shape emission is what
lets block_writes_state be deleted in favour of the CommitShape the IR already computes
(IrItem::Agent/IrHandler, P6.8/P6.9) — this closes a real, currently-shipping defect, not just a
structural cleanup.

The 9 ast_importers files are not uniform-sized work:

File AST coupling Slice treatment
emitter/wrangler.rs, emitter/runtime_use.rs Trivial (2 and 4 type refs) One standalone sweep — near-zero risk, shrinks the probe fast
emitter/serialisation.rs Substantive but single-kind (Type/TypeShape-driven JSON codec) Rides the Type slice, not its own
emitter/workers.rs, emitter/workers_entry.rs Substantive, multi-kind (Service+Agent-shaped Workers dispatch/entrypoint) Trail Agent+Service, don't lead them
project.rs Cross-cutting (EmitProjectCtx assembly, touches every kind) Shrinks incrementally as each kind converts; final small cleanup slice once all four land
project/tests_emit.rs Substantive, real production code (emits .bynk test/suite/integration declarations — not test scaffolding despite the name) Trails everything; reuses emitter.rs/emitter/lower.rs machinery directly

Known probe limitation, already documented — not a new gap. ast_importers's own doc comment
(xtask/src/greenfield_status.rs, above AST_IMPORTER_EXCEPTIONS, added at #1184's review) already
states the probe reaching 0 proves no remaining file spells bynk_syntax::ast directly — it does not
prove every IrItem field is AST-free (ir.rs itself still holds Arc<TypeDecl>, HandlerKind,
Refinement etc. in IrItem-adjacent fields), and a file reached only via use super::* (e.g.
emit.rs, which never spells bynk_syntax::ast literally but is the most AST-coupled file in the
crate through emitter.rs's re-exported use) is invisible to the probe by construction. Flagging
here only so slice reviewers don't read "9 → 0" as a stronger claim than the probe's own doc comment
already disclaims.

Decisions

[DECISION A] Slice order (Recommended: as below). Order by ascending emitter-side special-casing,
so the cutover pattern is proven on the plainest kind first and the riskiest kind (Service) is last,
by which point the pattern is well-exercised:

  1. Type/Fn — plainest, longest-baked IR (P6.1/P6.6). emitter/serialisation.rs rides along.
  2. emitter/wrangler.rs + emitter/runtime_use.rs — standalone trivial sweep, any point after (1).
  3. Capability/Provider — smallest remaining IrItem variants, no handler/commit-shape surface.
  4. Agent — claims the R6.5 fix as its stake; emitter/workers.rs/emitter/workers_entry.rs
    partially ride along or immediately follow (both depend on agent dispatch shapes).
  5. Service — most special-cased (protocol variants, CORS/security/limits PolicyIr,
    emit_make_surface/cross-context surface assembly, WebSocket DO-method generation at
    emit.rs:3631-3990). emitter/workers_entry.rs's route/fetch generation finishes trailing this.
  6. project.rs cleanup — once (1)–(5) land, whatever AST coupling remains here is residue, not new
    work; small closing slice.
  7. project/tests_emit.rs — last; depends on all prior machinery being IR-native to convert cleanly.

[DECISION B] Actor (Recommended: out of scope, no slice). Settled as a deliberate non-build
decision (#1172) — no IrItem::Actor variant exists or is planned. Any ActorDecl reference
remaining in emitter.rs/project.rs/emitter/workers*.rs after slices (1)–(7) is expected residue
under that settled decision, not a gap this track needs to close.

Risks & mitigations

  • emitter.rs/emitter/lower.rs are hot files (39/54 commits in 30 days at the-ir.md's own last
    measurement) → per-slice freeze only (§3.1, already settled), never track-wide.
  • Partial conversion means both AST-walking and IR-reading code paths coexist mid-track → each slice's
    own PR should assert byte-identical emitted output for its converted kind (differential test against
    main), the same discipline P6.0–P6.14's additive slices already used for "no emission-behaviour
    change."
  • Probe can read a false "closer to 0" without real progress if a slice spells around
    bynk_syntax::ast via use super::* the way emit.rs already does → each slice's PR should state
    its ast_importers delta explicitly, not just rely on the number.

Done when

  • Slices (1)–(7) above are each cut as their own increment-proposal sub-issue of Phase 6 — The IR (spine) #1137, in this order
    (re-scoping any slice as it's cut is expected and fine — this is the candidate decomposition, not a
    locked plan, per design/tracks/README.md step 3).
  • design/tracks/the-ir.md §6 is updated to reference this decomposition once accepted.

Part of #1137.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions