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.
170///
171/// A *C-style* enum (every variant a bare discriminant) carries only
172/// [`variants`](Self::variants) and crosses the ABI by value as an integer. An
173/// *algebraic* (sum-type) enum — at least one variant with associated data —
174/// additionally carries [`rich`](Self::rich) and crosses the ABI as an opaque
175/// object pointer (tag getter + per-variant constructors and field getters +
176/// destructor), exactly like a struct.
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct EnumBinding {
179    pub name: String,
180    pub doc: Option<String>,
181    /// `{prefix}_{module_path}_{name}`.
182    pub c_tag: String,
183    /// Every variant's discriminant name/value, in declaration order. Present
184    /// for both kinds (the discriminant of a rich enum is its tag value).
185    pub variants: Vec<EnumVariantBinding>,
186    /// `Some` iff this is a rich (algebraic) enum.
187    pub rich: Option<RichEnumBinding>,
188}
189
190impl EnumBinding {
191    /// `true` when this is a rich (algebraic) sum-type enum.
192    pub fn is_rich(&self) -> bool {
193        self.rich.is_some()
194    }
195}
196
197/// A single enum variant with its precomputed C constant name.
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub struct EnumVariantBinding {
200    pub name: String,
201    pub value: i32,
202    pub doc: Option<String>,
203    /// `{enum_c_tag}_{variant}`.
204    pub c_const: String,
205}
206
207/// The opaque-object surface of a rich (algebraic) enum: how its tag is read,
208/// how each variant is constructed and projected, and how the object is freed.
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct RichEnumBinding {
211    /// `int32_t {tag_symbol}(const {c_tag}* self)` — returns the active
212    /// variant's discriminant (matching the per-variant
213    /// [`c_const`](EnumVariantBinding::c_const) values).
214    pub tag_symbol: String,
215    /// `void {destroy_symbol}({c_tag}* self)`.
216    pub destroy_symbol: String,
217    /// Per-variant constructors and field getters, in declaration order
218    /// (parallel to [`EnumBinding::variants`]).
219    pub variants: Vec<RichVariantBinding>,
220}
221
222/// One variant of a rich enum: its constructor and the getters for its
223/// associated data.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct RichVariantBinding {
226    pub name: String,
227    pub doc: Option<String>,
228    /// The variant's discriminant value (matches the tag getter's result).
229    pub value: i32,
230    /// `{enum_c_tag}_{variant}` — the discriminant constant.
231    pub c_const: String,
232    /// `{c_tag}_{variant}_new(<field slots>, error* out_err) -> {c_tag}*`.
233    /// A unit variant's constructor takes only `out_err`.
234    pub create: AbiFn,
235    /// Associated data. Each field's getter is `{c_tag}_{variant}_get_{field}`
236    /// with an implicit leading `const {c_tag}* self`; empty for a unit variant.
237    pub fields: Vec<FieldBinding>,
238}
239
240/// A callback function-pointer typedef declared at module scope.
241#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct CallbackBinding {
243    pub name: String,
244    pub doc: Option<String>,
245    /// `{prefix}_{module_path}_{name}_fn`.
246    pub c_fn_type: String,
247    /// IR parameters of the callback (without the trailing context).
248    pub params: Vec<ParamBinding>,
249    /// The full ABI slot list, including the trailing `void* context`.
250    pub abi_params: Vec<AbiParam>,
251}
252
253/// A listener: a register/unregister pair bound to a callback.
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct ListenerBinding {
256    pub name: String,
257    pub doc: Option<String>,
258    /// The callback this listener fires (name within the same module).
259    pub event_callback: String,
260    /// The referenced callback's `_fn` typedef name.
261    pub callback_c_fn_type: String,
262    /// `uint64_t {prefix}_{path}_register_{name}({cb}_fn callback, void* context)`.
263    pub register_symbol: String,
264    /// `void {prefix}_{path}_unregister_{name}(uint64_t id)`.
265    pub unregister_symbol: String,
266}
267
268/// One module, flattened with its underscore-joined symbol path.
269#[derive(Debug, Clone, PartialEq, Eq)]
270pub struct ModuleBinding {
271    pub name: String,
272    /// Path segments from the root (e.g. `["outer", "inner"]`).
273    pub segments: Vec<String>,
274    /// Underscore-joined path used as the C symbol segment (e.g. `outer_inner`).
275    pub path: String,
276    pub doc: Option<String>,
277    pub enums: Vec<EnumBinding>,
278    pub structs: Vec<StructBinding>,
279    pub callbacks: Vec<CallbackBinding>,
280    pub listeners: Vec<ListenerBinding>,
281    pub functions: Vec<FnBinding>,
282}
283
284impl ModuleBinding {
285    /// Find a callback declared in this module by name.
286    pub fn callback(&self, name: &str) -> Option<&CallbackBinding> {
287        self.callbacks.iter().find(|c| c.name == name)
288    }
289
290    /// True when this module declares no API surface at all.
291    pub fn is_empty(&self) -> bool {
292        self.enums.is_empty()
293            && self.structs.is_empty()
294            && self.callbacks.is_empty()
295            && self.listeners.is_empty()
296            && self.functions.is_empty()
297    }
298}
299
300/// The whole API, normalized and lowered for code generation.
301#[derive(Debug, Clone, PartialEq, Eq)]
302pub struct BindingModel {
303    /// The C symbol prefix every emitted name is built from.
304    pub prefix: String,
305    /// The IR schema version of the source `Api`.
306    pub version: String,
307    /// Modules in depth-first pre-order, each carrying its joined symbol path.
308    pub modules: Vec<ModuleBinding>,
309}
310
311impl BindingModel {
312    /// Build the model from a validated [`Api`], using `prefix` for every C
313    /// symbol name. `prefix` is the single global ABI prefix (default
314    /// `"weaveffi"`); passing the same prefix to every backend is what keeps
315    /// the producer header and all consumers calling identical symbols.
316    pub fn build(api: &Api, prefix: &str) -> Self {
317        let mut modules = Vec::new();
318        for m in &api.modules {
319            lower_module(m, &[], prefix, &mut modules);
320        }
321        Self {
322            prefix: prefix.to_string(),
323            version: api.version.clone(),
324            modules,
325        }
326    }
327
328    /// Iterate every function across all modules, paired with its module.
329    pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
330        self.modules
331            .iter()
332            .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
333    }
334}
335
336/// Recursively lower `module` and its descendants into the flat `out` list,
337/// pre-order (parent before children) so symbol declarations precede uses.
338fn lower_module(module: &Module, parent: &[String], prefix: &str, out: &mut Vec<ModuleBinding>) {
339    let mut segments = parent.to_vec();
340    segments.push(module.name.clone());
341    let path = segments.join("_");
342
343    let enums = module
344        .enums
345        .iter()
346        .map(|e| lower_enum(e, &path, prefix))
347        .collect();
348    let structs = module
349        .structs
350        .iter()
351        .map(|s| lower_struct(s, &path, prefix))
352        .collect();
353    let callbacks: Vec<CallbackBinding> = module
354        .callbacks
355        .iter()
356        .map(|c| lower_callback(c, &path, prefix))
357        .collect();
358    let listeners = module
359        .listeners
360        .iter()
361        .map(|l| lower_listener(l, &path, prefix))
362        .collect();
363    let functions = module
364        .functions
365        .iter()
366        .map(|f| lower_function(f, &path, prefix))
367        .collect();
368
369    // Module doc is synthesized from the first documented function, matching
370    // the `EmptyModuleDoc` lint's notion of "the module is documented".
371    let doc = module.functions.iter().find_map(|f| f.doc.clone());
372
373    out.push(ModuleBinding {
374        name: module.name.clone(),
375        segments: segments.clone(),
376        path,
377        doc,
378        enums,
379        structs,
380        callbacks,
381        listeners,
382        functions,
383    });
384
385    for child in &module.modules {
386        lower_module(child, &segments, prefix, out);
387    }
388}
389
390fn lower_param_binding(p: &weaveffi_ir::ir::Param, module: &str) -> ParamBinding {
391    ParamBinding {
392        name: p.name.clone(),
393        ty: p.ty.clone(),
394        mutable: p.mutable,
395        doc: p.doc.clone(),
396        abi: lower_param(&p.name, &p.ty, module, p.mutable),
397    }
398}
399
400fn lower_enum(e: &EnumDef, path: &str, prefix: &str) -> EnumBinding {
401    let c_tag = format!("{prefix}_{path}_{}", e.name);
402    let variants = e
403        .variants
404        .iter()
405        .map(|v| EnumVariantBinding {
406            name: v.name.clone(),
407            value: v.value,
408            doc: v.doc.clone(),
409            c_const: format!("{c_tag}_{}", v.name),
410        })
411        .collect();
412
413    // A rich (algebraic) enum gains an opaque-object surface mirroring a
414    // struct: a tag getter, a destructor, and per-variant constructors and
415    // field getters. The variant name namespaces the per-variant symbols.
416    let rich = e.is_rich().then(|| {
417        let variants = e
418            .variants
419            .iter()
420            .map(|v| {
421                let fields: Vec<FieldBinding> = v
422                    .fields
423                    .iter()
424                    .map(|f| {
425                        let r = lower_return(&f.ty, path);
426                        FieldBinding {
427                            name: f.name.clone(),
428                            doc: f.doc.clone(),
429                            ty: f.ty.clone(),
430                            getter_symbol: format!("{c_tag}_{}_get_{}", v.name, f.name),
431                            getter_ret: r.ret,
432                            getter_out_params: r.out_params,
433                            value_params: lower_param(&f.name, &f.ty, path, false),
434                        }
435                    })
436                    .collect();
437                let mut create_params: Vec<AbiParam> = v
438                    .fields
439                    .iter()
440                    .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
441                    .collect();
442                create_params.push(error_out_param());
443                let create = AbiFn {
444                    symbol: format!("{c_tag}_{}_new", v.name),
445                    params: create_params,
446                    ret: CType::ptr(CType::Named(format!("{path}_{}", e.name))),
447                };
448                RichVariantBinding {
449                    name: v.name.clone(),
450                    doc: v.doc.clone(),
451                    value: v.value,
452                    c_const: format!("{c_tag}_{}", v.name),
453                    create,
454                    fields,
455                }
456            })
457            .collect();
458        RichEnumBinding {
459            tag_symbol: format!("{c_tag}_tag"),
460            destroy_symbol: format!("{c_tag}_destroy"),
461            variants,
462        }
463    });
464
465    EnumBinding {
466        name: e.name.clone(),
467        doc: e.doc.clone(),
468        c_tag,
469        variants,
470        rich,
471    }
472}
473
474fn lower_struct(s: &StructDef, path: &str, prefix: &str) -> StructBinding {
475    let c_tag = format!("{prefix}_{path}_{}", s.name);
476
477    let fields: Vec<FieldBinding> = s
478        .fields
479        .iter()
480        .map(|f| {
481            let r = lower_return(&f.ty, path);
482            FieldBinding {
483                name: f.name.clone(),
484                doc: f.doc.clone(),
485                ty: f.ty.clone(),
486                getter_symbol: format!("{c_tag}_get_{}", f.name),
487                getter_ret: r.ret,
488                getter_out_params: r.out_params,
489                value_params: lower_param(&f.name, &f.ty, path, false),
490            }
491        })
492        .collect();
493
494    // create: each field lowered as an input parameter, then out_err.
495    let mut create_params: Vec<AbiParam> = s
496        .fields
497        .iter()
498        .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
499        .collect();
500    create_params.push(error_out_param());
501    let create = AbiFn {
502        symbol: format!("{c_tag}_create"),
503        params: create_params,
504        ret: CType::ptr(CType::Named(format!("{path}_{}", s.name))),
505    };
506
507    let builder = s.builder.then(|| {
508        let builder_tag = format!("{c_tag}Builder");
509        let setters = s
510            .fields
511            .iter()
512            .map(|f| (f.name.clone(), format!("{c_tag}_Builder_set_{}", f.name)))
513            .collect();
514        BuilderBinding {
515            builder_tag,
516            new_symbol: format!("{c_tag}_Builder_new"),
517            build_symbol: format!("{c_tag}_Builder_build"),
518            destroy_symbol: format!("{c_tag}_Builder_destroy"),
519            setters,
520        }
521    });
522
523    StructBinding {
524        name: s.name.clone(),
525        doc: s.doc.clone(),
526        c_tag: c_tag.clone(),
527        fields,
528        create,
529        destroy_symbol: format!("{c_tag}_destroy"),
530        builder,
531    }
532}
533
534fn lower_callback(c: &CallbackDef, path: &str, prefix: &str) -> CallbackBinding {
535    let params: Vec<ParamBinding> = c
536        .params
537        .iter()
538        .map(|p| lower_param_binding(p, path))
539        .collect();
540    let mut abi_params: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.clone()).collect();
541    abi_params.push(context_param());
542    CallbackBinding {
543        name: c.name.clone(),
544        doc: c.doc.clone(),
545        c_fn_type: format!("{prefix}_{path}_{}_fn", c.name),
546        params,
547        abi_params,
548    }
549}
550
551fn lower_listener(l: &ListenerDef, path: &str, prefix: &str) -> ListenerBinding {
552    ListenerBinding {
553        name: l.name.clone(),
554        doc: l.doc.clone(),
555        event_callback: l.event_callback.clone(),
556        callback_c_fn_type: format!("{prefix}_{path}_{}_fn", l.event_callback),
557        register_symbol: format!("{prefix}_{path}_register_{}", l.name),
558        unregister_symbol: format!("{prefix}_{path}_unregister_{}", l.name),
559    }
560}
561
562fn lower_function(f: &Function, path: &str, prefix: &str) -> FnBinding {
563    let params: Vec<ParamBinding> = f
564        .params
565        .iter()
566        .map(|p| lower_param_binding(p, path))
567        .collect();
568    let c_base = format!("{prefix}_{path}_{}", f.name);
569
570    let shape = if let Some(TypeRef::Iterator(inner)) = &f.returns {
571        let pascal = f.name.to_upper_camel_case();
572        let iter_tag = format!("{prefix}_{path}_{pascal}Iterator");
573        let iter_core = format!("{path}_{pascal}Iterator");
574
575        // launcher: input slots + out_err, returns iter_tag*.
576        let mut launch_params: Vec<AbiParam> = f
577            .params
578            .iter()
579            .flat_map(|p| lower_param(&p.name, &p.ty, path, p.mutable))
580            .collect();
581        launch_params.push(error_out_param());
582        let launch = AbiFn {
583            symbol: c_base.clone(),
584            params: launch_params,
585            ret: CType::ptr(CType::Named(iter_core.clone())),
586        };
587
588        // next: (iter, out_item, <item out_params>, out_err) -> int32.
589        let item = lower_return(inner, path);
590        let mut next_params = vec![
591            AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
592            AbiParam::new("out_item", CType::ptr(item.ret)),
593        ];
594        next_params.extend(item.out_params);
595        next_params.push(error_out_param());
596        let next = AbiFn {
597            symbol: format!("{iter_tag}_next"),
598            params: next_params,
599            ret: CType::Int32,
600        };
601
602        CallShape::Iterator(IteratorBinding {
603            elem: (**inner).clone(),
604            iter_tag: iter_tag.clone(),
605            launch,
606            next,
607            destroy_symbol: format!("{iter_tag}_destroy"),
608        })
609    } else if f.r#async {
610        let callback_type = format!("{c_base}_callback");
611        let mut launch_params = async_input_params(f, path);
612        launch_params.push(AbiParam::new(
613            "callback",
614            CType::Named(format!("{path}_{}_callback", f.name)),
615        ));
616        launch_params.push(context_param());
617        let launch = AbiFn {
618            symbol: format!("{c_base}_async"),
619            params: launch_params,
620            ret: CType::Void,
621        };
622        CallShape::Async(AsyncBinding {
623            launch,
624            callback_type,
625            callback_params: async_callback_params(f.returns.as_ref(), path),
626        })
627    } else {
628        let sig = sync_signature(&f.params, f.returns.as_ref(), path);
629        CallShape::Sync(AbiFn {
630            symbol: c_base.clone(),
631            params: sig.params,
632            ret: sig.ret,
633        })
634    };
635
636    FnBinding {
637        name: f.name.clone(),
638        doc: f.doc.clone(),
639        deprecated: f.deprecated.clone(),
640        since: f.since.clone(),
641        cancellable: f.cancellable,
642        is_async: f.r#async,
643        params,
644        ret: f.returns.clone(),
645        c_base,
646        shape,
647    }
648}
649
650/// The element C type of an iterator's `out_item` slot (the pointee of
651/// `T* out_item`). Exposed for backends that materialize iterator results.
652pub fn iterator_item_ctype(elem: &TypeRef, module: &str) -> CType {
653    abi::lower_return(elem, module).ret
654}
655
656#[cfg(test)]
657mod tests {
658    use super::*;
659    use weaveffi_ir::ir::{
660        CallbackDef, EnumDef, EnumVariant, Function, ListenerDef, Module, Param, StructDef,
661        StructField,
662    };
663
664    fn param(name: &str, ty: TypeRef) -> Param {
665        Param {
666            name: name.into(),
667            ty,
668            mutable: false,
669            doc: None,
670        }
671    }
672
673    fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
674        Function {
675            name: name.into(),
676            params,
677            returns,
678            doc: None,
679            r#async: false,
680            cancellable: false,
681            deprecated: None,
682            since: None,
683        }
684    }
685
686    fn module(name: &str) -> Module {
687        Module {
688            name: name.into(),
689            functions: vec![],
690            structs: vec![],
691            enums: vec![],
692            callbacks: vec![],
693            listeners: vec![],
694            errors: None,
695            modules: vec![],
696        }
697    }
698
699    fn api(modules: Vec<Module>) -> Api {
700        Api {
701            version: "0.4.0".into(),
702            modules,
703            generators: None,
704            package: None,
705        }
706    }
707
708    #[test]
709    fn sync_function_symbol_and_sig() {
710        let m = Module {
711            functions: vec![func(
712                "add",
713                vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
714                Some(TypeRef::I32),
715            )],
716            ..module("math")
717        };
718        let model = BindingModel::build(&api(vec![m]), "weaveffi");
719        let f = &model.modules[0].functions[0];
720        assert_eq!(f.c_base, "weaveffi_math_add");
721        match &f.shape {
722            CallShape::Sync(abi) => {
723                assert_eq!(abi.symbol, "weaveffi_math_add");
724                assert_eq!(abi.ret, CType::Int32);
725                let rendered: Vec<String> = abi
726                    .params
727                    .iter()
728                    .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
729                    .collect();
730                assert_eq!(
731                    rendered,
732                    ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
733                );
734            }
735            _ => panic!("expected sync"),
736        }
737    }
738
739    #[test]
740    fn prefix_is_honored_everywhere() {
741        let m = Module {
742            functions: vec![func("ping", vec![], None)],
743            ..module("net")
744        };
745        let model = BindingModel::build(&api(vec![m]), "acme");
746        let f = &model.modules[0].functions[0];
747        assert_eq!(f.c_base, "acme_net_ping");
748    }
749
750    #[test]
751    fn async_function_has_launch_and_callback() {
752        let m = Module {
753            functions: vec![Function {
754                cancellable: true,
755                r#async: true,
756                ..func(
757                    "fetch",
758                    vec![param("id", TypeRef::I64)],
759                    Some(TypeRef::StringUtf8),
760                )
761            }],
762            ..module("net")
763        };
764        let model = BindingModel::build(&api(vec![m]), "weaveffi");
765        match &model.modules[0].functions[0].shape {
766            CallShape::Async(a) => {
767                assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
768                assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
769                let last_two: Vec<&str> = a
770                    .launch
771                    .params
772                    .iter()
773                    .rev()
774                    .take(2)
775                    .map(|p| p.name.as_str())
776                    .collect();
777                assert_eq!(last_two, ["context", "callback"]);
778                // cancel_token slot is present before callback/context.
779                assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
780                // callback prefix is (context, err, result).
781                assert_eq!(a.callback_params[0].name, "context");
782                assert_eq!(a.callback_params[1].name, "err");
783            }
784            _ => panic!("expected async"),
785        }
786    }
787
788    #[test]
789    fn iterator_function_has_next_and_destroy() {
790        let m = Module {
791            functions: vec![func(
792                "get_messages",
793                vec![],
794                Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
795            )],
796            ..module("events")
797        };
798        let model = BindingModel::build(&api(vec![m]), "weaveffi");
799        match &model.modules[0].functions[0].shape {
800            CallShape::Iterator(it) => {
801                assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
802                assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
803                assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
804                assert_eq!(
805                    it.destroy_symbol,
806                    "weaveffi_events_GetMessagesIterator_destroy"
807                );
808                assert_eq!(it.next.ret, CType::Int32);
809                // out_item is `const char** out_item` for a string element.
810                let out_item = &it.next.params[1];
811                assert_eq!(out_item.name, "out_item");
812                assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
813            }
814            _ => panic!("expected iterator"),
815        }
816    }
817
818    #[test]
819    fn struct_create_getters_and_builder() {
820        let m = Module {
821            structs: vec![StructDef {
822                name: "Contact".into(),
823                doc: None,
824                fields: vec![
825                    StructField {
826                        name: "name".into(),
827                        ty: TypeRef::StringUtf8,
828                        doc: None,
829                        default: None,
830                    },
831                    StructField {
832                        name: "age".into(),
833                        ty: TypeRef::I32,
834                        doc: None,
835                        default: None,
836                    },
837                ],
838                builder: true,
839            }],
840            ..module("contacts")
841        };
842        let model = BindingModel::build(&api(vec![m]), "weaveffi");
843        let s = &model.modules[0].structs[0];
844        assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
845        assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
846        assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
847        assert_eq!(
848            s.fields[0].getter_symbol,
849            "weaveffi_contacts_Contact_get_name"
850        );
851        let b = s.builder.as_ref().unwrap();
852        assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
853        assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
854        assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
855    }
856
857    #[test]
858    fn enum_constants_are_prefixed() {
859        let m = Module {
860            enums: vec![EnumDef {
861                name: "Color".into(),
862                doc: None,
863                variants: vec![
864                    EnumVariant {
865                        name: "Red".into(),
866                        value: 0,
867                        doc: None,
868                        fields: vec![],
869                    },
870                    EnumVariant {
871                        name: "Green".into(),
872                        value: 1,
873                        doc: None,
874                        fields: vec![],
875                    },
876                ],
877            }],
878            ..module("gfx")
879        };
880        let model = BindingModel::build(&api(vec![m]), "weaveffi");
881        let e = &model.modules[0].enums[0];
882        assert_eq!(e.c_tag, "weaveffi_gfx_Color");
883        assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
884        assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
885    }
886
887    #[test]
888    fn callbacks_and_listeners_are_linked() {
889        let m = Module {
890            callbacks: vec![CallbackDef {
891                name: "on_message".into(),
892                params: vec![param("text", TypeRef::StringUtf8)],
893                doc: None,
894            }],
895            listeners: vec![ListenerDef {
896                name: "messages".into(),
897                event_callback: "on_message".into(),
898                doc: None,
899            }],
900            ..module("events")
901        };
902        let model = BindingModel::build(&api(vec![m]), "weaveffi");
903        let mb = &model.modules[0];
904        let cb = &mb.callbacks[0];
905        assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
906        // context appended last.
907        assert_eq!(cb.abi_params.last().unwrap().name, "context");
908        let l = &mb.listeners[0];
909        assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
910        assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
911        assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
912        assert!(mb.callback("on_message").is_some());
913    }
914
915    #[test]
916    fn nested_modules_flatten_pre_order_with_paths() {
917        let inner = Module {
918            functions: vec![func("leaf_fn", vec![], None)],
919            ..module("inner")
920        };
921        let outer = Module {
922            functions: vec![func("outer_fn", vec![], None)],
923            modules: vec![inner],
924            ..module("outer")
925        };
926        let model = BindingModel::build(&api(vec![outer]), "weaveffi");
927        let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
928        assert_eq!(paths, ["outer", "outer_inner"]);
929        assert_eq!(
930            model.modules[1].functions[0].c_base,
931            "weaveffi_outer_inner_leaf_fn"
932        );
933    }
934}