Skip to main content

prebindgen_registry/
write.rs

1//! Rust file emission for the resolved `Registry`.
2//!
3//! `write_rust` collects every resolved input/output converter (each entry
4//! already carries its full `ItemFn`), every per-item `on_<kind>` output,
5//! and every anonymous const; concatenates them; and hands them to
6//! `Destination::write` (which does prettyplease formatting and
7//! resolves the path against `OUT_DIR`).
8//!
9//! This module is `pub`, so **every `pub` item in it is public API of the
10//! crate**. That is meant to be exactly two — [`write_rust`] and
11//! [`WriteError`] — which is what an out-of-crate adapter calls to emit its
12//! generated file. Anything else added here stays private unless publishing it
13//! is a deliberate decision.
14
15use std::{
16    collections::BTreeMap,
17    path::{Path, PathBuf},
18};
19
20use proc_macro2::TokenStream;
21
22use crate::{
23    destination::Destination,
24    prebindgen::Prebindgen,
25    registry::{Registry, TypeEntry, TypeKey},
26};
27
28/// Errors surfaced by the file-emission phase.
29///
30/// Binding validation is NOT here — it runs once in
31/// [`RegistryBuilder::build`](crate::RegistryBuilder::build)
32/// (see [`Prebindgen::validate_resolved`]), so an invalid binding fails
33/// before a built generator exists and never reaches a writer.
34#[derive(Debug)]
35pub enum WriteError {
36    /// A `TokenStream` produced by an `on_*` trait method failed to parse
37    /// as `syn::Item`s. Indicates a codegen bug in the adapter.
38    BadTokens {
39        phase: &'static str,
40        source: syn::Error,
41    },
42}
43
44impl std::fmt::Display for WriteError {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            WriteError::BadTokens { phase, source } => {
48                write!(
49                    f,
50                    "generated tokens from {} did not parse: {}",
51                    phase, source
52                )
53            }
54        }
55    }
56}
57
58impl std::error::Error for WriteError {}
59
60/// Emit the resolved registry to a Rust file.
61///
62/// `out_path` may be relative (resolved against `OUT_DIR` by prebindgen) or
63/// absolute. Returns the path actually written.
64pub fn write_rust<P: AsRef<Path>, E: Prebindgen>(
65    registry: &Registry<E::Metadata>,
66    ext: &E,
67    out_path: P,
68) -> Result<PathBuf, WriteError> {
69    // Validation already ran ONCE in the generator's `build` — a built generator
70    // (the only source of a resolved registry) is valid by construction, so
71    // this writer is a pure emission.
72    // The capability, minted here and nowhere else in this function's reach.
73    // Every callback below is handed a borrow; nothing else in the pipeline is.
74    // See `prebindgen_flat::flat::emit` for what that buys and what it
75    // deliberately does not.
76    let emit = prebindgen_flat::Emit::new();
77    let mut items: Vec<syn::Item> = Vec::new();
78
79    // 0. Adapter prerequisites — runtime-support items (helper structs,
80    //    type aliases) the converter bodies depend on. Emitted first so
81    //    everything below can reference them.
82    items.extend(ext.prerequisites(registry, &emit));
83
84    // 1. Auto-generated converter wrappers (sorted by ident, deduped).
85    for (_, item_fn) in collect_converter_items(registry) {
86        items.push(syn::Item::Fn(item_fn));
87    }
88
89    // 2. Per-item Rust output from the adapter — only for items the adapter
90    //    explicitly declared. Undeclared items were already announced
91    //    via `cargo:warning=` by the generator's own unclaimed-item report.
92    let declared = registry.declared();
93    let declared_fns = &declared.functions;
94    let declared_types = &declared.types;
95    let flat = registry.flat();
96    items.extend(parse_items_from_tokens(
97        "on_function",
98        sorted_by_name(flat.functions().map(|f| (&f.name, f)))
99            .into_iter()
100            .filter(|(ident, _)| declared_fns.contains(*ident))
101            .map(|(_, item)| ext.on_function(item, registry, &emit)),
102    )?);
103    items.extend(parse_items_from_tokens(
104        "on_struct",
105        sorted_by_name(flat.types().filter_map(|t| match t {
106            prebindgen_flat::flat::Type::Struct(s) => Some((&s.name, s)),
107            _ => None,
108        }))
109        .into_iter()
110        .filter(|(ident, _)| declared_types.contains_key(&TypeKey::from_ident(ident)))
111        .map(|(_, item)| ext.on_struct(item, registry, &emit)),
112    )?);
113    // Both enum shapes emit through `on_enum` and sort together: they were one
114    // map here before they were two elements. They still SORT together — the
115    // emission order is one sequence — but they dispatch to their own methods
116    // now, because handing an adapter a `Type` it has to re-match is worse than
117    // handing it the element the model already decided on.
118    items.extend(parse_items_from_tokens(
119        "on_enum",
120        sorted_by_name(flat.types().filter_map(|t| match t {
121            prebindgen_flat::flat::Type::Variant(v) => Some((&v.name, t)),
122            prebindgen_flat::flat::Type::Enum(e) => Some((&e.name, t)),
123            _ => None,
124        }))
125        .into_iter()
126        .filter(|(ident, _)| declared_types.contains_key(&TypeKey::from_ident(ident)))
127        .map(|(_, t)| match t {
128            prebindgen_flat::flat::Type::Variant(v) => ext.on_variant(v, registry, &emit),
129            prebindgen_flat::flat::Type::Enum(e) => ext.on_enum(e, registry, &emit),
130            _ => unreachable!("filtered to the two enum shapes above"),
131        }),
132    )?);
133    // Consts: an adapter WITH a const declaration mechanism
134    // (`declared_consts() == Some(set)`) emits declared consts only,
135    // symmetric with functions; an adapter without one (`None`) gets every
136    // const passed through verbatim via the default `on_const`. Prebindgen's
137    // own injected feature guards are not consts at all — see the guards loop.
138    let declared_consts = &declared.consts;
139    items.extend(parse_items_from_tokens(
140        "on_const",
141        sorted_by_name(flat.constants().map(|c| (&c.name, c)))
142            .into_iter()
143            .filter(|(ident, _)| {
144                declared_consts
145                    .as_ref()
146                    .is_none_or(|set| set.contains(*ident))
147            })
148            .map(|(_, item)| ext.on_const(item, registry, &emit)),
149    )?);
150
151    // 3. Anonymous consts, verbatim. Last, and in stream order. Ungated on
152    //    purpose: with no name there is nothing for an adapter to declare, so
153    //    the const gate above cannot apply to them.
154    for guard in flat.guards() {
155        items.push(syn::Item::Const(emit.guard(guard)));
156    }
157
158    // 4. Cross-cutting post-process pass. Adapters use this to qualify
159    //    bare type references etc. — see Prebindgen::post_process_item.
160    for item in &mut items {
161        ext.post_process_item(item, registry, &emit);
162    }
163
164    let dest: Destination = items.into_iter().collect();
165    Ok(dest.write(out_path))
166}
167
168/// Walk both type tables, dedupe each entry's stored `function` AND each
169/// of its [`crate::prebindgen::Stage`] functions by name, sort
170/// for determinism. Names are read directly off `function.sig.ident` —
171/// the adapter owns the naming.
172///
173/// Private: an internal step of [`write_rust`], not part of the
174/// adapter-facing surface this module exposes.
175fn collect_converter_items<M>(registry: &Registry<M>) -> Vec<(syn::Ident, syn::ItemFn)> {
176    let mut by_name: BTreeMap<String, (syn::Ident, syn::ItemFn)> = BTreeMap::new();
177    let mut collect = |entry: &TypeEntry<M>| {
178        let name = entry.function.sig.ident.clone();
179        by_name
180            .entry(name.to_string())
181            .or_insert_with(|| (name, entry.function.clone()));
182        for stage in &entry.pre_stages {
183            let sname = stage.function.sig.ident.clone();
184            by_name
185                .entry(sname.to_string())
186                .or_insert_with(|| (sname, stage.function.clone()));
187        }
188    };
189    walk_resolved(&registry.input_types, |_, entry| collect(entry));
190    walk_resolved(&registry.output_types, |_, entry| collect(entry));
191    by_name.into_values().collect()
192}
193
194fn walk_resolved<M, F: FnMut(&TypeKey, &TypeEntry<M>)>(
195    table: &std::collections::HashMap<TypeKey, crate::registry::TypeCell<M>>,
196    mut f: F,
197) {
198    let mut keys: Vec<&TypeKey> = table.keys().collect();
199    keys.sort_by(|a, b| a.as_str().cmp(b.as_str()));
200    for key in keys {
201        if let Some(entry) = table.get(key).and_then(|c| c.entry.as_ref()) {
202            f(key, entry);
203        }
204    }
205}
206
207/// Name-sorted, because emission order is part of the generated file and the
208/// model is in source order. Was `sorted_items_by_ident` over the registry's
209/// maps; same ordering, read from the one index.
210fn sorted_by_name<'a, T>(
211    items: impl Iterator<Item = (&'a syn::Ident, &'a T)>,
212) -> Vec<(&'a syn::Ident, &'a T)>
213where
214    T: 'a,
215{
216    let mut items: Vec<(&syn::Ident, &T)> = items.collect();
217    items.sort_by_key(|(left, _)| left.to_string());
218    items
219}
220
221/// Parse a per-item `TokenStream` (which may be empty) as a sequence of
222/// `syn::Item`s. Empty token streams yield zero items.
223fn parse_items_from_tokens<I: IntoIterator<Item = TokenStream>>(
224    phase: &'static str,
225    iter: I,
226) -> Result<Vec<syn::Item>, WriteError> {
227    let mut out = Vec::new();
228    for ts in iter {
229        if ts.is_empty() {
230            continue;
231        }
232        let file: syn::File =
233            syn::parse2(ts.clone()).map_err(|source| WriteError::BadTokens { phase, source })?;
234        out.extend(file.items);
235    }
236    Ok(out)
237}
238
239#[cfg(test)]
240mod tests;