Skip to main content

weaveffi_model/model/
mod.rs

1//! The **binding model**: a normalized, fully-lowered view of a
2//! [`ResolvedApi`] that every language backend consumes.
3//!
4//! [`BindingModel::build`] walks the IR exactly once and produces a flat list
5//! of [`ModuleBinding`]s in which:
6//!
7//! * every **type** is a resolved [`Ty`] (its kind and owning module known,
8//!   no unresolved names), so a backend dispatches on [`Ty::family`] and
9//!   [`Ty::wire`] instead of re-deriving what a name means;
10//! * every emitted **C symbol name** is precomputed once, so all backends
11//!   agree by construction and a non-default prefix is honored everywhere; and
12//! * every function, interface member, and callback-interface method is
13//!   paired with its lowered [`AbiFn`] signature (built from [`crate::abi`]),
14//!   so no backend re-derives parameter arity, ordering, or `out_*`/`out_err`
15//!   placement.
16//!
17//! A backend reads the *idiomatic* shape from the retained [`Ty`]s
18//! (`param.ty`, `field.ty`, ...) and the *native* shape from the [`AbiFn`]s,
19//! then writes only the marshalling that bridges the two (see
20//! [`crate::plan`]) in its own idioms. The hard, drift-prone facts live here;
21//! only language syntax lives in the backends.
22
23mod ty;
24
25pub use ty::{Family, Prim, Ty, WireType};
26
27use crate::ir::{
28    CallbackInterfaceDef, EnumDef, ErrorDomain, Function, InterfaceDef, Module, StructDef,
29    StructField, TypeRef,
30};
31use heck::ToUpperCamelCase;
32
33use crate::abi::{
34    async_callback_params, async_input_params, callback_method_signature, context_param,
35    error_out_param, lower_param, lower_return, sync_signature, AbiParam, CType, ConstPos,
36};
37use crate::resolved::ResolvedApi;
38
39/// The runtime symbols every producer exports (and every C header declares),
40/// without the `{prefix}_` that begins each one. See the C ABI contract for
41/// their signatures.
42pub const RUNTIME_SYMBOLS: &[&str] = &[
43    "str",
44    "bytes",
45    "reader",
46    "writer",
47    "abi_version",
48    "error",
49    "error_set",
50    "error_clear",
51    "error_free",
52    "free_bytes",
53    "cancel_token",
54    "cancel_token_create",
55    "cancel_token_cancel",
56    "cancel_token_is_cancelled",
57    "cancel_token_destroy",
58    "alloc",
59    "dealloc",
60    "debug_live",
61];
62
63/// Identifier families the generated C value-buffer helper header
64/// (`{library}_buffer.h`) declares, without the `{prefix}_` that begins each:
65/// a user declaration whose C symbol falls in one of these families would
66/// collide with a helper.
67pub const RESERVED_SYMBOL_FAMILIES: &[&str] = &[
68    "str_", "bytes_", "reader_", "writer_", "list_", "map_", "opt_",
69];
70
71/// The C ABI revision this model lowers to. Producers export it from
72/// `{prefix}_abi_version()` and every generated consumer checks it at load.
73pub const ABI_VERSION: u32 = 3;
74
75/// The symbol of a top-level module's contract checksum function,
76/// `{prefix}_{module}_checksum`.
77#[must_use]
78pub fn checksum_symbol(prefix: &str, module: &str) -> String {
79    format!("{prefix}_{module}_checksum")
80}
81
82/// A single lowered C symbol: its name, ordered ABI parameter slots, and C
83/// return type. This is what a backend declares to its FFI layer and calls.
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct AbiFn {
86    /// The fully-qualified, prefixed C symbol (e.g. `weaveffi_math_add`).
87    pub symbol: String,
88    /// Ordered parameter slots, including any trailing `out_*` and `out_err`.
89    pub params: Vec<AbiParam>,
90    /// The C return type.
91    pub ret: CType,
92}
93
94/// How a function crosses the boundary. Exactly one shape applies to any given
95/// function: synchronous, asynchronous (callback-completed), or iterator-returning.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum CallShape {
98    /// A plain blocking call: [`AbiFn`] is the symbol to invoke.
99    Sync(AbiFn),
100    /// An async launcher plus its completion-callback typedef.
101    Async(AsyncBinding),
102    /// An iterator-returning function: an opaque handle plus `next`/`destroy`.
103    Iterator(IteratorBinding),
104}
105
106/// The lowered surface of an `async` function.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct AsyncBinding {
109    /// The launcher: input slots, optional `cancel_token`, then `callback` and
110    /// `context`. Returns `void`.
111    pub launch: AbiFn,
112    /// The completion-callback function-pointer typedef name
113    /// (`{symbol}_callback`).
114    pub callback_type: String,
115    /// The callback's parameter slots: `(void* context, {prefix}_error* err,
116    /// <result fields>)`.
117    pub callback_params: Vec<AbiParam>,
118}
119
120/// The lowered surface of an `iter<T>`-returning function.
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct IteratorBinding {
123    /// The element type `T` of `iter<T>`.
124    pub elem: Ty,
125    /// The opaque iterator tag (`{prefix}_{path}_{Pascal}Iterator`).
126    pub iter_tag: String,
127    /// The launcher returning `{iter_tag}*`.
128    pub launch: AbiFn,
129    /// `int32_t {iter_tag}_next({iter_tag}* iter, T* out_item, ..., error* out_err)`.
130    pub next: AbiFn,
131    /// `void {iter_tag}_destroy({iter_tag}* iter)`.
132    pub destroy_symbol: String,
133}
134
135/// One IR parameter, retained with its lowered ABI slots.
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct ParamBinding {
138    /// The parameter name as written in the IDL.
139    pub name: String,
140    /// The resolved type a backend renders the parameter as.
141    pub ty: Ty,
142    /// Optional doc comment carried from the IDL.
143    pub doc: Option<String>,
144    /// The ordered C ABI slots this single parameter expands into.
145    pub abi: Vec<AbiParam>,
146}
147
148impl ParamBinding {
149    /// Lower one parameter declared in the module whose underscore-joined C
150    /// path is `module`.
151    pub fn new(name: impl Into<String>, ty: Ty, doc: Option<String>, module: &str) -> Self {
152        let name = name.into();
153        let abi = lower_param(&name, &ty, module);
154        Self { name, ty, doc, abi }
155    }
156}
157
158/// A function, fully lowered.
159///
160/// Free functions and interface members share this shape. For an instance
161/// method, [`has_self`](Self::has_self) is `true` and every [`AbiFn`] in
162/// [`shape`](Self::shape) carries an implicit leading `const {c_tag}* self`
163/// slot that does **not** appear in [`params`](Self::params); a wrapper
164/// passes its own native handle there.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct FnBinding {
167    /// The function name as written in the IDL.
168    pub name: String,
169    /// Optional doc comment carried from the IDL.
170    pub doc: Option<String>,
171    /// Deprecation message when the function is marked deprecated, else `None`.
172    pub deprecated: Option<String>,
173    /// Whether an async function accepts a trailing `cancel_token` slot.
174    pub cancellable: bool,
175    /// Whether the function is `async` (lowered as a callback-completed launcher).
176    pub is_async: bool,
177    /// Whether the function reports typed domain errors. A throwing function
178    /// surfaces as `throws`/`raises` in idiomatic wrappers using the module's
179    /// [`ErrorBinding`]; a non-throwing function has a plain signature, and a
180    /// reported error (only ever a producer panic) surfaces as the target's
181    /// unrecoverable-error idiom instead.
182    pub throws: bool,
183    /// `true` for an instance method: the ABI signatures carry an implicit
184    /// leading `self` slot not present in [`params`](Self::params).
185    pub has_self: bool,
186    /// Input parameters with their lowered slots.
187    pub params: Vec<ParamBinding>,
188    /// The resolved return type (`None` = void). For an iterator function this
189    /// is the `iter<T>` type itself; the element `T` also lives in
190    /// [`IteratorBinding`]. For an interface constructor this is the
191    /// constructed interface type.
192    pub ret: Option<Ty>,
193    /// Base C symbol (`{prefix}_{module_path}_{name}` for a free function,
194    /// `{c_tag}_{name}` for an interface member) before any `_async`/iterator
195    /// suffixing.
196    pub c_base: String,
197    /// The call shape (sync / async / iterator).
198    pub shape: CallShape,
199}
200
201/// A field of a record, a rich-enum variant, or an error code's payload.
202///
203/// Records and rich enums are value types: they declare no C symbols of their
204/// own and cross the ABI serialized inside a value buffer, so a field is just
205/// its name and type. Field declaration order **is** the wire order.
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct FieldBinding {
208    /// The field name as written in the IDL.
209    pub name: String,
210    /// Optional doc comment carried from the IDL.
211    pub doc: Option<String>,
212    /// The resolved type of the field.
213    pub ty: Ty,
214}
215
216/// A struct (record), fully lowered: a plain value type generators emit as a
217/// native data class plus buffer read/write functions. No C symbols exist for
218/// a record; instances cross the ABI serialized in value buffers.
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct StructBinding {
221    /// The struct name as written in the IDL.
222    pub name: String,
223    /// Optional doc comment carried from the IDL.
224    pub doc: Option<String>,
225    /// Deprecation message when the struct is marked deprecated, else `None`.
226    pub deprecated: Option<String>,
227    /// The fields in declaration (and wire) order.
228    pub fields: Vec<FieldBinding>,
229}
230
231/// An enum, fully lowered.
232///
233/// A *C-style* enum (every variant a bare discriminant) crosses the ABI by
234/// value as an integer. An *algebraic* (rich) enum, at least one variant with
235/// associated data, is a value type exactly like a struct: it crosses the ABI
236/// serialized in a value buffer as an `i32` tag followed by the active
237/// variant's fields in declaration order. Either way, the C header still
238/// emits the discriminant constants ([`EnumVariantBinding::c_const`]) so C
239/// consumers can switch on the value or tag.
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct EnumBinding {
242    /// The enum name as written in the IDL.
243    pub name: String,
244    /// Optional doc comment carried from the IDL.
245    pub doc: Option<String>,
246    /// Deprecation message when the enum is marked deprecated, else `None`.
247    pub deprecated: Option<String>,
248    /// `{prefix}_{module_path}_{name}`.
249    pub c_tag: String,
250    /// Every variant, in declaration order.
251    pub variants: Vec<EnumVariantBinding>,
252    /// `true` when this is a rich (algebraic) sum-type enum: at least one
253    /// variant carries fields, and values cross the ABI as buffers.
254    pub rich: bool,
255}
256
257impl EnumBinding {
258    /// `true` when this is a rich (algebraic) sum-type enum.
259    pub fn is_rich(&self) -> bool {
260        self.rich
261    }
262}
263
264/// A single enum variant with its precomputed C constant name and any
265/// associated data.
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct EnumVariantBinding {
268    /// The variant name as written in the IDL.
269    pub name: String,
270    /// The variant's integer discriminant. Doubles as the buffer tag for a
271    /// rich enum.
272    pub value: i32,
273    /// Optional doc comment carried from the IDL.
274    pub doc: Option<String>,
275    /// `{enum_c_tag}_{variant}`.
276    pub c_const: String,
277    /// Associated data in declaration (and wire) order; empty for a unit
278    /// variant or a C-style enum.
279    pub fields: Vec<FieldBinding>,
280}
281
282/// An interface (reference-counted object type), fully lowered.
283///
284/// Constructors, methods, and statics are all [`FnBinding`]s sharing the
285/// member symbol scheme `{c_tag}_{name}`. Methods additionally carry an
286/// implicit leading `const {c_tag}* self` ABI slot ([`FnBinding::has_self`]).
287/// A constructor's [`FnBinding::ret`] is synthesized as the interface type
288/// itself, so wrappers can reuse their ordinary return-marshalling path.
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct InterfaceBinding {
291    /// The interface name as written in the IDL.
292    pub name: String,
293    /// Optional doc comment carried from the IDL.
294    pub doc: Option<String>,
295    /// Deprecation message when the interface is marked deprecated, else `None`.
296    pub deprecated: Option<String>,
297    /// `{prefix}_{module_path}_{name}`, the opaque tag.
298    pub c_tag: String,
299    /// Constructors, lowered as statics returning `{c_tag}*`.
300    pub constructors: Vec<FnBinding>,
301    /// Instance methods, each with the implicit `self` slot.
302    pub methods: Vec<FnBinding>,
303    /// Static functions namespaced under the interface.
304    pub statics: Vec<FnBinding>,
305    /// `{c_tag}* {c_tag}_clone(const {c_tag}* self)`: returns a new strong
306    /// reference to the same object.
307    pub clone_symbol: String,
308    /// `void {c_tag}_destroy({c_tag}* self)`: releases one strong reference.
309    pub destroy_symbol: String,
310}
311
312/// One method of a callback interface, lowered to its vtable entry.
313///
314/// The consumer implements this; the producer calls it through the vtable.
315/// [`abi_params`](Self::abi_params) is the full C slot list of the vtable
316/// entry (`void* ctx`, the parameter slots, `{prefix}_error* out_err`) and
317/// [`abi_ret`](Self::abi_ret) its C return type.
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct CallbackMethodBinding {
320    /// The method name as written in the IDL; also the vtable field name.
321    pub name: String,
322    /// Optional doc comment carried from the IDL.
323    pub doc: Option<String>,
324    /// Deprecation message when the method is marked deprecated, else `None`.
325    pub deprecated: Option<String>,
326    /// Input parameters with their lowered slots.
327    pub params: Vec<ParamBinding>,
328    /// The resolved return type (`None` = void); always in the direct family.
329    pub ret: Option<Ty>,
330    /// The vtable entry's C parameter slots: `ctx`, then every parameter's
331    /// slots, then `out_err`.
332    pub abi_params: Vec<AbiParam>,
333    /// The vtable entry's C return type.
334    pub abi_ret: CType,
335}
336
337/// A callback interface, fully lowered.
338///
339/// The C ABI sees a vtable struct ([`vtable_tag`](Self::vtable_tag)) with one
340/// function-pointer field per method, in declaration order, followed by a
341/// trailing `void (*free)(void* ctx)`. A parameter of this type lowers to two
342/// slots, `void* {name}_ctx` and `const {vtable_tag}* {name}_vtable`.
343#[derive(Debug, Clone, PartialEq, Eq)]
344pub struct CallbackInterfaceBinding {
345    /// The callback interface name as written in the IDL.
346    pub name: String,
347    /// Optional doc comment carried from the IDL.
348    pub doc: Option<String>,
349    /// Deprecation message when the callback interface is marked deprecated,
350    /// else `None`.
351    pub deprecated: Option<String>,
352    /// `{prefix}_{module_path}_{name}`, the type's C name stem.
353    pub c_tag: String,
354    /// `{c_tag}_vtable`, the vtable struct tag.
355    pub vtable_tag: String,
356    /// Methods in declaration (and vtable) order. Never empty.
357    pub methods: Vec<CallbackMethodBinding>,
358}
359
360/// One error code of a module's error domain, with its C constant name.
361#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct ErrorCodeBinding {
363    /// The code name exactly as written in the IDL (e.g. `KEY_NOT_FOUND`).
364    pub name: String,
365    /// The numeric ABI code carried in `{prefix}_error.code`.
366    pub value: i32,
367    /// The default human-readable message for the code.
368    pub message: String,
369    /// Optional doc comment carried from the IDL.
370    pub doc: Option<String>,
371    /// `{domain_c_tag}_{name}`, the C enum constant.
372    pub c_const: String,
373    /// Structured payload fields this code carries, in declaration (and wire)
374    /// order. When non-empty, a matching error's `payload_ptr`/`payload_len`
375    /// slots hold these fields serialized in the value-buffer format; empty
376    /// means the payload slots are null.
377    pub fields: Vec<FieldBinding>,
378}
379
380/// The error domain in effect for a module: its own `errors:` block, or the
381/// nearest ancestor's when the module declares none.
382///
383/// Every throwing function in the module reports codes from this domain.
384/// Backends emit one error type per *declaring* module
385/// ([`declared_here`](Self::declared_here) is `true`) and reference the
386/// ancestor's type from inheriting submodules.
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct ErrorBinding {
389    /// The domain name as written in the IDL (e.g. `KvError`).
390    pub name: String,
391    /// PascalCase type name with exactly one `Error` suffix (e.g. `KvError`);
392    /// backends that brand exceptions swap the suffix via
393    /// [`crate::errors::type_name`].
394    pub type_name: String,
395    /// Underscore-joined path of the module that *declares* the domain.
396    pub owner_path: String,
397    /// `true` when this module declares the domain itself; `false` when it
398    /// inherits the domain from an ancestor module.
399    pub declared_here: bool,
400    /// `{prefix}_{owner_path}_{name}`, the C tag naming the domain's code
401    /// constants.
402    pub c_tag: String,
403    /// The domain's codes in declaration order.
404    pub codes: Vec<ErrorCodeBinding>,
405}
406
407/// One module, flattened with its underscore-joined symbol path.
408#[derive(Debug, Clone, PartialEq, Eq)]
409pub struct ModuleBinding {
410    /// The module name (its final path segment).
411    pub name: String,
412    /// The contract checksum ([`crate::checksum::module_checksum`]) of a
413    /// top-level module; `None` for nested modules, which are covered by
414    /// their root's checksum.
415    pub checksum: Option<u64>,
416    /// Path segments from the root (e.g. `["outer", "inner"]`).
417    pub segments: Vec<String>,
418    /// Underscore-joined path used as the C symbol segment (e.g. `outer_inner`).
419    pub path: String,
420    /// Dot-joined path used to qualify cross-module type names
421    /// (e.g. `outer.inner`).
422    pub dot_path: String,
423    /// The module's doc comment when the IDL records one, else the doc of the
424    /// first documented function in the module.
425    pub doc: Option<String>,
426    /// The error domain in effect for this module's throwing functions:
427    /// its own domain, the nearest ancestor's, or `None` when no domain is in
428    /// scope (in which case validation has rejected any `throws` here).
429    pub error: Option<ErrorBinding>,
430    /// Enums declared in this module, fully lowered.
431    pub enums: Vec<EnumBinding>,
432    /// Structs declared in this module, fully lowered.
433    pub structs: Vec<StructBinding>,
434    /// Interfaces declared in this module, fully lowered.
435    pub interfaces: Vec<InterfaceBinding>,
436    /// Callback interfaces declared in this module, fully lowered.
437    pub callback_interfaces: Vec<CallbackInterfaceBinding>,
438    /// Functions declared in this module, fully lowered.
439    pub functions: Vec<FnBinding>,
440}
441
442impl ModuleBinding {
443    /// Find a callback interface declared in this module by name.
444    pub fn callback_interface(&self, name: &str) -> Option<&CallbackInterfaceBinding> {
445        self.callback_interfaces.iter().find(|c| c.name == name)
446    }
447
448    /// True when this module declares no API surface at all.
449    pub fn is_empty(&self) -> bool {
450        self.enums.is_empty()
451            && self.structs.is_empty()
452            && self.interfaces.is_empty()
453            && self.callback_interfaces.is_empty()
454            && self.functions.is_empty()
455            && !self.declares_error()
456    }
457
458    /// True when this module declares its own error domain (as opposed to
459    /// inheriting one from an ancestor).
460    pub fn declares_error(&self) -> bool {
461        self.error.as_ref().is_some_and(|e| e.declared_here)
462    }
463
464    /// Every callable in this module: free functions, then each interface's
465    /// constructors, methods, and statics.
466    pub fn callables(&self) -> impl Iterator<Item = &FnBinding> {
467        self.functions
468            .iter()
469            .chain(self.interfaces.iter().flat_map(|i| {
470                i.constructors
471                    .iter()
472                    .chain(i.methods.iter())
473                    .chain(i.statics.iter())
474            }))
475    }
476
477    /// `true` when any callable in this module is `async`.
478    pub fn has_async(&self) -> bool {
479        self.callables().any(|f| f.is_async)
480    }
481
482    /// `true` when any callable in this module returns an iterator.
483    pub fn has_iterators(&self) -> bool {
484        self.callables()
485            .any(|f| matches!(f.shape, CallShape::Iterator(_)))
486    }
487}
488
489/// The whole API, normalized and lowered for code generation.
490#[derive(Debug, Clone, PartialEq, Eq)]
491pub struct BindingModel {
492    /// The C symbol prefix every emitted name is built from.
493    pub prefix: String,
494    /// The IR schema version of the source `Api`.
495    pub version: String,
496    /// Modules in depth-first pre-order, each carrying its joined symbol path.
497    pub modules: Vec<ModuleBinding>,
498}
499
500impl BindingModel {
501    /// Build the model from a [`ResolvedApi`]. Every C symbol name starts
502    /// with the API identity's prefix, which is the same for the producer and
503    /// every consumer of one library.
504    pub fn build(api: &ResolvedApi) -> Self {
505        let prefix = api.identity().prefix.as_str();
506        let mut modules = Vec::new();
507        let lowerer = Lowerer { api, prefix };
508        for m in &api.modules {
509            lowerer.module(m, &[], None, &mut modules);
510        }
511        Self {
512            prefix: prefix.to_string(),
513            version: api.version.clone(),
514            modules,
515        }
516    }
517
518    /// The top-level modules (those with a single path segment), in order.
519    pub fn roots(&self) -> impl Iterator<Item = &ModuleBinding> {
520        self.modules.iter().filter(|m| m.segments.len() == 1)
521    }
522
523    /// The direct submodules of `parent`, in declaration order. Backends that
524    /// render nested namespaces recurse with this instead of re-walking the
525    /// IR tree.
526    pub fn children<'a>(
527        &'a self,
528        parent: &'a ModuleBinding,
529    ) -> impl Iterator<Item = &'a ModuleBinding> + 'a {
530        self.modules.iter().filter(move |m| {
531            m.segments.len() == parent.segments.len() + 1
532                && m.segments[..parent.segments.len()] == parent.segments[..]
533        })
534    }
535
536    /// Iterate every function across all modules, paired with its module.
537    pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
538        self.modules
539            .iter()
540            .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
541    }
542
543    /// Iterate every callable (free functions and interface members) across
544    /// all modules, paired with its module.
545    pub fn callables(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
546        self.modules
547            .iter()
548            .flat_map(|m| m.callables().map(move |f| (m, f)))
549    }
550
551    /// Iterate every callback interface across all modules, paired with its
552    /// module.
553    pub fn callback_interfaces(
554        &self,
555    ) -> impl Iterator<Item = (&ModuleBinding, &CallbackInterfaceBinding)> {
556        self.modules
557            .iter()
558            .flat_map(|m| m.callback_interfaces.iter().map(move |c| (m, c)))
559    }
560
561    /// Find an interface anywhere in the API by its (possibly dot-qualified)
562    /// resolved name, as carried by [`Ty::Interface`]. A bare name is looked
563    /// up in `current` (the referencing module's dot path) first.
564    pub fn interface(&self, name: &str, current: &str) -> Option<&InterfaceBinding> {
565        let (dot_path, bare) = match name.rsplit_once('.') {
566            Some((p, n)) => (p, n),
567            None => (current, name),
568        };
569        self.modules
570            .iter()
571            .find(|m| m.dot_path == dot_path)
572            .and_then(|m| m.interfaces.iter().find(|i| i.name == bare))
573    }
574
575    /// Every C identifier the library's ABI declares, paired with a
576    /// human-readable description of the declaration that owns it, in
577    /// declaration order. Validation rejects an API in which two entries share
578    /// an identifier.
579    ///
580    /// Covers the runtime surface ([`RUNTIME_SYMBOLS`]), each top-level
581    /// module's checksum function, every callable's symbols (sync entry
582    /// point, async launcher and completion type, iterator launcher, type,
583    /// `_next`, and `_destroy`), interface types with their `_clone` and
584    /// `_destroy`, C-style enum types and constants, error-domain types and
585    /// code constants, and callback-interface vtable types.
586    pub fn c_symbols(&self) -> Vec<(String, String)> {
587        let p = &self.prefix;
588        let mut out: Vec<(String, String)> = RUNTIME_SYMBOLS
589            .iter()
590            .map(|s| (format!("{p}_{s}"), format!("the runtime symbol '{p}_{s}'")))
591            .collect();
592        for m in self.roots() {
593            out.push((
594                checksum_symbol(p, &m.name),
595                format!("the checksum of module '{}'", m.name),
596            ));
597        }
598        let callable = |out: &mut Vec<(String, String)>, f: &FnBinding, owner: &str| {
599            let what = format!("'{owner}.{}'", f.name);
600            match &f.shape {
601                CallShape::Sync(abi) => out.push((abi.symbol.clone(), format!("function {what}"))),
602                CallShape::Async(a) => {
603                    out.push((a.launch.symbol.clone(), format!("async function {what}")));
604                    out.push((
605                        a.callback_type.clone(),
606                        format!("the completion type of {what}"),
607                    ));
608                }
609                CallShape::Iterator(it) => {
610                    out.push((it.launch.symbol.clone(), format!("function {what}")));
611                    out.push((it.iter_tag.clone(), format!("the iterator type of {what}")));
612                    out.push((
613                        it.next.symbol.clone(),
614                        format!("the iterator step of {what}"),
615                    ));
616                    out.push((
617                        it.destroy_symbol.clone(),
618                        format!("the iterator destructor of {what}"),
619                    ));
620                }
621            }
622        };
623        for m in &self.modules {
624            let dot = &m.dot_path;
625            if let Some(e) = m.error.as_ref().filter(|e| e.declared_here) {
626                out.push((e.c_tag.clone(), format!("error domain '{dot}.{}'", e.name)));
627                for c in &e.codes {
628                    out.push((
629                        c.c_const.clone(),
630                        format!("error code '{dot}.{}.{}'", e.name, c.name),
631                    ));
632                }
633            }
634            for e in &m.enums {
635                out.push((e.c_tag.clone(), format!("enum '{dot}.{}'", e.name)));
636                if e.rich {
637                    out.push((
638                        format!("{}_Tag", e.c_tag),
639                        format!("the tag type of enum '{dot}.{}'", e.name),
640                    ));
641                }
642                for v in &e.variants {
643                    out.push((
644                        v.c_const.clone(),
645                        format!("enum variant '{dot}.{}.{}'", e.name, v.name),
646                    ));
647                }
648            }
649            for s in &m.structs {
650                out.push((
651                    format!("{p}_{}_{}", m.path, s.name),
652                    format!("record '{dot}.{}'", s.name),
653                ));
654            }
655            for c in &m.callback_interfaces {
656                out.push((
657                    c.vtable_tag.clone(),
658                    format!("the vtable of callback interface '{dot}.{}'", c.name),
659                ));
660            }
661            for i in &m.interfaces {
662                let owner = format!("{dot}.{}", i.name);
663                out.push((i.c_tag.clone(), format!("interface '{owner}'")));
664                out.push((i.clone_symbol.clone(), format!("the clone of '{owner}'")));
665                out.push((
666                    i.destroy_symbol.clone(),
667                    format!("the destructor of '{owner}'"),
668                ));
669                for f in i.constructors.iter().chain(&i.methods).chain(&i.statics) {
670                    callable(&mut out, f, &owner);
671                }
672            }
673            for f in &m.functions {
674                callable(&mut out, f, dot);
675            }
676        }
677        out
678    }
679
680    /// `true` when any callable anywhere in the API is `async`.
681    pub fn has_async(&self) -> bool {
682        self.modules.iter().any(ModuleBinding::has_async)
683    }
684
685    /// `true` when any callable anywhere in the API returns an iterator.
686    pub fn has_iterators(&self) -> bool {
687        self.modules.iter().any(ModuleBinding::has_iterators)
688    }
689
690    /// `true` when the API declares any callback interface.
691    pub fn has_callback_interfaces(&self) -> bool {
692        self.modules
693            .iter()
694            .any(|m| !m.callback_interfaces.is_empty())
695    }
696
697    /// `true` when the API declares any interface.
698    pub fn has_interfaces(&self) -> bool {
699        self.modules.iter().any(|m| !m.interfaces.is_empty())
700    }
701
702    /// `true` when any type anywhere in the API crosses the ABI as a value
703    /// buffer (records, rich enums, optionals, lists, maps, error payloads).
704    pub fn has_buffers(&self) -> bool {
705        self.modules.iter().any(|m| {
706            !m.structs.is_empty()
707                || m.enums.iter().any(|e| e.rich)
708                || m.error
709                    .as_ref()
710                    .is_some_and(|e| e.codes.iter().any(|c| !c.fields.is_empty()))
711        }) || self.any_type(&|t| t.is_buffered())
712    }
713
714    /// `true` when any interface appears inside a buffered position (a record
715    /// field, a collection element, an optional payload, a rich-enum field),
716    /// so codecs need object-token support.
717    pub fn has_buffered_objects(&self) -> bool {
718        self.any_top_type(&|ty, position| match position {
719            // Fields are always serialized, so any object inside one is a
720            // buffered object.
721            TypePosition::Field => ty.contains_object(),
722            // A top-level `Store` or `Store?` is a plain pointer slot; only a
723            // top-level *buffered* type that nests an object counts.
724            TypePosition::Boundary => ty.is_buffered() && ty.contains_object(),
725        })
726    }
727
728    /// Visit every boundary-crossing type in the API (callable and
729    /// callback-method parameters and returns, iterator elements, record,
730    /// variant, and error payload fields), recursing into composites, and
731    /// return whether any satisfies `pred`. Backends use this to decide which
732    /// runtime helpers a given API actually needs.
733    pub fn any_type(&self, pred: &dyn Fn(&Ty) -> bool) -> bool {
734        self.any_top_type(&|ty, _| ty.any(pred))
735    }
736
737    /// Visit every *top-level* type in the API (no recursion into composites)
738    /// together with the kind of position it occupies, and return whether any
739    /// satisfies `pred`.
740    fn any_top_type(&self, pred: &dyn Fn(&Ty, TypePosition) -> bool) -> bool {
741        let boundary = |ty: &Ty| pred(ty, TypePosition::Boundary);
742        let field = |ty: &Ty| pred(ty, TypePosition::Field);
743        self.modules.iter().any(|m| {
744            m.callables().any(|f| {
745                f.params.iter().any(|p| boundary(&p.ty)) || f.ret.as_ref().is_some_and(boundary)
746            }) || m.callback_interfaces.iter().any(|c| {
747                c.methods.iter().any(|f| {
748                    f.params.iter().any(|p| boundary(&p.ty)) || f.ret.as_ref().is_some_and(boundary)
749                })
750            }) || m
751                .structs
752                .iter()
753                .any(|s| s.fields.iter().any(|f| field(&f.ty)))
754                || m.enums.iter().any(|e| {
755                    e.variants
756                        .iter()
757                        .any(|v| v.fields.iter().any(|f| field(&f.ty)))
758                })
759                || m.error.as_ref().is_some_and(|e| {
760                    e.declared_here
761                        && e.codes
762                            .iter()
763                            .any(|c| c.fields.iter().any(|f| field(&f.ty)))
764                })
765        })
766    }
767}
768
769/// Where a top-level type sits, for [`BindingModel::any_top_type`].
770#[derive(Clone, Copy)]
771enum TypePosition {
772    /// A callable or callback-method parameter or return.
773    Boundary,
774    /// A record, rich-enum variant, or error payload field (always serialized).
775    Field,
776}
777
778/// The per-build lowering context: the resolver and the symbol prefix.
779struct Lowerer<'a> {
780    api: &'a ResolvedApi,
781    prefix: &'a str,
782}
783
784/// The per-module lowering context: where we are in the tree.
785struct Scope<'a> {
786    /// Underscore-joined C path (`outer_inner`).
787    path: &'a str,
788}
789
790impl Lowerer<'_> {
791    fn ty(&self, ty: &TypeRef) -> Ty {
792        self.api.resolve(ty)
793    }
794
795    /// Recursively lower `module` and its descendants into the flat `out`
796    /// list, pre-order (parent before children) so symbol declarations precede
797    /// uses. `inherited_error` is the nearest ancestor's error domain,
798    /// threaded down so every module knows which domain its throwing
799    /// functions report.
800    fn module(
801        &self,
802        module: &Module,
803        parent: &[String],
804        inherited_error: Option<&ErrorBinding>,
805        out: &mut Vec<ModuleBinding>,
806    ) {
807        let mut segments = parent.to_vec();
808        segments.push(module.name.clone());
809        let path = segments.join("_");
810        let dot_path = segments.join(".");
811        let scope = Scope { path: &path };
812        let prefix = self.prefix;
813
814        let error = match &module.errors {
815            Some(domain) => Some(self.error_domain(domain, &scope)),
816            None => inherited_error.cloned().map(|mut e| {
817                e.declared_here = false;
818                e
819            }),
820        };
821
822        let enums = module
823            .enums
824            .iter()
825            .map(|e| self.enum_def(e, &scope))
826            .collect();
827        let structs = module.structs.iter().map(|s| self.struct_def(s)).collect();
828        let interfaces = module
829            .interfaces
830            .iter()
831            .map(|i| self.interface(i, &scope))
832            .collect();
833        let callback_interfaces = module
834            .callback_interfaces
835            .iter()
836            .map(|c| self.callback_interface(c, &scope))
837            .collect();
838        let functions = module
839            .functions
840            .iter()
841            .map(|f| {
842                let c_base = format!("{prefix}_{path}_{}", f.name);
843                self.callable(f, &scope, &c_base, None)
844            })
845            .collect();
846
847        let doc = module
848            .doc
849            .clone()
850            .or_else(|| module.functions.iter().find_map(|f| f.doc.clone()));
851
852        out.push(ModuleBinding {
853            name: module.name.clone(),
854            checksum: parent
855                .is_empty()
856                .then(|| crate::checksum::module_checksum(module)),
857            segments: segments.clone(),
858            path: path.clone(),
859            dot_path: dot_path.clone(),
860            doc,
861            error: error.clone(),
862            enums,
863            structs,
864            interfaces,
865            callback_interfaces,
866            functions,
867        });
868
869        for child in &module.modules {
870            self.module(child, &segments, error.as_ref(), out);
871        }
872    }
873
874    fn error_domain(&self, domain: &ErrorDomain, scope: &Scope<'_>) -> ErrorBinding {
875        let c_tag = format!("{}_{}_{}", self.prefix, scope.path, domain.name);
876        ErrorBinding {
877            name: domain.name.clone(),
878            type_name: crate::errors::type_name(&domain.name, "Error"),
879            owner_path: scope.path.to_string(),
880            declared_here: true,
881            c_tag: c_tag.clone(),
882            codes: domain
883                .codes
884                .iter()
885                .map(|c| ErrorCodeBinding {
886                    name: c.name.clone(),
887                    value: c.code,
888                    message: c.message.clone(),
889                    doc: c.doc.clone(),
890                    c_const: format!("{c_tag}_{}", c.name),
891                    fields: self.fields(&c.fields),
892                })
893                .collect(),
894        }
895    }
896
897    fn fields(&self, fields: &[StructField]) -> Vec<FieldBinding> {
898        fields
899            .iter()
900            .map(|f| FieldBinding {
901                name: f.name.clone(),
902                doc: f.doc.clone(),
903                ty: self.ty(&f.ty),
904            })
905            .collect()
906    }
907
908    fn struct_def(&self, s: &StructDef) -> StructBinding {
909        StructBinding {
910            name: s.name.clone(),
911            doc: s.doc.clone(),
912            deprecated: s.deprecated.clone(),
913            fields: self.fields(&s.fields),
914        }
915    }
916
917    fn enum_def(&self, e: &EnumDef, scope: &Scope<'_>) -> EnumBinding {
918        let c_tag = format!("{}_{}_{}", self.prefix, scope.path, e.name);
919        let variants = e
920            .variants
921            .iter()
922            .map(|v| EnumVariantBinding {
923                name: v.name.clone(),
924                value: v.value,
925                doc: v.doc.clone(),
926                c_const: format!("{c_tag}_{}", v.name),
927                fields: self.fields(&v.fields),
928            })
929            .collect();
930        EnumBinding {
931            name: e.name.clone(),
932            doc: e.doc.clone(),
933            deprecated: e.deprecated.clone(),
934            c_tag,
935            variants,
936            rich: e.is_rich(),
937        }
938    }
939
940    fn params(&self, params: &[crate::ir::Param], scope: &Scope<'_>) -> Vec<ParamBinding> {
941        params
942            .iter()
943            .map(|p| ParamBinding::new(&p.name, self.ty(&p.ty), p.doc.clone(), scope.path))
944            .collect()
945    }
946
947    fn callback_interface(
948        &self,
949        c: &CallbackInterfaceDef,
950        scope: &Scope<'_>,
951    ) -> CallbackInterfaceBinding {
952        let c_tag = format!("{}_{}_{}", self.prefix, scope.path, c.name);
953        let methods = c
954            .methods
955            .iter()
956            .map(|m| {
957                let params = self.params(&m.params, scope);
958                let ret = m.returns.as_ref().map(|r| self.ty(r));
959                let sig = callback_method_signature(&params, ret.as_ref(), scope.path);
960                CallbackMethodBinding {
961                    name: m.name.clone(),
962                    doc: m.doc.clone(),
963                    deprecated: m.deprecated.clone(),
964                    params,
965                    ret,
966                    abi_params: sig.params,
967                    abi_ret: sig.ret,
968                }
969            })
970            .collect();
971        CallbackInterfaceBinding {
972            name: c.name.clone(),
973            doc: c.doc.clone(),
974            deprecated: c.deprecated.clone(),
975            vtable_tag: format!("{c_tag}_vtable"),
976            c_tag,
977            methods,
978        }
979    }
980
981    /// Lower an interface: constructors become statics returning the
982    /// interface, methods gain the implicit `self` slot, and all member
983    /// symbols hang off the interface's `c_tag`.
984    fn interface(&self, iface: &InterfaceDef, scope: &Scope<'_>) -> InterfaceBinding {
985        let c_tag = format!("{}_{}_{}", self.prefix, scope.path, iface.name);
986        let self_slot = AbiParam::new(
987            "self",
988            CType::Ptr {
989                konst: ConstPos::West,
990                pointee: Box::new(CType::StructTag {
991                    module: scope.path.to_string(),
992                    name: iface.name.clone(),
993                }),
994            },
995        );
996        let member = |name: &str| format!("{c_tag}_{name}");
997        let constructors = iface
998            .constructors
999            .iter()
1000            .map(|c| {
1001                // A constructor yields a new strong reference to the
1002                // interface, exactly like a static returning it.
1003                let mut f = c.clone();
1004                f.returns = Some(TypeRef::Named(iface.name.clone()));
1005                self.callable(&f, scope, &member(&c.name), None)
1006            })
1007            .collect();
1008        let methods = iface
1009            .methods
1010            .iter()
1011            .map(|m| self.callable(m, scope, &member(&m.name), Some(self_slot.clone())))
1012            .collect();
1013        let statics = iface
1014            .statics
1015            .iter()
1016            .map(|s| self.callable(s, scope, &member(&s.name), None))
1017            .collect();
1018        InterfaceBinding {
1019            name: iface.name.clone(),
1020            doc: iface.doc.clone(),
1021            deprecated: iface.deprecated.clone(),
1022            c_tag: c_tag.clone(),
1023            constructors,
1024            methods,
1025            statics,
1026            clone_symbol: format!("{c_tag}_clone"),
1027            destroy_symbol: format!("{c_tag}_destroy"),
1028        }
1029    }
1030
1031    /// Lower one callable (free function or interface member) whose full base
1032    /// C symbol is `c_base`. When `self_slot` is given (an instance method),
1033    /// it is prepended to every ABI signature but never appears in the
1034    /// retained [`ParamBinding`] list.
1035    fn callable(
1036        &self,
1037        f: &Function,
1038        scope: &Scope<'_>,
1039        c_base: &str,
1040        self_slot: Option<AbiParam>,
1041    ) -> FnBinding {
1042        let prefix = self.prefix;
1043        let path = scope.path;
1044        let params = self.params(&f.params, scope);
1045        let ret = f.returns.as_ref().map(|r| self.ty(r));
1046        // The prefix-stripped spelling used for `CType::Named` cores (which
1047        // render as `{prefix}_{core}`), e.g. `kv_Store_scan` from
1048        // `weaveffi_kv_Store_scan`.
1049        let core_base = c_base
1050            .strip_prefix(&format!("{prefix}_"))
1051            .expect("c_base always starts with the symbol prefix")
1052            .to_string();
1053        let with_self = |mut params: Vec<AbiParam>| {
1054            if let Some(s) = &self_slot {
1055                params.insert(0, s.clone());
1056            }
1057            params
1058        };
1059
1060        let shape = if let Some(elem) = ret.as_ref().and_then(Ty::iterator_elem) {
1061            let pascal = f.name.to_upper_camel_case();
1062            // `{owner}_{Pascal}Iterator`, where owner is the module path for a
1063            // free function or `{module path}_{Interface}` for a method.
1064            let owner = &core_base[..core_base.len() - f.name.len() - 1];
1065            let iter_core = format!("{owner}_{pascal}Iterator");
1066            let iter_tag = format!("{prefix}_{iter_core}");
1067
1068            let mut launch_params: Vec<AbiParam> =
1069                params.iter().flat_map(|p| p.abi.iter().cloned()).collect();
1070            launch_params.push(error_out_param());
1071            let launch = AbiFn {
1072                symbol: c_base.to_string(),
1073                params: with_self(launch_params),
1074                ret: CType::ptr(CType::Named(iter_core.clone())),
1075            };
1076
1077            let item = lower_return(elem, path);
1078            let mut next_params = vec![
1079                AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
1080                AbiParam::new("out_item", CType::ptr(item.ret)),
1081            ];
1082            next_params.extend(item.out_params);
1083            next_params.push(error_out_param());
1084            let next = AbiFn {
1085                symbol: format!("{iter_tag}_next"),
1086                params: next_params,
1087                ret: CType::Int32,
1088            };
1089
1090            CallShape::Iterator(IteratorBinding {
1091                elem: elem.clone(),
1092                iter_tag: iter_tag.clone(),
1093                launch,
1094                next,
1095                destroy_symbol: format!("{iter_tag}_destroy"),
1096            })
1097        } else if f.r#async {
1098            let callback_type = format!("{c_base}_callback");
1099            let mut launch_params = async_input_params(&params, f.cancellable);
1100            launch_params.push(AbiParam::new(
1101                "callback",
1102                CType::Named(format!("{core_base}_callback")),
1103            ));
1104            launch_params.push(context_param());
1105            let launch = AbiFn {
1106                symbol: c_base.to_string(),
1107                params: with_self(launch_params),
1108                ret: CType::Void,
1109            };
1110            CallShape::Async(AsyncBinding {
1111                launch,
1112                callback_type,
1113                callback_params: async_callback_params(ret.as_ref(), path),
1114            })
1115        } else {
1116            let sig = sync_signature(&params, ret.as_ref(), path);
1117            CallShape::Sync(AbiFn {
1118                symbol: c_base.to_string(),
1119                params: with_self(sig.params),
1120                ret: sig.ret,
1121            })
1122        };
1123
1124        FnBinding {
1125            name: f.name.clone(),
1126            doc: f.doc.clone(),
1127            deprecated: f.deprecated.clone(),
1128            cancellable: f.cancellable,
1129            is_async: f.r#async,
1130            throws: f.throws,
1131            has_self: self_slot.is_some(),
1132            params,
1133            ret,
1134            c_base: c_base.to_string(),
1135            shape,
1136        }
1137    }
1138}
1139
1140/// The element C type of an iterator's `out_item` slot (the pointee of
1141/// `T* out_item`). Exposed for backends that materialize iterator results.
1142pub fn iterator_item_ctype(elem: &Ty, module: &str) -> CType {
1143    lower_return(elem, module).ret
1144}
1145
1146#[cfg(test)]
1147mod tests {
1148    use super::*;
1149    use crate::ir::{
1150        Api, CallbackInterfaceDef, EnumDef, EnumVariant, Function, InterfaceDef, Module, Param,
1151        StructDef, StructField,
1152    };
1153
1154    fn param(name: &str, ty: TypeRef) -> Param {
1155        Param {
1156            name: name.into(),
1157            ty,
1158            doc: None,
1159        }
1160    }
1161
1162    fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
1163        Function {
1164            name: name.into(),
1165            params,
1166            returns,
1167            doc: None,
1168            throws: false,
1169            r#async: false,
1170            cancellable: false,
1171            deprecated: None,
1172        }
1173    }
1174
1175    fn module(name: &str) -> Module {
1176        Module {
1177            name: name.into(),
1178            doc: None,
1179            functions: vec![],
1180            interfaces: vec![],
1181            callback_interfaces: vec![],
1182            structs: vec![],
1183            enums: vec![],
1184            errors: None,
1185            modules: vec![],
1186        }
1187    }
1188
1189    fn api(modules: Vec<Module>) -> ResolvedApi {
1190        ResolvedApi::assume_valid(Api {
1191            version: crate::ir::CURRENT_SCHEMA_VERSION.into(),
1192            modules,
1193        })
1194    }
1195
1196    fn rendered(abi: &AbiFn) -> Vec<String> {
1197        abi.params
1198            .iter()
1199            .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
1200            .collect()
1201    }
1202
1203    #[test]
1204    fn sync_function_symbol_and_sig() {
1205        let m = Module {
1206            functions: vec![func(
1207                "add",
1208                vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
1209                Some(TypeRef::I32),
1210            )],
1211            ..module("math")
1212        };
1213        let model = BindingModel::build(
1214            &api(vec![m]).with_identity(crate::pkg::Identity::named("weaveffi")),
1215        );
1216        let f = &model.modules[0].functions[0];
1217        assert_eq!(f.c_base, "weaveffi_math_add");
1218        let CallShape::Sync(abi) = &f.shape else {
1219            panic!("expected sync")
1220        };
1221        assert_eq!(abi.symbol, "weaveffi_math_add");
1222        assert_eq!(abi.ret, CType::Int32);
1223        assert_eq!(
1224            rendered(abi),
1225            ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
1226        );
1227
1228        let model = BindingModel::build(
1229            &api(vec![module("net")]).with_identity(crate::pkg::Identity::named("acme")),
1230        );
1231        assert_eq!(model.prefix, "acme");
1232    }
1233
1234    #[test]
1235    fn async_function_has_launch_and_callback() {
1236        let m = Module {
1237            functions: vec![Function {
1238                cancellable: true,
1239                r#async: true,
1240                ..func(
1241                    "fetch",
1242                    vec![param("id", TypeRef::I64)],
1243                    Some(TypeRef::StringUtf8),
1244                )
1245            }],
1246            ..module("net")
1247        };
1248        let model = BindingModel::build(
1249            &api(vec![m]).with_identity(crate::pkg::Identity::named("weaveffi")),
1250        );
1251        let CallShape::Async(a) = &model.modules[0].functions[0].shape else {
1252            panic!("expected async")
1253        };
1254        assert_eq!(a.launch.symbol, "weaveffi_net_fetch");
1255        assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
1256        let names: Vec<&str> = a.launch.params.iter().map(|p| p.name.as_str()).collect();
1257        assert_eq!(names, ["id", "cancel_token", "callback", "context"]);
1258        assert_eq!(a.callback_params[0].name, "context");
1259        assert_eq!(a.callback_params[1].name, "err");
1260        assert_eq!(a.callback_params[2].name, "result_ptr");
1261        assert!(model.has_async());
1262    }
1263
1264    #[test]
1265    fn iterator_function_has_next_and_destroy() {
1266        let m = Module {
1267            functions: vec![func(
1268                "get_messages",
1269                vec![],
1270                Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
1271            )],
1272            ..module("events")
1273        };
1274        let model = BindingModel::build(
1275            &api(vec![m]).with_identity(crate::pkg::Identity::named("weaveffi")),
1276        );
1277        let CallShape::Iterator(it) = &model.modules[0].functions[0].shape else {
1278            panic!("expected iterator")
1279        };
1280        assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
1281        assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
1282        assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
1283        assert_eq!(
1284            it.destroy_symbol,
1285            "weaveffi_events_GetMessagesIterator_destroy"
1286        );
1287        assert_eq!(it.elem, Ty::StringUtf8);
1288        assert_eq!(it.next.ret, CType::Int32);
1289        assert_eq!(it.next.params[1].ty.render_c("weaveffi"), "const uint8_t**");
1290        assert!(model.has_iterators());
1291    }
1292
1293    #[test]
1294    fn user_types_resolve_to_kinds_and_buffers() {
1295        let shared = Module {
1296            structs: vec![StructDef {
1297                name: "Contact".into(),
1298                doc: None,
1299                deprecated: Some("use Person".into()),
1300                fields: vec![
1301                    StructField {
1302                        name: "name".into(),
1303                        ty: TypeRef::StringUtf8,
1304                        doc: None,
1305                    },
1306                    StructField {
1307                        name: "status".into(),
1308                        ty: TypeRef::Named("Status".into()),
1309                        doc: None,
1310                    },
1311                    StructField {
1312                        name: "store".into(),
1313                        ty: TypeRef::Optional(Box::new(TypeRef::Named("Store".into()))),
1314                        doc: None,
1315                    },
1316                ],
1317            }],
1318            enums: vec![EnumDef {
1319                name: "Status".into(),
1320                doc: None,
1321                deprecated: None,
1322                variants: vec![EnumVariant {
1323                    name: "Ok".into(),
1324                    value: 0,
1325                    doc: None,
1326                    fields: vec![],
1327                }],
1328            }],
1329            interfaces: vec![InterfaceDef {
1330                name: "Store".into(),
1331                doc: None,
1332                deprecated: None,
1333                constructors: vec![func("open", vec![], None)],
1334                methods: vec![func(
1335                    "save",
1336                    vec![param("contact", TypeRef::Named("Contact".into()))],
1337                    Some(TypeRef::List(Box::new(TypeRef::Named("Contact".into())))),
1338                )],
1339                statics: vec![],
1340            }],
1341            ..module("contacts")
1342        };
1343        let other = Module {
1344            functions: vec![func(
1345                "status_of",
1346                vec![param("store", TypeRef::Named("Store".into()))],
1347                Some(TypeRef::Named("Status".into())),
1348            )],
1349            ..module("ops")
1350        };
1351        let model = BindingModel::build(
1352            &api(vec![shared, other]).with_identity(crate::pkg::Identity::named("weaveffi")),
1353        );
1354        let contacts = &model.modules[0];
1355        let s = &contacts.structs[0];
1356        assert_eq!(s.deprecated.as_deref(), Some("use Person"));
1357        assert_eq!(s.fields[1].ty, Ty::Enum("contacts.Status".into()));
1358        assert_eq!(
1359            s.fields[2].ty,
1360            Ty::Optional(Box::new(Ty::Interface("contacts.Store".into())))
1361        );
1362        assert_eq!(contacts.enums[0].c_tag, "weaveffi_contacts_Status");
1363        assert_eq!(
1364            contacts.enums[0].variants[0].c_const,
1365            "weaveffi_contacts_Status_Ok"
1366        );
1367
1368        let iface = &contacts.interfaces[0];
1369        assert_eq!(iface.c_tag, "weaveffi_contacts_Store");
1370        assert_eq!(iface.clone_symbol, "weaveffi_contacts_Store_clone");
1371        assert_eq!(iface.destroy_symbol, "weaveffi_contacts_Store_destroy");
1372        assert_eq!(
1373            iface.constructors[0].ret,
1374            Some(Ty::Interface("contacts.Store".into()))
1375        );
1376        let save = &iface.methods[0];
1377        assert!(save.has_self);
1378        assert_eq!(save.params[0].ty, Ty::Record("contacts.Contact".into()));
1379        let CallShape::Sync(abi) = &save.shape else {
1380            panic!("expected sync")
1381        };
1382        assert_eq!(
1383            rendered(abi),
1384            [
1385                "const weaveffi_contacts_Store* self",
1386                "const uint8_t* contact_ptr",
1387                "size_t contact_len",
1388                "size_t* out_len",
1389                "weaveffi_error* out_err"
1390            ]
1391        );
1392        assert_eq!(abi.ret.render_c("weaveffi"), "const uint8_t*");
1393
1394        let ops = &model.modules[1];
1395        let f = &ops.functions[0];
1396        assert_eq!(f.params[0].ty, Ty::Interface("contacts.Store".into()));
1397        assert_eq!(f.ret, Some(Ty::Enum("contacts.Status".into())));
1398        let CallShape::Sync(abi) = &f.shape else {
1399            panic!("expected sync")
1400        };
1401        assert_eq!(
1402            rendered(abi),
1403            [
1404                "const weaveffi_contacts_Store* store",
1405                "weaveffi_error* out_err"
1406            ]
1407        );
1408        assert_eq!(abi.ret.render_c("weaveffi"), "weaveffi_contacts_Status");
1409        assert!(model.has_buffers());
1410        assert!(model.has_buffered_objects());
1411        assert!(model.has_interfaces());
1412        assert_eq!(
1413            model.interface("contacts.Store", "ops").map(|i| &i.c_tag),
1414            Some(&"weaveffi_contacts_Store".to_string())
1415        );
1416        assert_eq!(
1417            model.interface("Store", "contacts").map(|i| &i.c_tag),
1418            Some(&"weaveffi_contacts_Store".to_string())
1419        );
1420    }
1421
1422    #[test]
1423    fn callback_interfaces_lower_to_vtables() {
1424        let m = Module {
1425            callback_interfaces: vec![CallbackInterfaceDef {
1426                name: "Listener".into(),
1427                doc: Some("Receives messages.".into()),
1428                deprecated: None,
1429                methods: vec![
1430                    func("on_message", vec![param("text", TypeRef::StringUtf8)], None),
1431                    func("should_stop", vec![], Some(TypeRef::Bool)),
1432                ],
1433            }],
1434            functions: vec![func(
1435                "subscribe",
1436                vec![param("listener", TypeRef::Named("Listener".into()))],
1437                None,
1438            )],
1439            ..module("events")
1440        };
1441        let model = BindingModel::build(
1442            &api(vec![m]).with_identity(crate::pkg::Identity::named("weaveffi")),
1443        );
1444        let mb = &model.modules[0];
1445        let cb = &mb.callback_interfaces[0];
1446        assert_eq!(cb.c_tag, "weaveffi_events_Listener");
1447        assert_eq!(cb.vtable_tag, "weaveffi_events_Listener_vtable");
1448        let on_message = &cb.methods[0];
1449        let slots: Vec<String> = on_message
1450            .abi_params
1451            .iter()
1452            .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
1453            .collect();
1454        assert_eq!(
1455            slots,
1456            [
1457                "void* ctx",
1458                "const uint8_t* text_ptr",
1459                "size_t text_len",
1460                "weaveffi_error* out_err"
1461            ]
1462        );
1463        assert_eq!(on_message.abi_ret, CType::Void);
1464        assert_eq!(cb.methods[1].abi_ret, CType::Bool);
1465        assert!(mb.callback_interface("Listener").is_some());
1466        assert!(model.has_callback_interfaces());
1467
1468        let f = &mb.functions[0];
1469        assert_eq!(
1470            f.params[0].ty,
1471            Ty::CallbackInterface("events.Listener".into())
1472        );
1473        let CallShape::Sync(abi) = &f.shape else {
1474            panic!("expected sync")
1475        };
1476        assert_eq!(
1477            rendered(abi),
1478            [
1479                "void* listener_ctx",
1480                "const weaveffi_events_Listener_vtable* listener_vtable",
1481                "weaveffi_error* out_err"
1482            ]
1483        );
1484        assert!(!model.has_buffers());
1485        assert!(!model.has_buffered_objects());
1486    }
1487
1488    #[test]
1489    fn nested_modules_flatten_pre_order_with_paths_and_docs() {
1490        let inner = Module {
1491            functions: vec![Function {
1492                doc: Some("Leaf.".into()),
1493                ..func("leaf_fn", vec![], None)
1494            }],
1495            ..module("inner")
1496        };
1497        let outer = Module {
1498            doc: Some("Outer module.".into()),
1499            functions: vec![func("outer_fn", vec![], None)],
1500            modules: vec![inner],
1501            ..module("outer")
1502        };
1503        let model = BindingModel::build(
1504            &api(vec![outer]).with_identity(crate::pkg::Identity::named("weaveffi")),
1505        );
1506        let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
1507        assert_eq!(paths, ["outer", "outer_inner"]);
1508        assert_eq!(model.modules[1].dot_path, "outer.inner");
1509        assert_eq!(
1510            model.modules[1].functions[0].c_base,
1511            "weaveffi_outer_inner_leaf_fn"
1512        );
1513        assert_eq!(model.modules[0].doc.as_deref(), Some("Outer module."));
1514        assert_eq!(model.modules[1].doc.as_deref(), Some("Leaf."));
1515    }
1516}