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