externref_macro/
lib.rs

1//! Procedural macro for [`externref`].
2//!
3//! This macro wraps imported or exported functions with `Resource` args / return type
4//! doing all heavy lifting to prepare these functions for usage with `externref`s.
5//! Note that it is necessary to post-process the module with the module processor provided
6//! by the `externref` crate.
7//!
8//! See `externref` docs for more details and examples of usage.
9//!
10//! [`externref`]: https://docs.rs/externref
11
12#![recursion_limit = "128"]
13// Documentation settings.
14#![doc(html_root_url = "https://docs.rs/externref-macro/0.3.0-beta.1")]
15// Linter settings.
16#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
17#![warn(clippy::all, clippy::pedantic)]
18#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]
19
20extern crate proc_macro;
21
22use proc_macro::TokenStream;
23use syn::{
24    parse::{Error as SynError, Parser},
25    Item, Path,
26};
27
28mod externref;
29
30use crate::externref::{for_export, for_foreign_module};
31
32#[derive(Default)]
33struct ExternrefAttrs {
34    crate_path: Option<Path>,
35}
36
37impl ExternrefAttrs {
38    fn parse(tokens: TokenStream) -> syn::Result<Self> {
39        let mut attrs = Self::default();
40        if tokens.is_empty() {
41            return Ok(attrs);
42        }
43
44        let parser = syn::meta::parser(|meta| {
45            if meta.path.is_ident("crate") {
46                let value = meta.value()?;
47                attrs.crate_path = Some(if let Ok(path_str) = value.parse::<syn::LitStr>() {
48                    path_str.parse()?
49                } else {
50                    value.parse()?
51                });
52                Ok(())
53            } else {
54                Err(meta.error("unsupported attribute"))
55            }
56        });
57        parser.parse(tokens)?;
58        Ok(attrs)
59    }
60
61    fn crate_path(&self) -> Path {
62        self.crate_path
63            .clone()
64            .unwrap_or_else(|| syn::parse_quote!(externref))
65    }
66}
67
68/// Prepares imported functions or an exported function with `Resource` args and/or return type.
69///
70/// # Inputs
71///
72/// This attribute must be placed on an `extern "C" { ... }` block or an `extern "C" fn`.
73/// If placed on block, all enclosed functions with `Resource` args / return type will be
74/// wrapped.
75///
76/// # Processing
77///
78/// The following arg / return types are recognized as resources:
79///
80/// - `Resource<_>`, `&Resource<_>`, `&mut Resource<_>`
81/// - `Option<_>` of any of the above three variations
82#[proc_macro_attribute]
83pub fn externref(attr: TokenStream, input: TokenStream) -> TokenStream {
84    const MSG: &str = "Unsupported item; only `extern \"C\" {}` modules and `extern \"C\" fn ...` \
85        exports are supported";
86
87    let attrs = match ExternrefAttrs::parse(attr) {
88        Ok(attrs) => attrs,
89        Err(err) => return err.into_compile_error().into(),
90    };
91
92    let output = match syn::parse::<Item>(input) {
93        Ok(Item::ForeignMod(mut module)) => for_foreign_module(&mut module, &attrs),
94        Ok(Item::Fn(mut function)) => for_export(&mut function, &attrs),
95        Ok(other) => {
96            return SynError::new_spanned(other, MSG)
97                .into_compile_error()
98                .into()
99        }
100        Err(err) => return err.into_compile_error().into(),
101    };
102    output.into()
103}