Expand description
bynk-lower: the AST-analysis helpers bynk-emit reads resolved
declaration-level facts through — handler kinds and given clauses,
service protocols, store-field shapes, capability op and attached-method
signatures, route cache/limit annotations, event-subscriber shapes, and
the store-write walk behind emit_agent’s implicit-commit decision. Each
takes a checked program (or its TypedCommons) and a syntax-tree node
and returns a bynk_ir value; none lowers an expression body.
Reachability, corrected twice and now settled (Slice D0 of #1542,
the IR cutover, closing summary in design/archive/retired-tracks.md). Through the crate carve (Arc D,
P7.12) this paragraph claimed nothing here was reached from bynk-emit’s
emission path; that was false — lower_event_subscriber_shapes_ir
(called from bynk-emit/src/project.rs) went through
lower_service_item_ir, which lowers every handler’s own body, so the
whole recursive expression-lowering machinery (lower_service_handler_ir
→ lower_service_handler_body_ir → lower_block_ir → lower_expr_ir/
lower_stmt_ir) ran in production for every from Events(E) service and
had its result discarded. Slice D0 repointed that one caller at the
shape-only helpers it actually needs (see its own doc comment), so the
original claim is now true by construction rather than false by
oversight: every item constructor and the expression/statement/body
lowering beneath it — lower_service_item_ir, lower_agent_item_ir,
lower_provider_item_ir, lower_fn_item_ir, lower_type_item_ir,
lower_capability_item_ir, the handler/store-field/commit-shape/
invariant/transition helpers, lower_fn_body_ir, lower_block_ir,
lower_expr_ir and everything they call — has no caller outside this
crate’s own test module. The 30 August 2026 review’s “zero callers”
finding was right about the end state and wrong about the path; the
track doc’s §10.2 has the trace. Slice D1 of the same track then deleted
that machinery outright — 48 functions, the lowering context’s scope
stack/temp counter/return-type/store-queryable fields, and every
todo!() this crate ever carried — so rustc’s own dead-code analysis,
not this paragraph, is the reachability record from here on. What stays
is the AST-analysis helper vocabulary bynk-emit consumes today —
lower_handler_kind_ir, lower_handler_given_ir,
lower_protocol_ir{,_from_commons}, lower_type_shape_ir,
lower_service_handler_signature_ir, body_writes_state, and their
siblings — each with a production call site.
Totality discipline (ADR 0334, Q2), as it stands after Slice D1: the
rule was “every entry point takes a certified &CheckedProgram, so a
per-expression type lookup that misses is a compiler bug, not a
recoverable state.” The per-expression lookup went with the expression
lowering, so what the rule now governs is narrower: a helper that reads
a declaration’s own type references still takes &CheckedProgram by
default (lower_type_shape_ir’s doc comment has the full argument — a
bare TypedCommons is not certified by construction), and the four that
take a bare &TypedCommons instead — body_writes_state,
lower_protocol_ir_from_commons, capability_op_sig_from_commons,
lower_attached_fn_sig_ir_from_types — each document why their reads
cannot reach a certification-dependent panic. The same discipline
bynk-emit/src/emitter/emit.rs’s lower_workers_cross_context_call
applies to its own bynk.emit.unresolved_cross_context_signature panic.
Functions§
- body_
writes_ state - [DECISION B]/[DECISION C] (#1165): does
bodyreach a mutatingCallee::Storewrite, or an unconditionalStatement::Assign(:=), anywhere — including inside a nestedif/match/lambda? Drives, as of #1196 (the #1187 emitter-cutover track’s own R6.5 stake),emit_agent‘s (bynk-emit/src/emitter/emit.rs) real implicit-commit-wrapper decision (it also drove the IR-sideCommitShape::Transactionaldecision until Slice D1 of#1542deleted that constructor) — its previous own name-matchingblock_writes_state(emitter.rs) is deleted, this function is its sole, direct replacement. The walk’s own shape is that deleted function’s own already-correct skeleton reused structurally, not re-derived:Block/If/Matchare hand-matched so crossing a nested block re-enters the statement-aware case (anexpr_childrendescent alone flattens a block straight to its statements’ values, losing theStatement::Assigntag), everywhere else recurses overexpr_children’s total child iterator. - capability_
op_ sig_ from_ commons - P6.29 (design/tracks/the-ir.md §6a): the
TypedCommons-only counterpart tolower_capability_ops_ir, for call sites (emitter/lower.rs’scap_op_param_names) that have aTypedCommonsin hand but noCheckedProgram—LowerCtx/ModuleCtxnever carry one (seelower_op_sig_ir_from_commons, this function’s own single-op sibling, for the identical reason it exists as a separate entry point rather than a thin wrapper over theCheckedProgram-drivenlower_op_sig_ir). - is_
effectful_ return - P6.50 (design/tracks/the-ir.md §6b): a return type’s own syntactic
Effect[...]wrapper —TypeRef::Effect(_, _), not the resolvedTy::Effect(_)shapelower_handler_signature_irreads above viacx.program.ty_intern. Relocated here fromemitter/emit.rs(its original home,#[allow(dead_code)]-free and with eight call sites acrossemit.rs/workers.rs/workers_entry.rs) becauselower_service_handler_signature_irbelow was already calling up into it (bynk_emit::emitter::is_effectful_return) — theAst → Irboundary running backwards, anIr-side lowering function reaching into theemittermodule it should only ever be called from.emit.rsand friends now callbynk_lower::is_effectful_returninstead (relocated again at the P7.12 crate carve —emitter/ir::lowerare now separate crates,bynk-emit/bynk-lowerrespectively). - lower_
actor_ seam_ ir - #1187’s slice 3: a handler’s resolved actor-verification seam — the same
“narrow, standalone reader of already-resolved data” precedent
body_writes_state/lower_service_handler_signature_irestablished, applied tobynk-check’s own five actor-seam resolvers (bynk-check/src/actors.rs) instead of a full handler assembly.ActorSeamIr’s own doc comment has the full grounding for the priority order and for the deliberately-missingSignaturevariant. - lower_
attached_ fn_ sig_ ir_ from_ types - P6.x (#1137):
lower_fn_sig_ir_from_typesover an entireMethodTable’s own instance + static entries — the attached-method gathering [bynk-emit’sbuild_emit_unit_ctx] needs for auses-imported type. Filters toFnName::Methodbefore lowering:ResolverMethodTableonly ever collects attached methods in practice (bynk-check/src/resolver.rs’s own doc comment onMethodTable), but the filter stays as a defensive match rather than an assumption, matching the caller’s own pre-existing posture one step earlier — this just moves that posture in front of the lowering call instead of behind it, so theFnNameread (and the filter itself) never has to leave this module. - lower_
capability_ ops_ ir - A capability declaration’s resolved op signatures, in declaration order
— what
emitter.rs’s own capability-item loop reads (the caller already holds the capability’s name from the AST declaration). Slice 1 of#1542split this out of a fullIrItem::Capabilityconstructor to end a build-then-discard-defround-trip; Slice D1 deleted that constructor. - lower_
event_ subscriber_ shapes_ ir - Every
from Events(E)service inprogram’s own unit, captured as anbynk_ir::EventSubscriberShapekeyed by service name — see that struct’s own doc comment for why this is captured now rather than re-derived cross-unit at compose time (P6.47,#1254). - lower_
handler_ given_ ir - #1187’s slice 6 plumbing (sibling of
lower_provider_given_ir): a handler’s owngivenclause, resolved standalone — the entry point forproject.rs’splan_agent_given_deps,EmitProjectCtx:: agent_method_givens, andemitter/workers.rs’s owngivencollection. Reuseslower_cap_ref_irverbatim; a handler’sgivenis syntactically identical to a provider’s (bynk_syntax::ast::CapRef), so this is the same one-line adapter, not a new design. - lower_
handler_ kind_ ir - P6.24a: pure, unconditional
HandlerKind→IrHandlerKindconversion — every field is already fully resolved at parse time, so unlike almost every other function in this module this one takes no&CheckedProgram/&TypedCommonsat all and can never miss. - lower_
protocol_ ir - P6.11 ([DECISION A], #1171): lower a service’s own
from <protocol>header into a realProtocolIr— standalone, takes the sub-node rather than the owningServiceDecl(mirrorslower_store_field_shape_ir), so afrom websocket/from Eventsfixture can pin the descriptor by itself. - lower_
protocol_ ir_ from_ commons - P6.24a: a
TypedCommons-only sibling oflower_protocol_ir, the same splitlower_op_sig_ir/lower_op_sig_ir_from_commonsalready established — for a call site holding only a unit’s ownTypedCommons, never a&CheckedProgram(emitter.rs’semit_project_imports, a header-import-collection pass that runs well outside the per-declaration emission loop anyCheckedProgramis threaded through). Sound for the identical reason: nothing here reads a per-expression type, the one lookup whose.expect()-panic needed a genuinely certified program. - lower_
provider_ given_ ir - A provider’s own
givenclause, resolved standalone — the entry pointbynk-emit/src/project.rs’sinstantiate_provider_ts_expractually calls. This never touches the provider’sopsor their bodies; the full-provider assembly that did (and lowered everyBynkop body through the expression lowerer) had no caller and went with Slice D1 of#1542. - lower_
route_ cache_ ir - #1228: a GET handler’s own
@cache(maxAge:, scope:)freshness policy —bynk_ir::CacheIr’s own doc comment has the full grounding for why this is a standalone per-route reader. Field-for- field the same extractionemitter/workers_entry.rs’s own (now superseded)cache_policy_fordid: only aGETyields a policy; project validation (bynk.http.cache_*) has already rejected a@cacheanywhere else, and a malformedmaxAgethere, so a missing or ill-formed annotation here simply yieldsNone— no&CheckedProgramneeded, the same posturelower_policy_ir’s own doc comment already argues for:maxAge/scopeare already-resolved syntactic literals (ExprKind::DurationLit/Ident), not a type this pass would ever need to resolve. - lower_
route_ limit_ ir - #1228: a route’s own
@limit(maxBody:)annotation, if present — the override half ofemitter/workers_entry.rs’s own (now superseded)effective_max_body; the service-widelimits { maxBody }fallback stays that function’s own concern (read from a service’slimits {}block, not a per-routeHandler, so it does not move here). Project validation (bynk.http.limit_*/limits_*) has already rejected a malformed or misplaced@limit, so an absent/ill-formed annotation here simply yieldsNone— the caller’s own service-default fallback still applies. No&CheckedProgramneeded, same reasoning aslower_route_cache_ir:maxBodyis an already-resolvedExprKind::IntLit, not a type. - lower_
service_ handler_ signature_ ir - A service handler’s resolved signature —
params/given/ret/effectful— and never its body. This is whatemit_service(bynk-emit/src/emitter/emit.rs) reads per handler, and whatlower_event_subscriber_shapes_irreads for a subscriber’s parameter count. Mirrorsbody_writes_state’s posture (#1196): a narrow, standalone reader of already-resolved data, applied to signature data instead of a body walk. - lower_
store_ field_ shape_ ir - A store field’s storage shape — its
Cell/Map/Set/Cache/Logkind and@indexedkeys (viastore_field_kind_and_indexed), withinitalwaysNone. This is the entry pointemit_agent’s own state section actually needs; aCellfield’s zero/initial-value expression is rendered by the emitter from the AST, never lowered here. (Aninit-lowering sibling existed until Slice D1 of#1542; it lowered the initialiser through the deleted expression lowerer and had no caller.) - lower_
type_ shape_ ir - A
typedeclaration’s resolved structure as aTypeShape— the readeremitter.rs’s owntype_shape_forcalls directly. (Slice 1 of#1542split this out of a fullIrItem::Typeconstructor whose single field it was, ending a build-then-unreachable!-discard round-trip; Slice D1 then deleted that constructor, leaving this as the type reader.)
Type Aliases§
- Handler
Signature Ir (params, given, ret, effectful)—lower_service_handler_signature_ir’s return shape (#1187’s slice 5), a named alias rather than a bare tuple because its consumer (emit_service,bynk-emit/src/emitter/emit.rs) has to spell it out in a function signature. (The agent-handler signature reader that shared it went with Slice D1 of#1542.)