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}