Skip to main content

ezffi_macros/
lib.rs

1//! Proc-macros backing the [`ezffi`](https://docs.rs/ezffi) crate. Most users
2//! should depend on `ezffi` directly and use [`macro@export`], not this crate
3use std::{env, sync::LazyLock};
4
5use quote::quote;
6use syn::{Item, parse_macro_input};
7
8use crate::{
9    enums::{expand_c_enum, expand_enum, is_c_compatible_enum},
10    functions::{WrapMethods, expand_fn, expand_impl, expand_wrap_methods},
11    structs::{expand_c_struct, expand_struct, is_c_compatible_struct},
12};
13
14mod config;
15mod enums;
16mod functions;
17mod namer;
18mod structs;
19mod type_resolver;
20mod typemap;
21
22use config::CONFIG;
23use namer::Namer;
24use type_resolver::FFITypeResolver;
25
26static PKG_NAME: LazyLock<String> = LazyLock::new(|| env::var("CARGO_PKG_NAME").unwrap());
27static MANIFEST_DIR: LazyLock<String> = LazyLock::new(|| env::var("CARGO_MANIFEST_DIR").unwrap());
28
29const EZFFI_CODEGEN_VERSION: u32 = 1;
30const MIN_COMPATIBLE_CODEGEN: u32 = 1;
31const CODEGEN_INTRODUCED_IN: &[(u32, &str)] = &[(1, "0.1.0")];
32
33fn crate_version_for_codegen(v: u32) -> &'static str {
34    CODEGEN_INTRODUCED_IN
35        .iter()
36        .find(|(cv, _)| *cv == v)
37        .map(|(_, ver)| *ver)
38        .unwrap_or("?")
39}
40
41/// Expose the annotated item through the FFI layer.
42///
43/// Applies to `struct`, `enum` and `impl` blocks. The macro
44/// decides the right layout and emits the extern wrappers
45/// cbindgen will pick up.
46///
47/// ```ignore
48/// #[ezffi::export]
49/// pub fn add(a: u32, b: u32) -> u32 { a + b }
50///
51/// #[ezffi::export]
52/// pub struct Point { pub x: f64, pub y: f64 }
53///
54/// #[ezffi::export]
55/// impl Point {
56///     pub fn new(x: f64, y: f64) -> Self { Self { x, y } }
57/// }
58/// ```
59///
60/// Full reference (which shapes are supported, naming rules, configuration,
61/// cross-crate behaviour): [The ezffi Book](https://zocolini.github.io/ezffi/the-macro.html).
62#[proc_macro_attribute]
63pub fn export(
64    attr: proc_macro::TokenStream,
65    item: proc_macro::TokenStream,
66) -> proc_macro::TokenStream {
67    export_impl(attr, item, false)
68}
69
70/// Registers an external type (one defined outside the current crate, e.g.
71/// `std::string::String` or `Vec<T>`) as opaque-pointer-exported. Used
72/// internally by `ezffi`'s std prelude; downstream users cannot use it
73/// unless they define the same traits this macro expands to
74#[proc_macro]
75pub fn export_extern_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
76    let path = parse_macro_input!(input as syn::Path);
77
78    let dummy_struct = quote! { struct #path {} }.into();
79
80    export_impl(proc_macro::TokenStream::new(), dummy_struct, true)
81}
82
83/// Wraps a list of methods on an externally-defined type with C-FFI shims.
84/// Used by `ezffi` to expose Rust std struct methods to C.
85///
86/// ```ignore
87/// wrap_methods!(String {
88///     fn new() -> String;
89///     fn len(&self) -> usize;
90///     fn push_str(&mut self, s: &str);
91/// });
92/// ```
93///
94/// Not meant for downstream users (yet). It may be made public in a future
95/// release, probably under a different name
96#[proc_macro]
97pub fn wrap_methods(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
98    let parsed = parse_macro_input!(input as WrapMethods);
99
100    let result = FFITypeResolver::check_registry().and_then(|()| expand_wrap_methods(&parsed));
101
102    match result {
103        Ok(ts) => ts.into(),
104        Err(e) => e.to_compile_error().into(),
105    }
106}
107
108fn export_impl(
109    _attr: proc_macro::TokenStream,
110    item: proc_macro::TokenStream,
111    skip_input: bool,
112) -> proc_macro::TokenStream {
113    let item = parse_macro_input!(item as Item);
114
115    match expand_item(&item, skip_input) {
116        Ok(ts) => ts.into(),
117        Err(e) => e.to_compile_error().into(),
118    }
119}
120
121fn expand_item(item: &Item, mut skip_input: bool) -> syn::Result<proc_macro2::TokenStream> {
122    FFITypeResolver::check_registry()?;
123
124    let output = match item {
125        Item::Struct(item) => {
126            if item.generics.gt_token.is_none() && is_c_compatible_struct(item) {
127                skip_input = true;
128                expand_c_struct(item)
129            } else {
130                expand_struct(item)?
131            }
132        }
133        Item::Enum(item) => {
134            if is_c_compatible_enum(item) {
135                skip_input = true;
136                expand_c_enum(item)
137            } else {
138                expand_enum(item)?
139            }
140        }
141        Item::Fn(item) => expand_fn(item)?,
142        Item::Impl(item) => expand_impl(item)?,
143        other => {
144            return Err(syn::Error::new_spanned(
145                other,
146                format!(
147                    "#[ezffi::export] doesn't support this item: `{}`",
148                    quote!(#other)
149                ),
150            ));
151        }
152    };
153
154    if skip_input {
155        Ok(output)
156    } else {
157        Ok(quote! {
158            #item
159            #output
160        })
161    }
162}