Skip to main content

weaveffi_macros/
lib.rs

1//! Procedural macros that turn safe, annotated Rust into the WeaveFFI C ABI.
2//!
3//! A producer annotates an ordinary Rust module with `#[weaveffi::module]`,
4//! tags the items it wants to export, and calls `weaveffi::export_runtime!()`
5//! once. The module macro lowers the module tree to the WeaveFFI IR (through
6//! [`weaveffi_model::rust`]), builds the canonical
7//! [`Model`](weaveffi_model::model::Model), and emits the
8//! `extern "C"` thunks every generated language binding calls. All of the
9//! `unsafe` marshalling lives in the `weaveffi::abi` runtime, so the producer
10//! writes only safe Rust.
11//!
12//! ```ignore
13//! #[weaveffi::module]
14//! pub mod calculator {
15//!     /// Add two integers.
16//!     #[weaveffi::export]
17//!     pub fn add(a: i32, b: i32) -> i32 {
18//!         a + b
19//!     }
20//! }
21//!
22//! weaveffi::export_runtime!();
23//! ```
24//!
25//! Every C symbol starts with the crate's name (`CARGO_CRATE_NAME`). The
26//! macro also embeds the module's API in the library as metadata, which
27//! `weaveffi generate` reads back out of the built library, so the generated
28//! bindings describe exactly what the library was compiled with.
29//!
30//! # Attributes
31//!
32//! * [`macro@module`] marks an exported namespace (the driver attribute).
33//! * [`macro@export`] exports a function; [`macro@record`] a by-value struct;
34//!   [`macro@enumeration`] a `#[repr(i32)]` C-style enum or a rich enum with
35//!   data-carrying variants.
36//! * [`macro@interface`] declares an opaque, reference-counted object type
37//!   whose `impl` block's `pub fn`s become constructors, methods, and statics.
38//! * [`macro@error`] declares the module's error domain from an enum with
39//!   explicit discriminants; a variant's named fields become the code's
40//!   structured payload.
41//! * [`macro@callback_interface`] declares a trait the consumer implements,
42//!   whose methods return `Result<T, weaveffi::ForeignError>`;
43//!   [`macro@throws`] lets one of them report the module's domain errors.
44//!   [`macro@cancellable`] marks an async function as cancellable.
45//! * [`export_runtime!`] emits the runtime symbols (memory, errors, cancel
46//!   tokens, ABI version) once per library.
47//!
48//! The item-level attributes are inert markers that [`macro@module`] reads; on
49//! their own they expand to the item unchanged.
50
51#![deny(missing_docs)]
52
53use proc_macro::TokenStream;
54
55mod codegen;
56mod runtime;
57
58/// Mark an inline `mod` as an exported WeaveFFI namespace.
59///
60/// The macro validates the module tree with the same rules the CLI applies
61/// to an IDL, re-emits the module, and appends the generated C ABI thunks
62/// for every tagged item it contains, recursing into nested
63/// `#[weaveffi::module]` submodules (whose symbols carry the joined module
64/// path). A `#[cfg]` on an exported item applies to its thunks too. A
65/// top-level module also exports `{prefix}_{module}_contract()`, the
66/// contract table generated bindings verify when they load the library.
67#[proc_macro_attribute]
68pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream {
69    let item_mod = syn::parse_macro_input!(item as syn::ItemMod);
70    codegen::expand_module(&item_mod)
71        .unwrap_or_else(syn::Error::into_compile_error)
72        .into()
73}
74
75/// Emit the runtime symbols every WeaveFFI library exports, prefixed with the
76/// crate's name: `{prefix}_abi_version`, `{prefix}_error_set`,
77/// `{prefix}_error_set_payload`, `{prefix}_error_clear`,
78/// `{prefix}_error_free`, `{prefix}_alloc`, `{prefix}_free_bytes`, the four
79/// `{prefix}_cancel_token_*` functions, and `{prefix}_debug_live`.
80///
81/// Invoke it exactly once, at the crate root of the `cdylib`. It takes no
82/// arguments.
83#[proc_macro]
84pub fn export_runtime(input: TokenStream) -> TokenStream {
85    runtime::expand(input.into())
86        .unwrap_or_else(syn::Error::into_compile_error)
87        .into()
88}
89
90/// Generate `#[doc(hidden)]` no-op marker attributes that [`macro@module`]
91/// reads. Each expands to the annotated item unchanged.
92macro_rules! marker_attr {
93    ($(#[$meta:meta])* $name:ident) => {
94        $(#[$meta])*
95        #[proc_macro_attribute]
96        pub fn $name(_attr: TokenStream, item: TokenStream) -> TokenStream {
97            item
98        }
99    };
100}
101
102marker_attr! {
103    /// Export a function across the FFI boundary. An `async fn` lowers to an
104    /// asynchronous symbol; a `fn -> Result<T, E>` is fallible.
105    export
106}
107marker_attr! {
108    /// Declare a by-value record (struct) serialized in the value-buffer
109    /// format when it crosses the ABI.
110    record
111}
112marker_attr! {
113    /// Declare an interface: an opaque, reference-counted object type with
114    /// constructors, methods, and statics read from its `impl` block. Methods
115    /// take `&self` or `self: Arc<Self>`; the type must be `Send + Sync`.
116    interface
117}
118marker_attr! {
119    /// Declare the module's error domain from an enum with explicit
120    /// discriminants (the stable error codes). The enum must implement
121    /// `std::fmt::Display`, which supplies the runtime message; each
122    /// variant's doc comment is the documented default message.
123    error
124}
125marker_attr! {
126    /// Declare an enum exported by value: a `#[repr(i32)]` C-style enum, or a
127    /// rich enum whose variants carry named fields.
128    enumeration
129}
130marker_attr! {
131    /// Declare a callback interface: a trait whose `&self` methods the
132    /// consumer implements. Producers accept one as `Arc<dyn Trait>` (or
133    /// `Option<Arc<dyn Trait>>`). Every method returns
134    /// `Result<T, weaveffi::ForeignError>`, which carries the consumer's
135    /// failure as a value.
136    callback_interface
137}
138marker_attr! {
139    /// Mark a callback-interface method as able to report the error domain
140    /// in scope: the consumer may fail with one of its codes, which the
141    /// producer decodes with `weaveffi::ForeignError::domain`.
142    throws
143}
144marker_attr! {
145    /// Mark an `async fn` as cancellable: it takes a `weaveffi::CancelToken`
146    /// as its final parameter, and cancelling the token completes the call
147    /// with the cancelled code.
148    cancellable
149}