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’sOrigin<syn::Type>holds a type the build script wrote, never captured, which #280 leaves the model no reading for.Field::memberandField::bind— they read the field’snameandindex, 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.