Skip to main content

Module emit

Module emit 

Source
Expand description

Emit — the capability to render captured Rust syntax.

§Why this exists

The model pairs every element with the syntax it was built from, and generated Rust has to spell that syntax: a converter’s signature says what the source said. But reading the same syntax to decide what a type means is the thing #211 removed — a decision belongs to kind, which cannot disagree with itself the way a spelling can.

Those two are the same capability if the model simply hands syntax out, so the difference has to be enforced somewhere. Enforcing it by measurement — counting how many places name a door, and failing the build when the count moves — was tried and retired: a count can be walked around without moving (spell()parse_quote! recovers a node while naming no door), and it cannot see an out-of-crate adapter at all.

So the difference is a capability. Syntax is reachable only through this type, this type cannot be constructed outside this crate, and write_rust (in the separate prebindgen-registry crate, which is where the callbacks below now live) hands one out only to the callbacks whose job is producing Rust. Adapter code that classifies, plans, names or validates never receives one, and a call to a door from there does not compile.

§Where one comes from

Prebindgen::on_function and its four peers, prerequisites, post_process_item, and the closure RegistryBuilder::convert_with calls — all in the separate prebindgen-registry crate — a converter is generated Rust, since ConverterImpl::function is a complete syn::ItemFn the adapter writes. Nothing else.

If a helper needs an &Emit, that is the helper saying it emits; if threading one to it feels wrong, it is probably deciding something and wants the model instead.

§What is closed

Every route from the model to captured syntax, except the ones the registry pipeline itself needs. Every accessor that hands out a syn node — a type’s own node and its stripped form, an element’s item, an origin’s node, the syntax rebuilt from a kind, and the shape-spelling helpers — is crate-internal; nothing outside this crate calls them. TypeRef::spell, Origin::spell and Flat::enum_item are pub: the registry pipeline that legitimately calls them (write_rust’s emission, and its own tests) is now the separate prebindgen-registry crate, and a module-path seal cannot reach across a crate boundary. The compile_fail examples on Emit check what remains closed from outside the crate.

Two things stay public because they are not that:

  • Origin::declared_spelling — an adapter declaration’s Origin<syn::Type> holds a type the build script wrote, never captured, which #280 leaves the model no reading for.
  • Field::member and Field::bind — they read the field’s name and index, model facts, no syntax.

Display for TypeRef renders the identity, not the spelling: a message is decision code explaining itself and must not need this capability, and delegating to spell() would have handed the captured tokens back out through format!.

§The residual

Two things visibility does not do, both accepted.

Emit::spell yields a TokenStream, so emission code can re-parse it and take the node apart. That is deliberate — emission is where syntax belongs — and closing it would mean an emission IR for Rust, mirroring the kotlin-codegen crate, which is a much larger piece of work.

And nothing stops a new door being added: someone can write pub fn as_syn2 in flat tomorrow. The reason that is tolerable is that such a method has to be added inside flat and surfaced here before an adapter can reach it — a two-file diff in the one module a reviewer of this subsystem already reads.

Structs§

Emit
The capability to render captured Rust syntax.