gmeta_codegen/
lib.rs

1// This file is part of Gear.
2
3// Copyright (C) 2022-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use proc_macro::TokenStream;
20use quote::{quote, ToTokens};
21use std::{borrow::Borrow, fmt::Display, iter};
22use syn::{
23    parse_macro_input, spanned::Spanned, Attribute, Error, FnArg, Item, ItemMod, Pat, ReturnType,
24    Type, TypePath, Visibility,
25};
26
27static MODULE_NAME: &str = "metafns";
28
29fn error<T>(spanned: impl Spanned, message: impl Display) -> Result<T, Error> {
30    Err(Error::new(spanned.span(), message))
31}
32
33macro_rules! return_error_if_some {
34    ($option:expr, $message:expr) => {
35        if let Some(spanned) = $option {
36            return error(spanned, $message);
37        }
38    };
39}
40
41fn validate_if_private(spanned: impl Spanned, visibility: &Visibility) -> Result<(), Error> {
42    match visibility {
43        Visibility::Public(_) => Ok(()),
44        other => match other {
45            Visibility::Inherited => {
46                error(spanned, "visibility must be public, add the `pub` keyword")
47            }
48            _ => error(
49                other,
50                "visibility mustn't be restricted, use the `pub` keyword alone",
51            ),
52        },
53    }
54}
55
56fn validate_if_has_no_attributes(
57    attributes: impl IntoIterator<Item = impl Borrow<Attribute>>,
58    message: impl Display,
59) -> Result<(), Error> {
60    let mut attributes = attributes.into_iter();
61
62    if let Some(attribute) = attributes.next() {
63        let span = attributes.fold(attribute.borrow().span(), |span, attribute| {
64            span.join(attribute.borrow().span()).unwrap_or(span)
65        });
66
67        error(span, message)
68    } else {
69        Ok(())
70    }
71}
72
73/// Generates metawasm functions.
74///
75/// An example of the expected structure:
76///
77/// ```
78/// use gstd::prelude::*;
79///
80/// #[derive(Decode, Encode, TypeInfo)]
81/// pub struct StateType;
82///
83/// #[derive(Encode, TypeInfo)]
84/// pub struct SomeReturnType;
85///
86/// #[derive(Decode, TypeInfo)]
87/// pub struct SomeArg;
88///
89/// #[gmeta::metawasm]
90/// pub mod metafns {
91///     pub type State = StateType;
92///
93///     /// Documentation...
94///     pub fn some_function(_: State) -> SomeReturnType {
95///         unimplemented!()
96///     }
97///
98///     pub fn another_function_but_with_arg(mut _state: State, _arg: SomeArg) -> State {
99///         unimplemented!()
100///     }
101///
102///     /// Another doc...
103///     pub fn function_with_multiple_args(
104///         _state: State,
105///         mut _arg1: SomeArg,
106///         _arg2: u16,
107///         mut _arg3: u32,
108///     ) -> SomeReturnType {
109///         unimplemented!()
110///     }
111/// }
112/// # fn main() {}
113/// ```
114///
115/// # Syntax
116///
117/// - This attribute **must** be used on the `pub`lic `mod` container with the
118///   `metafns` identifier.
119/// - The first item in the module **must** be a `pub`lic `type` alias with the
120///   `State` identifier. The type for which `State` will be an alias **must**
121///   implement [`Decode`] trait.
122///
123/// Usually the state type should be imported from the implemented associated
124/// [`Metadata::State`](../gmeta/trait.Metadata.html#associatedtype.State) type
125/// from the program's `io` crate.
126///
127/// - The rest of items **must** be `pub`lic functions.
128/// - The first argument's type of metafunctions **must** be `State`.
129/// - If the first argument uses
130///   [the identifier pattern](https://doc.rust-lang.org/stable/reference/patterns.html#identifier-patterns),
131///   the identifier **must** be `state` or `_state`.
132///
133/// In addition to the mandatory first argument, functions can have additional
134/// ones.
135///
136/// - The maximum amount of additional arguments is 18 due restrictions of the
137///   SCALE codec.
138/// - All additional arguments **must** implement the [`Decode`] &
139///   [`TypeInfo`] traits.
140/// - A function **mustn't** return `()` or nothing.
141/// - A returned type **must** implement the
142///   [`Encode`](../gmeta/trait.Encode.html) & [`TypeInfo`] traits.
143///
144/// [`Decode`]: ../gmeta/trait.Decode.html
145/// [`TypeInfo`]: ../gmeta/trait.TypeInfo.html
146///
147/// # Expansion result
148///
149/// This attribute doesn't change the `metafns` module and items inside, but
150/// adds `use super::*;` inside the module because, in most cases, it'll be
151/// useful for importing items from an upper namespace. So every item in the
152/// same namespace where the module is located is accessible inside it.
153///
154/// The rest of the magic happens in the another generated private `extern`
155/// module. It registers all metawasm functions, their arguments & return types,
156/// and generates extern functions with the same names. Later, they can be
157/// called from a metaWASM binary inside a blockchain.
158///
159/// **Important note**: although metafunctions can take more than 1 additional
160/// arguments, on the metaWASM binary level, they must be passed as one. So if
161/// the amount of additional arguments is 0 or 1, nothing needs to be changed,
162/// but if more - they all must be placed inside a tuple in the same order as in
163/// their function's signature.
164///
165/// E.g., argument definitions for the above example:
166/// - For `some_function` an argument must be [`None`].
167/// - For `another_function_but_with_arg` an argument must be `Some(SomeArg)`.
168/// - For `function_with_multiple_args` an argument must be
169///   `Some((SomeArg, u16, u32))`.
170#[proc_macro_attribute]
171pub fn metawasm(_: TokenStream, item: TokenStream) -> TokenStream {
172    process(parse_macro_input!(item)).unwrap_or_else(|error| error.into_compile_error().into())
173}
174
175fn process(module: ItemMod) -> Result<TokenStream, Error> {
176    let module_span = module.span();
177
178    validate_if_has_no_attributes(
179        module.attrs,
180        "module with #[metawasm] mustn't have attributes",
181    )?;
182    validate_if_private(module_span, &module.vis)?;
183
184    if module.ident != MODULE_NAME {
185        return error(
186            module.ident,
187            format_args!("name of a module with #[metawasm] must be `{MODULE_NAME}`"),
188        );
189    }
190
191    let Some((_, items)) = module.content else {
192        return error(
193            module_span,
194            "`#[metawasm]` doesn't work with modules without a body",
195        );
196    };
197
198    if items.is_empty() {
199        return Ok(Default::default());
200    }
201
202    let mut items = items.into_iter();
203    let two_first_items = (items.next(), items.next());
204
205    let (potential_type_item, potential_functions) =
206        if let (Some(first), Some(second)) = two_first_items {
207            (first, iter::once(second).chain(items))
208        } else {
209            return error(
210                module_span,
211                "module with #[metawasm] must contain the `State` type alias & at least 1 function",
212            );
213        };
214
215    // Checking the `State` type
216
217    let Item::Type(type_item) = potential_type_item else {
218        return error(
219            potential_type_item,
220            "first item of a module with `#[metawasm]` must be a type alias to a state type (e.g. `type State = StateType;`)",
221        );
222    };
223    let type_item_attributes = &type_item.attrs;
224
225    let (state_type, state_type_inner) = if type_item.ident == "State" {
226        validate_if_private(&type_item, &type_item.vis)?;
227
228        if type_item.generics.params.is_empty() {
229            (
230                TypePath {
231                    qself: None,
232                    path: type_item.ident.into(),
233                }
234                .into(),
235                *type_item.ty,
236            )
237        } else {
238            return error(type_item.generics, "must be without generics");
239        }
240    } else {
241        return error(
242            type_item.ident,
243            "identifier of the state type must be `State`",
244        );
245    };
246
247    // Checking functions
248
249    let mut functions = vec![];
250
251    for potential_function in potential_functions {
252        let Item::Fn(function) = potential_function else {
253            return error(
254                potential_function,
255                "rest of items in a module with `#[metawasm]` must be functions",
256            );
257        };
258
259        validate_if_private(&function, &function.vis)?;
260
261        let signature = function.sig;
262
263        return_error_if_some!(signature.constness, "mustn't be constant");
264        return_error_if_some!(signature.asyncness, "mustn't be asynchronous");
265        return_error_if_some!(signature.unsafety, "mustn't be unsafe");
266        return_error_if_some!(signature.abi, "mustn't have a binary interface");
267        return_error_if_some!(signature.variadic, "mustn't have the variadic argument");
268
269        if !signature.generics.params.is_empty() {
270            return error(signature.generics, "mustn't have generics");
271        }
272
273        if signature.inputs.len() > 19 {
274            return error(
275                signature.inputs,
276                "too many arguments, no more 19 arguments must be here due restrictions of the SCALE codec",
277            );
278        }
279
280        let signature_span = signature.span();
281        let mut inputs = signature.inputs.into_iter();
282
283        // Retrieving the first argument
284
285        let first = if let Some(first) = inputs.next() {
286            if let FnArg::Typed(first) = first {
287                validate_if_has_no_attributes(&first.attrs, "mustn't have attributes")?;
288
289                first
290            } else {
291                return error(first, "mustn't be `self`");
292            }
293        } else {
294            return error(
295                signature.paren_token.span,
296                "mustn't be empty, add `state: State` or `_: State`",
297            );
298        };
299
300        // Checking the first argument's name
301
302        if let Pat::Ident(ref pat_ident) = *first.pat {
303            if pat_ident.ident != "state" && pat_ident.ident != "_state" {
304                return error(&pat_ident.ident, "must be `state` or `_state`");
305            }
306        }
307
308        // Checking the first argument's type
309
310        match *first.ty {
311            Type::Reference(reference) if *reference.elem == state_type => {
312                let lifetime_span = reference.lifetime.map(|lifetime| lifetime.span());
313                let mutability_span = reference.mutability.map(|mutability| mutability.span());
314
315                let lifetime_mutability = lifetime_span.map_or(mutability_span, |lifetime_span| {
316                    Some(
317                        mutability_span
318                            .and_then(|mutability_span| mutability_span.join(lifetime_span))
319                            .unwrap_or(lifetime_span),
320                    )
321                });
322
323                let span = lifetime_mutability
324                    .and_then(|lifetime_mutability| {
325                        lifetime_mutability.join(reference.and_token.span)
326                    })
327                    .unwrap_or(reference.and_token.span);
328
329                return error(span, "mustn't take a reference");
330            }
331            first_type => {
332                if first_type != state_type {
333                    return error(first_type, "first argument's type must be `State`");
334                }
335            }
336        }
337
338        // Checking the rest of arguments
339
340        let mut arguments = vec![];
341
342        for argument in inputs {
343            if let FnArg::Typed(argument) = argument {
344                validate_if_has_no_attributes(&argument.attrs, "mustn't have attributes")?;
345
346                arguments.push((argument.pat, argument.ty));
347            } else {
348                // The rest of arguments can't be the `self` argument because
349                // the compiler won't allow this.
350                unreachable!("unexpected `self` argument");
351            }
352        }
353
354        // Checking an output
355
356        let return_type = match signature.output {
357            ReturnType::Default => {
358                return error(signature_span, "return type must be specified");
359            }
360            ReturnType::Type(_, return_type) => {
361                if let Type::Tuple(ref tuple) = *return_type {
362                    if tuple.elems.is_empty() {
363                        return error(tuple, "return type mustn't be `()`");
364                    }
365                }
366
367                return_type
368            }
369        };
370
371        functions.push((
372            function.attrs,
373            signature.ident,
374            first.pat,
375            arguments,
376            return_type,
377            function.block,
378        ));
379    }
380
381    // Code generating
382
383    let mut type_registrations = Vec::with_capacity(functions.len());
384    let (mut extern_functions, mut public_functions) =
385        (type_registrations.clone(), type_registrations.clone());
386
387    for (attributes, function_identifier, state_pattern, arguments, return_type, block) in functions
388    {
389        let CodeGenItems {
390            input_type,
391            variables,
392            variables_types,
393            variables_wo_parentheses,
394            arguments,
395        } = process_arguments(arguments, state_pattern);
396
397        let stringed_fn_ident = function_identifier.to_string();
398        let output = register_type(&return_type);
399
400        type_registrations.push(quote! {
401            funcs.insert(#stringed_fn_ident.into(), ::gmeta::TypesRepr { input: #input_type, output: #output });
402        });
403
404        extern_functions.push(quote! {
405            #[unsafe(no_mangle)]
406            extern "C" fn #function_identifier() {
407                let #variables: #variables_types = ::gstd::msg::load()
408                    .expect("Failed to load or decode a payload");
409
410                ::gstd::msg::reply(super::#function_identifier(#variables_wo_parentheses), 0)
411                    .expect("Failed to encode or reply with a result from a metawasm function");
412            }
413        });
414
415        public_functions.push(quote! {
416            #(#attributes)*
417            pub fn #function_identifier(#arguments) -> #return_type #block
418        });
419    }
420
421    let module_ident = quote::format_ident!("{MODULE_NAME}");
422
423    Ok(quote! {
424        pub mod #module_ident {
425            use super::*;
426
427            mod r#extern {
428                use super::*;
429
430                #[unsafe(no_mangle)]
431                extern "C" fn metadata() {
432                    let mut funcs = ::gstd::collections::BTreeMap::new();
433                    let mut registry = ::gmeta::Registry::new();
434
435                    #(#type_registrations)*
436
437                    let metawasm_data = ::gmeta::MetawasmData {
438                        funcs,
439                        registry: ::gstd::Encode::encode(&::gmeta::PortableRegistry::from(registry)),
440                    };
441
442                    ::gstd::msg::reply(metawasm_data, 0).expect("Failed to encode or reply with metawasm data");
443                }
444
445                #(#extern_functions)*
446            }
447
448            #(#type_item_attributes)*
449            pub type #state_type = #state_type_inner;
450
451            #(#public_functions)*
452        }
453    }.into())
454}
455
456struct CodeGenItems {
457    input_type: proc_macro2::TokenStream,
458    variables: proc_macro2::TokenStream,
459    variables_types: proc_macro2::TokenStream,
460    variables_wo_parentheses: proc_macro2::TokenStream,
461    arguments: proc_macro2::TokenStream,
462}
463
464fn process_arguments(
465    arguments: Vec<(Box<Pat>, Box<Type>)>,
466    state_pattern: Box<Pat>,
467) -> CodeGenItems {
468    if arguments.is_empty() {
469        let variables = quote!(state);
470
471        CodeGenItems {
472            input_type: quote!(None),
473            variables: variables.clone(),
474            variables_types: quote!(State),
475            variables_wo_parentheses: variables,
476            arguments: quote!(#state_pattern: State),
477        }
478    } else {
479        let arguments_types = arguments.iter().map(|argument| &argument.1);
480        let variables_types_wo_parentheses = quote!(#(#arguments_types),*);
481
482        let (variables_wo_parentheses, variables, variables_types) = if arguments.len() > 1 {
483            let variables_wo_parentheses =
484                (0..arguments.len()).map(|index| quote::format_ident!("arg{}", index));
485            let variables_wo_parentheses = quote!(#(#variables_wo_parentheses),*);
486
487            let variables_with_parentheses = quote!((#variables_wo_parentheses));
488
489            (
490                variables_wo_parentheses,
491                variables_with_parentheses,
492                quote!((#variables_types_wo_parentheses)),
493            )
494        } else {
495            let variables_wo_parentheses = quote!(arg);
496
497            (
498                variables_wo_parentheses.clone(),
499                variables_wo_parentheses,
500                variables_types_wo_parentheses,
501            )
502        };
503
504        let input_type = register_type(variables_types.clone());
505
506        let arguments = arguments
507            .into_iter()
508            .map(|(pattern, ty)| quote!(#pattern: #ty));
509
510        CodeGenItems {
511            input_type,
512            variables: quote!((#variables, state)),
513            variables_types: quote!((#variables_types, State)),
514            variables_wo_parentheses: quote!(state, #variables_wo_parentheses),
515            arguments: quote!(#state_pattern: State, #(#arguments),*),
516        }
517    }
518}
519
520fn register_type(ty: impl ToTokens) -> proc_macro2::TokenStream {
521    let ty = ty.to_token_stream();
522
523    quote! {
524        Some(registry.register_type(&::gmeta::MetaType::new::<#ty>()).id)
525    }
526}