gstd_codegen/
lib.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-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
19//! Provides macros for async runtime of Gear programs.
20
21use core::fmt::Display;
22use gprimitives::ActorId;
23use proc_macro::TokenStream;
24use proc_macro2::Ident;
25use quote::{quote, ToTokens};
26use std::{collections::BTreeSet, str::FromStr};
27use syn::{
28    parse::{Parse, ParseStream},
29    punctuated::Punctuated,
30    Path, Token,
31};
32
33mod utils;
34
35/// A global flag, determining if `handle_reply` already was generated.
36static mut HANDLE_REPLY_FLAG: Flag = Flag(false);
37
38/// A global flag, determining if `handle_signal` already was generated.
39#[cfg(not(feature = "ethexe"))]
40static mut HANDLE_SIGNAL_FLAG: Flag = Flag(false);
41
42#[cfg(feature = "ethexe")]
43static mut HANDLE_SIGNAL_FLAG: Flag = Flag(true);
44
45fn literal_to_actor_id(literal: syn::LitStr) -> syn::Result<TokenStream> {
46    let actor_id: [u8; 32] = ActorId::from_str(&literal.value())
47        .map_err(|err| syn::Error::new_spanned(literal, err))?
48        .into();
49
50    let actor_id_array = format!("{actor_id:?}")
51        .parse::<proc_macro2::TokenStream>()
52        .expect("failed to parse token stream");
53
54    Ok(quote! { gstd::ActorId::new(#actor_id_array) }.into())
55}
56
57/// Macro to declare `ActorId` from hexadecimal and ss58 format.
58///
59/// # Example
60/// ```
61/// use gstd::{actor_id, ActorId};
62///
63/// # fn main() {
64/// //polkadot address
65/// let alice_1: ActorId = actor_id!("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
66/// //vara address
67/// let alice_2: ActorId = actor_id!("kGkLEU3e3XXkJp2WK4eNpVmSab5xUNL9QtmLPh8QfCL2EgotW");
68/// //hex address
69/// let alice_3: ActorId =
70///     actor_id!("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
71///
72/// assert_eq!(alice_1, alice_2);
73/// assert_eq!(alice_2, alice_3);
74/// # }
75/// ```
76#[proc_macro]
77pub fn actor_id(input: TokenStream) -> TokenStream {
78    literal_to_actor_id(syn::parse_macro_input!(input as syn::LitStr))
79        .unwrap_or_else(|err| err.to_compile_error().into())
80}
81
82struct Flag(bool);
83
84impl Flag {
85    fn get_and_set(&mut self) -> bool {
86        let ret = self.0;
87        self.0 = true;
88        ret
89    }
90}
91
92struct MainAttrs {
93    handle_reply: Option<Path>,
94    handle_signal: Option<Path>,
95}
96
97impl MainAttrs {
98    fn check_attrs_not_exist(&self) -> Result<(), TokenStream> {
99        let Self {
100            handle_reply,
101            handle_signal,
102        } = self;
103
104        for (path, flag) in unsafe {
105            [
106                (handle_reply, HANDLE_REPLY_FLAG.0),
107                (handle_signal, HANDLE_SIGNAL_FLAG.0),
108            ]
109        } {
110            if let (Some(path), true) = (path, flag) {
111                return Err(compile_error(path, "parameter already defined"));
112            }
113        }
114
115        Ok(())
116    }
117}
118
119impl Parse for MainAttrs {
120    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
121        let punctuated: Punctuated<MainAttr, Token![,]> = Punctuated::parse_terminated(input)?;
122        let mut attrs = MainAttrs {
123            handle_reply: None,
124            handle_signal: None,
125        };
126        let mut existing_attrs = BTreeSet::new();
127
128        for MainAttr { name, path, .. } in punctuated {
129            let name = name.to_string();
130            if existing_attrs.contains(&name) {
131                return Err(syn::Error::new_spanned(name, "parameter already defined"));
132            }
133
134            match &*name {
135                "handle_reply" => {
136                    attrs.handle_reply = Some(path);
137                }
138                #[cfg(not(feature = "ethexe"))]
139                "handle_signal" => {
140                    attrs.handle_signal = Some(path);
141                }
142                #[cfg(feature = "ethexe")]
143                "handle_signal" => {
144                    return Err(syn::Error::new_spanned(
145                        name,
146                        "`handle_signal` is forbidden with `ethexe` feature on",
147                    ));
148                }
149                _ => return Err(syn::Error::new_spanned(name, "unknown parameter")),
150            }
151
152            existing_attrs.insert(name);
153        }
154
155        Ok(attrs)
156    }
157}
158
159struct MainAttr {
160    name: Ident,
161    path: Path,
162}
163
164impl Parse for MainAttr {
165    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
166        let name: Ident = input.parse()?;
167        let _: Token![=] = input.parse()?;
168        let path: Path = input.parse()?;
169
170        Ok(Self { name, path })
171    }
172}
173
174fn compile_error<T: ToTokens, U: Display>(tokens: T, msg: U) -> TokenStream {
175    syn::Error::new_spanned(tokens, msg)
176        .to_compile_error()
177        .into()
178}
179
180fn check_signature(name: &str, function: &syn::ItemFn) -> Result<(), TokenStream> {
181    if function.sig.ident != name {
182        return Err(compile_error(
183            &function.sig.ident,
184            format!("function must be called `{name}`"),
185        ));
186    }
187
188    if !function.sig.inputs.is_empty() {
189        return Err(compile_error(
190            &function.sig.ident,
191            "function must have no arguments",
192        ));
193    }
194
195    if function.sig.asyncness.is_none() {
196        return Err(compile_error(
197            function.sig.fn_token,
198            "function must be async",
199        ));
200    }
201
202    Ok(())
203}
204
205fn generate_handle_reply_if_required(mut code: TokenStream, attr: Option<Path>) -> TokenStream {
206    #[allow(clippy::deref_addrof)] // https://github.com/rust-lang/rust-clippy/issues/13783
207    let reply_generated = unsafe { (*&raw mut HANDLE_REPLY_FLAG).get_and_set() };
208    if !reply_generated {
209        let handle_reply: TokenStream = quote!(
210            #[unsafe(no_mangle)]
211            extern "C" fn handle_reply() {
212                gstd::handle_reply_with_hook();
213                #attr ();
214            }
215        )
216        .into();
217        code.extend([handle_reply]);
218    }
219
220    code
221}
222
223fn generate_handle_signal_if_required(mut code: TokenStream, attr: Option<Path>) -> TokenStream {
224    #[allow(clippy::deref_addrof)] // https://github.com/rust-lang/rust-clippy/issues/13783
225    let signal_generated = unsafe { (*&raw mut HANDLE_SIGNAL_FLAG).get_and_set() };
226    if !signal_generated {
227        let handle_signal: TokenStream = quote!(
228            #[unsafe(no_mangle)]
229            extern "C" fn handle_signal() {
230                gstd::handle_signal();
231                #attr ();
232            }
233        )
234        .into();
235        code.extend([handle_signal]);
236    }
237
238    code
239}
240
241fn generate_if_required(code: TokenStream, attrs: MainAttrs) -> TokenStream {
242    let code = generate_handle_reply_if_required(code, attrs.handle_reply);
243    generate_handle_signal_if_required(code, attrs.handle_signal)
244}
245
246/// Mark the main async function to be the program entry point.
247///
248/// Can be used together with [`macro@async_init`].
249///
250/// When this macro is used, it’s not possible to specify the `handle` function.
251/// If you need to specify the `handle` function explicitly, don't use this
252/// macro.
253///
254/// # Examples
255///
256/// Simple async handle function:
257///
258/// ```
259/// #[gstd::async_main]
260/// async fn main() {
261///     gstd::debug!("Hello world!");
262/// }
263/// # fn main() {}
264/// ```
265///
266/// Use `handle_reply` and `handle_signal` parameters to specify corresponding
267/// handlers. Note that custom reply and signal handlers derive their default
268/// behavior.
269///
270/// ```
271/// #[gstd::async_main(handle_reply = my_handle_reply)]
272/// async fn main() {
273///     // ...
274/// }
275///
276/// fn my_handle_reply() {
277///     // ...
278/// }
279/// # fn main() {}
280/// ```
281#[proc_macro_attribute]
282pub fn async_main(attr: TokenStream, item: TokenStream) -> TokenStream {
283    let function = syn::parse_macro_input!(item as syn::ItemFn);
284    if let Err(tokens) = check_signature("main", &function) {
285        return tokens;
286    }
287
288    let attrs = syn::parse_macro_input!(attr as MainAttrs);
289    if let Err(tokens) = attrs.check_attrs_not_exist() {
290        return tokens;
291    }
292
293    let body = &function.block;
294    let code: TokenStream = quote!(
295
296        fn __main_safe() {
297            gstd::message_loop(async #body);
298        }
299
300        #[unsafe(no_mangle)]
301        extern "C" fn handle() {
302            __main_safe();
303        }
304    )
305    .into();
306
307    generate_if_required(code, attrs)
308}
309
310/// Mark async function to be the program initialization method.
311///
312/// Can be used together with [`macro@async_main`].
313///
314/// The `init` function cannot be specified if this macro is used.
315/// If you need to specify the `init` function explicitly, don't use this macro.
316///
317///
318/// # Examples
319///
320/// Simple async init function:
321///
322/// ```
323/// #[gstd::async_init]
324/// async fn init() {
325///     gstd::debug!("Hello world!");
326/// }
327/// ```
328///
329/// Use `handle_reply` and `handle_signal` parameters to specify corresponding
330/// handlers. Note that custom reply and signal handlers derive their default
331/// behavior.
332///
333/// ```ignore
334/// #[gstd::async_init(handle_signal = my_handle_signal)]
335/// async fn init() {
336///     // ...
337/// }
338///
339/// fn my_handle_signal() {
340///     // ...
341/// }
342/// ```
343#[proc_macro_attribute]
344pub fn async_init(attr: TokenStream, item: TokenStream) -> TokenStream {
345    let function = syn::parse_macro_input!(item as syn::ItemFn);
346    if let Err(tokens) = check_signature("init", &function) {
347        return tokens;
348    }
349
350    let attrs = syn::parse_macro_input!(attr as MainAttrs);
351    if let Err(tokens) = attrs.check_attrs_not_exist() {
352        return tokens;
353    }
354
355    let body = &function.block;
356    let code: TokenStream = quote!(
357        #[unsafe(no_mangle)]
358        extern "C" fn init() {
359            gstd::message_loop(async #body);
360        }
361    )
362    .into();
363
364    generate_if_required(code, attrs)
365}
366
367/// Extends async methods `for_reply` and `for_reply_as` for sending
368/// methods.
369///
370/// # Usage
371///
372/// ```ignore
373/// use gcore::errors::Result;
374///
375/// #[wait_for_reply]
376/// pub fn send_bytes<T: AsRef<[u8]>>(program: ActorId, payload: T, value: u128) -> Result<MessageId> {
377///   gcore::msg::send(program.into(), payload.as_ref(), value)
378/// }
379/// ```
380///
381/// outputs:
382///
383/// ```ignore
384/// /// Same as [`send_bytes`](self::send_bytes), but the program
385/// /// will interrupt until the reply is received.
386/// ///
387/// /// Argument `reply_deposit: u64` used to provide gas for
388/// /// future reply handling (skipped if zero).
389/// ///
390/// /// # See also
391/// ///
392/// /// - [`send_bytes_for_reply_as`](self::send_bytes_for_reply_as)
393/// pub fn send_bytes_for_reply<T: AsRef<[u8]>>(
394///     program: ActorId,
395///     payload: T,
396///     value: u128,
397///     reply_deposit: u64
398/// ) -> Result<crate::msg::MessageFuture> {
399///     // Function call.
400///     let waiting_reply_to = send_bytes(program, payload, value)?;
401///
402///     // Depositing gas for future reply handling if not zero.
403///     if reply_deposit != 0 {
404///         crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
405///     }
406///
407///     // Registering signal.
408///     crate::async_runtime::signals().register_signal(waiting_reply_to);
409///
410///     Ok(crate::msg::MessageFuture { waiting_reply_to })
411/// }
412///
413/// /// Same as [`send_bytes`](self::send_bytes), but the program
414/// /// will interrupt until the reply is received.
415/// ///
416/// /// Argument `reply_deposit: u64` used to provide gas for
417/// /// future reply handling (skipped if zero).
418/// ///
419/// /// The output should be decodable via SCALE codec.
420/// ///
421/// /// # See also
422/// ///
423/// /// - [`send_bytes_for_reply`](self::send_bytes_for_reply)
424/// /// - <https://docs.substrate.io/reference/scale-codec>
425/// pub fn send_bytes_for_reply_as<T: AsRef<[u8]>, D: crate::codec::Decode>(
426///     program: ActorId,
427///     payload: T,
428///     value: u128,
429///     reply_deposit: u64,
430/// ) -> Result<crate::msg::CodecMessageFuture<D>> {
431///     // Function call.
432///     let waiting_reply_to = send_bytes(program, payload, value)?;
433///
434///     // Depositing gas for future reply handling if not zero.
435///     if reply_deposit != 0 {
436///         crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
437///     }
438///
439///     // Registering signal.
440///     crate::async_runtime::signals().register_signal(waiting_reply_to);
441///
442///     Ok(crate::msg::CodecMessageFuture::<D> {
443///         waiting_reply_to,
444///         _marker: Default::default(),
445///     })
446/// }
447/// ```
448#[proc_macro_attribute]
449pub fn wait_for_reply(attr: TokenStream, item: TokenStream) -> TokenStream {
450    let function = syn::parse_macro_input!(item as syn::ItemFn);
451    let ident = &function.sig.ident;
452
453    // Generate functions' idents.
454    let (for_reply, for_reply_as) = (
455        utils::with_suffix(ident, "_for_reply"),
456        utils::with_suffix(ident, "_for_reply_as"),
457    );
458
459    // Generate docs.
460    let style = if !attr.is_empty() {
461        utils::DocumentationStyle::Method
462    } else {
463        utils::DocumentationStyle::Function
464    };
465
466    let (for_reply_docs, for_reply_as_docs) = utils::wait_for_reply_docs(ident.to_string(), style);
467
468    // Generate arguments.
469    #[cfg_attr(feature = "ethexe", allow(unused_mut))]
470    let (mut inputs, variadic) = (function.sig.inputs.clone(), function.sig.variadic.clone());
471    let args = utils::get_args(&inputs);
472
473    // Add `reply_deposit` argument.
474    #[cfg(not(feature = "ethexe"))]
475    inputs.push(syn::parse_quote!(reply_deposit: u64));
476
477    // Generate generics.
478    let decodable_ty = utils::ident("D");
479    let decodable_traits = vec![syn::parse_quote!(crate::codec::Decode)];
480    let (for_reply_generics, for_reply_as_generics) = (
481        function.sig.generics.clone(),
482        utils::append_generic(
483            function.sig.generics.clone(),
484            decodable_ty,
485            decodable_traits,
486        ),
487    );
488
489    let ident = if !attr.is_empty() {
490        assert_eq!(
491            attr.to_string(),
492            "self",
493            "Proc macro attribute should be used only to specify self source of the function"
494        );
495
496        quote! { self.#ident }
497    } else {
498        quote! { #ident }
499    };
500
501    match () {
502        #[cfg(not(feature = "ethexe"))]
503        () => quote! {
504            #function
505
506            #[doc = #for_reply_docs]
507            pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::MessageFuture> {
508                // Function call.
509                let waiting_reply_to = #ident #args ?;
510
511                // Depositing gas for future reply handling if not zero.
512                if reply_deposit != 0 {
513                    crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
514                }
515
516                // Registering signal.
517                crate::async_runtime::signals().register_signal(waiting_reply_to);
518
519            Ok(crate::msg::MessageFuture { waiting_reply_to, reply_deposit })
520        }
521
522            #[doc = #for_reply_as_docs]
523            pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecMessageFuture<D>> {
524                // Function call.
525                let waiting_reply_to = #ident #args ?;
526
527                // Depositing gas for future reply handling if not zero.
528                if reply_deposit != 0 {
529                    crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
530                }
531
532                // Registering signal.
533                crate::async_runtime::signals().register_signal(waiting_reply_to);
534
535                Ok(crate::msg::CodecMessageFuture::<D> { waiting_reply_to, reply_deposit, _marker: Default::default() })
536            }
537        },
538        #[cfg(feature = "ethexe")]
539        () => quote! {
540            #function
541
542            #[doc = #for_reply_docs]
543            pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::MessageFuture> {
544                // Function call.
545                let waiting_reply_to = #ident #args ?;
546
547                // Registering signal.
548                crate::async_runtime::signals().register_signal(waiting_reply_to);
549
550                Ok(crate::msg::MessageFuture { waiting_reply_to, reply_deposit: 0 })
551            }
552
553            #[doc = #for_reply_as_docs]
554            pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecMessageFuture<D>> {
555                // Function call.
556                let waiting_reply_to = #ident #args ?;
557
558                // Registering signal.
559                crate::async_runtime::signals().register_signal(waiting_reply_to);
560
561                Ok(crate::msg::CodecMessageFuture::<D> { waiting_reply_to, reply_deposit: 0, _marker: Default::default() })
562            }
563        },
564    }.into()
565}
566
567/// Similar to [`macro@wait_for_reply`], but works with functions that create
568/// programs: It returns a message id with a newly created program id.
569#[proc_macro_attribute]
570pub fn wait_create_program_for_reply(attr: TokenStream, item: TokenStream) -> TokenStream {
571    let function = syn::parse_macro_input!(item as syn::ItemFn);
572
573    let function_ident = &function.sig.ident;
574
575    let ident = if !attr.is_empty() {
576        assert_eq!(
577            attr.to_string(),
578            "Self",
579            "Proc macro attribute should be used only to specify Self source of the function"
580        );
581
582        quote! { Self::#function_ident }
583    } else {
584        quote! { #function_ident }
585    };
586
587    // Generate functions' idents.
588    let (for_reply, for_reply_as) = (
589        utils::with_suffix(&function.sig.ident, "_for_reply"),
590        utils::with_suffix(&function.sig.ident, "_for_reply_as"),
591    );
592
593    // Generate docs.
594    let style = if !attr.is_empty() {
595        utils::DocumentationStyle::Method
596    } else {
597        utils::DocumentationStyle::Function
598    };
599
600    let (for_reply_docs, for_reply_as_docs) =
601        utils::wait_for_reply_docs(function_ident.to_string(), style);
602
603    // Generate arguments.
604    #[cfg_attr(feature = "ethexe", allow(unused_mut))]
605    let (mut inputs, variadic) = (function.sig.inputs.clone(), function.sig.variadic.clone());
606    let args = utils::get_args(&inputs);
607
608    // Add `reply_deposit` argument.
609    #[cfg(not(feature = "ethexe"))]
610    inputs.push(syn::parse_quote!(reply_deposit: u64));
611
612    // Generate generics.
613    let decodable_ty = utils::ident("D");
614    let decodable_traits = vec![syn::parse_quote!(crate::codec::Decode)];
615    let (for_reply_generics, for_reply_as_generics) = (
616        function.sig.generics.clone(),
617        utils::append_generic(
618            function.sig.generics.clone(),
619            decodable_ty,
620            decodable_traits,
621        ),
622    );
623
624    match () {
625        #[cfg(not(feature = "ethexe"))]
626        () => quote! {
627            #function
628
629            #[doc = #for_reply_docs]
630            pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::CreateProgramFuture> {
631                // Function call.
632                let (waiting_reply_to, program_id) = #ident #args ?;
633
634                // Depositing gas for future reply handling if not zero.
635                if reply_deposit != 0 {
636                    crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
637                }
638
639                // Registering signal.
640                crate::async_runtime::signals().register_signal(waiting_reply_to);
641
642            Ok(crate::msg::CreateProgramFuture { waiting_reply_to, program_id, reply_deposit })
643        }
644
645            #[doc = #for_reply_as_docs]
646            pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecCreateProgramFuture<D>> {
647                // Function call.
648                let (waiting_reply_to, program_id) = #ident #args ?;
649
650                // Depositing gas for future reply handling if not zero.
651                if reply_deposit != 0 {
652                    crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
653                }
654
655                // Registering signal.
656                crate::async_runtime::signals().register_signal(waiting_reply_to);
657
658                Ok(crate::msg::CodecCreateProgramFuture::<D> { waiting_reply_to, program_id, reply_deposit, _marker: Default::default() })
659            }
660        },
661        #[cfg(feature = "ethexe")]
662        () => quote! {
663            #function
664
665            #[doc = #for_reply_docs]
666            pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::CreateProgramFuture> {
667                // Function call.
668                let (waiting_reply_to, program_id) = #ident #args ?;
669
670                // Registering signal.
671                crate::async_runtime::signals().register_signal(waiting_reply_to);
672
673                Ok(crate::msg::CreateProgramFuture { waiting_reply_to, program_id, reply_deposit: 0 })
674            }
675
676            #[doc = #for_reply_as_docs]
677            pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecCreateProgramFuture<D>> {
678                // Function call.
679                let (waiting_reply_to, program_id) = #ident #args ?;
680
681                // Registering signal.
682                crate::async_runtime::signals().register_signal(waiting_reply_to);
683
684                Ok(crate::msg::CodecCreateProgramFuture::<D> { waiting_reply_to, program_id, reply_deposit: 0, _marker: Default::default() })
685            }
686        },
687    }.into()
688}
689
690#[cfg(test)]
691mod tests {
692    #[test]
693    fn ui() {
694        let t = trybuild::TestCases::new();
695
696        #[cfg(not(feature = "ethexe"))]
697        {
698            t.pass("tests/ui/async_init_works.rs");
699            t.pass("tests/ui/async_main_works.rs");
700            t.compile_fail("tests/ui/signal_double_definition_not_work.rs");
701            t.compile_fail("tests/ui/reply_double_definition_not_work.rs");
702        }
703
704        #[cfg(feature = "ethexe")]
705        {
706            t.compile_fail("tests/ui/signal_doesnt_work_with_ethexe.rs");
707        }
708    }
709}