1use 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
35static mut HANDLE_REPLY_FLAG: Flag = Flag(false);
37
38#[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#[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)] 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)] 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#[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#[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#[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 let (for_reply, for_reply_as) = (
455 utils::with_suffix(ident, "_for_reply"),
456 utils::with_suffix(ident, "_for_reply_as"),
457 );
458
459 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 #[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 #[cfg(not(feature = "ethexe"))]
475 inputs.push(syn::parse_quote!(reply_deposit: u64));
476
477 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 let waiting_reply_to = #ident #args ?;
510
511 if reply_deposit != 0 {
513 crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
514 }
515
516 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 let waiting_reply_to = #ident #args ?;
526
527 if reply_deposit != 0 {
529 crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
530 }
531
532 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 let waiting_reply_to = #ident #args ?;
546
547 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 let waiting_reply_to = #ident #args ?;
557
558 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#[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 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 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 #[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 #[cfg(not(feature = "ethexe"))]
610 inputs.push(syn::parse_quote!(reply_deposit: u64));
611
612 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 let (waiting_reply_to, program_id) = #ident #args ?;
633
634 if reply_deposit != 0 {
636 crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
637 }
638
639 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 let (waiting_reply_to, program_id) = #ident #args ?;
649
650 if reply_deposit != 0 {
652 crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
653 }
654
655 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 let (waiting_reply_to, program_id) = #ident #args ?;
669
670 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 let (waiting_reply_to, program_id) = #ident #args ?;
680
681 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}