ark-api-macros 0.11.0

Macros utilities for Ark API
Documentation
//! Set of macros designed to help creating and implementing WebAssembly APIs and bindings,
//! designed to run in the Ark environment.
//!
//! See also the [private Ark
//! documentation](https://ark.embark.dev/internals/creating-apis/index.html) for details of how
//! things work, motivation behind this crate, as well as thorough examples of what it can do.
//!
//! # The problem
//!
//! Implementing WebAssembly FFI (foreign function interfaces) by hand is tedious and doesn't
//! allow using idiomatic Rust, like taking or returning non-plain types (user structs, enum, etc.
//! but also idiomatic Rust types like `Result`/`String` and so on and so forth).
//!
//! # The solution
//!
//! This crate provides macros that are designed to work together to solve this particular
//! problem, and allow the usage of high-level patterns in APIs.
//!
//! ## User-side `ark_bindgen` macro
//!
//! This macro applies on a `mod` defined in a Rust file that is to be compiled to WebAssembly.
//! The output of this macro depends on whether it's running on the host or not:
//!
//! - On the host, this will create an `HostShim` trait, that contains a few static internal
//! methods, as well as methods mirroring the extern C function declarations, using high-level,
//! idiomatic Rust patterns. This macro can be partially implemented automatically with the
//! `host_exports` macro, described below.
//! - In user code, this will also create the trait as well as a `safe` Rust `mod` containing
//! functions with the same signatures as in the declaration.
//!
//! Here are the different kind of declarations one can do with it:
//!
//! // TODO reinclude, see also #4516
//! //```rust,ignore
//! //#![doc = include_str!("../../api-ffi/src/ffi/example_automatic.rs")]
//! //```
//!
//! ## Host-side `host_export` macro
//!
//! This macro applied on impl blocks for the `HostShim` trait generated by the above macro. An end
//! user only needs to implement the `_shim` functions in the trait, and get the rest implemented
//! for free by this macro.
//!
//! // TODO reinclude, see also #4516
//! //```rust,ignore
//! //#![doc = include_str!("../../../components/module-host/src/host_api/examples/automatic_macro.rs")]
//! //```
//!
//! ## `ark_test` macro
//!
//! This macro is designed to replicate the behavior of `#[test]` in Rust modules compiled to
//! WebAssembly.

#![allow(clippy::wildcard_enum_match_arm)] // not very practical with `syn` crate in here

use proc_macro::TokenStream;
use quote::quote;

mod bindgen;
mod ffi_union;
mod host_exports;
mod test;
mod utils;

/// Replaces an impl block of an `HostShim` trait (generated by the `ark_bindgen` macro) by an
/// augmented one that automatically implements some functions, making it more ergonomic and
/// pleasant to use.
///
/// Note this macro is designed to be used on the **host**.
///
/// See the crate description for examples of what you can do with it.
#[proc_macro_attribute]
pub fn host_exports(_args: TokenStream, input: TokenStream) -> TokenStream {
    let mut item = syn::parse_macro_input!(input as host_exports::Item);
    match host_exports::expand(&mut item) {
        Ok(_) => TokenStream::from(quote!(#item)),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Replaces a `mod` block by another one that augments type/struct/external function declarations
/// to make them more concise, ergonomic and pleasant to use.
///
/// In particular, generates an `HostShim` trait that is implemented partially automatically thanks
/// to the `host_exports` macro above.
///
/// Note this macro is designed to be used in WebAssembly code (creating a `safe` Rust module
/// that can used directly) *and* on the host (creating the trait that will be implemented by the
/// above macro).
///
/// See the crate description for examples of what you can do with it.
///
/// This unfortunately can't be applied at module scope yet; see also
/// <https://github.com/rust-lang/rust/issues/54726>.
#[proc_macro_attribute]
pub fn ark_bindgen(args: TokenStream, input: TokenStream) -> TokenStream {
    let args = syn::parse_macro_input!(args as bindgen::Args);
    let mut item = syn::parse_macro_input!(input as bindgen::Item);

    match bindgen::expand(&mut item, args) {
        Ok(_) => TokenStream::from(quote!(#item)),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Macro emulating the behavior of the standard `#[test]` macro, but for module code compiled to
/// WebAssembly. Such a test can then be executed with ark using `ark module test`.
///
/// ```rust,ignore
/// #[ark_test]
/// fn makes_a_sad() {
///     assert_eq!(1, 2, "me am good in nummers");
/// }
/// ```
#[proc_macro_attribute]
pub fn ark_test(
    attr: proc_macro::TokenStream,
    body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    match test::try_ark_test(attr, body) {
        Ok(result) => result.into(),
        Err(err) => err.to_compile_error().into(),
    }
}

/// Performs modification necessary to make a union ffi-safe.
///
/// Ultimately this checks for invariants and implements `bytemuck::NoUninit` and `bytemuck::AnyBitPattern`. It is
/// technically possible to make the `bytemuck::NoUninit` implementation invalid with *sound* code,
/// but only through using unsafe code that writes uninit memory into the union. Therefore you have
/// to promise not to do this... which ultimately shouldn't be an issue as this isn't really something
/// that should be a want to do.
///
/// This macro wraps each field in a union into an `ark_api_ffi::TransparentPad<T, N>`
/// such that the total size of the new type is equal to the size specified in the
/// `ffi_union` macro. The size is specified in bytes.
///
/// This macro automatically derives `bytemuck::NoUninit` and `bytemuck::AnyBitPattern`
/// for the union it is applied to, which is necessary so that those derives use the
/// modified version of the union definition.
///
/// # Expected form
///
/// ```ignore
/// #[ark_api_macros::ffi_union(size = <size>, <pod_accessors | checked_accessors>)]
/// ```
///
/// # Example
///
/// ```ignore
/// #[ark_api_macros::ffi_union(size = 8, pod_accessors)]
/// union AutoPadExample {
///     field1: u64,
///     field2: u32,
/// }
/// ```
///
/// Usually, this would result in a union of size 8 but with padding for bytes 4-8
/// when the used field is `field2`. This macro will expand this to (basically):
///
/// ```ignore
/// #[derive(NoUninit, AnyBitPattern)]
/// union AutoPadExample {
///     field1: TransparentPad<u64, 0>,
///     field2: TransparentPad<u32, 4>,
/// }
/// ```
///
/// which adds "explicit"/"manual" padding bytes to the end of `field2` -- making the
/// compiler not treat them as *real* padding.
///
/// This macro will also derive accessor methods on the union for each field in the form
/// `as_{field_name}` (and `try_as_{field_name}`, if using `checked_accessors`) that return references
/// to the field as the field's original type rather than as the wrapped `TransparentPad`.
///
/// These accessors will either use `bytemuck`'s raw or checked casting functions based on
/// the second argument to the proc macro, which can be either `pod_accessors` or `checked_accessors`.
///
/// All fields must implement `Pod` for `pod_accessors`, while all fields must implement
/// `CheckedBitPattern` for `checked_accessors`.
#[proc_macro_attribute]
pub fn ffi_union(
    args: proc_macro::TokenStream,
    body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let args = syn::parse_macro_input!(args as ffi_union::Args);
    let mut item = syn::parse_macro_input!(body as syn::ItemUnion);

    let item_ident = item.ident.clone();

    match ffi_union::expand(&mut item, args) {
        Ok(ffi_union::ExpansionExtras { extras, accessors }) => TokenStream::from(quote!(
            #(#extras)*
            #item
            impl #item_ident {
                #(#accessors)*
            }
        )),
        Err(e) => e.to_compile_error().into(),
    }
}