Skip to main content

prebindgen_flat/flat/
emit.rs

1//! [`Emit`] — the capability to render captured Rust syntax.
2//!
3//! # Why this exists
4//!
5//! The model pairs every element with the syntax it was built from, and
6//! generated Rust has to **spell** that syntax: a converter's signature says
7//! what the source said. But *reading* the same syntax to decide what a type
8//! means is the thing [#211](https://github.com/milyin/prebindgen/issues/211)
9//! removed — a decision belongs to `kind`, which cannot disagree with itself
10//! the way a spelling can.
11//!
12//! Those two are the same capability if the model simply hands syntax out, so
13//! the difference has to be enforced somewhere. Enforcing it by *measurement* —
14//! counting how many places name a door, and failing the build when the count
15//! moves — was tried and retired: a count can be walked around without moving
16//! (`spell()` → `parse_quote!` recovers a node while naming no door), and it
17//! cannot see an out-of-crate adapter at all.
18//!
19//! So the difference is a **capability**. Syntax is reachable only
20//! through this type, this type cannot be constructed outside this crate, and
21//! `write_rust` (in the separate `prebindgen-registry` crate, which is where
22//! the callbacks below now live) hands one out only to the callbacks whose job
23//! is producing Rust. Adapter code that classifies, plans, names or validates
24//! never receives one, and a call to a door from there does not compile.
25//!
26//! # Where one comes from
27//!
28//! `Prebindgen::on_function` and its four peers, `prerequisites`,
29//! `post_process_item`, and the closure `RegistryBuilder::convert_with`
30//! calls — all in the separate `prebindgen-registry` crate — a converter is
31//! generated Rust, since `ConverterImpl::function` is a complete
32//! `syn::ItemFn` the adapter writes. Nothing else.
33//!
34//! If a helper needs an `&Emit`, that is the helper saying it emits; if
35//! threading one to it feels wrong, it is probably deciding something and wants
36//! the model instead.
37//!
38//! # What is closed
39//!
40//! **Every route from the model to captured syntax, except the ones the
41//! registry pipeline itself needs.** Every accessor that hands out a `syn`
42//! node — a type's own node and its stripped form, an element's item, an
43//! origin's node, the syntax rebuilt from a kind, and the shape-spelling
44//! helpers — is crate-internal; nothing outside this crate calls them.
45//! `TypeRef::spell`, `Origin::spell`
46//! and `Flat::enum_item` are `pub`: the registry pipeline that legitimately
47//! calls them (`write_rust`'s emission, and its own tests) is now the separate
48//! `prebindgen-registry` crate, and a module-path seal cannot reach across a
49//! crate boundary. The `compile_fail` examples on [`Emit`] check what remains
50//! closed from outside the crate.
51//!
52//! Two things stay public because they are not that:
53//!
54//! * [`Origin::declared_spelling`](super::Origin::declared_spelling) — an
55//!   adapter declaration's `Origin<syn::Type>` holds a type the **build script**
56//!   wrote, never captured, which #280 leaves the model no reading for.
57//! * [`Field::member`](super::Field::member) and
58//!   [`Field::bind`](super::Field::bind) — they read the field's `name`
59//!   and `index`, model facts, no syntax.
60//!
61//! `Display for TypeRef` renders the **identity**, not the spelling: a message
62//! is decision code explaining itself and must not need this capability, and
63//! delegating to `spell()` would have handed the captured tokens back out
64//! through `format!`.
65//!
66//! # The residual
67//!
68//! Two things visibility does not do, both accepted.
69//!
70//! [`Emit::spell`] yields a `TokenStream`, so emission code can re-parse it and
71//! take the node apart. That is deliberate — emission is where syntax belongs —
72//! and closing it would mean an emission IR for Rust, mirroring the
73//! `kotlin-codegen` crate, which is a much larger piece of work.
74//!
75//! And nothing stops a *new* door being added: someone can write `pub fn
76//! as_syn2` in `flat` tomorrow. The reason that is tolerable is that such a
77//! method has to be added inside `flat` **and** surfaced here before an adapter
78//! can reach it — a two-file diff in the one module a reviewer of this
79//! subsystem already reads.
80
81use proc_macro2::TokenStream;
82
83use super::{Element, EnumValue, Field, Struct, Type, TypeRef};
84
85/// Re-emit a captured `#[prebindgen]` const as a **path-alias** to its
86/// source-of-truth: same attributes (doc comments), visibility, name and
87/// type, with the initializer replaced by `<source_module>::<ident>`. Used
88/// by `Prebindgen::on_const` implementations so consts whose initializers
89/// reference source-crate internals (private helpers, upstream constants)
90/// still compile in the generated file.
91///
92/// Lives here rather than beside the `Prebindgen` trait because it is pure
93/// syntax rendering with no pipeline dependency, and [`Emit::const_alias`] is
94/// its only caller.
95fn const_path_alias(c: &syn::ItemConst, source_module: &syn::Path) -> TokenStream {
96    let attrs = &c.attrs;
97    let vis = &c.vis;
98    let ident = &c.ident;
99    let ty = &c.ty;
100    quote::quote! {
101        #(#attrs)*
102        #vis const #ident: #ty = #source_module::#ident;
103    }
104}
105
106/// The capability to render captured Rust syntax.
107///
108/// Unforgeable outside this crate: the field is private and there is no public
109/// constructor, so the only way to hold one is to have been handed one. See the
110/// [module docs](self) for where that happens and why.
111///
112/// Every method here is a *rendering* — it answers "what did the source write",
113/// never "what does this mean". The second question is the model's, and its
114/// answers ([`TypeRef::kind`], [`TypeRef::key`], the layer readings) need no
115/// capability precisely because they cannot be misused into re-deriving a
116/// classification.
117///
118/// # The seal, as compiled assertions
119///
120/// A doctest builds as its **own crate** against the published API, so these
121/// check the property that matters: what an out-of-crate adapter can reach.
122/// Each names a route that used to be open.
123///
124/// An element's item (`E0624` — the method is private):
125///
126/// ```compile_fail
127/// # use prebindgen_flat::{Element, flat};
128/// fn leak(e: &Element) -> syn::Item { e.as_syn() }
129/// ```
130///
131/// A declared type's item:
132///
133/// ```compile_fail
134/// # use prebindgen_flat::flat;
135/// fn leak(t: &flat::Type) -> syn::Item { t.as_syn() }
136/// ```
137///
138/// A captured function's own node, through its `Origin`:
139///
140/// ```compile_fail
141/// # use prebindgen_flat::flat;
142/// fn leak(f: &flat::Function) -> &syn::ItemFn { f.origin.as_syn() }
143/// ```
144///
145/// …and its tokens, which re-parse to the same item — the door under another
146/// name, and the one a reviewer found still open when this type was introduced.
147/// **No longer closed**: `Origin::spell` is `pub` now that the registry
148/// pipeline's own tests, this method's other legitimate caller, are the
149/// separate `prebindgen-registry` crate rather than code inside this one:
150///
151/// ```
152/// # use prebindgen_flat::flat;
153/// fn leak(f: &flat::Function) -> proc_macro2::TokenStream { f.origin.spell() }
154/// ```
155///
156/// A type's **node** — the door C5 claimed to have closed and did not:
157///
158/// ```compile_fail
159/// # use prebindgen_flat::flat;
160/// fn leak(t: &flat::TypeRef) -> &syn::Type { t.as_syn() }
161/// ```
162///
163/// A declared enum's item, by name. **No longer closed**, for the same reason
164/// as `Origin::spell` above — `Flat::enum_item` is a registry-pipeline test
165/// helper:
166///
167/// ```
168/// # use prebindgen_flat::Flat;
169/// fn leak(f: &Flat) -> Option<&syn::ItemEnum> { f.enum_item("E") }
170/// ```
171///
172/// The delimiters a shape was written with — `S { a }` vs `S(a)` vs `S`:
173///
174/// ```compile_fail
175/// # use prebindgen_flat::flat;
176/// fn leak(s: &flat::Struct) -> proc_macro2::TokenStream {
177///     s.spell(Default::default(), &[])
178/// }
179/// ```
180///
181/// ```compile_fail
182/// # use prebindgen_flat::flat;
183/// fn leak(v: &flat::EnumValue) -> proc_macro2::TokenStream {
184///     v.spell(Default::default(), &[])
185/// }
186/// ```
187///
188/// A type's spelling. **No longer closed**: `TypeRef::spell` is `pub` now
189/// that `write_rust`'s own emission code, this method's other legitimate
190/// caller, lives in the separate `prebindgen-registry` crate:
191///
192/// ```
193/// # use prebindgen_flat::flat;
194/// fn leak(t: &flat::TypeRef) -> proc_macro2::TokenStream { t.spell() }
195/// ```
196///
197/// …its stripped form, and the kind's reconstruction:
198///
199/// ```compile_fail
200/// # use prebindgen_flat::flat;
201/// fn leak(t: &flat::TypeRef) -> syn::Type { t.stripped_syntax() }
202/// ```
203///
204/// ```compile_fail
205/// # use prebindgen_flat::flat;
206/// fn leak(k: &flat::TypeKind) -> syn::Type { k.to_syn() }
207/// ```
208///
209/// Minting one by naming the struct literal is not available either — the
210/// field is private:
211///
212/// ```compile_fail
213/// # use prebindgen_flat::Emit;
214/// let forged = Emit { _seal: () };
215/// ```
216#[derive(Debug)]
217pub struct Emit {
218    _seal: (),
219}
220
221impl Emit {
222    /// Mint one. Previously `pub(crate)`, the whole enforcement
223    /// mechanism when the registry pipeline that is this method's sole
224    /// legitimate caller lived in this crate; now `pub`, since that pipeline
225    /// is the separate `prebindgen-registry` crate and a module-path seal
226    /// cannot reach across the boundary. `write_rust` there mints the one
227    /// `Emit` per generation and hands out only borrows of it — see the module
228    /// doc.
229    ///
230    /// No `Default` impl on purpose: `Emit::default()` would be one more
231    /// trivially-derivable way to mint one, undermining the "only where a
232    /// capability is deliberately needed" convention `new` itself relies on
233    /// now that visibility alone cannot enforce it.
234    #[allow(clippy::new_without_default)]
235    pub fn new() -> Self {
236        Self { _seal: () }
237    }
238
239    /// A capability for a test that drives an emission helper directly.
240    ///
241    /// Gated on `cfg(test)` or the non-default `testing` feature, so it does
242    /// not exist in an ordinary built crate — production code still cannot
243    /// mint one, and the `compile_fail` examples above still prove the
244    /// out-of-crate seal, since a doctest compiles against the built crate
245    /// where this is absent.
246    ///
247    /// The feature exists because the adapters that drive emission are now
248    /// separate crates, so their test suites need the same capability this
249    /// crate's own tests do — and a test suite is exactly the caller the seal
250    /// was never aimed at.
251    #[cfg(any(test, feature = "testing"))]
252    pub fn for_test() -> Self {
253        Self { _seal: () }
254    }
255
256    /// The type as the **source spelled it** — what generated Rust must say.
257    ///
258    /// Not a canonical form rebuilt from the classification to check the
259    /// lowering against: this is the crate's own
260    /// tokens, so a generated signature names the type the way the source crate
261    /// does and compiles in its scope.
262    pub fn spell(&self, ty: &TypeRef) -> TokenStream {
263        ty.spell()
264    }
265
266    /// [`Self::spell`] as a node, for an emitter that builds a `syn::Type`
267    /// around it (`*mut #ty`, `&[#elem]`).
268    ///
269    /// A convenience over `parse_quote!(#spelled)`, which is what the call
270    /// sites wrote before.
271    pub fn spell_ty(&self, ty: &TypeRef) -> syn::Type {
272        let toks = ty.spell();
273        syn::parse_quote!(#toks)
274    }
275
276    /// The type under every transparent wrapper, spelled — `Box<Payload>` →
277    /// `Payload`.
278    ///
279    /// The spelling peer of [`TypeRef::stripped_key`](super::TypeRef::stripped_key),
280    /// for an emitter that must name what a declaration is *about* rather than
281    /// what the use site wrote.
282    pub fn spell_stripped(&self, ty: &TypeRef) -> syn::Type {
283        ty.stripped_syntax()
284    }
285
286    /// A captured item, verbatim — attributes, visibility and body included.
287    ///
288    /// The legitimate reason to reach for an item at all: an emitter re-stating
289    /// one as written. Reading a *fact* off an item is a missing accessor, and
290    /// the model is where it belongs.
291    pub fn item(&self, e: &Element) -> syn::Item {
292        e.as_syn()
293    }
294
295    /// A declared type's item, verbatim. The [`Type`] peer of [`Self::item`].
296    pub fn type_item(&self, t: &Type) -> syn::Item {
297        t.as_syn()
298    }
299
300    /// A captured function's tokens, as written.
301    ///
302    /// One of four per-shape peers of [`Self::item`], for the callback that
303    /// already holds the specific element rather than an [`Element`]. An
304    /// adapter that re-emits its input unchanged is the whole use — both
305    /// in-tree adapters build wrappers instead, so this is what a
306    /// pass-through generator would call.
307    pub fn verbatim_fn(&self, f: &super::Function) -> TokenStream {
308        f.origin.spell()
309    }
310
311    /// A captured struct's tokens, as written. See [`Self::verbatim_fn`].
312    pub fn verbatim_struct(&self, s: &Struct) -> TokenStream {
313        s.origin.spell()
314    }
315
316    /// A captured sum's tokens, as written. See [`Self::verbatim_fn`].
317    pub fn verbatim_variant(&self, v: &super::Variant) -> TokenStream {
318        v.origin.spell()
319    }
320
321    /// A captured fieldless enum's tokens, as written. See [`Self::verbatim_fn`].
322    pub fn verbatim_enum(&self, e: &super::Enum) -> TokenStream {
323        e.origin.spell()
324    }
325
326    /// A constant re-emitted as an alias into `source_module`, so the
327    /// initializer is never copied and a const referencing source-crate
328    /// internals stays valid in the generated file.
329    ///
330    /// Takes the element rather than its item because the alias needs four
331    /// facts off it and nothing else; handing over the whole `syn::ItemConst`
332    /// to read four fields is what an accessor is for.
333    pub fn const_alias(&self, c: &super::Constant, source_module: &syn::Path) -> TokenStream {
334        const_path_alias(c.origin.as_syn(), source_module)
335    }
336
337    /// A constant re-emitted verbatim, for an adapter with no source module.
338    pub fn const_verbatim(&self, c: &super::Constant) -> TokenStream {
339        c.origin.spell()
340    }
341
342    /// A [`Guard`](super::Guard)'s anonymous `const _`, as written.
343    pub fn guard(&self, g: &super::Guard) -> syn::ItemConst {
344        g.origin.as_syn().clone()
345    }
346
347    /// An enum value's discriminant **as written** — `= 0x07` stays `0x07`.
348    ///
349    /// `None` when the source wrote none. Distinct from
350    /// [`EnumValue::discriminant`], which is the *evaluated* number and this
351    /// shape's identity: a C mirror re-states the spelling, a destination
352    /// language that transmits a value wants the number.
353    pub fn discriminant(&self, v: &EnumValue) -> Option<TokenStream> {
354        v.origin
355            .as_syn()
356            .discriminant
357            .as_ref()
358            .map(|(_, expr)| quote::quote!(#expr))
359    }
360
361    /// A struct, alternative or enum value spelled with **the delimiters the
362    /// source wrote** — `S { a: x }`, `S(x)`, `S` — for a pattern or a
363    /// constructor alike.
364    ///
365    /// `B` and `B()` are both payload-free and still spelled differently, which
366    /// is why this is a rendering rather than something `kind` could answer.
367    ///
368    /// Note what is *not* here: [`Field::member`](super::Field::member)
369    /// and [`Field::bind`](super::Field::bind) stay ungated, because they
370    /// read the field's `name` and `index` — model facts, no captured syntax.
371    // `Shaped` is deliberately more private than this method: that is the
372    // sealed-trait pattern, and it is what stops the trait itself becoming a
373    // door. An out-of-crate consumer can call `shape` on the three elements
374    // and cannot implement it for anything else, or name it to route around.
375    #[allow(private_bounds)]
376    pub fn shape<S: super::spell::Shaped>(
377        &self,
378        s: &S,
379        head: TokenStream,
380        parts: &[TokenStream],
381    ) -> TokenStream {
382        super::spell::fields(s.shape(), head, parts)
383    }
384
385    /// How a field is addressed in a pattern or an initializer — by name when
386    /// it has one, else by position.
387    pub fn member(&self, f: &Field) -> syn::Member {
388        f.member()
389    }
390
391    /// A field bound to `bind`, shaped for whichever address it uses:
392    /// `id: __f0` for a named field, `__f0` for a positional one.
393    pub fn bind(&self, f: &Field, bind: &impl quote::ToTokens) -> TokenStream {
394        f.bind(bind)
395    }
396}