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}