Skip to main content

bitwarden_ffi_macro/
attrs.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{Attribute, Item, Meta, Token, parse::Parser, punctuated::Punctuated};
4
5/// The arguments of an attribute, as written between its parentheses.
6pub(crate) type Args = Punctuated<Meta, Token![,]>;
7
8/// Parses a macro's own arguments, all of which are forwarded to the attribute it stands in for.
9pub(crate) fn parse_args(attr: TokenStream) -> syn::Result<Args> {
10    if attr.is_empty() {
11        return Ok(Args::new());
12    }
13    Args::parse_terminated.parse2(attr)
14}
15
16/// `#[cfg_attr(feature = "wasm", wasm_bindgen(<args>))]`, named through `bitwarden_ffi` so a call
17/// site imports nothing.
18pub(crate) fn wasm_bindgen_attr(args: &Args) -> TokenStream {
19    let call = if args.is_empty() {
20        quote!(wasm_bindgen)
21    } else {
22        quote!(wasm_bindgen(#args))
23    };
24    quote!(#[cfg_attr(feature = "wasm", ::bitwarden_ffi::_macro::#call)])
25}
26
27/// The `wasm_bindgen(..)` arguments an attribute carries, whether written plainly or under
28/// `#[cfg_attr(feature = "wasm", ..)]`.
29pub(crate) fn wasm_bindgen_args(attr: &Attribute) -> Option<Args> {
30    let inner = if attr.path().is_ident("wasm_bindgen") {
31        attr.meta.clone()
32    } else if attr.path().is_ident("cfg_attr") {
33        let Meta::List(list) = &attr.meta else {
34            return None;
35        };
36        list.parse_args_with(Args::parse_terminated)
37            .ok()?
38            .into_iter()
39            .find(|meta| meta.path().is_ident("wasm_bindgen"))?
40    } else {
41        return None;
42    };
43
44    Some(match inner {
45        Meta::List(list) => list.parse_args_with(Args::parse_terminated).ok()?,
46        _ => Args::new(),
47    })
48}
49
50/// The `#[wasm_bindgen]` an item already carries, which a macro standing in for one must reject.
51pub(crate) fn find_wasm_bindgen(attrs: &[Attribute]) -> Option<&Attribute> {
52    attrs.iter().find(|attr| wasm_bindgen_args(attr).is_some())
53}
54
55/// Rejects an item `#[wasm_bindgen]` cannot export: anything that is not a struct or an enum, and
56/// any generic parameters on one.
57pub(crate) fn check_exportable_type(item: &Item, macro_name: &str) -> syn::Result<()> {
58    let generics = match item {
59        Item::Struct(item) => &item.generics,
60        Item::Enum(item) => &item.generics,
61        other => {
62            return Err(syn::Error::new_spanned(
63                other,
64                format!("#[{macro_name}] applies to a struct or an enum"),
65            ));
66        }
67    };
68
69    if !generics.params.is_empty() {
70        return Err(syn::Error::new_spanned(
71            generics,
72            format!(
73                "#[{macro_name}] does not support generics; wasm_bindgen cannot export generic types"
74            ),
75        ));
76    }
77
78    Ok(())
79}