Skip to main content

weaveffi_core/model/
mod.rs

1//! The **binding model**: a normalized, fully-lowered view of an [`Api`] that
2//! every language backend consumes.
3//!
4//! Before this module existed, each of the eleven generators re-walked the IR,
5//! re-derived the C ABI calling convention, and re-invented every emitted C
6//! symbol name. They drifted: iterators were lowered as lists in some targets,
7//! listeners were emitted only by the C header, and a custom `c_prefix` reached
8//! only the C and C++ outputs while the other nine hard-coded `weaveffi_`.
9//!
10//! [`BindingModel::build`] walks the IR exactly once and produces a flat list
11//! of [`ModuleBinding`]s in which:
12//!
13//! * every emitted **C symbol name** is precomputed once (so all backends agree
14//!   by construction, and a non-default prefix is honored everywhere); and
15//! * every function/struct/callback is paired with its lowered [`AbiFn`]
16//!   signature (built from [`crate::abi`]), so no backend re-derives parameter
17//!   arity, ordering, or `out_*`/`out_err` placement.
18//!
19//! A backend reads the *idiomatic* shape from the retained [`TypeRef`]s
20//! (`param.ty`, `field.ty`, …) and the *native* shape from the [`AbiFn`]s, then
21//! writes only the marshalling that bridges the two in its own idioms. The hard,
22//! drift-prone facts live here; only language syntax lives in the backends.
23
24use heck::ToUpperCamelCase;
25use weaveffi_ir::ir::{
26    Api, CallbackDef, EnumDef, Function, ListenerDef, Module, StructDef, TypeRef,
27};
28
29use crate::abi::{
30    self, async_callback_params, async_input_params, context_param, error_out_param, lower_param,
31    lower_return, sync_signature, AbiParam, CType,
32};
33
34/// A single lowered C symbol: its name, ordered ABI parameter slots, and C
35/// return type. This is what a backend declares to its FFI layer and calls.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct AbiFn {
38    /// The fully-qualified, prefixed C symbol (e.g. `weaveffi_math_add`).
39    pub symbol: String,
40    /// Ordered parameter slots, including any trailing `out_*` and `out_err`.
41    pub params: Vec<AbiParam>,
42    /// The C return type.
43    pub ret: CType,
44}
45
46/// How a function crosses the boundary. Exactly one shape applies to any given
47/// function: synchronous, asynchronous (callback-completed), or iterator-returning.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum CallShape {
50    /// A plain blocking call: [`AbiFn`] is the symbol to invoke.
51    Sync(AbiFn),
52    /// An async launcher plus its completion-callback typedef.
53    Async(AsyncBinding),
54    /// An iterator-returning function: an opaque handle plus `next`/`destroy`.
55    Iterator(IteratorBinding),
56}
57
58/// The lowered surface of an `async` function.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct AsyncBinding {
61    /// The launcher: input slots, optional `cancel_token`, then `callback` and
62    /// `context`. Returns `void`.
63    pub launch: AbiFn,
64    /// The completion-callback function-pointer typedef name
65    /// (`{symbol}_callback`).
66    pub callback_type: String,
67    /// The callback's parameter slots: `(void* context, {prefix}_error* err,
68    /// <result fields>)`.
69    pub callback_params: Vec<AbiParam>,
70}
71
72/// The lowered surface of an `iter<T>`-returning function.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct IteratorBinding {
75    /// The element type `T` of `iter<T>`.
76    pub elem: TypeRef,
77    /// The opaque iterator tag (`{prefix}_{path}_{Pascal}Iterator`).
78    pub iter_tag: String,
79    /// The launcher returning `{iter_tag}*`.
80    pub launch: AbiFn,
81    /// `int32_t {iter_tag}_next({iter_tag}* iter, T* out_item, …, error* out_err)`.
82    pub next: AbiFn,
83    /// `void {iter_tag}_destroy({iter_tag}* iter)`.
84    pub destroy_symbol: String,
85}
86
87/// One IR parameter, retained with its lowered ABI slots.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct ParamBinding {
90    pub name: String,
91    pub ty: TypeRef,
92    pub mutable: bool,
93    pub doc: Option<String>,
94    /// The ordered C ABI slots this single parameter expands into.
95    pub abi: Vec<AbiParam>,
96}
97
98/// A function, fully lowered.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct FnBinding {
101    pub name: String,
102    pub doc: Option<String>,
103    pub deprecated: Option<String>,
104    pub since: Option<String>,
105    pub cancellable: bool,
106    pub is_async: bool,
107    /// IR input parameters with their lowered slots.
108    pub params: Vec<ParamBinding>,
109    /// The IR return type (`None` = void). For an iterator function this is the
110    /// `iter<T>` type itself; the element `T` also lives in [`IteratorBinding`].
111    pub ret: Option<TypeRef>,
112    /// Base C symbol (`{prefix}_{module_path}_{name}`) before any
113    /// `_async`/iterator suffixing.
114    pub c_base: String,
115    /// The call shape (sync / async / iterator).
116    pub shape: CallShape,
117}
118
119/// A struct field, retained with its getter symbol and lowered return.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct FieldBinding {
122    pub name: String,
123    pub doc: Option<String>,
124    pub ty: TypeRef,
125    /// `{c_tag}_get_{field}`. Receiver is an implicit `const {c_tag}* ptr`; any
126    /// `out_*` slots are in [`getter_out_params`](Self::getter_out_params).
127    pub getter_symbol: String,
128    /// The C return type of the getter.
129    pub getter_ret: CType,
130    /// Trailing `out_*` slots of the getter (e.g. `size_t* out_len` for bytes).
131    pub getter_out_params: Vec<AbiParam>,
132    /// The ABI slots this field expands into when passed *in* (struct create,
133    /// builder setter).
134    pub value_params: Vec<AbiParam>,
135}
136
137/// The fluent builder lowered for a struct that opted in with `builder: true`.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct BuilderBinding {
140    /// `{c_tag}Builder`.
141    pub builder_tag: String,
142    /// `{c_tag}_Builder_new`.
143    pub new_symbol: String,
144    /// `{c_tag}_Builder_build` (carries a trailing `out_err`).
145    pub build_symbol: String,
146    /// `{c_tag}_Builder_destroy`.
147    pub destroy_symbol: String,
148    /// One `(field_name, setter_symbol)` per field; the value slots are the
149    /// field's [`FieldBinding::value_params`].
150    pub setters: Vec<(String, String)>,
151}
152
153/// A struct, fully lowered.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct StructBinding {
156    pub name: String,
157    pub doc: Option<String>,
158    /// `{prefix}_{module_path}_{name}` — the opaque tag.
159    pub c_tag: String,
160    pub fields: Vec<FieldBinding>,
161    /// `{c_tag}_create(<field slots>, error* out_err) -> {c_tag}*`.
162    pub create: AbiFn,
163    /// `{c_tag}_destroy`.
164    pub destroy_symbol: String,
165    /// Present when `builder: true`.
166    pub builder: Option<BuilderBinding>,
167}
168
169/// An enum, fully lowered. WeaveFFI enums are C-style integer discriminants.
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct EnumBinding {
172    pub name: String,
173    pub doc: Option<String>,
174    /// `{prefix}_{module_path}_{name}`.
175    pub c_tag: String,
176    pub variants: Vec<EnumVariantBinding>,
177}
178
179/// A single enum variant with its precomputed C constant name.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct EnumVariantBinding {
182    pub name: String,
183    pub value: i32,
184    pub doc: Option<String>,
185    /// `{enum_c_tag}_{variant}`.
186    pub c_const: String,
187}
188
189/// A callback function-pointer typedef declared at module scope.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct CallbackBinding {
192    pub name: String,
193    pub doc: Option<String>,
194    /// `{prefix}_{module_path}_{name}_fn`.
195    pub c_fn_type: String,
196    /// IR parameters of the callback (without the trailing context).
197    pub params: Vec<ParamBinding>,
198    /// The full ABI slot list, including the trailing `void* context`.
199    pub abi_params: Vec<AbiParam>,
200}
201
202/// A listener: a register/unregister pair bound to a callback.
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct ListenerBinding {
205    pub name: String,
206    pub doc: Option<String>,
207    /// The callback this listener fires (name within the same module).
208    pub event_callback: String,
209    /// The referenced callback's `_fn` typedef name.
210    pub callback_c_fn_type: String,
211    /// `uint64_t {prefix}_{path}_register_{name}({cb}_fn callback, void* context)`.
212    pub register_symbol: String,
213    /// `void {prefix}_{path}_unregister_{name}(uint64_t id)`.
214    pub unregister_symbol: String,
215}
216
217/// One module, flattened with its underscore-joined symbol path.
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct ModuleBinding {
220    pub name: String,
221    /// Path segments from the root (e.g. `["outer", "inner"]`).
222    pub segments: Vec<String>,
223    /// Underscore-joined path used as the C symbol segment (e.g. `outer_inner`).
224    pub path: String,
225    pub doc: Option<String>,
226    pub enums: Vec<EnumBinding>,
227    pub structs: Vec<StructBinding>,
228    pub callbacks: Vec<CallbackBinding>,
229    pub listeners: Vec<ListenerBinding>,
230    pub functions: Vec<FnBinding>,
231}
232
233impl ModuleBinding {
234    /// Find a callback declared in this module by name.
235    pub fn callback(&self, name: &str) -> Option<&CallbackBinding> {
236        self.callbacks.iter().find(|c| c.name == name)
237    }
238
239    /// True when this module declares no API surface at all.
240    pub fn is_empty(&self) -> bool {
241        self.enums.is_empty()
242            && self.structs.is_empty()
243            && self.callbacks.is_empty()
244            && self.listeners.is_empty()
245            && self.functions.is_empty()
246    }
247}
248
249/// The whole API, normalized and lowered for code generation.
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct BindingModel {
252    /// The C symbol prefix every emitted name is built from.
253    pub prefix: String,
254    /// The IR schema version of the source `Api`.
255    pub version: String,
256    /// Modules in depth-first pre-order, each carrying its joined symbol path.
257    pub modules: Vec<ModuleBinding>,
258}
259
260impl BindingModel {
261    /// Build the model from a validated [`Api`], using `prefix` for every C
262    /// symbol name. `prefix` is the single global ABI prefix (default
263    /// `"weaveffi"`); passing the same prefix to every backend is what keeps
264    /// the producer header and all consumers calling identical symbols.
265    pub fn build(api: &Api, prefix: &str) -> Self {
266        let mut modules = Vec::new();
267        for m in &api.modules {
268            lower_module(m, &[], prefix, &mut modules);
269        }
270        Self {
271            prefix: prefix.to_string(),
272            version: api.version.clone(),
273            modules,
274        }
275    }
276
277    /// Iterate every function across all modules, paired with its module.
278    pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
279        self.modules
280            .iter()
281            .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
282    }
283}
284
285/// Recursively lower `module` and its descendants into the flat `out` list,
286/// pre-order (parent before children) so symbol declarations precede uses.
287fn lower_module(module: &Module, parent: &[String], prefix: &str, out: &mut Vec<ModuleBinding>) {
288    let mut segments = parent.to_vec();
289    segments.push(module.name.clone());
290    let path = segments.join("_");
291
292    let enums = module
293        .enums
294        .iter()
295        .map(|e| lower_enum(e, &path, prefix))
296        .collect();
297    let structs = module
298        .structs
299        .iter()
300        .map(|s| lower_struct(s, &path, prefix))
301        .collect();
302    let callbacks: Vec<CallbackBinding> = module
303        .callbacks
304        .iter()
305        .map(|c| lower_callback(c, &path, prefix))
306        .collect();
307    let listeners = module
308        .listeners
309        .iter()
310        .map(|l| lower_listener(l, &path, prefix))
311        .collect();
312    let functions = module
313        .functions
314        .iter()
315        .map(|f| lower_function(f, &path, prefix))
316        .collect();
317
318    // Module doc is synthesized from the first documented function, matching
319    // the `EmptyModuleDoc` lint's notion of "the module is documented".
320    let doc = module.functions.iter().find_map(|f| f.doc.clone());
321
322    out.push(ModuleBinding {
323        name: module.name.clone(),
324        segments: segments.clone(),
325        path,
326        doc,
327        enums,
328        structs,
329        callbacks,
330        listeners,
331        functions,
332    });
333
334    for child in &module.modules {
335        lower_module(child, &segments, prefix, out);
336    }
337}
338
339fn lower_param_binding(p: &weaveffi_ir::ir::Param, module: &str) -> ParamBinding {
340    ParamBinding {
341        name: p.name.clone(),
342        ty: p.ty.clone(),
343        mutable: p.mutable,
344        doc: p.doc.clone(),
345        abi: lower_param(&p.name, &p.ty, module, p.mutable),
346    }
347}
348
349fn lower_enum(e: &EnumDef, path: &str, prefix: &str) -> EnumBinding {
350    let c_tag = format!("{prefix}_{path}_{}", e.name);
351    let variants = e
352        .variants
353        .iter()
354        .map(|v| EnumVariantBinding {
355            name: v.name.clone(),
356            value: v.value,
357            doc: v.doc.clone(),
358            c_const: format!("{c_tag}_{}", v.name),
359        })
360        .collect();
361    EnumBinding {
362        name: e.name.clone(),
363        doc: e.doc.clone(),
364        c_tag,
365        variants,
366    }
367}
368
369fn lower_struct(s: &StructDef, path: &str, prefix: &str) -> StructBinding {
370    let c_tag = format!("{prefix}_{path}_{}", s.name);
371
372    let fields: Vec<FieldBinding> = s
373        .fields
374        .iter()
375        .map(|f| {
376            let r = lower_return(&f.ty, path);
377            FieldBinding {
378                name: f.name.clone(),
379                doc: f.doc.clone(),
380                ty: f.ty.clone(),
381                getter_symbol: format!("{c_tag}_get_{}", f.name),
382                getter_ret: r.ret,
383                getter_out_params: r.out_params,
384                value_params: lower_param(&f.name, &f.ty, path, false),
385            }
386        })
387        .collect();
388
389    // create: each field lowered as an input parameter, then out_err.
390    let mut create_params: Vec<AbiParam> = s
391        .fields
392        .iter()
393        .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
394        .collect();
395    create_params.push(error_out_param());
396    let create = AbiFn {
397        symbol: format!("{c_tag}_create"),
398        params: create_params,
399        ret: CType::ptr(CType::Named(format!("{path}_{}", s.name))),
400    };
401
402    let builder = s.builder.then(|| {
403        let builder_tag = format!("{c_tag}Builder");
404        let setters = s
405            .fields
406            .iter()
407            .map(|f| (f.name.clone(), format!("{c_tag}_Builder_set_{}", f.name)))
408            .collect();
409        BuilderBinding {
410            builder_tag,
411            new_symbol: format!("{c_tag}_Builder_new"),
412            build_symbol: format!("{c_tag}_Builder_build"),
413            destroy_symbol: format!("{c_tag}_Builder_destroy"),
414            setters,
415        }
416    });
417
418    StructBinding {
419        name: s.name.clone(),
420        doc: s.doc.clone(),
421        c_tag: c_tag.clone(),
422        fields,
423        create,
424        destroy_symbol: format!("{c_tag}_destroy"),
425        builder,
426    }
427}
428
429fn lower_callback(c: &CallbackDef, path: &str, prefix: &str) -> CallbackBinding {
430    let params: Vec<ParamBinding> = c
431        .params
432        .iter()
433        .map(|p| lower_param_binding(p, path))
434        .collect();
435    let mut abi_params: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.clone()).collect();
436    abi_params.push(context_param());
437    CallbackBinding {
438        name: c.name.clone(),
439        doc: c.doc.clone(),
440        c_fn_type: format!("{prefix}_{path}_{}_fn", c.name),
441        params,
442        abi_params,
443    }
444}
445
446fn lower_listener(l: &ListenerDef, path: &str, prefix: &str) -> ListenerBinding {
447    ListenerBinding {
448        name: l.name.clone(),
449        doc: l.doc.clone(),
450        event_callback: l.event_callback.clone(),
451        callback_c_fn_type: format!("{prefix}_{path}_{}_fn", l.event_callback),
452        register_symbol: format!("{prefix}_{path}_register_{}", l.name),
453        unregister_symbol: format!("{prefix}_{path}_unregister_{}", l.name),
454    }
455}
456
457fn lower_function(f: &Function, path: &str, prefix: &str) -> FnBinding {
458    let params: Vec<ParamBinding> = f
459        .params
460        .iter()
461        .map(|p| lower_param_binding(p, path))
462        .collect();
463    let c_base = format!("{prefix}_{path}_{}", f.name);
464
465    let shape = if let Some(TypeRef::Iterator(inner)) = &f.returns {
466        let pascal = f.name.to_upper_camel_case();
467        let iter_tag = format!("{prefix}_{path}_{pascal}Iterator");
468        let iter_core = format!("{path}_{pascal}Iterator");
469
470        // launcher: input slots + out_err, returns iter_tag*.
471        let mut launch_params: Vec<AbiParam> = f
472            .params
473            .iter()
474            .flat_map(|p| lower_param(&p.name, &p.ty, path, p.mutable))
475            .collect();
476        launch_params.push(error_out_param());
477        let launch = AbiFn {
478            symbol: c_base.clone(),
479            params: launch_params,
480            ret: CType::ptr(CType::Named(iter_core.clone())),
481        };
482
483        // next: (iter, out_item, <item out_params>, out_err) -> int32.
484        let item = lower_return(inner, path);
485        let mut next_params = vec![
486            AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
487            AbiParam::new("out_item", CType::ptr(item.ret)),
488        ];
489        next_params.extend(item.out_params);
490        next_params.push(error_out_param());
491        let next = AbiFn {
492            symbol: format!("{iter_tag}_next"),
493            params: next_params,
494            ret: CType::Int32,
495        };
496
497        CallShape::Iterator(IteratorBinding {
498            elem: (**inner).clone(),
499            iter_tag: iter_tag.clone(),
500            launch,
501            next,
502            destroy_symbol: format!("{iter_tag}_destroy"),
503        })
504    } else if f.r#async {
505        let callback_type = format!("{c_base}_callback");
506        let mut launch_params = async_input_params(f, path);
507        launch_params.push(AbiParam::new(
508            "callback",
509            CType::Named(format!("{path}_{}_callback", f.name)),
510        ));
511        launch_params.push(context_param());
512        let launch = AbiFn {
513            symbol: format!("{c_base}_async"),
514            params: launch_params,
515            ret: CType::Void,
516        };
517        CallShape::Async(AsyncBinding {
518            launch,
519            callback_type,
520            callback_params: async_callback_params(f.returns.as_ref(), path),
521        })
522    } else {
523        let sig = sync_signature(&f.params, f.returns.as_ref(), path);
524        CallShape::Sync(AbiFn {
525            symbol: c_base.clone(),
526            params: sig.params,
527            ret: sig.ret,
528        })
529    };
530
531    FnBinding {
532        name: f.name.clone(),
533        doc: f.doc.clone(),
534        deprecated: f.deprecated.clone(),
535        since: f.since.clone(),
536        cancellable: f.cancellable,
537        is_async: f.r#async,
538        params,
539        ret: f.returns.clone(),
540        c_base,
541        shape,
542    }
543}
544
545/// The element C type of an iterator's `out_item` slot (the pointee of
546/// `T* out_item`). Exposed for backends that materialize iterator results.
547pub fn iterator_item_ctype(elem: &TypeRef, module: &str) -> CType {
548    abi::lower_return(elem, module).ret
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554    use weaveffi_ir::ir::{
555        CallbackDef, EnumDef, EnumVariant, Function, ListenerDef, Module, Param, StructDef,
556        StructField,
557    };
558
559    fn param(name: &str, ty: TypeRef) -> Param {
560        Param {
561            name: name.into(),
562            ty,
563            mutable: false,
564            doc: None,
565        }
566    }
567
568    fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
569        Function {
570            name: name.into(),
571            params,
572            returns,
573            doc: None,
574            r#async: false,
575            cancellable: false,
576            deprecated: None,
577            since: None,
578        }
579    }
580
581    fn module(name: &str) -> Module {
582        Module {
583            name: name.into(),
584            functions: vec![],
585            structs: vec![],
586            enums: vec![],
587            callbacks: vec![],
588            listeners: vec![],
589            errors: None,
590            modules: vec![],
591        }
592    }
593
594    fn api(modules: Vec<Module>) -> Api {
595        Api {
596            version: "0.3.0".into(),
597            modules,
598            generators: None,
599        }
600    }
601
602    #[test]
603    fn sync_function_symbol_and_sig() {
604        let m = Module {
605            functions: vec![func(
606                "add",
607                vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
608                Some(TypeRef::I32),
609            )],
610            ..module("math")
611        };
612        let model = BindingModel::build(&api(vec![m]), "weaveffi");
613        let f = &model.modules[0].functions[0];
614        assert_eq!(f.c_base, "weaveffi_math_add");
615        match &f.shape {
616            CallShape::Sync(abi) => {
617                assert_eq!(abi.symbol, "weaveffi_math_add");
618                assert_eq!(abi.ret, CType::Int32);
619                let rendered: Vec<String> = abi
620                    .params
621                    .iter()
622                    .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
623                    .collect();
624                assert_eq!(
625                    rendered,
626                    ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
627                );
628            }
629            _ => panic!("expected sync"),
630        }
631    }
632
633    #[test]
634    fn prefix_is_honored_everywhere() {
635        let m = Module {
636            functions: vec![func("ping", vec![], None)],
637            ..module("net")
638        };
639        let model = BindingModel::build(&api(vec![m]), "acme");
640        let f = &model.modules[0].functions[0];
641        assert_eq!(f.c_base, "acme_net_ping");
642    }
643
644    #[test]
645    fn async_function_has_launch_and_callback() {
646        let m = Module {
647            functions: vec![Function {
648                cancellable: true,
649                r#async: true,
650                ..func(
651                    "fetch",
652                    vec![param("id", TypeRef::I64)],
653                    Some(TypeRef::StringUtf8),
654                )
655            }],
656            ..module("net")
657        };
658        let model = BindingModel::build(&api(vec![m]), "weaveffi");
659        match &model.modules[0].functions[0].shape {
660            CallShape::Async(a) => {
661                assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
662                assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
663                let last_two: Vec<&str> = a
664                    .launch
665                    .params
666                    .iter()
667                    .rev()
668                    .take(2)
669                    .map(|p| p.name.as_str())
670                    .collect();
671                assert_eq!(last_two, ["context", "callback"]);
672                // cancel_token slot is present before callback/context.
673                assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
674                // callback prefix is (context, err, result).
675                assert_eq!(a.callback_params[0].name, "context");
676                assert_eq!(a.callback_params[1].name, "err");
677            }
678            _ => panic!("expected async"),
679        }
680    }
681
682    #[test]
683    fn iterator_function_has_next_and_destroy() {
684        let m = Module {
685            functions: vec![func(
686                "get_messages",
687                vec![],
688                Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
689            )],
690            ..module("events")
691        };
692        let model = BindingModel::build(&api(vec![m]), "weaveffi");
693        match &model.modules[0].functions[0].shape {
694            CallShape::Iterator(it) => {
695                assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
696                assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
697                assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
698                assert_eq!(
699                    it.destroy_symbol,
700                    "weaveffi_events_GetMessagesIterator_destroy"
701                );
702                assert_eq!(it.next.ret, CType::Int32);
703                // out_item is `const char** out_item` for a string element.
704                let out_item = &it.next.params[1];
705                assert_eq!(out_item.name, "out_item");
706                assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
707            }
708            _ => panic!("expected iterator"),
709        }
710    }
711
712    #[test]
713    fn struct_create_getters_and_builder() {
714        let m = Module {
715            structs: vec![StructDef {
716                name: "Contact".into(),
717                doc: None,
718                fields: vec![
719                    StructField {
720                        name: "name".into(),
721                        ty: TypeRef::StringUtf8,
722                        doc: None,
723                        default: None,
724                    },
725                    StructField {
726                        name: "age".into(),
727                        ty: TypeRef::I32,
728                        doc: None,
729                        default: None,
730                    },
731                ],
732                builder: true,
733            }],
734            ..module("contacts")
735        };
736        let model = BindingModel::build(&api(vec![m]), "weaveffi");
737        let s = &model.modules[0].structs[0];
738        assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
739        assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
740        assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
741        assert_eq!(
742            s.fields[0].getter_symbol,
743            "weaveffi_contacts_Contact_get_name"
744        );
745        let b = s.builder.as_ref().unwrap();
746        assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
747        assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
748        assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
749    }
750
751    #[test]
752    fn enum_constants_are_prefixed() {
753        let m = Module {
754            enums: vec![EnumDef {
755                name: "Color".into(),
756                doc: None,
757                variants: vec![
758                    EnumVariant {
759                        name: "Red".into(),
760                        value: 0,
761                        doc: None,
762                    },
763                    EnumVariant {
764                        name: "Green".into(),
765                        value: 1,
766                        doc: None,
767                    },
768                ],
769            }],
770            ..module("gfx")
771        };
772        let model = BindingModel::build(&api(vec![m]), "weaveffi");
773        let e = &model.modules[0].enums[0];
774        assert_eq!(e.c_tag, "weaveffi_gfx_Color");
775        assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
776        assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
777    }
778
779    #[test]
780    fn callbacks_and_listeners_are_linked() {
781        let m = Module {
782            callbacks: vec![CallbackDef {
783                name: "on_message".into(),
784                params: vec![param("text", TypeRef::StringUtf8)],
785                doc: None,
786            }],
787            listeners: vec![ListenerDef {
788                name: "messages".into(),
789                event_callback: "on_message".into(),
790                doc: None,
791            }],
792            ..module("events")
793        };
794        let model = BindingModel::build(&api(vec![m]), "weaveffi");
795        let mb = &model.modules[0];
796        let cb = &mb.callbacks[0];
797        assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
798        // context appended last.
799        assert_eq!(cb.abi_params.last().unwrap().name, "context");
800        let l = &mb.listeners[0];
801        assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
802        assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
803        assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
804        assert!(mb.callback("on_message").is_some());
805    }
806
807    #[test]
808    fn nested_modules_flatten_pre_order_with_paths() {
809        let inner = Module {
810            functions: vec![func("leaf_fn", vec![], None)],
811            ..module("inner")
812        };
813        let outer = Module {
814            functions: vec![func("outer_fn", vec![], None)],
815            modules: vec![inner],
816            ..module("outer")
817        };
818        let model = BindingModel::build(&api(vec![outer]), "weaveffi");
819        let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
820        assert_eq!(paths, ["outer", "outer_inner"]);
821        assert_eq!(
822            model.modules[1].functions[0].c_base,
823            "weaveffi_outer_inner_leaf_fn"
824        );
825    }
826}