Skip to main content

weaveffi_model/model/
mod.rs

1//! The **model**: the one validated, fully lowered view of an API that every
2//! language backend consumes.
3//!
4//! [`validate`](crate::validate::validate) checks an [`Api`] and builds the
5//! [`Model`] exactly once per run. The model owns everything a generator
6//! reads:
7//!
8//! * the library's [`Identity`] (C symbol prefix, library name, package
9//!   metadata) and the schema version;
10//! * a flat list of [`ModuleBinding`]s in which every **type** is a resolved
11//!   [`Ty`] (no unresolved names), so a backend dispatches on [`Ty::family`]
12//!   and [`Ty::wire`] instead of re-deriving what a name means;
13//! * every emitted **C symbol name**, precomputed once, so all backends agree
14//!   by construction;
15//! * every function, interface member, and callback-interface method paired
16//!   with its lowered [`AbiFn`] signature (built from [`crate::abi`]), so no
17//!   backend re-derives parameter arity, ordering, or `out_*`/`out_err`
18//!   placement; and
19//! * the [`TypeIndex`], which maps each (global) type name to its
20//!   declaration and owning module, behind typed lookups such as
21//!   [`Model::interface`] and [`Model::owner`].
22//!
23//! A backend reads the *idiomatic* shape from the retained [`Ty`]s
24//! (`param.ty`, `field.ty`, ...) and the *native* shape from the [`AbiFn`]s,
25//! then writes only the marshalling that bridges the two (see
26//! [`crate::plan`]) in its own idioms. Generators never see the [`Api`].
27
28mod build;
29
30pub(crate) use build::{build_indexed, index};
31
32use crate::abi::{AbiParam, CType};
33use crate::ir::{Api, TypeRef};
34use crate::pkg::Identity;
35use crate::ty::{Ty, TypeDecl, TypeIndex, TypeKind};
36
37/// The runtime symbols every producer exports (and every C header declares),
38/// without the `{prefix}_` that begins each one. See the C ABI contract for
39/// their signatures.
40pub const RUNTIME_SYMBOLS: &[&str] = &[
41    "str",
42    "bytes",
43    "reader",
44    "writer",
45    "abi_version",
46    "error",
47    "error_set",
48    "error_set_payload",
49    "error_clear",
50    "error_free",
51    "free_bytes",
52    "cancel_token",
53    "cancel_token_create",
54    "cancel_token_cancel",
55    "cancel_token_is_cancelled",
56    "cancel_token_destroy",
57    "alloc",
58    "debug_live",
59    "contract_entry",
60];
61
62/// Identifier families the generated C value-buffer helper header
63/// (`{library}_buffer.h`) declares, without the `{prefix}_` that begins each:
64/// a user declaration whose C symbol falls in one of these families would
65/// collide with a helper.
66pub const RESERVED_SYMBOL_FAMILIES: &[&str] = &[
67    "str_", "bytes_", "reader_", "writer_", "list_", "map_", "opt_",
68];
69
70/// The codec functions the C value-buffer helper header declares for each
71/// record, rich enum, and error payload struct `T`: `T_write`, `T_read`,
72/// `T_decode`, and `T_free`.
73pub const VALUE_CODECS: &[&str] = &["write", "read", "decode", "free"];
74
75/// The C ABI revision this model lowers to. Producers export it from
76/// `{prefix}_abi_version()` and every generated consumer checks it at load.
77pub const ABI_VERSION: u32 = 4;
78
79/// The symbol of a top-level module's contract table function,
80/// `{prefix}_{module}_contract` (see [`crate::contract`]).
81#[must_use]
82pub fn contract_symbol(prefix: &str, module: &str) -> String {
83    format!("{prefix}_{module}_contract")
84}
85
86/// The C header's `static inline` checker of a top-level module's contract,
87/// `{prefix}_{module}_contract_check`.
88#[must_use]
89pub fn contract_check_symbol(prefix: &str, module: &str) -> String {
90    format!("{prefix}_{module}_contract_check")
91}
92
93/// A single lowered C symbol: its name, ordered ABI parameter slots, and C
94/// return type. This is what a backend declares to its FFI layer and calls.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct AbiFn {
97    /// The fully-qualified, prefixed C symbol (e.g. `weaveffi_math_add`).
98    pub symbol: String,
99    /// Ordered parameter slots, including any trailing `out_*` and `out_err`.
100    pub params: Vec<AbiParam>,
101    /// The C return type.
102    pub ret: CType,
103}
104
105/// How a function crosses the boundary. Exactly one shape applies to any given
106/// function: synchronous, asynchronous (callback-completed), or iterator-returning.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum CallShape {
109    /// A plain blocking call: [`AbiFn`] is the symbol to invoke.
110    Sync(AbiFn),
111    /// An async launcher plus its completion-callback typedef.
112    Async(AsyncBinding),
113    /// An iterator-returning function: an opaque handle plus `next`/`destroy`.
114    Iterator(IteratorBinding),
115}
116
117/// The lowered surface of an `async` function.
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct AsyncBinding {
120    /// The launcher: input slots, optional `cancel_token`, then `callback` and
121    /// `context`. Returns `void`.
122    pub launch: AbiFn,
123    /// The completion-callback function-pointer typedef name
124    /// (`{symbol}_callback`).
125    pub callback_type: String,
126    /// The callback's parameter slots: `(void* context, {prefix}_error* err,
127    /// <result fields>)`.
128    pub callback_params: Vec<AbiParam>,
129}
130
131/// The lowered surface of an `iter<T>`-returning function.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct IteratorBinding {
134    /// The element type `T` of `iter<T>`.
135    pub elem: Ty,
136    /// The opaque iterator tag (`{prefix}_{path}_{Pascal}Iterator`).
137    pub iter_tag: String,
138    /// The launcher returning `{iter_tag}*`.
139    pub launch: AbiFn,
140    /// `int32_t {iter_tag}_next({iter_tag}* iter, T* out_item, ..., error* out_err)`.
141    pub next: AbiFn,
142    /// `void {iter_tag}_destroy({iter_tag}* iter)`.
143    pub destroy_symbol: String,
144}
145
146impl IteratorBinding {
147    /// The C type of one element: the pointee of `next`'s `T* out_item`
148    /// slot.
149    ///
150    /// # Panics
151    ///
152    /// Panics if the model built `next` without its `out_item` pointer slot,
153    /// which would be a bug in the model construction.
154    pub fn item_ctype(&self) -> &CType {
155        match &self.next.params[1].ty {
156            CType::Ptr { pointee, .. } => pointee,
157            other => panic!("iterator `out_item` slot is {other:?}, not a pointer"),
158        }
159    }
160}
161
162/// One IR parameter, retained with its lowered ABI slots.
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct ParamBinding {
165    /// The parameter name as written in the IDL.
166    pub name: String,
167    /// The resolved type a backend renders the parameter as.
168    pub ty: Ty,
169    /// Optional doc comment carried from the IDL.
170    pub doc: Option<String>,
171    /// The ordered C ABI slots this single parameter expands into.
172    pub abi: Vec<AbiParam>,
173}
174
175/// A function, fully lowered.
176///
177/// Free functions and interface members share this shape. For an instance
178/// method, [`has_self`](Self::has_self) is `true` and every [`AbiFn`] in
179/// [`shape`](Self::shape) carries an implicit leading `const {c_tag}* self`
180/// slot that does **not** appear in [`params`](Self::params); a wrapper
181/// passes its own native handle there.
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct FnBinding {
184    /// The function name as written in the IDL.
185    pub name: String,
186    /// Optional doc comment carried from the IDL.
187    pub doc: Option<String>,
188    /// Deprecation message when the function is marked deprecated, else `None`.
189    pub deprecated: Option<String>,
190    /// Whether an async function accepts a trailing `cancel_token` slot.
191    pub cancellable: bool,
192    /// Whether the function reports typed domain errors. A throwing function
193    /// surfaces as `throws`/`raises` in idiomatic wrappers using the error
194    /// domain in scope ([`Model::error_domain`]); a non-throwing function
195    /// has a plain signature, and a
196    /// reported error (only ever a producer panic) surfaces as the target's
197    /// unrecoverable-error idiom instead.
198    pub throws: bool,
199    /// `true` for an instance method: the ABI signatures carry an implicit
200    /// leading `self` slot not present in [`params`](Self::params).
201    pub has_self: bool,
202    /// Input parameters with their lowered slots.
203    pub params: Vec<ParamBinding>,
204    /// The resolved return type (`None` = void). For an iterator function this
205    /// is the `iter<T>` type itself; the element `T` also lives in
206    /// [`IteratorBinding`]. For an interface constructor this is the
207    /// constructed interface type.
208    pub ret: Option<Ty>,
209    /// Base C symbol (`{prefix}_{module_path}_{name}` for a free function,
210    /// `{c_tag}_{name}` for an interface member) before any `_async`/iterator
211    /// suffixing.
212    pub c_base: String,
213    /// The call shape (sync / async / iterator).
214    pub shape: CallShape,
215}
216
217impl FnBinding {
218    /// `true` when the function is `async` (lowered as a callback-completed
219    /// launcher).
220    pub fn is_async(&self) -> bool {
221        matches!(self.shape, CallShape::Async(_))
222    }
223}
224
225/// A field of a record, a rich-enum variant, or an error code's payload.
226///
227/// Records and rich enums are value types: they declare no C symbols of their
228/// own and cross the ABI serialized inside a value buffer, so a field is just
229/// its name and type. Field declaration order **is** the wire order.
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct FieldBinding {
232    /// The field name as written in the IDL.
233    pub name: String,
234    /// Optional doc comment carried from the IDL.
235    pub doc: Option<String>,
236    /// The resolved type of the field.
237    pub ty: Ty,
238}
239
240/// A struct (record), fully lowered: a plain value type generators emit as a
241/// native data class plus buffer read/write functions. No C symbols exist for
242/// a record; instances cross the ABI serialized in value buffers.
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct StructBinding {
245    /// The struct name as written in the IDL.
246    pub name: String,
247    /// Optional doc comment carried from the IDL.
248    pub doc: Option<String>,
249    /// Deprecation message when the struct is marked deprecated, else `None`.
250    pub deprecated: Option<String>,
251    /// The fields in declaration (and wire) order.
252    pub fields: Vec<FieldBinding>,
253}
254
255/// An enum, fully lowered.
256///
257/// A *C-style* enum (every variant a bare discriminant) crosses the ABI by
258/// value as an integer. An *algebraic* (rich) enum, at least one variant with
259/// associated data, is a value type exactly like a struct: it crosses the ABI
260/// serialized in a value buffer as an `i32` tag followed by the active
261/// variant's fields in declaration order. Either way, the C header still
262/// emits the discriminant constants ([`EnumVariantBinding::c_const`]) so C
263/// consumers can switch on the value or tag.
264#[derive(Debug, Clone, PartialEq, Eq)]
265pub struct EnumBinding {
266    /// The enum name as written in the IDL.
267    pub name: String,
268    /// Optional doc comment carried from the IDL.
269    pub doc: Option<String>,
270    /// Deprecation message when the enum is marked deprecated, else `None`.
271    pub deprecated: Option<String>,
272    /// `{prefix}_{module_path}_{name}`.
273    pub c_tag: String,
274    /// Every variant, in declaration order.
275    pub variants: Vec<EnumVariantBinding>,
276    /// `true` when this is a rich (algebraic) sum-type enum: at least one
277    /// variant carries fields, and values cross the ABI as buffers.
278    pub rich: bool,
279}
280
281impl EnumBinding {
282    /// `true` when this is a rich (algebraic) sum-type enum.
283    pub fn is_rich(&self) -> bool {
284        self.rich
285    }
286}
287
288/// A single enum variant with its precomputed C constant name and any
289/// associated data.
290#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct EnumVariantBinding {
292    /// The variant name as written in the IDL.
293    pub name: String,
294    /// The variant's integer discriminant. Doubles as the buffer tag for a
295    /// rich enum.
296    pub value: i32,
297    /// Optional doc comment carried from the IDL.
298    pub doc: Option<String>,
299    /// `{enum_c_tag}_{variant}`.
300    pub c_const: String,
301    /// Associated data in declaration (and wire) order; empty for a unit
302    /// variant or a C-style enum.
303    pub fields: Vec<FieldBinding>,
304}
305
306/// An interface (reference-counted object type), fully lowered.
307///
308/// Constructors, methods, and statics are all [`FnBinding`]s sharing the
309/// member symbol scheme `{c_tag}_{name}`. Methods additionally carry an
310/// implicit leading `const {c_tag}* self` ABI slot ([`FnBinding::has_self`]).
311/// A constructor's [`FnBinding::ret`] is synthesized as the interface type
312/// itself, so wrappers can reuse their ordinary return-marshalling path.
313#[derive(Debug, Clone, PartialEq, Eq)]
314pub struct InterfaceBinding {
315    /// The interface name as written in the IDL.
316    pub name: String,
317    /// Optional doc comment carried from the IDL.
318    pub doc: Option<String>,
319    /// Deprecation message when the interface is marked deprecated, else `None`.
320    pub deprecated: Option<String>,
321    /// `{prefix}_{module_path}_{name}`, the opaque tag.
322    pub c_tag: String,
323    /// Constructors, lowered as statics returning `{c_tag}*`.
324    pub constructors: Vec<FnBinding>,
325    /// Instance methods, each with the implicit `self` slot.
326    pub methods: Vec<FnBinding>,
327    /// Static functions namespaced under the interface.
328    pub statics: Vec<FnBinding>,
329    /// `{c_tag}* {c_tag}_clone(const {c_tag}* self)`: returns a new strong
330    /// reference to the same object.
331    pub clone_symbol: String,
332    /// `void {c_tag}_destroy({c_tag}* self)`: releases one strong reference.
333    pub destroy_symbol: String,
334}
335
336/// One method of a callback interface, lowered to its vtable entry.
337///
338/// The consumer implements this; the producer calls it through the vtable.
339/// [`abi_params`](Self::abi_params) is the full C slot list of the vtable
340/// entry (`void* ctx`, the parameter slots, the return's out slots, then
341/// `{prefix}_error* out_err`) and [`abi_ret`](Self::abi_ret) its C return
342/// type.
343#[derive(Debug, Clone, PartialEq, Eq)]
344pub struct CallbackMethodBinding {
345    /// The method name as written in the IDL; also the vtable field name.
346    pub name: String,
347    /// Optional doc comment carried from the IDL.
348    pub doc: Option<String>,
349    /// Deprecation message when the method is marked deprecated, else `None`.
350    pub deprecated: Option<String>,
351    /// Whether the method may report a positive code of the error domain in
352    /// scope ([`Model::error_domain`]) through its `out_err`.
353    pub throws: bool,
354    /// Input parameters with their lowered slots.
355    pub params: Vec<ParamBinding>,
356    /// The resolved return type (`None` = void). Any family but iterators
357    /// and callback interfaces; see
358    /// [`lower_callback_return`](crate::abi::lower_callback_return).
359    pub ret: Option<Ty>,
360    /// The vtable entry's C parameter slots: `ctx`, then every parameter's
361    /// slots, then the return's out slots (`out_ptr` and `out_len` for a
362    /// string, bytes, or buffer return), then `out_err`.
363    pub abi_params: Vec<AbiParam>,
364    /// The vtable entry's C return type.
365    pub abi_ret: CType,
366}
367
368/// A callback interface, fully lowered.
369///
370/// The C ABI sees a vtable struct ([`vtable_tag`](Self::vtable_tag)) that
371/// starts with a fixed header, `uint32_t size` (the vtable's size as the
372/// consumer compiled it), `uint32_t flags` (reserved, `0`), and
373/// `void (*free)(void* ctx)`, followed by one function-pointer field per
374/// method in declaration order. A parameter of this type lowers to two
375/// slots, `void* {name}_ctx` and `const {vtable_tag}* {name}_vtable`.
376#[derive(Debug, Clone, PartialEq, Eq)]
377pub struct CallbackInterfaceBinding {
378    /// The callback interface name as written in the IDL.
379    pub name: String,
380    /// Optional doc comment carried from the IDL.
381    pub doc: Option<String>,
382    /// Deprecation message when the callback interface is marked deprecated,
383    /// else `None`.
384    pub deprecated: Option<String>,
385    /// `{prefix}_{module_path}_{name}`, the type's C name stem.
386    pub c_tag: String,
387    /// `{c_tag}_vtable`, the vtable struct tag.
388    pub vtable_tag: String,
389    /// Methods in declaration (and vtable) order. Never empty.
390    pub methods: Vec<CallbackMethodBinding>,
391}
392
393/// One error code of a module's error domain, with its C constant name.
394#[derive(Debug, Clone, PartialEq, Eq)]
395pub struct ErrorCodeBinding {
396    /// The code name exactly as written in the IDL (e.g. `KEY_NOT_FOUND`).
397    pub name: String,
398    /// The numeric ABI code carried in `{prefix}_error.code`.
399    pub value: i32,
400    /// The default human-readable message for the code.
401    pub message: String,
402    /// Optional doc comment carried from the IDL.
403    pub doc: Option<String>,
404    /// `{domain_c_tag}_{name}`, the C enum constant.
405    pub c_const: String,
406    /// Structured payload fields this code carries, in declaration (and wire)
407    /// order. When non-empty, a matching error's `payload_ptr`/`payload_len`
408    /// slots hold these fields serialized in the value-buffer format; empty
409    /// means the payload slots are null.
410    pub fields: Vec<FieldBinding>,
411}
412
413impl ErrorCodeBinding {
414    /// `{c_const}_payload`, the struct the C value-buffer header declares
415    /// for the code's [`fields`](Self::fields) (only when there are any).
416    pub fn payload_tag(&self) -> String {
417        format!("{}_payload", self.c_const)
418    }
419}
420
421/// An error domain, lowered on the module that declares it.
422///
423/// Every throwing function reports codes from the domain in scope for its
424/// module: the module's own, or the nearest ancestor's
425/// ([`Model::error_domain`]). Backends emit one error type per declaring
426/// module and reference it from inheriting submodules.
427#[derive(Debug, Clone, PartialEq, Eq)]
428pub struct ErrorBinding {
429    /// The domain name as written in the IDL (e.g. `KvError`).
430    pub name: String,
431    /// PascalCase type name with exactly one `Error` suffix (e.g. `KvError`);
432    /// backends that brand exceptions swap the suffix via
433    /// [`crate::errors::type_name`].
434    pub type_name: String,
435    /// Underscore-joined path of the module that declares the domain.
436    pub owner_path: String,
437    /// `{prefix}_{owner_path}_{name}`, the C tag naming the domain's code
438    /// constants.
439    pub c_tag: String,
440    /// The domain's codes in declaration order.
441    pub codes: Vec<ErrorCodeBinding>,
442}
443
444/// One module, flattened with its underscore-joined symbol path.
445#[derive(Debug, Clone, PartialEq, Eq)]
446pub struct ModuleBinding {
447    /// The module name (its final path segment).
448    pub name: String,
449    /// Path segments from the root (e.g. `["outer", "inner"]`).
450    pub segments: Vec<String>,
451    /// Underscore-joined path used as the C symbol segment (e.g. `outer_inner`).
452    pub path: String,
453    /// Dot-joined path (e.g. `outer.inner`), the declaration path diagnostics
454    /// and generated comments quote.
455    pub dot_path: String,
456    /// The module's doc comment, if the IDL records one.
457    pub doc: Option<String>,
458    /// The error domain this module declares, if any. A module without one
459    /// inherits the nearest ancestor's ([`Model::error_domain`]).
460    pub errors: Option<ErrorBinding>,
461    /// Enums declared in this module, fully lowered.
462    pub enums: Vec<EnumBinding>,
463    /// Structs declared in this module, fully lowered.
464    pub structs: Vec<StructBinding>,
465    /// Interfaces declared in this module, fully lowered.
466    pub interfaces: Vec<InterfaceBinding>,
467    /// Callback interfaces declared in this module, fully lowered.
468    pub callback_interfaces: Vec<CallbackInterfaceBinding>,
469    /// Functions declared in this module, fully lowered.
470    pub functions: Vec<FnBinding>,
471}
472
473impl ModuleBinding {
474    /// Every callable in this module: free functions, then each interface's
475    /// constructors, methods, and statics.
476    pub fn callables(&self) -> impl Iterator<Item = &FnBinding> {
477        self.functions
478            .iter()
479            .chain(self.interfaces.iter().flat_map(|i| {
480                i.constructors
481                    .iter()
482                    .chain(i.methods.iter())
483                    .chain(i.statics.iter())
484            }))
485    }
486
487    /// `true` when any callable in this module is `async`.
488    pub fn has_async(&self) -> bool {
489        self.callables().any(FnBinding::is_async)
490    }
491
492    /// `true` when any callable in this module returns an iterator.
493    pub fn has_iterators(&self) -> bool {
494        self.callables()
495            .any(|f| matches!(f.shape, CallShape::Iterator(_)))
496    }
497}
498
499/// The whole API, validated and lowered for code generation.
500///
501/// Built once per run by [`validate`](crate::validate::validate); every
502/// generator renders from it alone.
503#[derive(Debug, Clone, PartialEq, Eq)]
504pub struct Model {
505    /// The library's identity: its name, C symbol prefix, native library
506    /// name, and package metadata.
507    pub identity: Identity,
508    /// The IR schema version of the source document.
509    pub version: String,
510    /// Modules in depth-first pre-order, each carrying its joined symbol path.
511    pub modules: Vec<ModuleBinding>,
512    /// Every user type name, mapped to its declaration.
513    pub types: TypeIndex,
514}
515
516impl Model {
517    /// Build the model without validating the document.
518    ///
519    /// Type names that resolve to no declaration become [`Ty::Record`]
520    /// references named exactly as written. Tests use it for hand-built
521    /// trees; everything else goes through
522    /// [`validate`](crate::validate::validate).
523    #[doc(hidden)]
524    #[must_use]
525    pub fn assume_valid(api: &Api, identity: Identity) -> Self {
526        build::build(api, identity)
527    }
528
529    /// Resolve a written type reference against this model's declarations.
530    /// A name no declaration provides resolves to a [`Ty::Record`] (see
531    /// [`assume_valid`](Self::assume_valid)).
532    pub fn resolve(&self, ty: &TypeRef) -> Ty {
533        build::resolve(&self.types, ty)
534    }
535
536    /// The C symbol prefix every emitted name starts with.
537    pub fn prefix(&self) -> &str {
538        &self.identity.prefix
539    }
540
541    /// The top-level modules (those with a single path segment), in order.
542    pub fn roots(&self) -> impl Iterator<Item = &ModuleBinding> {
543        self.modules.iter().filter(|m| m.segments.len() == 1)
544    }
545
546    /// The direct submodules of `parent`, in declaration order. Backends that
547    /// render nested namespaces recurse with this instead of re-walking the
548    /// IR tree.
549    pub fn children<'a>(
550        &'a self,
551        parent: &'a ModuleBinding,
552    ) -> impl Iterator<Item = &'a ModuleBinding> + 'a {
553        self.modules.iter().filter(move |m| {
554            m.segments.len() == parent.segments.len() + 1
555                && m.segments[..parent.segments.len()] == parent.segments[..]
556        })
557    }
558
559    /// Iterate every function across all modules, paired with its module.
560    pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
561        self.modules
562            .iter()
563            .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
564    }
565
566    /// Iterate every callable (free functions and interface members) across
567    /// all modules, paired with its module.
568    pub fn callables(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
569        self.modules
570            .iter()
571            .flat_map(|m| m.callables().map(move |f| (m, f)))
572    }
573
574    /// Iterate every callback interface across all modules, paired with its
575    /// module.
576    pub fn callback_interfaces(
577        &self,
578    ) -> impl Iterator<Item = (&ModuleBinding, &CallbackInterfaceBinding)> {
579        self.modules
580            .iter()
581            .flat_map(|m| m.callback_interfaces.iter().map(move |c| (m, c)))
582    }
583
584    /// The declaration of the user type `name`.
585    ///
586    /// # Panics
587    ///
588    /// Panics when `name` isn't declared, which validation rules out for
589    /// every name a [`Ty`] carries.
590    fn decl(&self, name: &str, kinds: &[TypeKind]) -> &TypeDecl {
591        match self.types.get(name) {
592            Some(d) if kinds.contains(&d.kind) => d,
593            Some(d) => panic!("type '{name}' is a {:?}, not one of {kinds:?}", d.kind),
594            None => panic!("type '{name}' is not declared"),
595        }
596    }
597
598    /// The module declaring the user type `name`.
599    ///
600    /// # Panics
601    ///
602    /// Panics when `name` isn't declared, which validation rules out for
603    /// every name a [`Ty`] carries.
604    pub fn owner(&self, name: &str) -> &ModuleBinding {
605        let decl = self.types.get(name);
606        let decl = decl.unwrap_or_else(|| panic!("type '{name}' is not declared"));
607        &self.modules[decl.module]
608    }
609
610    /// The interface named `name`, as carried by [`Ty::Interface`].
611    ///
612    /// # Panics
613    ///
614    /// Panics when no interface has that name.
615    pub fn interface(&self, name: &str) -> &InterfaceBinding {
616        let d = self.decl(name, &[TypeKind::Interface]);
617        &self.modules[d.module].interfaces[d.index]
618    }
619
620    /// The callback interface named `name`, as carried by
621    /// [`Ty::CallbackInterface`].
622    ///
623    /// # Panics
624    ///
625    /// Panics when no callback interface has that name.
626    pub fn callback_interface(&self, name: &str) -> &CallbackInterfaceBinding {
627        let d = self.decl(name, &[TypeKind::CallbackInterface]);
628        &self.modules[d.module].callback_interfaces[d.index]
629    }
630
631    /// The enum (C-style or rich) named `name`, as carried by [`Ty::Enum`]
632    /// and [`Ty::RichEnum`].
633    ///
634    /// # Panics
635    ///
636    /// Panics when no enum has that name.
637    pub fn enumeration(&self, name: &str) -> &EnumBinding {
638        let d = self.decl(name, &[TypeKind::Enum, TypeKind::RichEnum]);
639        &self.modules[d.module].enums[d.index]
640    }
641
642    /// The error domain in scope for `module`'s throwing functions: its own,
643    /// else the nearest ancestor's, else `None` (in which case validation
644    /// has rejected any `throws` there).
645    pub fn error_domain(&self, module: &ModuleBinding) -> Option<&ErrorBinding> {
646        (1..=module.segments.len()).rev().find_map(|n| {
647            let scope = &module.segments[..n];
648            self.modules
649                .iter()
650                .find(|m| m.segments == scope)
651                .and_then(|m| m.errors.as_ref())
652        })
653    }
654
655    /// Every C identifier the library's ABI declares, paired with a
656    /// human-readable description of the declaration that owns it, in
657    /// declaration order. Validation rejects an API in which two entries share
658    /// an identifier.
659    ///
660    /// Covers the runtime surface ([`RUNTIME_SYMBOLS`]), each top-level
661    /// module's contract table function and the C header's checker for it,
662    /// every callable's symbols (sync entry point, async launcher and
663    /// completion type, iterator launcher, type, `_next`, and `_destroy`),
664    /// interface types with their `_clone` and `_destroy`, enum types and
665    /// constants (and a rich enum's tag type), error-domain types and code
666    /// constants, callback-interface vtable types, and what the C
667    /// value-buffer helper header declares per user type: the struct of
668    /// every record, rich enum, and error code with fields
669    /// ([`ErrorCodeBinding::payload_tag`]), each with its
670    /// [`VALUE_CODECS`]. (The header's list, map, and optional shapes fall
671    /// in [`RESERVED_SYMBOL_FAMILIES`].)
672    pub fn c_symbols(&self) -> Vec<(String, String)> {
673        let p = self.prefix();
674        let mut out: Vec<(String, String)> = RUNTIME_SYMBOLS
675            .iter()
676            .map(|s| (format!("{p}_{s}"), format!("the runtime symbol '{p}_{s}'")))
677            .collect();
678        for m in self.roots() {
679            out.push((
680                contract_symbol(p, &m.name),
681                format!("the contract table of module '{}'", m.name),
682            ));
683            out.push((
684                contract_check_symbol(p, &m.name),
685                format!("the contract check of module '{}'", m.name),
686            ));
687        }
688        let callable = |out: &mut Vec<(String, String)>, f: &FnBinding, owner: &str| {
689            let what = format!("'{owner}.{}'", f.name);
690            match &f.shape {
691                CallShape::Sync(abi) => out.push((abi.symbol.clone(), format!("function {what}"))),
692                CallShape::Async(a) => {
693                    out.push((a.launch.symbol.clone(), format!("async function {what}")));
694                    out.push((
695                        a.callback_type.clone(),
696                        format!("the completion type of {what}"),
697                    ));
698                }
699                CallShape::Iterator(it) => {
700                    out.push((it.launch.symbol.clone(), format!("function {what}")));
701                    out.push((it.iter_tag.clone(), format!("the iterator type of {what}")));
702                    out.push((
703                        it.next.symbol.clone(),
704                        format!("the iterator step of {what}"),
705                    ));
706                    out.push((
707                        it.destroy_symbol.clone(),
708                        format!("the iterator destructor of {what}"),
709                    ));
710                }
711            }
712        };
713        let codecs = |out: &mut Vec<(String, String)>, tag: &str, what: &str| {
714            for codec in VALUE_CODECS {
715                out.push((
716                    format!("{tag}_{codec}"),
717                    format!("the value-buffer codec '{tag}_{codec}' of {what}"),
718                ));
719            }
720        };
721        for m in &self.modules {
722            let dot = &m.dot_path;
723            if let Some(e) = &m.errors {
724                out.push((e.c_tag.clone(), format!("error domain '{dot}.{}'", e.name)));
725                for c in &e.codes {
726                    let what = format!("error code '{dot}.{}.{}'", e.name, c.name);
727                    out.push((c.c_const.clone(), what.clone()));
728                    if !c.fields.is_empty() {
729                        let payload = c.payload_tag();
730                        codecs(&mut out, &payload, &format!("the payload of {what}"));
731                        out.push((payload, format!("the payload struct of {what}")));
732                    }
733                }
734            }
735            for e in &m.enums {
736                out.push((e.c_tag.clone(), format!("enum '{dot}.{}'", e.name)));
737                if e.rich {
738                    out.push((
739                        format!("{}_Tag", e.c_tag),
740                        format!("the tag type of enum '{dot}.{}'", e.name),
741                    ));
742                    codecs(&mut out, &e.c_tag, &format!("enum '{dot}.{}'", e.name));
743                }
744                for v in &e.variants {
745                    out.push((
746                        v.c_const.clone(),
747                        format!("enum variant '{dot}.{}.{}'", e.name, v.name),
748                    ));
749                }
750            }
751            for s in &m.structs {
752                let tag = format!("{p}_{}_{}", m.path, s.name);
753                let what = format!("record '{dot}.{}'", s.name);
754                codecs(&mut out, &tag, &what);
755                out.push((tag, what));
756            }
757            for c in &m.callback_interfaces {
758                out.push((
759                    c.vtable_tag.clone(),
760                    format!("the vtable of callback interface '{dot}.{}'", c.name),
761                ));
762            }
763            for i in &m.interfaces {
764                let owner = format!("{dot}.{}", i.name);
765                out.push((i.c_tag.clone(), format!("interface '{owner}'")));
766                out.push((i.clone_symbol.clone(), format!("the clone of '{owner}'")));
767                out.push((
768                    i.destroy_symbol.clone(),
769                    format!("the destructor of '{owner}'"),
770                ));
771                for f in i.constructors.iter().chain(&i.methods).chain(&i.statics) {
772                    callable(&mut out, f, &owner);
773                }
774            }
775            for f in &m.functions {
776                callable(&mut out, f, dot);
777            }
778        }
779        out
780    }
781
782    /// `true` when any callable anywhere in the API is `async`.
783    pub fn has_async(&self) -> bool {
784        self.modules.iter().any(ModuleBinding::has_async)
785    }
786
787    /// `true` when any callable anywhere in the API returns an iterator.
788    pub fn has_iterators(&self) -> bool {
789        self.modules.iter().any(ModuleBinding::has_iterators)
790    }
791
792    /// `true` when the API declares any callback interface.
793    pub fn has_callback_interfaces(&self) -> bool {
794        self.modules
795            .iter()
796            .any(|m| !m.callback_interfaces.is_empty())
797    }
798
799    /// `true` when the API declares any interface.
800    pub fn has_interfaces(&self) -> bool {
801        self.modules.iter().any(|m| !m.interfaces.is_empty())
802    }
803
804    /// `true` when any type anywhere in the API crosses the ABI as a value
805    /// buffer (records, rich enums, optionals, lists, maps, error payloads).
806    pub fn has_buffers(&self) -> bool {
807        let buffered = |ty: &Ty| ty.any(&Ty::is_buffered);
808        let signature = |params: &[ParamBinding], ret: &Option<Ty>| {
809            params.iter().any(|p| buffered(&p.ty)) || ret.as_ref().is_some_and(buffered)
810        };
811        self.modules.iter().any(|m| {
812            !m.structs.is_empty()
813                || m.enums.iter().any(|e| e.rich)
814                || m.errors
815                    .as_ref()
816                    .is_some_and(|e| e.codes.iter().any(|c| !c.fields.is_empty()))
817                || m.callables().any(|f| signature(&f.params, &f.ret))
818                || m.callback_interfaces
819                    .iter()
820                    .any(|c| c.methods.iter().any(|f| signature(&f.params, &f.ret)))
821        })
822    }
823}
824
825#[cfg(test)]
826mod tests {
827    use super::*;
828    use crate::ir::{
829        CallbackInterfaceDef, EnumDef, EnumVariant, ErrorCode, ErrorDomain, Function, InterfaceDef,
830        Module, Param, StructDef, StructField, TypeRef,
831    };
832    use crate::ty::Prim;
833
834    fn param(name: &str, ty: TypeRef) -> Param {
835        Param {
836            name: name.into(),
837            ty,
838            doc: None,
839        }
840    }
841
842    fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
843        Function {
844            name: name.into(),
845            params,
846            returns,
847            doc: None,
848            throws: false,
849            r#async: false,
850            cancellable: false,
851            deprecated: None,
852        }
853    }
854
855    fn module(name: &str) -> Module {
856        Module {
857            name: name.into(),
858            doc: None,
859            functions: vec![],
860            interfaces: vec![],
861            callback_interfaces: vec![],
862            structs: vec![],
863            enums: vec![],
864            errors: None,
865            modules: vec![],
866        }
867    }
868
869    fn build(modules: Vec<Module>, prefix: &str) -> Model {
870        let api = Api {
871            version: crate::ir::CURRENT_SCHEMA_VERSION.into(),
872            modules,
873        };
874        Model::assume_valid(&api, Identity::named(prefix))
875    }
876
877    fn rendered(abi: &AbiFn) -> Vec<String> {
878        abi.params
879            .iter()
880            .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
881            .collect()
882    }
883
884    #[test]
885    fn sync_function_symbol_and_sig() {
886        let m = Module {
887            functions: vec![func(
888                "add",
889                vec![
890                    param("a", TypeRef::Prim(Prim::I32)),
891                    param("b", TypeRef::Prim(Prim::I32)),
892                ],
893                Some(TypeRef::Prim(Prim::I32)),
894            )],
895            ..module("math")
896        };
897        let model = build(vec![m], "weaveffi");
898        let f = &model.modules[0].functions[0];
899        assert_eq!(f.c_base, "weaveffi_math_add");
900        let CallShape::Sync(abi) = &f.shape else {
901            panic!("expected sync")
902        };
903        assert_eq!(abi.symbol, "weaveffi_math_add");
904        assert_eq!(abi.ret, CType::Int32);
905        assert_eq!(
906            rendered(abi),
907            ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
908        );
909
910        let model = build(vec![module("net")], "acme");
911        assert_eq!(model.prefix(), "acme");
912    }
913
914    #[test]
915    fn async_function_has_launch_and_callback() {
916        let m = Module {
917            functions: vec![Function {
918                cancellable: true,
919                r#async: true,
920                ..func(
921                    "fetch",
922                    vec![param("id", TypeRef::Prim(Prim::I64))],
923                    Some(TypeRef::Prim(Prim::String)),
924                )
925            }],
926            ..module("net")
927        };
928        let model = build(vec![m], "weaveffi");
929        let CallShape::Async(a) = &model.modules[0].functions[0].shape else {
930            panic!("expected async")
931        };
932        assert_eq!(a.launch.symbol, "weaveffi_net_fetch");
933        assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
934        let names: Vec<&str> = a.launch.params.iter().map(|p| p.name.as_str()).collect();
935        assert_eq!(names, ["id", "cancel_token", "callback", "context"]);
936        assert_eq!(a.callback_params[0].name, "context");
937        assert_eq!(a.callback_params[1].name, "err");
938        assert_eq!(a.callback_params[2].name, "result_ptr");
939        assert!(model.has_async());
940    }
941
942    #[test]
943    fn iterator_function_has_next_and_destroy() {
944        let m = Module {
945            functions: vec![func(
946                "get_messages",
947                vec![],
948                Some(TypeRef::Iterator(Box::new(TypeRef::Prim(Prim::String)))),
949            )],
950            ..module("events")
951        };
952        let model = build(vec![m], "weaveffi");
953        let CallShape::Iterator(it) = &model.modules[0].functions[0].shape else {
954            panic!("expected iterator")
955        };
956        assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
957        assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
958        assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
959        assert_eq!(
960            it.destroy_symbol,
961            "weaveffi_events_GetMessagesIterator_destroy"
962        );
963        assert_eq!(it.elem, Ty::Prim(Prim::String));
964        assert_eq!(it.next.ret, CType::Int32);
965        assert_eq!(it.next.params[1].ty.render_c("weaveffi"), "const uint8_t**");
966        assert_eq!(it.item_ctype().render_c("weaveffi"), "const uint8_t*");
967        assert!(model.has_iterators());
968    }
969
970    #[test]
971    fn user_types_resolve_to_kinds_and_buffers() {
972        let shared = Module {
973            structs: vec![StructDef {
974                name: "Contact".into(),
975                doc: None,
976                deprecated: Some("use Person".into()),
977                fields: vec![
978                    StructField {
979                        name: "name".into(),
980                        ty: TypeRef::Prim(Prim::String),
981                        doc: None,
982                    },
983                    StructField {
984                        name: "status".into(),
985                        ty: TypeRef::Named("Status".into()),
986                        doc: None,
987                    },
988                    StructField {
989                        name: "store".into(),
990                        ty: TypeRef::Optional(Box::new(TypeRef::Named("Store".into()))),
991                        doc: None,
992                    },
993                ],
994            }],
995            enums: vec![EnumDef {
996                name: "Status".into(),
997                doc: None,
998                deprecated: None,
999                variants: vec![EnumVariant {
1000                    name: "Ok".into(),
1001                    value: 0,
1002                    doc: None,
1003                    fields: vec![],
1004                }],
1005            }],
1006            interfaces: vec![InterfaceDef {
1007                name: "Store".into(),
1008                doc: None,
1009                deprecated: None,
1010                constructors: vec![func("open", vec![], None)],
1011                methods: vec![func(
1012                    "save",
1013                    vec![param("contact", TypeRef::Named("Contact".into()))],
1014                    Some(TypeRef::List(Box::new(TypeRef::Named("Contact".into())))),
1015                )],
1016                statics: vec![],
1017            }],
1018            ..module("contacts")
1019        };
1020        let other = Module {
1021            functions: vec![func(
1022                "status_of",
1023                vec![param("store", TypeRef::Named("Store".into()))],
1024                Some(TypeRef::Named("Status".into())),
1025            )],
1026            ..module("ops")
1027        };
1028        let model = build(vec![shared, other], "weaveffi");
1029        let contacts = &model.modules[0];
1030        let s = &contacts.structs[0];
1031        assert_eq!(s.deprecated.as_deref(), Some("use Person"));
1032        assert_eq!(s.fields[1].ty, Ty::Enum("Status".into()));
1033        assert_eq!(
1034            s.fields[2].ty,
1035            Ty::Optional(Box::new(Ty::Interface("Store".into())))
1036        );
1037        assert_eq!(contacts.enums[0].c_tag, "weaveffi_contacts_Status");
1038        assert_eq!(
1039            contacts.enums[0].variants[0].c_const,
1040            "weaveffi_contacts_Status_Ok"
1041        );
1042
1043        let iface = &contacts.interfaces[0];
1044        assert_eq!(iface.c_tag, "weaveffi_contacts_Store");
1045        assert_eq!(iface.clone_symbol, "weaveffi_contacts_Store_clone");
1046        assert_eq!(iface.destroy_symbol, "weaveffi_contacts_Store_destroy");
1047        assert_eq!(
1048            iface.constructors[0].ret,
1049            Some(Ty::Interface("Store".into()))
1050        );
1051        let save = &iface.methods[0];
1052        assert!(save.has_self);
1053        assert_eq!(save.params[0].ty, Ty::Record("Contact".into()));
1054        let CallShape::Sync(abi) = &save.shape else {
1055            panic!("expected sync")
1056        };
1057        assert_eq!(
1058            rendered(abi),
1059            [
1060                "const weaveffi_contacts_Store* self",
1061                "const uint8_t* contact_ptr",
1062                "size_t contact_len",
1063                "size_t* out_len",
1064                "weaveffi_error* out_err"
1065            ]
1066        );
1067        assert_eq!(abi.ret.render_c("weaveffi"), "const uint8_t*");
1068
1069        let ops = &model.modules[1];
1070        let f = &ops.functions[0];
1071        assert_eq!(f.params[0].ty, Ty::Interface("Store".into()));
1072        assert_eq!(f.ret, Some(Ty::Enum("Status".into())));
1073        let CallShape::Sync(abi) = &f.shape else {
1074            panic!("expected sync")
1075        };
1076        assert_eq!(
1077            rendered(abi),
1078            [
1079                "const weaveffi_contacts_Store* store",
1080                "weaveffi_error* out_err"
1081            ]
1082        );
1083        assert_eq!(abi.ret.render_c("weaveffi"), "weaveffi_contacts_Status");
1084        assert!(model.has_buffers());
1085        assert!(model.has_interfaces());
1086        assert_eq!(model.interface("Store").c_tag, "weaveffi_contacts_Store");
1087        assert_eq!(model.owner("Status").path, "contacts");
1088        assert_eq!(
1089            model.enumeration("Status").c_tag,
1090            "weaveffi_contacts_Status"
1091        );
1092    }
1093
1094    #[test]
1095    fn callback_interfaces_lower_to_vtables() {
1096        let m = Module {
1097            callback_interfaces: vec![CallbackInterfaceDef {
1098                name: "Listener".into(),
1099                doc: Some("Receives messages.".into()),
1100                deprecated: None,
1101                methods: vec![
1102                    func(
1103                        "on_message",
1104                        vec![param("text", TypeRef::Prim(Prim::String))],
1105                        None,
1106                    ),
1107                    func("should_stop", vec![], Some(TypeRef::Prim(Prim::Bool))),
1108                ],
1109            }],
1110            functions: vec![func(
1111                "subscribe",
1112                vec![param("listener", TypeRef::Named("Listener".into()))],
1113                None,
1114            )],
1115            ..module("events")
1116        };
1117        let model = build(vec![m], "weaveffi");
1118        let mb = &model.modules[0];
1119        let cb = &mb.callback_interfaces[0];
1120        assert_eq!(cb.c_tag, "weaveffi_events_Listener");
1121        assert_eq!(cb.vtable_tag, "weaveffi_events_Listener_vtable");
1122        let on_message = &cb.methods[0];
1123        let slots: Vec<String> = on_message
1124            .abi_params
1125            .iter()
1126            .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
1127            .collect();
1128        assert_eq!(
1129            slots,
1130            [
1131                "void* ctx",
1132                "const uint8_t* text_ptr",
1133                "size_t text_len",
1134                "weaveffi_error* out_err"
1135            ]
1136        );
1137        assert_eq!(on_message.abi_ret, CType::Void);
1138        assert_eq!(cb.methods[1].abi_ret, CType::Bool);
1139        assert_eq!(model.callback_interface("Listener").c_tag, cb.c_tag);
1140        assert!(model.has_callback_interfaces());
1141
1142        let f = &mb.functions[0];
1143        assert_eq!(f.params[0].ty, Ty::CallbackInterface("Listener".into()));
1144        let CallShape::Sync(abi) = &f.shape else {
1145            panic!("expected sync")
1146        };
1147        assert_eq!(
1148            rendered(abi),
1149            [
1150                "void* listener_ctx",
1151                "const weaveffi_events_Listener_vtable* listener_vtable",
1152                "weaveffi_error* out_err"
1153            ]
1154        );
1155        assert!(!model.has_buffers());
1156    }
1157
1158    #[test]
1159    fn nested_modules_flatten_pre_order_with_paths_and_docs() {
1160        let inner = Module {
1161            functions: vec![Function {
1162                doc: Some("Leaf.".into()),
1163                ..func("leaf_fn", vec![], None)
1164            }],
1165            ..module("inner")
1166        };
1167        let outer = Module {
1168            doc: Some("Outer module.".into()),
1169            functions: vec![func("outer_fn", vec![], None)],
1170            modules: vec![inner],
1171            ..module("outer")
1172        };
1173        let model = build(vec![outer], "weaveffi");
1174        let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
1175        assert_eq!(paths, ["outer", "outer_inner"]);
1176        assert_eq!(model.modules[1].dot_path, "outer.inner");
1177        assert_eq!(
1178            model.modules[1].functions[0].c_base,
1179            "weaveffi_outer_inner_leaf_fn"
1180        );
1181        assert_eq!(model.modules[0].doc.as_deref(), Some("Outer module."));
1182        assert_eq!(model.modules[1].doc, None);
1183    }
1184
1185    #[test]
1186    fn error_domains_are_declared_once_and_inherited_by_lookup() {
1187        let domain = ErrorDomain {
1188            name: "OuterError".into(),
1189            codes: vec![ErrorCode {
1190                name: "Bad".into(),
1191                code: 1,
1192                message: "bad".into(),
1193                doc: None,
1194                fields: vec![],
1195            }],
1196        };
1197        let outer = Module {
1198            errors: Some(domain),
1199            modules: vec![module("inner")],
1200            ..module("outer")
1201        };
1202        let model = build(vec![outer, module("other")], "weaveffi");
1203        let [outer, inner, other] = &model.modules[..] else {
1204            panic!("three modules");
1205        };
1206        assert!(outer.errors.is_some() && inner.errors.is_none());
1207        assert_eq!(
1208            model.error_domain(inner).map(|e| e.c_tag.as_str()),
1209            Some("weaveffi_outer_OuterError")
1210        );
1211        assert_eq!(model.error_domain(outer), outer.errors.as_ref());
1212        assert!(model.error_domain(other).is_none());
1213    }
1214}