Skip to main content

freenet_macros/
lib.rs

1use quote::{quote, quote_spanned};
2use syn::punctuated::Punctuated;
3use syn::spanned::Spanned;
4use syn::{ItemImpl, Meta, Token};
5
6pub(crate) mod common;
7mod contract_impl;
8mod delegate_impl;
9
10struct AttributeArgs {
11    args: Punctuated<Meta, Token![,]>,
12}
13
14impl syn::parse::Parse for AttributeArgs {
15    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
16        let mut args = Punctuated::new();
17        let mut punctuated;
18        let mut count = 0;
19        while !input.is_empty() {
20            punctuated = input.parse::<Token![,]>().ok().is_some();
21            if count > 0 && !punctuated {
22                return Err(syn::Error::new(
23                    input.span(),
24                    "arguments must be comma separated",
25                ));
26            }
27            let meta = input.parse::<Meta>()?;
28            args.push(meta);
29            count += 1;
30        }
31        Ok(AttributeArgs { args })
32    }
33}
34
35enum ContractType {
36    Raw,
37    Typed,
38    Composable,
39}
40
41/// Generate the necessary code for the WASM runtime to interact with your contract ergonomically and safely.
42#[proc_macro_attribute]
43pub fn contract(
44    args: proc_macro::TokenStream,
45    input: proc_macro::TokenStream,
46) -> proc_macro::TokenStream {
47    let args = syn::parse_macro_input!(args as AttributeArgs);
48    let input = syn::parse_macro_input!(input as ItemImpl);
49    let Some((_, path, _)) = &input.trait_ else {
50        return proc_macro::TokenStream::from(quote_spanned! {
51            input.span() =>
52            compile_error!("only allowed for traits");
53        });
54    };
55    match path.segments.last() {
56        Some(segment) => {
57            let c_type = match segment.ident.to_string().as_str() {
58                "ContractInterface" => ContractType::Raw,
59                "TypedContract" => ContractType::Typed,
60                "ContractComponent" => ContractType::Composable,
61                _ => {
62                    return proc_macro::TokenStream::from(quote_spanned! {
63                        segment.ident.span() =>
64                        compile_error!("trait not supported for contract interaction");
65                    })
66                }
67            };
68            contract_impl::contract_ffi_impl(&input, &args, c_type)
69        }
70        None => proc_macro::TokenStream::from(quote_spanned! {
71            path.span() =>
72            compile_error!("missing trait identifier");
73        }),
74    }
75}
76
77/// Generate the necessary code for the WASM runtime to interact with your delegate ergonomically and safely.
78///
79/// # Declaring a manifest
80///
81/// A delegate that wants lifecycle events or node-enforced capabilities
82/// declares them, and the macro embeds a manifest in the WASM
83/// (see `freenet_stdlib::prelude::DelegateManifest`):
84///
85/// ```ignore
86/// #[delegate(manifest(lifecycle = [Installed, NodeStarted], capabilities = [Background]))]
87/// impl DelegateInterface for MyDelegate { /* ... */ }
88/// ```
89///
90/// Without `manifest(...)` nothing is embedded and the delegate behaves as
91/// delegates always have. Adding one changes the WASM, and so the delegate key.
92///
93/// Periodic wake-ups are declared the same way, as `tag = interval in seconds`
94/// (60 s to 7 days, at most 4); the node then delivers
95/// `InboundDelegateMsg::WakeupFired { tag }` on that schedule with no app open:
96///
97/// ```ignore
98/// #[delegate(manifest(
99///     lifecycle = [NodeStarted],
100///     capabilities = [Background],
101///     wakeups = [heartbeat = 300],
102/// ))]
103/// impl DelegateInterface for MyDelegate { /* ... */ }
104/// ```
105///
106/// A node that predates wake-ups ignores the `wakeups` entry and still honours
107/// the rest, so one build works on both. Such a node asks the user for
108/// `Background` only when a lifecycle kind is listed, so list one (as above)
109/// if the delegate should be granted there too.
110///
111/// Listing any lifecycle kind or wake-up requires `capabilities = [Background]`. Only one
112/// manifest per crate: the section is per WASM module. Custom sections must
113/// survive any post-processing of the module (`wasm-opt --strip-*`,
114/// `wasm-strip` remove them); a missing section means "no manifest", silently.
115#[proc_macro_attribute]
116pub fn delegate(
117    args: proc_macro::TokenStream,
118    input: proc_macro::TokenStream,
119) -> proc_macro::TokenStream {
120    let args = syn::parse_macro_input!(args as AttributeArgs);
121    let input = syn::parse_macro_input!(input as ItemImpl);
122    let manifest = match delegate_impl::parse_manifest_args(&args.args) {
123        Ok(m) => m,
124        Err(err) => return proc_macro::TokenStream::from(err.to_compile_error()),
125    };
126    let mut output = delegate_impl::ffi_impl_wrap(&input);
127    if let Some(manifest) = manifest {
128        output.extend(delegate_impl::manifest_section(&input, &manifest));
129    }
130    // println!("{}", quote!(#input));
131    // println!("{output}");
132    proc_macro::TokenStream::from(quote! {
133        #input
134        #output
135    })
136}