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, ErrorDomain, Function, InterfaceDef, ListenerDef, Module, StructDef,
27    TypeRef,
28};
29
30use crate::abi::{
31    self, async_callback_params, async_input_params, context_param, error_out_param, lower_param,
32    lower_return, sync_signature, AbiParam, CType, ConstPos,
33};
34
35/// A single lowered C symbol: its name, ordered ABI parameter slots, and C
36/// return type. This is what a backend declares to its FFI layer and calls.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct AbiFn {
39    /// The fully-qualified, prefixed C symbol (e.g. `weaveffi_math_add`).
40    pub symbol: String,
41    /// Ordered parameter slots, including any trailing `out_*` and `out_err`.
42    pub params: Vec<AbiParam>,
43    /// The C return type.
44    pub ret: CType,
45}
46
47/// How a function crosses the boundary. Exactly one shape applies to any given
48/// function: synchronous, asynchronous (callback-completed), or iterator-returning.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum CallShape {
51    /// A plain blocking call: [`AbiFn`] is the symbol to invoke.
52    Sync(AbiFn),
53    /// An async launcher plus its completion-callback typedef.
54    Async(AsyncBinding),
55    /// An iterator-returning function: an opaque handle plus `next`/`destroy`.
56    Iterator(IteratorBinding),
57}
58
59/// The lowered surface of an `async` function.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct AsyncBinding {
62    /// The launcher: input slots, optional `cancel_token`, then `callback` and
63    /// `context`. Returns `void`.
64    pub launch: AbiFn,
65    /// The completion-callback function-pointer typedef name
66    /// (`{symbol}_callback`).
67    pub callback_type: String,
68    /// The callback's parameter slots: `(void* context, {prefix}_error* err,
69    /// <result fields>)`.
70    pub callback_params: Vec<AbiParam>,
71}
72
73/// The lowered surface of an `iter<T>`-returning function.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct IteratorBinding {
76    /// The element type `T` of `iter<T>`.
77    pub elem: TypeRef,
78    /// The opaque iterator tag (`{prefix}_{path}_{Pascal}Iterator`).
79    pub iter_tag: String,
80    /// The launcher returning `{iter_tag}*`.
81    pub launch: AbiFn,
82    /// `int32_t {iter_tag}_next({iter_tag}* iter, T* out_item, …, error* out_err)`.
83    pub next: AbiFn,
84    /// `void {iter_tag}_destroy({iter_tag}* iter)`.
85    pub destroy_symbol: String,
86}
87
88/// One IR parameter, retained with its lowered ABI slots.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct ParamBinding {
91    /// The parameter name as written in the IDL.
92    pub name: String,
93    /// The idiomatic IR type a backend renders the parameter as.
94    pub ty: TypeRef,
95    /// Whether the parameter is mutable (drops the `const` on its pointer slots).
96    pub mutable: bool,
97    /// Optional doc comment carried from the IDL.
98    pub doc: Option<String>,
99    /// The ordered C ABI slots this single parameter expands into.
100    pub abi: Vec<AbiParam>,
101}
102
103/// A function, fully lowered.
104///
105/// Free functions and interface members share this shape. For an instance
106/// method, [`has_self`](Self::has_self) is `true` and every [`AbiFn`] in
107/// [`shape`](Self::shape) carries an implicit leading `const {c_tag}* self`
108/// slot that does **not** appear in [`params`](Self::params); a wrapper
109/// passes its own native handle there.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct FnBinding {
112    /// The function name as written in the IDL.
113    pub name: String,
114    /// Optional doc comment carried from the IDL.
115    pub doc: Option<String>,
116    /// Deprecation message when the function is marked deprecated, else `None`.
117    pub deprecated: Option<String>,
118    /// The version the function was introduced, when the IDL records one.
119    pub since: Option<String>,
120    /// Whether an async function accepts a trailing `cancel_token` slot.
121    pub cancellable: bool,
122    /// Whether the function is `async` (lowered as a callback-completed launcher).
123    pub is_async: bool,
124    /// Whether the function reports typed domain errors. A throwing function
125    /// surfaces as `throws`/`raises` in idiomatic wrappers using the module's
126    /// [`ErrorBinding`]; a non-throwing function has a plain signature, and a
127    /// reported error (only ever a producer panic) surfaces as the target's
128    /// unrecoverable-error idiom instead.
129    pub throws: bool,
130    /// `true` for an instance method: the ABI signatures carry an implicit
131    /// leading `self` slot not present in [`params`](Self::params).
132    pub has_self: bool,
133    /// IR input parameters with their lowered slots.
134    pub params: Vec<ParamBinding>,
135    /// The IR return type (`None` = void). For an iterator function this is the
136    /// `iter<T>` type itself; the element `T` also lives in [`IteratorBinding`].
137    /// For an interface constructor this is the constructed interface type.
138    pub ret: Option<TypeRef>,
139    /// Base C symbol (`{prefix}_{module_path}_{name}` for a free function,
140    /// `{c_tag}_{name}` for an interface member) before any `_async`/iterator
141    /// suffixing.
142    pub c_base: String,
143    /// The call shape (sync / async / iterator).
144    pub shape: CallShape,
145}
146
147/// A struct field, retained with its getter symbol and lowered return.
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct FieldBinding {
150    /// The field name as written in the IDL.
151    pub name: String,
152    /// Optional doc comment carried from the IDL.
153    pub doc: Option<String>,
154    /// The idiomatic IR type of the field.
155    pub ty: TypeRef,
156    /// `{c_tag}_get_{field}`. Receiver is an implicit `const {c_tag}* ptr`; any
157    /// `out_*` slots are in [`getter_out_params`](Self::getter_out_params).
158    pub getter_symbol: String,
159    /// The C return type of the getter.
160    pub getter_ret: CType,
161    /// Trailing `out_*` slots of the getter (e.g. `size_t* out_len` for bytes).
162    pub getter_out_params: Vec<AbiParam>,
163    /// The ABI slots this field expands into when passed *in* (struct create,
164    /// builder setter).
165    pub value_params: Vec<AbiParam>,
166}
167
168/// The fluent builder lowered for a struct that opted in with `builder: true`.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct BuilderBinding {
171    /// `{c_tag}Builder`.
172    pub builder_tag: String,
173    /// `{c_tag}_Builder_new`.
174    pub new_symbol: String,
175    /// `{c_tag}_Builder_build` (carries a trailing `out_err`).
176    pub build_symbol: String,
177    /// `{c_tag}_Builder_destroy`.
178    pub destroy_symbol: String,
179    /// One `(field_name, setter_symbol)` per field; the value slots are the
180    /// field's [`FieldBinding::value_params`].
181    pub setters: Vec<(String, String)>,
182}
183
184/// A struct, fully lowered.
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct StructBinding {
187    /// The struct name as written in the IDL.
188    pub name: String,
189    /// Optional doc comment carried from the IDL.
190    pub doc: Option<String>,
191    /// `{prefix}_{module_path}_{name}`, the opaque tag.
192    pub c_tag: String,
193    /// The struct's fields, each with its getter symbol and lowered slots.
194    pub fields: Vec<FieldBinding>,
195    /// `{c_tag}_create(<field slots>, error* out_err) -> {c_tag}*`.
196    pub create: AbiFn,
197    /// `{c_tag}_destroy`.
198    pub destroy_symbol: String,
199    /// Present when `builder: true`.
200    pub builder: Option<BuilderBinding>,
201}
202
203/// An enum, fully lowered.
204///
205/// A *C-style* enum (every variant a bare discriminant) carries only
206/// [`variants`](Self::variants) and crosses the ABI by value as an integer. An
207/// *algebraic* (sum-type) enum, at least one variant with associated data,
208/// additionally carries [`rich`](Self::rich) and crosses the ABI as an opaque
209/// object pointer (tag getter + per-variant constructors and field getters +
210/// destructor), exactly like a struct.
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct EnumBinding {
213    /// The enum name as written in the IDL.
214    pub name: String,
215    /// Optional doc comment carried from the IDL.
216    pub doc: Option<String>,
217    /// `{prefix}_{module_path}_{name}`.
218    pub c_tag: String,
219    /// Every variant's discriminant name/value, in declaration order. Present
220    /// for both kinds (the discriminant of a rich enum is its tag value).
221    pub variants: Vec<EnumVariantBinding>,
222    /// `Some` iff this is a rich (algebraic) enum.
223    pub rich: Option<RichEnumBinding>,
224}
225
226impl EnumBinding {
227    /// `true` when this is a rich (algebraic) sum-type enum.
228    pub fn is_rich(&self) -> bool {
229        self.rich.is_some()
230    }
231}
232
233/// A single enum variant with its precomputed C constant name.
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct EnumVariantBinding {
236    /// The variant name as written in the IDL.
237    pub name: String,
238    /// The variant's integer discriminant.
239    pub value: i32,
240    /// Optional doc comment carried from the IDL.
241    pub doc: Option<String>,
242    /// `{enum_c_tag}_{variant}`.
243    pub c_const: String,
244}
245
246/// The opaque-object surface of a rich (algebraic) enum: how its tag is read,
247/// how each variant is constructed and projected, and how the object is freed.
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct RichEnumBinding {
250    /// `int32_t {tag_symbol}(const {c_tag}* self)`: returns the active
251    /// variant's discriminant (matching the per-variant
252    /// [`c_const`](EnumVariantBinding::c_const) values).
253    pub tag_symbol: String,
254    /// `void {destroy_symbol}({c_tag}* self)`.
255    pub destroy_symbol: String,
256    /// Per-variant constructors and field getters, in declaration order
257    /// (parallel to [`EnumBinding::variants`]).
258    pub variants: Vec<RichVariantBinding>,
259}
260
261/// One variant of a rich enum: its constructor and the getters for its
262/// associated data.
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct RichVariantBinding {
265    /// The variant name as written in the IDL.
266    pub name: String,
267    /// Optional doc comment carried from the IDL.
268    pub doc: Option<String>,
269    /// The variant's discriminant value (matches the tag getter's result).
270    pub value: i32,
271    /// `{enum_c_tag}_{variant}`, the discriminant constant.
272    pub c_const: String,
273    /// `{c_tag}_{variant}_new(<field slots>, error* out_err) -> {c_tag}*`.
274    /// A unit variant's constructor takes only `out_err`.
275    pub create: AbiFn,
276    /// Associated data. Each field's getter is `{c_tag}_{variant}_get_{field}`
277    /// with an implicit leading `const {c_tag}* self`; empty for a unit variant.
278    pub fields: Vec<FieldBinding>,
279}
280
281/// A callback function-pointer typedef declared at module scope.
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct CallbackBinding {
284    /// The callback name as written in the IDL.
285    pub name: String,
286    /// Optional doc comment carried from the IDL.
287    pub doc: Option<String>,
288    /// `{prefix}_{module_path}_{name}_fn`.
289    pub c_fn_type: String,
290    /// IR parameters of the callback (without the trailing context).
291    pub params: Vec<ParamBinding>,
292    /// The full ABI slot list, including the trailing `void* context`.
293    pub abi_params: Vec<AbiParam>,
294}
295
296/// A listener: a register/unregister pair bound to a callback.
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct ListenerBinding {
299    /// The listener name as written in the IDL.
300    pub name: String,
301    /// Optional doc comment carried from the IDL.
302    pub doc: Option<String>,
303    /// The callback this listener fires (name within the same module).
304    pub event_callback: String,
305    /// The referenced callback's `_fn` typedef name.
306    pub callback_c_fn_type: String,
307    /// `uint64_t {prefix}_{path}_register_{name}({cb}_fn callback, void* context)`.
308    pub register_symbol: String,
309    /// `void {prefix}_{path}_unregister_{name}(uint64_t id)`.
310    pub unregister_symbol: String,
311}
312
313/// An interface (opaque object type), fully lowered.
314///
315/// Constructors, methods, and statics are all [`FnBinding`]s sharing the
316/// member symbol scheme `{c_tag}_{name}`. Methods additionally carry an
317/// implicit leading `const {c_tag}* self` ABI slot ([`FnBinding::has_self`]).
318/// A constructor's [`FnBinding::ret`] is synthesized as the interface type
319/// itself, so wrappers can reuse their ordinary return-marshalling path.
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct InterfaceBinding {
322    /// The interface name as written in the IDL.
323    pub name: String,
324    /// Optional doc comment carried from the IDL.
325    pub doc: Option<String>,
326    /// `{prefix}_{module_path}_{name}`, the opaque tag.
327    pub c_tag: String,
328    /// Constructors, lowered as statics returning `{c_tag}*`.
329    pub constructors: Vec<FnBinding>,
330    /// Instance methods, each with the implicit `self` slot.
331    pub methods: Vec<FnBinding>,
332    /// Static functions namespaced under the interface.
333    pub statics: Vec<FnBinding>,
334    /// `void {c_tag}_destroy({c_tag}* self)`: releases the object reference.
335    pub destroy_symbol: String,
336}
337
338/// One error code of a module's error domain, with its C constant name.
339#[derive(Debug, Clone, PartialEq, Eq)]
340pub struct ErrorCodeBinding {
341    /// The code name exactly as written in the IDL (e.g. `KEY_NOT_FOUND`).
342    pub name: String,
343    /// The numeric ABI code carried in `{prefix}_error.code`.
344    pub value: i32,
345    /// The default human-readable message for the code.
346    pub message: String,
347    /// Optional doc comment carried from the IDL.
348    pub doc: Option<String>,
349    /// `{domain_c_tag}_{name}`, the C enum constant.
350    pub c_const: String,
351}
352
353/// The error domain in effect for a module: its own `errors:` block, or the
354/// nearest ancestor's when the module declares none.
355///
356/// Every throwing function in the module reports codes from this domain.
357/// Backends emit one error type per *declaring* module
358/// ([`declared_here`](Self::declared_here) is `true`) and reference the
359/// ancestor's type from inheriting submodules.
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct ErrorBinding {
362    /// The domain name as written in the IDL (e.g. `KvError`).
363    pub name: String,
364    /// PascalCase type name with exactly one `Error` suffix (e.g. `KvError`);
365    /// backends that brand exceptions swap the suffix via
366    /// [`crate::errors::type_name`].
367    pub type_name: String,
368    /// Underscore-joined path of the module that *declares* the domain.
369    pub owner_path: String,
370    /// `true` when this module declares the domain itself; `false` when it
371    /// inherits the domain from an ancestor module.
372    pub declared_here: bool,
373    /// `{prefix}_{owner_path}_{name}`, the C tag naming the domain's code
374    /// constants.
375    pub c_tag: String,
376    /// The domain's codes in declaration order.
377    pub codes: Vec<ErrorCodeBinding>,
378}
379
380/// One module, flattened with its underscore-joined symbol path.
381#[derive(Debug, Clone, PartialEq, Eq)]
382pub struct ModuleBinding {
383    /// The module name (its final path segment).
384    pub name: String,
385    /// Path segments from the root (e.g. `["outer", "inner"]`).
386    pub segments: Vec<String>,
387    /// Underscore-joined path used as the C symbol segment (e.g. `outer_inner`).
388    pub path: String,
389    /// Module doc, taken from the first documented function in the module.
390    pub doc: Option<String>,
391    /// The error domain in effect for this module's throwing functions:
392    /// its own domain, the nearest ancestor's, or `None` when no domain is in
393    /// scope (in which case validation has rejected any `throws` here).
394    pub error: Option<ErrorBinding>,
395    /// Enums declared in this module, fully lowered.
396    pub enums: Vec<EnumBinding>,
397    /// Structs declared in this module, fully lowered.
398    pub structs: Vec<StructBinding>,
399    /// Interfaces declared in this module, fully lowered.
400    pub interfaces: Vec<InterfaceBinding>,
401    /// Callback typedefs declared in this module.
402    pub callbacks: Vec<CallbackBinding>,
403    /// Listeners declared in this module.
404    pub listeners: Vec<ListenerBinding>,
405    /// Functions declared in this module, fully lowered.
406    pub functions: Vec<FnBinding>,
407}
408
409impl ModuleBinding {
410    /// Find a callback declared in this module by name.
411    pub fn callback(&self, name: &str) -> Option<&CallbackBinding> {
412        self.callbacks.iter().find(|c| c.name == name)
413    }
414
415    /// True when this module declares no API surface at all.
416    pub fn is_empty(&self) -> bool {
417        self.enums.is_empty()
418            && self.structs.is_empty()
419            && self.interfaces.is_empty()
420            && self.callbacks.is_empty()
421            && self.listeners.is_empty()
422            && self.functions.is_empty()
423            && !self.declares_error()
424    }
425
426    /// True when this module declares its own error domain (as opposed to
427    /// inheriting one from an ancestor).
428    pub fn declares_error(&self) -> bool {
429        self.error.as_ref().is_some_and(|e| e.declared_here)
430    }
431
432    /// Every callable in this module: free functions, then each interface's
433    /// constructors, methods, and statics.
434    pub fn callables(&self) -> impl Iterator<Item = &FnBinding> {
435        self.functions
436            .iter()
437            .chain(self.interfaces.iter().flat_map(|i| {
438                i.constructors
439                    .iter()
440                    .chain(i.methods.iter())
441                    .chain(i.statics.iter())
442            }))
443    }
444}
445
446/// The whole API, normalized and lowered for code generation.
447#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct BindingModel {
449    /// The C symbol prefix every emitted name is built from.
450    pub prefix: String,
451    /// The IR schema version of the source `Api`.
452    pub version: String,
453    /// Modules in depth-first pre-order, each carrying its joined symbol path.
454    pub modules: Vec<ModuleBinding>,
455}
456
457impl BindingModel {
458    /// Build the model from a validated [`Api`], using `prefix` for every C
459    /// symbol name. `prefix` is the single global ABI prefix (default
460    /// `"weaveffi"`); passing the same prefix to every backend is what keeps
461    /// the producer header and all consumers calling identical symbols.
462    pub fn build(api: &Api, prefix: &str) -> Self {
463        let mut modules = Vec::new();
464        for m in &api.modules {
465            lower_module(m, &[], prefix, None, &mut modules);
466        }
467        Self {
468            prefix: prefix.to_string(),
469            version: api.version.clone(),
470            modules,
471        }
472    }
473
474    /// Iterate every function across all modules, paired with its module.
475    pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
476        self.modules
477            .iter()
478            .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
479    }
480}
481
482/// Recursively lower `module` and its descendants into the flat `out` list,
483/// pre-order (parent before children) so symbol declarations precede uses.
484/// `inherited_error` is the nearest ancestor's error domain, threaded down so
485/// every module knows which domain its throwing functions report.
486fn lower_module(
487    module: &Module,
488    parent: &[String],
489    prefix: &str,
490    inherited_error: Option<&ErrorBinding>,
491    out: &mut Vec<ModuleBinding>,
492) {
493    let mut segments = parent.to_vec();
494    segments.push(module.name.clone());
495    let path = segments.join("_");
496
497    let error = match &module.errors {
498        Some(domain) => Some(lower_error_domain(domain, &path, prefix)),
499        None => inherited_error.cloned().map(|mut e| {
500            e.declared_here = false;
501            e
502        }),
503    };
504
505    let enums = module
506        .enums
507        .iter()
508        .map(|e| lower_enum(e, &path, prefix))
509        .collect();
510    let structs = module
511        .structs
512        .iter()
513        .map(|s| lower_struct(s, &path, prefix))
514        .collect();
515    let interfaces = module
516        .interfaces
517        .iter()
518        .map(|i| lower_interface(i, &path, prefix))
519        .collect();
520    let callbacks: Vec<CallbackBinding> = module
521        .callbacks
522        .iter()
523        .map(|c| lower_callback(c, &path, prefix))
524        .collect();
525    let listeners = module
526        .listeners
527        .iter()
528        .map(|l| lower_listener(l, &path, prefix))
529        .collect();
530    let functions = module
531        .functions
532        .iter()
533        .map(|f| lower_function(f, &path, prefix))
534        .collect();
535
536    // Module doc is synthesized from the first documented function, matching
537    // the `EmptyModuleDoc` lint's notion of "the module is documented".
538    let doc = module.functions.iter().find_map(|f| f.doc.clone());
539
540    out.push(ModuleBinding {
541        name: module.name.clone(),
542        segments: segments.clone(),
543        path,
544        doc,
545        error: error.clone(),
546        enums,
547        structs,
548        interfaces,
549        callbacks,
550        listeners,
551        functions,
552    });
553
554    for child in &module.modules {
555        lower_module(child, &segments, prefix, error.as_ref(), out);
556    }
557}
558
559fn lower_error_domain(domain: &ErrorDomain, path: &str, prefix: &str) -> ErrorBinding {
560    let c_tag = format!("{prefix}_{path}_{}", domain.name);
561    ErrorBinding {
562        name: domain.name.clone(),
563        type_name: crate::errors::type_name(&domain.name, "Error"),
564        owner_path: path.to_string(),
565        declared_here: true,
566        c_tag: c_tag.clone(),
567        codes: domain
568            .codes
569            .iter()
570            .map(|c| ErrorCodeBinding {
571                name: c.name.clone(),
572                value: c.code,
573                message: c.message.clone(),
574                doc: c.doc.clone(),
575                c_const: format!("{c_tag}_{}", c.name),
576            })
577            .collect(),
578    }
579}
580
581/// Lower an interface: constructors become statics returning the interface,
582/// methods gain the implicit `self` slot, and all member symbols hang off the
583/// interface's `c_tag`.
584fn lower_interface(iface: &InterfaceDef, path: &str, prefix: &str) -> InterfaceBinding {
585    let c_tag = format!("{prefix}_{path}_{}", iface.name);
586    let self_slot = AbiParam::new(
587        "self",
588        CType::Ptr {
589            konst: ConstPos::West,
590            pointee: Box::new(CType::StructTag {
591                module: path.to_string(),
592                name: iface.name.clone(),
593            }),
594        },
595    );
596    let constructors = iface
597        .constructors
598        .iter()
599        .map(|c| {
600            // Synthesize the return: a constructor yields a new owned
601            // reference to the interface, exactly like a static returning it.
602            let mut f = c.clone();
603            f.returns = Some(TypeRef::Interface(iface.name.clone()));
604            lower_callable(&f, path, prefix, &member_base(&c_tag, &c.name), None)
605        })
606        .collect();
607    let methods = iface
608        .methods
609        .iter()
610        .map(|m| {
611            lower_callable(
612                m,
613                path,
614                prefix,
615                &member_base(&c_tag, &m.name),
616                Some(self_slot.clone()),
617            )
618        })
619        .collect();
620    let statics = iface
621        .statics
622        .iter()
623        .map(|s| lower_callable(s, path, prefix, &member_base(&c_tag, &s.name), None))
624        .collect();
625    InterfaceBinding {
626        name: iface.name.clone(),
627        doc: iface.doc.clone(),
628        c_tag: c_tag.clone(),
629        constructors,
630        methods,
631        statics,
632        destroy_symbol: format!("{c_tag}_destroy"),
633    }
634}
635
636/// The base C symbol of an interface member: `{c_tag}_{name}`.
637fn member_base(c_tag: &str, name: &str) -> String {
638    format!("{c_tag}_{name}")
639}
640
641fn lower_param_binding(p: &weaveffi_ir::ir::Param, module: &str) -> ParamBinding {
642    ParamBinding {
643        name: p.name.clone(),
644        ty: p.ty.clone(),
645        mutable: p.mutable,
646        doc: p.doc.clone(),
647        abi: lower_param(&p.name, &p.ty, module, p.mutable),
648    }
649}
650
651fn lower_enum(e: &EnumDef, path: &str, prefix: &str) -> EnumBinding {
652    let c_tag = format!("{prefix}_{path}_{}", e.name);
653    let variants = e
654        .variants
655        .iter()
656        .map(|v| EnumVariantBinding {
657            name: v.name.clone(),
658            value: v.value,
659            doc: v.doc.clone(),
660            c_const: format!("{c_tag}_{}", v.name),
661        })
662        .collect();
663
664    // A rich (algebraic) enum gains an opaque-object surface mirroring a
665    // struct: a tag getter, a destructor, and per-variant constructors and
666    // field getters. The variant name namespaces the per-variant symbols.
667    let rich = e.is_rich().then(|| {
668        let variants = e
669            .variants
670            .iter()
671            .map(|v| {
672                let fields: Vec<FieldBinding> = v
673                    .fields
674                    .iter()
675                    .map(|f| {
676                        let r = lower_return(&f.ty, path);
677                        FieldBinding {
678                            name: f.name.clone(),
679                            doc: f.doc.clone(),
680                            ty: f.ty.clone(),
681                            getter_symbol: format!("{c_tag}_{}_get_{}", v.name, f.name),
682                            getter_ret: r.ret,
683                            getter_out_params: r.out_params,
684                            value_params: lower_param(&f.name, &f.ty, path, false),
685                        }
686                    })
687                    .collect();
688                let mut create_params: Vec<AbiParam> = v
689                    .fields
690                    .iter()
691                    .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
692                    .collect();
693                create_params.push(error_out_param());
694                let create = AbiFn {
695                    symbol: format!("{c_tag}_{}_new", v.name),
696                    params: create_params,
697                    ret: CType::ptr(CType::Named(format!("{path}_{}", e.name))),
698                };
699                RichVariantBinding {
700                    name: v.name.clone(),
701                    doc: v.doc.clone(),
702                    value: v.value,
703                    c_const: format!("{c_tag}_{}", v.name),
704                    create,
705                    fields,
706                }
707            })
708            .collect();
709        RichEnumBinding {
710            tag_symbol: format!("{c_tag}_tag"),
711            destroy_symbol: format!("{c_tag}_destroy"),
712            variants,
713        }
714    });
715
716    EnumBinding {
717        name: e.name.clone(),
718        doc: e.doc.clone(),
719        c_tag,
720        variants,
721        rich,
722    }
723}
724
725fn lower_struct(s: &StructDef, path: &str, prefix: &str) -> StructBinding {
726    let c_tag = format!("{prefix}_{path}_{}", s.name);
727
728    let fields: Vec<FieldBinding> = s
729        .fields
730        .iter()
731        .map(|f| {
732            let r = lower_return(&f.ty, path);
733            FieldBinding {
734                name: f.name.clone(),
735                doc: f.doc.clone(),
736                ty: f.ty.clone(),
737                getter_symbol: format!("{c_tag}_get_{}", f.name),
738                getter_ret: r.ret,
739                getter_out_params: r.out_params,
740                value_params: lower_param(&f.name, &f.ty, path, false),
741            }
742        })
743        .collect();
744
745    // create: each field lowered as an input parameter, then out_err.
746    let mut create_params: Vec<AbiParam> = s
747        .fields
748        .iter()
749        .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
750        .collect();
751    create_params.push(error_out_param());
752    let create = AbiFn {
753        symbol: format!("{c_tag}_create"),
754        params: create_params,
755        ret: CType::ptr(CType::Named(format!("{path}_{}", s.name))),
756    };
757
758    let builder = s.builder.then(|| {
759        let builder_tag = format!("{c_tag}Builder");
760        let setters = s
761            .fields
762            .iter()
763            .map(|f| (f.name.clone(), format!("{c_tag}_Builder_set_{}", f.name)))
764            .collect();
765        BuilderBinding {
766            builder_tag,
767            new_symbol: format!("{c_tag}_Builder_new"),
768            build_symbol: format!("{c_tag}_Builder_build"),
769            destroy_symbol: format!("{c_tag}_Builder_destroy"),
770            setters,
771        }
772    });
773
774    StructBinding {
775        name: s.name.clone(),
776        doc: s.doc.clone(),
777        c_tag: c_tag.clone(),
778        fields,
779        create,
780        destroy_symbol: format!("{c_tag}_destroy"),
781        builder,
782    }
783}
784
785fn lower_callback(c: &CallbackDef, path: &str, prefix: &str) -> CallbackBinding {
786    let params: Vec<ParamBinding> = c
787        .params
788        .iter()
789        .map(|p| lower_param_binding(p, path))
790        .collect();
791    let mut abi_params: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.clone()).collect();
792    abi_params.push(context_param());
793    CallbackBinding {
794        name: c.name.clone(),
795        doc: c.doc.clone(),
796        c_fn_type: format!("{prefix}_{path}_{}_fn", c.name),
797        params,
798        abi_params,
799    }
800}
801
802fn lower_listener(l: &ListenerDef, path: &str, prefix: &str) -> ListenerBinding {
803    ListenerBinding {
804        name: l.name.clone(),
805        doc: l.doc.clone(),
806        event_callback: l.event_callback.clone(),
807        callback_c_fn_type: format!("{prefix}_{path}_{}_fn", l.event_callback),
808        register_symbol: format!("{prefix}_{path}_register_{}", l.name),
809        unregister_symbol: format!("{prefix}_{path}_unregister_{}", l.name),
810    }
811}
812
813fn lower_function(f: &Function, path: &str, prefix: &str) -> FnBinding {
814    let c_base = format!("{prefix}_{path}_{}", f.name);
815    lower_callable(f, path, prefix, &c_base, None)
816}
817
818/// Lower one callable (free function or interface member) whose full base C
819/// symbol is `c_base`. When `self_slot` is given (an instance method), it is
820/// prepended to every ABI signature but never appears in the retained
821/// [`ParamBinding`] list.
822fn lower_callable(
823    f: &Function,
824    path: &str,
825    prefix: &str,
826    c_base: &str,
827    self_slot: Option<AbiParam>,
828) -> FnBinding {
829    let params: Vec<ParamBinding> = f
830        .params
831        .iter()
832        .map(|p| lower_param_binding(p, path))
833        .collect();
834    // The prefix-stripped spelling used for `CType::Named` cores (which render
835    // as `{prefix}_{core}`), e.g. `kv_Store_scan` from `weaveffi_kv_Store_scan`.
836    let core_base = c_base
837        .strip_prefix(&format!("{prefix}_"))
838        .expect("c_base always starts with the symbol prefix")
839        .to_string();
840    let with_self = |mut params: Vec<AbiParam>| {
841        if let Some(s) = &self_slot {
842            params.insert(0, s.clone());
843        }
844        params
845    };
846
847    let shape = if let Some(TypeRef::Iterator(inner)) = &f.returns {
848        let pascal = f.name.to_upper_camel_case();
849        // `{owner}_{Pascal}Iterator`, where owner is the module path for a
850        // free function or `{module path}_{Interface}` for a method.
851        let owner = &core_base[..core_base.len() - f.name.len() - 1];
852        let iter_core = format!("{owner}_{pascal}Iterator");
853        let iter_tag = format!("{prefix}_{iter_core}");
854
855        // launcher: (self,) input slots + out_err, returns iter_tag*.
856        let mut launch_params: Vec<AbiParam> = f
857            .params
858            .iter()
859            .flat_map(|p| lower_param(&p.name, &p.ty, path, p.mutable))
860            .collect();
861        launch_params.push(error_out_param());
862        let launch = AbiFn {
863            symbol: c_base.to_string(),
864            params: with_self(launch_params),
865            ret: CType::ptr(CType::Named(iter_core.clone())),
866        };
867
868        // next: (iter, out_item, <item out_params>, out_err) -> int32.
869        let item = lower_return(inner, path);
870        let mut next_params = vec![
871            AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
872            AbiParam::new("out_item", CType::ptr(item.ret)),
873        ];
874        next_params.extend(item.out_params);
875        next_params.push(error_out_param());
876        let next = AbiFn {
877            symbol: format!("{iter_tag}_next"),
878            params: next_params,
879            ret: CType::Int32,
880        };
881
882        CallShape::Iterator(IteratorBinding {
883            elem: (**inner).clone(),
884            iter_tag: iter_tag.clone(),
885            launch,
886            next,
887            destroy_symbol: format!("{iter_tag}_destroy"),
888        })
889    } else if f.r#async {
890        let callback_type = format!("{c_base}_callback");
891        let mut launch_params = async_input_params(f, path);
892        launch_params.push(AbiParam::new(
893            "callback",
894            CType::Named(format!("{core_base}_callback")),
895        ));
896        launch_params.push(context_param());
897        let launch = AbiFn {
898            symbol: format!("{c_base}_async"),
899            params: with_self(launch_params),
900            ret: CType::Void,
901        };
902        CallShape::Async(AsyncBinding {
903            launch,
904            callback_type,
905            callback_params: async_callback_params(f.returns.as_ref(), path),
906        })
907    } else {
908        let sig = sync_signature(&f.params, f.returns.as_ref(), path);
909        CallShape::Sync(AbiFn {
910            symbol: c_base.to_string(),
911            params: with_self(sig.params),
912            ret: sig.ret,
913        })
914    };
915
916    FnBinding {
917        name: f.name.clone(),
918        doc: f.doc.clone(),
919        deprecated: f.deprecated.clone(),
920        since: f.since.clone(),
921        cancellable: f.cancellable,
922        is_async: f.r#async,
923        throws: f.throws,
924        has_self: self_slot.is_some(),
925        params,
926        ret: f.returns.clone(),
927        c_base: c_base.to_string(),
928        shape,
929    }
930}
931
932/// The element C type of an iterator's `out_item` slot (the pointee of
933/// `T* out_item`). Exposed for backends that materialize iterator results.
934pub fn iterator_item_ctype(elem: &TypeRef, module: &str) -> CType {
935    abi::lower_return(elem, module).ret
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use weaveffi_ir::ir::{
942        CallbackDef, EnumDef, EnumVariant, Function, ListenerDef, Module, Param, StructDef,
943        StructField,
944    };
945
946    fn param(name: &str, ty: TypeRef) -> Param {
947        Param {
948            name: name.into(),
949            ty,
950            mutable: false,
951            doc: None,
952        }
953    }
954
955    fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
956        Function {
957            name: name.into(),
958            params,
959            returns,
960            doc: None,
961            throws: false,
962            r#async: false,
963            cancellable: false,
964            deprecated: None,
965            since: None,
966        }
967    }
968
969    fn module(name: &str) -> Module {
970        Module {
971            name: name.into(),
972            functions: vec![],
973            interfaces: vec![],
974            structs: vec![],
975            enums: vec![],
976            callbacks: vec![],
977            listeners: vec![],
978            errors: None,
979            modules: vec![],
980        }
981    }
982
983    fn api(modules: Vec<Module>) -> Api {
984        Api {
985            version: "0.5.0".into(),
986            modules,
987            generators: None,
988            package: None,
989        }
990    }
991
992    #[test]
993    fn sync_function_symbol_and_sig() {
994        let m = Module {
995            functions: vec![func(
996                "add",
997                vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
998                Some(TypeRef::I32),
999            )],
1000            ..module("math")
1001        };
1002        let model = BindingModel::build(&api(vec![m]), "weaveffi");
1003        let f = &model.modules[0].functions[0];
1004        assert_eq!(f.c_base, "weaveffi_math_add");
1005        match &f.shape {
1006            CallShape::Sync(abi) => {
1007                assert_eq!(abi.symbol, "weaveffi_math_add");
1008                assert_eq!(abi.ret, CType::Int32);
1009                let rendered: Vec<String> = abi
1010                    .params
1011                    .iter()
1012                    .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
1013                    .collect();
1014                assert_eq!(
1015                    rendered,
1016                    ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
1017                );
1018            }
1019            _ => panic!("expected sync"),
1020        }
1021    }
1022
1023    #[test]
1024    fn prefix_is_honored_everywhere() {
1025        let m = Module {
1026            functions: vec![func("ping", vec![], None)],
1027            ..module("net")
1028        };
1029        let model = BindingModel::build(&api(vec![m]), "acme");
1030        let f = &model.modules[0].functions[0];
1031        assert_eq!(f.c_base, "acme_net_ping");
1032    }
1033
1034    #[test]
1035    fn async_function_has_launch_and_callback() {
1036        let m = Module {
1037            functions: vec![Function {
1038                cancellable: true,
1039                throws: false,
1040                r#async: true,
1041                ..func(
1042                    "fetch",
1043                    vec![param("id", TypeRef::I64)],
1044                    Some(TypeRef::StringUtf8),
1045                )
1046            }],
1047            ..module("net")
1048        };
1049        let model = BindingModel::build(&api(vec![m]), "weaveffi");
1050        match &model.modules[0].functions[0].shape {
1051            CallShape::Async(a) => {
1052                assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
1053                assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
1054                let last_two: Vec<&str> = a
1055                    .launch
1056                    .params
1057                    .iter()
1058                    .rev()
1059                    .take(2)
1060                    .map(|p| p.name.as_str())
1061                    .collect();
1062                assert_eq!(last_two, ["context", "callback"]);
1063                // cancel_token slot is present before callback/context.
1064                assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
1065                // callback prefix is (context, err, result).
1066                assert_eq!(a.callback_params[0].name, "context");
1067                assert_eq!(a.callback_params[1].name, "err");
1068            }
1069            _ => panic!("expected async"),
1070        }
1071    }
1072
1073    #[test]
1074    fn iterator_function_has_next_and_destroy() {
1075        let m = Module {
1076            functions: vec![func(
1077                "get_messages",
1078                vec![],
1079                Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
1080            )],
1081            ..module("events")
1082        };
1083        let model = BindingModel::build(&api(vec![m]), "weaveffi");
1084        match &model.modules[0].functions[0].shape {
1085            CallShape::Iterator(it) => {
1086                assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
1087                assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
1088                assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
1089                assert_eq!(
1090                    it.destroy_symbol,
1091                    "weaveffi_events_GetMessagesIterator_destroy"
1092                );
1093                assert_eq!(it.next.ret, CType::Int32);
1094                // out_item is `const char** out_item` for a string element.
1095                let out_item = &it.next.params[1];
1096                assert_eq!(out_item.name, "out_item");
1097                assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
1098            }
1099            _ => panic!("expected iterator"),
1100        }
1101    }
1102
1103    #[test]
1104    fn struct_create_getters_and_builder() {
1105        let m = Module {
1106            interfaces: vec![],
1107            structs: vec![StructDef {
1108                name: "Contact".into(),
1109                doc: None,
1110                fields: vec![
1111                    StructField {
1112                        name: "name".into(),
1113                        ty: TypeRef::StringUtf8,
1114                        doc: None,
1115                        default: None,
1116                    },
1117                    StructField {
1118                        name: "age".into(),
1119                        ty: TypeRef::I32,
1120                        doc: None,
1121                        default: None,
1122                    },
1123                ],
1124                builder: true,
1125            }],
1126            ..module("contacts")
1127        };
1128        let model = BindingModel::build(&api(vec![m]), "weaveffi");
1129        let s = &model.modules[0].structs[0];
1130        assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
1131        assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
1132        assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
1133        assert_eq!(
1134            s.fields[0].getter_symbol,
1135            "weaveffi_contacts_Contact_get_name"
1136        );
1137        let b = s.builder.as_ref().unwrap();
1138        assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
1139        assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
1140        assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
1141    }
1142
1143    #[test]
1144    fn enum_constants_are_prefixed() {
1145        let m = Module {
1146            enums: vec![EnumDef {
1147                name: "Color".into(),
1148                doc: None,
1149                variants: vec![
1150                    EnumVariant {
1151                        name: "Red".into(),
1152                        value: 0,
1153                        doc: None,
1154                        fields: vec![],
1155                    },
1156                    EnumVariant {
1157                        name: "Green".into(),
1158                        value: 1,
1159                        doc: None,
1160                        fields: vec![],
1161                    },
1162                ],
1163            }],
1164            ..module("gfx")
1165        };
1166        let model = BindingModel::build(&api(vec![m]), "weaveffi");
1167        let e = &model.modules[0].enums[0];
1168        assert_eq!(e.c_tag, "weaveffi_gfx_Color");
1169        assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
1170        assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
1171    }
1172
1173    #[test]
1174    fn callbacks_and_listeners_are_linked() {
1175        let m = Module {
1176            callbacks: vec![CallbackDef {
1177                name: "on_message".into(),
1178                params: vec![param("text", TypeRef::StringUtf8)],
1179                doc: None,
1180            }],
1181            listeners: vec![ListenerDef {
1182                name: "messages".into(),
1183                event_callback: "on_message".into(),
1184                doc: None,
1185            }],
1186            ..module("events")
1187        };
1188        let model = BindingModel::build(&api(vec![m]), "weaveffi");
1189        let mb = &model.modules[0];
1190        let cb = &mb.callbacks[0];
1191        assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
1192        // context appended last.
1193        assert_eq!(cb.abi_params.last().unwrap().name, "context");
1194        let l = &mb.listeners[0];
1195        assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
1196        assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
1197        assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
1198        assert!(mb.callback("on_message").is_some());
1199    }
1200
1201    #[test]
1202    fn nested_modules_flatten_pre_order_with_paths() {
1203        let inner = Module {
1204            functions: vec![func("leaf_fn", vec![], None)],
1205            ..module("inner")
1206        };
1207        let outer = Module {
1208            functions: vec![func("outer_fn", vec![], None)],
1209            modules: vec![inner],
1210            ..module("outer")
1211        };
1212        let model = BindingModel::build(&api(vec![outer]), "weaveffi");
1213        let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
1214        assert_eq!(paths, ["outer", "outer_inner"]);
1215        assert_eq!(
1216            model.modules[1].functions[0].c_base,
1217            "weaveffi_outer_inner_leaf_fn"
1218        );
1219    }
1220}