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    Item, Path,
25    parse::{Error as SynError, Parser},
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    stubs: Option<proc_macro2::TokenStream>,
36}
37
38impl ExternrefAttrs {
39    fn parse(tokens: TokenStream) -> syn::Result<Self> {
40        let mut attrs = Self::default();
41        if tokens.is_empty() {
42            return Ok(attrs);
43        }
44
45        let parser = syn::meta::parser(|meta| {
46            if meta.path.is_ident("crate") {
47                let value = meta.value()?;
48                attrs.crate_path = Some(if let Ok(path_str) = value.parse::<syn::LitStr>() {
49                    path_str.parse()?
50                } else {
51                    value.parse()?
52                });
53                Ok(())
54            } else if meta.path.is_ident("stubs") {
55                attrs.stubs = Some(if meta.input.peek(syn::token::Paren) {
56                    let content;
57                    syn::parenthesized!(content in meta.input);
58                    content.parse()?
59                } else {
60                    syn::parse_quote!(target_family = "wasm")
61                });
62                Ok(())
63            } else {
64                Err(meta.error("unsupported attribute; expected `crate` or `stubs`"))
65            }
66        });
67        parser.parse(tokens)?;
68        Ok(attrs)
69    }
70
71    fn crate_path(&self) -> Path {
72        self.crate_path
73            .clone()
74            .unwrap_or_else(|| syn::parse_quote!(externref))
75    }
76}
77
78/// Prepares imported functions or an exported function with `Resource` args and/or return type.
79///
80/// # Inputs
81///
82/// This attribute must be placed on an `extern "C" { ... }` block or an `extern "C" fn`.
83/// If placed on block, all enclosed functions with `Resource` args / return type will be
84/// wrapped.
85///
86/// # Processing
87///
88/// The following arg / return types are recognized as resources:
89///
90/// - `Resource<..>`, `&Resource<..>`, `&mut Resource<..>` with one or two type args
91/// - `ResourceCopy<..>` and its references
92/// - `Option<_>` of the above six variations
93///
94/// For complex cases, resource detection can be controlled with a `#[resource]` attribute.
95/// This attribute can be placed on a function arg or on the function itself (in which case it corresponds
96/// to the return type; attributes cannot be placed on the return type directly).
97///
98/// - `#[resource]`, `#[resource = true]` or `#[resource(true)]` mark an arg / return type as a resource.
99/// - `#[resource = false]` or `#[resource(false)]` mark an arg / return type as a non-resource.
100///
101/// # Attributes
102///
103/// The `externref` macro supports attributes specified in parentheses after the macro
104/// (e.g., `#[externref(crate = path::to::externref)]`).
105///
106/// ## `crate`
107///
108/// **Type:** path or string
109///
110/// Allows specifying a path to the `externref` crate, e.g. to re-export it from a higher-level library.
111/// A path can be either a string (`"path::to::externref"`), or an unquoted path (`path::to::externref`).
112///
113/// ## `stubs`
114///
115/// **Type:** compilation condition, e.g. `target_family = "wasm"`.
116///
117/// Generates stub import functions for non-WASM targets with `unreachable!()` contents. This is useful
118/// if the library needs to be compiled (but not run) on non-WASM targets. By default, the compilation
119/// condition determining WASM targets is `target_family = "wasm"`; it can be changed by specifying
120/// the condition in parentheses, like `stubs(target_arch = "wasm32")`.
121///
122/// This attribute is only supported on `extern` modules and will lead to an error if specified on an export.
123#[proc_macro_attribute]
124pub fn externref(attr: TokenStream, input: TokenStream) -> TokenStream {
125    const MSG: &str = "Unsupported item; only `extern \"C\" {}` modules and `extern \"C\" fn ...` \
126        exports are supported";
127
128    let attrs = match ExternrefAttrs::parse(attr) {
129        Ok(attrs) => attrs,
130        Err(err) => return err.into_compile_error().into(),
131    };
132
133    let output = match syn::parse::<Item>(input) {
134        Ok(Item::ForeignMod(mut module)) => for_foreign_module(&mut module, attrs),
135        Ok(Item::Fn(mut function)) => for_export(&mut function, &attrs),
136        Ok(other) => {
137            return SynError::new_spanned(other, MSG)
138                .into_compile_error()
139                .into();
140        }
141        Err(err) => return err.into_compile_error().into(),
142    };
143    output.into()
144}