1use crate::{
2 export, sails_paths,
3 shared::{self, FnBuilder},
4};
5use args::ProgramArgs;
6use proc_macro_error::abort;
7use proc_macro2::{Span, TokenStream as TokenStream2};
8use quote::quote;
9use std::{
10 collections::BTreeMap,
11 env,
12 ops::{Deref, DerefMut},
13};
14use syn::{
15 Generics, Ident, ImplItem, ImplItemFn, ItemImpl, Path, PathArguments, Receiver, ReturnType,
16 Type, TypePath, Visibility, WhereClause, parse_quote, spanned::Spanned,
17};
18
19mod args;
20#[cfg(feature = "ethexe")]
21mod ethexe;
22
23static mut PROGRAM_SPANS: BTreeMap<String, Span> = BTreeMap::new();
25
26pub fn gprogram(args: TokenStream2, program_impl_tokens: TokenStream2) -> TokenStream2 {
27 let program_impl = parse_gprogram_impl(program_impl_tokens);
28 ensure_single_gprogram(&program_impl);
29 let args = parse_args(args);
30 gen_gprogram_impl(program_impl, args)
31}
32
33#[doc(hidden)]
34pub fn __gprogram_internal(args: TokenStream2, program_impl_tokens: TokenStream2) -> TokenStream2 {
35 let program_impl = parse_gprogram_impl(program_impl_tokens);
36 let args = parse_args(args);
37 gen_gprogram_impl(program_impl, args)
38}
39
40fn parse_args(args: TokenStream2) -> ProgramArgs {
41 syn::parse2(args).unwrap_or_else(|err| {
42 abort!(
43 err.span(),
44 "failed to parse `program` attribute arguments: {}",
45 err
46 )
47 })
48}
49
50fn parse_gprogram_impl(program_impl_tokens: TokenStream2) -> ItemImpl {
51 syn::parse2(program_impl_tokens).unwrap_or_else(|err| {
52 abort!(
53 err.span(),
54 "`program` attribute can be applied to impls only: {}",
55 err
56 )
57 })
58}
59
60#[allow(static_mut_refs)]
61fn ensure_single_gprogram(program_impl: &ItemImpl) {
62 let crate_name = env::var("CARGO_CRATE_NAME").unwrap_or("crate".to_string());
63 if unsafe { PROGRAM_SPANS.get(&crate_name) }.is_some() {
64 abort!(
65 program_impl,
66 "multiple `program` attributes are not allowed"
67 )
68 }
69 unsafe { PROGRAM_SPANS.insert(crate_name, program_impl.span()) };
70}
71
72struct ProgramBuilder {
73 program_impl: ItemImpl,
74 program_args: ProgramArgs,
75 type_constraints: Option<WhereClause>,
76 has_default_ctor_only: bool,
77}
78
79impl ProgramBuilder {
80 fn new(program_impl: ItemImpl, program_args: ProgramArgs) -> Self {
81 let mut program_impl = program_impl;
82 let type_constraints = program_impl.generics.where_clause.take();
83 let has_default_ctor_only = ensure_default_program_ctor(&mut program_impl);
84
85 Self {
86 program_impl,
87 program_args,
88 type_constraints,
89 has_default_ctor_only,
90 }
91 }
92
93 fn sails_path(&self) -> &Path {
94 self.program_args.sails_path()
95 }
96
97 fn impl_type(&self) -> (&TypePath, &PathArguments, &Ident) {
98 shared::impl_type_refs(self.program_impl.self_ty.as_ref())
99 }
100
101 fn impl_constraints(&self) -> (&Generics, Option<&WhereClause>) {
102 (&self.program_impl.generics, self.type_constraints.as_ref())
103 }
104
105 fn program_ctors(&self) -> Vec<FnBuilder<'_>> {
106 discover_program_ctors(&self.program_impl, self.sails_path())
107 }
108
109 fn handle_reply_fn(&mut self) -> Option<&mut ImplItemFn> {
110 let mut fn_iter = self.program_impl.items.iter_mut().filter_map(|item| {
111 if let ImplItem::Fn(fn_item) = item
112 && has_handle_reply_attr(fn_item) {
113 fn_item
114 .attrs
115 .retain(|attr| !attr.path().is_ident("handle_reply"));
116 if handle_reply_predicate(fn_item) {
117 return Some(fn_item);
118 } else {
119 abort!(
120 fn_item,
121 "`handle_reply` function must be private and have a single `&self` argument and no return type"
122 );
123 }
124 }
125 None
126 });
127 let handle_reply_fn = fn_iter.next();
128 if let Some(duplicate) = fn_iter.next() {
129 abort!(duplicate, "only one `handle_reply` function is allowed");
130 }
131 handle_reply_fn
132 }
133
134 #[cfg(feature = "ethexe")]
135 fn service_ctors(&self) -> Vec<FnBuilder<'_>> {
136 shared::discover_invocation_targets(self, service_ctor_predicate, self.sails_path())
137 }
138}
139
140impl ProgramBuilder {
141 fn wire_up_service_exposure(
142 &mut self,
143 program_ident: &Ident,
144 ) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) {
145 let mut services_meta = Vec::new();
146 let mut meta_asyncness = Vec::new();
147 let mut routes = BTreeMap::new();
148 #[allow(unused_mut)]
150 let mut solidity_dispatchers: Vec<TokenStream2> = Vec::new();
151
152 let has_async_ctor = self
153 .program_ctors()
154 .iter()
155 .any(|fn_builder| fn_builder.is_async());
156
157 if has_async_ctor {
158 meta_asyncness.push(quote!(true));
159 }
160
161 let mut modifications = Vec::new();
163 let mut services_count_data = Vec::new(); let mut services_ids_data = Vec::new(); let mut route_dispatch_data = Vec::new(); for (idx, impl_item) in self.program_impl.items.iter().enumerate() {
168 if let ImplItem::Fn(fn_item) = impl_item {
169 if is_mut_service_ctor(fn_item) {
170 abort!(
171 fn_item,
172 "service constructor must take `&self`, not `&mut self`: a `&mut self` \
173 factory can leak an exclusive borrow of program state across an `.await`"
174 );
175 }
176
177 if !service_ctor_predicate(fn_item) {
178 continue;
179 }
180
181 let mut invocation_export = shared::invocation_export_or_default(fn_item);
182 let entry_id = routes.len() as u16;
183
184 #[cfg(feature = "ethexe")]
185 {
186 use convert_case::{Case, Casing};
187 let camel_case_route = invocation_export.route.to_case(Case::Camel);
188 shared::validation::validate_identifier(
189 &camel_case_route,
190 fn_item.sig.ident.span(),
191 "Exposed Service",
192 );
193 }
194 if let Some(duplicate) = routes.insert(
195 invocation_export.route.clone(),
196 fn_item.sig.ident.to_string(),
197 ) {
198 abort!(
199 invocation_export.span,
200 "`export` attribute conflicts with one already assigned to '{}'",
201 duplicate
202 );
203 }
204
205 invocation_export.export = true;
206
207 let fn_builder =
208 FnBuilder::new(invocation_export, entry_id, fn_item, self.sails_path());
209
210 let original_service_ctor_fn = fn_builder.original_service_ctor_fn();
211 let wrapping_service_ctor_fn =
212 fn_builder.wrapping_service_ctor_fn(&original_service_ctor_fn.sig.ident);
213
214 services_meta.push(fn_builder.service_meta());
215
216 if !has_async_ctor {
217 meta_asyncness.push(fn_builder.service_meta_asyncness());
220 }
221
222 #[cfg(feature = "ethexe")]
223 if fn_builder.has_ethabi_codec() {
224 solidity_dispatchers.push(fn_builder.sol_service_invocation());
225 }
226
227 let service_type = fn_builder.result_type.clone();
229 let service_ctor_ident = fn_builder.ident.clone();
230
231 services_count_data.push(service_type.clone());
232 services_ids_data.push(service_type.clone());
233 if fn_builder.has_scale_codec() {
234 route_dispatch_data.push((
235 fn_builder.service_route_idx(),
236 service_ctor_ident,
237 service_type,
238 ));
239 }
240
241 modifications.push((idx, original_service_ctor_fn, wrapping_service_ctor_fn));
242 }
243 }
244
245 for (idx, original_service_ctor_fn, wrapping_service_ctor_fn) in modifications {
247 self.program_impl.items[idx] = ImplItem::Fn(original_service_ctor_fn);
248 self.program_impl
249 .items
250 .push(ImplItem::Fn(wrapping_service_ctor_fn));
251 }
252
253 if meta_asyncness.is_empty() {
254 meta_asyncness.push(quote!(false));
255 }
256
257 let handle_reply_fn = self.handle_reply_fn().map(|item_fn| {
258 let handle_reply_fn_ident = &item_fn.sig.ident;
259 quote! {
260 let program_ref = unsafe { #program_ident.as_ref() }.expect("Program not initialized");
261 program_ref.#handle_reply_fn_ident();
262 }
263 })
264 .unwrap_or_default();
265
266 let handle_signal_fn = self
267 .program_args
268 .handle_signal()
269 .map(|handle_signal_path| quote!( #handle_signal_path ();))
270 .unwrap_or_default();
271
272 let sails_path = self.sails_path();
273 let (program_type_path, _program_type_args, _) = self.impl_type();
274 let (generics, program_type_constraints) = self.impl_constraints();
275
276 let program_meta_impl = quote! {
277 impl #generics #sails_path::meta::ProgramMeta for #program_type_path #program_type_constraints {
278 type ConstructorsMeta = meta_in_program::ConstructorsMeta;
279
280 const SERVICES: &'static [(&'static str, #sails_path::meta::AnyServiceMeta)] = &[
281 #(#services_meta),*
282 ];
283 const ASYNC: bool = #( #meta_asyncness )||*;
284 }
285 };
286
287 let services_count_expr = {
289 let count_exprs = services_count_data.iter().map(|service_type| {
290 quote! {
291 + #sails_path::meta::count_base_services::<#service_type>()
292 }
293 });
294
295 let base_count = services_count_data.len();
296
297 quote! {
298 const SERVICES_COUNT: usize = #base_count #(#count_exprs)*;
299 }
300 };
301
302 let services_ids_expr = {
303 let ids_exprs = services_ids_data.iter().map(|service_type| {
304 quote! {
305 #sails_path::meta::BaseServiceMeta::new::<#service_type>(""),
306 }
307 });
308
309 quote! {
310 const INTERFACE_IDS: &'static [(#sails_path::meta::InterfaceId, u8)] =
311 &#sails_path::meta::interface_ids::<SERVICES_COUNT>(&[
312 #(#ids_exprs)*
313 ]);
314 }
315 };
316
317 let route_dispatches = route_dispatch_data
319 .iter()
320 .map(|(route_idx, service_ctor_ident, service_type)| {
321 quote! {
322 #route_idx => {
323 let svc = program_ref.#service_ctor_ident();
324 #sails_path::service_route_dispatch!(
325 svc: #service_type,
326 interface_id = interface_id,
327 entry_id = entry_id,
328 input = &input[header_len..],
329 );
330 }
331 }
332 })
333 .collect::<Vec<_>>();
334
335 let solidity_main = self.sol_main(solidity_dispatchers.as_slice());
336
337 let payable = self.program_args.payable().then(|| {
338 quote! {
339 if gstd::msg::value() > 0 && gstd::msg::size() == 0 {
340 return;
341 }
342 }
343 });
344
345 let main_fn = quote!(
346 #services_count_expr
347 #services_ids_expr
348
349 #[unsafe(no_mangle)]
350 extern "C" fn handle() {
351 #payable
352
353 let mut input = gstd::msg::load_bytes().expect("Failed to read input");
354
355 let program_ref = unsafe { #program_ident.as_ref() }.expect("Program not initialized");
356
357 #solidity_main
358
359 if let Ok(header) = <#sails_path::meta::SailsMessageHeader as #sails_path::Decode>::decode(&mut input.as_slice()) {
360 let header_len = header.hlen().inner() as usize;
361 let (interface_id, route_id, entry_id) = header
362 .try_match_interfaces(INTERFACE_IDS)
363 .expect("Failed to find matching service")
364 .into_inner();
365 match route_id {
366 #(#route_dispatches)*
367 _ => gstd::unknown_input_panic("Unknown route_id", &[route_id])
368 }
369 }
370 }
371 );
372
373 let handle_reply_fn = quote! {
374 #[unsafe(no_mangle)]
375 extern "C" fn handle_reply() {
376 use #sails_path::meta::ProgramMeta;
377
378 if #program_type_path::ASYNC {
379 gstd::handle_reply_with_hook();
380 }
381
382 #handle_reply_fn
383 }
384 };
385
386 #[cfg(not(feature = "ethexe"))]
387 let handle_signal_fn = quote! {
388 #[unsafe(no_mangle)]
389 extern "C" fn handle_signal() {
390 use #sails_path::meta::ProgramMeta;
391
392 if #program_type_path::ASYNC {
393 gstd::handle_signal();
394 }
395
396 #handle_signal_fn
397 }
398 };
399
400 (
401 program_meta_impl,
402 main_fn,
403 handle_reply_fn,
404 handle_signal_fn,
405 )
406 }
407
408 fn generate_init(&self, program_ident: &Ident) -> (TokenStream2, TokenStream2) {
409 let sails_path = self.sails_path();
410 let type_info_path = sails_paths::type_info_path(sails_path);
411
412 let (program_type_path, ..) = self.impl_type();
413 let input_ident = Ident::new("input", Span::call_site());
414
415 let program_ctors = self.program_ctors();
416
417 let mut ctor_dispatches = Vec::with_capacity(program_ctors.len() + 1);
418 let mut ctor_params_structs = Vec::with_capacity(program_ctors.len());
419 let mut ctor_meta_variants = Vec::with_capacity(program_ctors.len());
420
421 for fn_builder in &program_ctors {
422 if fn_builder.has_scale_codec() {
423 ctor_dispatches.push(fn_builder.ctor_branch_impl(
424 program_type_path,
425 &input_ident,
426 program_ident,
427 ));
428 }
429 ctor_params_structs.push(fn_builder.ctor_params_struct());
430 ctor_meta_variants.push(fn_builder.ctor_meta_variant());
431 }
432
433 let solidity_init = self.sol_init(&input_ident);
434
435 let sails_init = quote! {
436 if let Ok(header) = <#sails_path::meta::SailsMessageHeader as #sails_path::Decode>::decode(&mut #input_ident) {
437 if header.interface_id() != #sails_path::meta::InterfaceId::zero() {
438 #sails_path::gstd::unknown_input_panic("Non zero ctor interface_id", header.to_bytes().as_slice());
439 }
440 match header.entry_id() {
441 #(#ctor_dispatches)*
442 _ => #sails_path::gstd::unknown_input_panic("Unexpected ctor entry_id", input),
443 }
444 }
445 };
446
447 let empty_input_guard = if self.has_default_ctor_only {
451 let fn_builder = program_ctors
452 .first()
453 .expect("default ctor must exist when has_default_ctor_only is true");
454 let ctor_ident = fn_builder.ident;
455 let params_struct_ident = &fn_builder.params_struct_ident;
456 quote! {
457 if #input_ident.is_empty() {
458 #sails_path::program_ctor!(
459 #program_ident = #program_type_path :: #ctor_ident (),
460 params_struct = meta_in_program::#params_struct_ident
461 );
462 return;
463 }
464 }
465 } else {
466 quote!()
467 };
468
469 let init_fn = quote! {
470 #[unsafe(no_mangle)]
471 extern "C" fn init() {
472 let mut #input_ident: &[u8] = &gstd::msg::load_bytes().expect("Failed to read input");
473
474 #empty_input_guard
475
476 #solidity_init
477
478 #sails_init
479 }
480 };
481
482 let meta_in_program = quote! {
483 mod meta_in_program {
484 use super::*;
485
486 #( #ctor_params_structs )*
487
488 #[derive(#sails_path::TypeInfo)]
489 #[type_info(crate = #type_info_path)]
490 pub enum ConstructorsMeta {
491 #( #ctor_meta_variants ),*
492 }
493 }
494 };
495 (meta_in_program, init_fn)
496 }
497}
498
499#[cfg(not(feature = "ethexe"))]
501impl ProgramBuilder {
502 fn program_signature_impl(&self) -> TokenStream2 {
503 quote!()
504 }
505
506 fn match_ctor_impl(&self, _program_ident: &Ident) -> TokenStream2 {
507 quote!()
508 }
509
510 fn program_const(&self) -> TokenStream2 {
511 quote!()
512 }
513
514 fn sol_init(&self, _input_ident: &Ident) -> TokenStream2 {
515 quote!()
516 }
517
518 fn sol_main(&self, _solidity_dispatchers: &[TokenStream2]) -> TokenStream2 {
519 quote!()
520 }
521}
522
523impl Deref for ProgramBuilder {
524 type Target = ItemImpl;
525
526 fn deref(&self) -> &Self::Target {
527 &self.program_impl
528 }
529}
530
531impl DerefMut for ProgramBuilder {
532 fn deref_mut(&mut self) -> &mut Self::Target {
533 &mut self.program_impl
534 }
535}
536
537fn gen_gprogram_impl(program_impl: ItemImpl, program_args: ProgramArgs) -> TokenStream2 {
538 let mut program_builder = ProgramBuilder::new(program_impl, program_args);
539
540 let sails_path = program_builder.sails_path().clone();
541
542 let program_ident = Ident::new("PROGRAM", Span::call_site());
543
544 let program_signature_impl = program_builder.program_signature_impl();
546 let match_ctor_impl = program_builder.match_ctor_impl(&program_ident);
547 let program_const = program_builder.program_const();
548
549 let (program_meta_impl, main_fn, handle_reply_fn, handle_signal_fn) =
550 program_builder.wire_up_service_exposure(&program_ident);
551 let (meta_in_program, init_fn) = program_builder.generate_init(&program_ident);
552
553 let (program_type_path, ..) = program_builder.impl_type();
554
555 let program_impl = program_builder.deref();
556
557 quote!(
558 #program_impl
559
560 #program_meta_impl
561
562 #meta_in_program
563
564 #program_signature_impl
565
566 #program_const
567
568 #[cfg(target_arch = "wasm32")]
569 pub mod wasm {
570 use super::*;
571 use #sails_path::{gstd, hex, prelude::*};
572
573 static mut #program_ident: Option<#program_type_path> = None;
574
575 #init_fn
576
577 #match_ctor_impl
578
579 #main_fn
580
581 #handle_reply_fn
582
583 #handle_signal_fn
584 }
585 )
586}
587
588fn ensure_default_program_ctor(program_impl: &mut ItemImpl) -> bool {
589 let sails_path = &sails_paths::sails_path_or_default(None);
590 if discover_program_ctors(program_impl, sails_path).is_empty() {
591 program_impl.items.push(ImplItem::Fn(parse_quote!(
592 pub fn create() -> Self {
593 Default::default()
594 }
595 )));
596 return true;
597 }
598 false
599}
600
601fn discover_program_ctors<'a>(
602 program_impl: &'a ItemImpl,
603 sails_path: &'a Path,
604) -> Vec<FnBuilder<'a>> {
605 let self_type_path: TypePath = parse_quote!(Self);
606 let (program_type_path, _, _) = shared::impl_type_refs(program_impl.self_ty.as_ref());
607 let mut vec = shared::discover_invocation_targets(
608 program_impl,
609 |fn_item| program_ctor_predicate(fn_item, &self_type_path, program_type_path),
610 sails_path,
611 );
612 vec.sort_by_key(|f| f.route.to_lowercase());
613 vec.iter_mut()
614 .enumerate()
615 .for_each(|(idx, f)| f.entry_id = idx as u16);
616
617 #[cfg(feature = "ethexe")]
618 {
619 for ctor in &vec {
620 shared::validation::validate_identifier(
621 &ctor.route_camel_case(),
622 ctor.ident.span(),
623 "Program constructor",
624 );
625 }
626 }
627 vec
628}
629
630fn program_ctor_predicate(
631 fn_item: &ImplItemFn,
632 self_type_path: &TypePath,
633 program_type_path: &TypePath,
634) -> bool {
635 if matches!(fn_item.vis, Visibility::Public(_))
636 && fn_item.sig.receiver().is_none()
637 && let ReturnType::Type(_, output_type) = &fn_item.sig.output
638 && let Type::Path(output_type_path) = output_type.as_ref()
639 {
640 if output_type_path == self_type_path || output_type_path == program_type_path {
641 return true;
642 }
643 if let Some((Type::Path(output_type_path), _)) =
644 shared::extract_result_types(output_type_path)
645 && (output_type_path == self_type_path || output_type_path == program_type_path)
646 {
647 return true;
648 }
649 }
650 false
651}
652
653fn service_ctor_receiver(fn_item: &ImplItemFn) -> Option<&Receiver> {
654 fn_item.sig.receiver().filter(|receiver| {
655 matches!(fn_item.vis, Visibility::Public(_))
656 && fn_item.sig.inputs.len() == 1
657 && !matches!(fn_item.sig.output, ReturnType::Default)
658 && receiver.reference.is_some()
659 })
660}
661
662fn service_ctor_predicate(fn_item: &ImplItemFn) -> bool {
663 service_ctor_receiver(fn_item).is_some_and(|receiver| receiver.mutability.is_none())
664}
665
666fn is_mut_service_ctor(fn_item: &ImplItemFn) -> bool {
670 service_ctor_receiver(fn_item).is_some_and(|receiver| receiver.mutability.is_some())
671}
672
673fn has_handle_reply_attr(fn_item: &ImplItemFn) -> bool {
674 fn_item
675 .attrs
676 .iter()
677 .any(|attr| attr.path().is_ident("handle_reply"))
678}
679
680fn handle_reply_predicate(fn_item: &ImplItemFn) -> bool {
681 matches!(fn_item.vis, Visibility::Inherited)
682 && matches!(
683 fn_item.sig.receiver(),
684 Some(Receiver {
685 mutability: None,
686 reference: Some(_),
687 ..
688 })
689 )
690 && fn_item.sig.inputs.len() == 1
691 && matches!(fn_item.sig.output, ReturnType::Default)
692}
693
694impl FnBuilder<'_> {
695 fn service_route_idx(&self) -> u8 {
696 if self.entry_id >= u8::MAX as u16 {
697 abort!(self.ident, "too many services; maximum is 255");
698 }
699 (self.entry_id + 1) as u8
700 }
701
702 fn service_meta(&self) -> TokenStream2 {
703 let sails_path = self.sails_path;
704 let route = &self.route;
705 let service_type = &self.result_type;
706 quote!(
707 ( #route , < #service_type as #sails_path::meta::ServiceMeta >::META )
708 )
709 }
710
711 fn service_meta_asyncness(&self) -> TokenStream2 {
712 let sails_path = self.sails_path;
713 let service_type = &self.result_type;
714 quote!(<#service_type as #sails_path::meta::ServiceMeta>::ASYNC )
715 }
716
717 fn original_service_ctor_fn(&self) -> ImplItemFn {
718 let mut original_service_ctor_fn = self.impl_fn.clone();
719 let original_service_ctor_fn_ident = Ident::new(
720 &format!("__{}", original_service_ctor_fn.sig.ident),
721 original_service_ctor_fn.sig.ident.span(),
722 );
723 original_service_ctor_fn.attrs.clear();
724 original_service_ctor_fn.vis = Visibility::Inherited;
725 original_service_ctor_fn.sig.ident = original_service_ctor_fn_ident;
726 original_service_ctor_fn
727 }
728
729 fn wrapping_service_ctor_fn(&self, original_service_ctor_fn_ident: &Ident) -> ImplItemFn {
730 let sails_path = self.sails_path;
731 let service_type = &self.result_type;
732 let route_idx = self.service_route_idx();
733 let unwrap_token = self.error_type.is_some().then(|| quote!(.unwrap()));
734
735 let mut wrapping_service_ctor_fn = self.impl_fn.clone();
736 wrapping_service_ctor_fn
738 .attrs
739 .retain(|attr| export::parse_attr(attr).is_none());
740 wrapping_service_ctor_fn.sig.output = parse_quote!(
741 -> < #service_type as #sails_path::gstd::services::Service>::Exposure
742 );
743 wrapping_service_ctor_fn.block = parse_quote!({
744 let service = self. #original_service_ctor_fn_ident () #unwrap_token;
745 let exposure = < #service_type as #sails_path::gstd::services::Service>::expose(
746 service,
747 #route_idx,
748 );
749 exposure
750 });
751 wrapping_service_ctor_fn
752 }
753
754 fn ctor_branch_impl(
755 &self,
756 program_type_path: &TypePath,
757 input_ident: &Ident,
758 program_ident: &Ident,
759 ) -> TokenStream2 {
760 let sails_path = self.sails_path;
761 let handler_ident = self.ident;
762 let entry_id = self.entry_id;
763 let handler_args = self.params_idents();
764 let handler_types = self.params_types();
765 let payable_check = {
766 #[cfg(feature = "ethexe")]
767 {
768 self.payable_check()
769 }
770 #[cfg(not(feature = "ethexe"))]
771 {
772 quote!()
773 }
774 };
775
776 let await_token = self.is_async().then(|| quote!(.await));
777 let unwrap_token = self.error_type.is_some().then(|| quote!(.unwrap()));
778 let raw_call = quote! { #program_type_path :: #handler_ident (#(#handler_args),*) #await_token #unwrap_token };
779 let params_struct_ident = &self.params_struct_ident;
780
781 let ctor_call_impl = quote! {
782 #sails_path::program_ctor!(
783 #program_ident = #raw_call,
784 params_struct = meta_in_program::#params_struct_ident
785 )
786 };
787
788 quote!(
789 #entry_id => {
790 let (#(#handler_args),*): (#(#handler_types),*) = #sails_path::Decode::decode(&mut #input_ident)
791 .unwrap_or_else(|_| #sails_path::gstd::unknown_input_panic("Unknown request", #input_ident));
792 #payable_check
793 #ctor_call_impl
794 }
795 )
796 }
797
798 fn ctor_params_struct(&self) -> TokenStream2 {
799 let sails_path = self.sails_path;
800 let params_struct_ident = &self.params_struct_ident;
801 let params_struct_members = self.params().map(|(ident, ty)| quote!(#ident: #ty));
802 let entry_id = &self.entry_id;
803 let decode_disabled = (!self.has_scale_codec()).then(|| quote!(decode = false,));
804
805 quote! {
806 #sails_path::invocation_io!(
807 pub struct #params_struct_ident {
808 #(pub(super) #params_struct_members,)*
809 },
810 entry_id = #entry_id,
811 #decode_disabled
812 );
813 }
814 }
815
816 fn ctor_meta_variant(&self) -> TokenStream2 {
817 let ctor_route = Ident::new(self.route.as_str(), Span::call_site());
818 let ctor_docs_attrs = self
819 .impl_fn
820 .attrs
821 .iter()
822 .filter(|attr| attr.path().is_ident("doc"));
823 let params_struct_ident = &self.params_struct_ident;
824
825 #[cfg(feature = "ethexe")]
826 let payable_ann = self.payable.then(|| quote!(#[annotate(payable)]));
827 #[cfg(not(feature = "ethexe"))]
828 let payable_ann: Option<TokenStream2> = None;
829
830 #[cfg(feature = "ethexe")]
831 let codec_ann: Option<TokenStream2> =
832 match (self.has_scale_codec(), self.has_ethabi_codec()) {
833 (true, false) => Some(quote!(#[annotate(codec = "scale")])),
834 (false, true) => Some(quote!(#[annotate(codec = "ethabi")])),
835 _ => None,
836 };
837 #[cfg(not(feature = "ethexe"))]
838 let codec_ann: Option<TokenStream2> = None;
839
840 if let Some(err_ty) = &self.error_type {
841 let err_ty = shared::replace_any_lifetime_with_static(err_ty.clone());
842 quote! {
843 #( #ctor_docs_attrs )*
844 #payable_ann
845 #codec_ann
846 #ctor_route(#params_struct_ident, #err_ty)
847 }
848 } else {
849 quote! {
850 #( #ctor_docs_attrs )*
851 #payable_ann
852 #codec_ann
853 #ctor_route(#params_struct_ident)
854 }
855 }
856 }
857}
858
859#[cfg(test)]
860mod tests {
861 use super::*;
862 use quote::quote;
863
864 #[test]
865 fn gprogram_discovers_public_associated_functions_returning_self_or_the_type_as_ctors() {
866 let program_impl = syn::parse2(quote!(
867 impl MyProgram {
868 fn non_public_associated_func_returning_self() -> Self {}
869 fn non_public_associated_func_returning_type() -> MyProgram {}
870 fn non_public_associated_func_returning_smth() -> u32 {}
871 pub fn public_associated_func_returning_self() -> Self {}
872 pub fn public_associated_func_returning_type() -> MyProgram {}
873 pub fn public_associated_func_returning_smth() -> u32 {}
874 fn non_public_method_returning_self(&self) -> Self {}
875 fn non_public_method_returning_type(&self) -> MyProgram {}
876 fn non_public_method_returning_smth(&self) -> u32 {}
877 pub fn public_method_returning_self(&self) -> Self {}
878 pub fn public_method_returning_type(&self) -> MyProgram {}
879 pub fn public_method_returning_smth(&self) -> u32 {}
880 }
881 ))
882 .unwrap();
883
884 let sails_path = &sails_paths::sails_path_or_default(None);
885 let discovered_ctors = discover_program_ctors(&program_impl, sails_path)
886 .iter()
887 .map(|fn_builder| fn_builder.ident.to_string())
888 .collect::<Vec<_>>();
889
890 assert_eq!(discovered_ctors.len(), 2);
891 assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_self")));
892 assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_type")));
893 }
894
895 #[test]
896 fn gprogram_discovers_public_methods_with_self_ref_only_and_some_return_as_service_funcs() {
897 let program_impl = syn::parse2(quote!(
898 impl MyProgram {
899 fn non_public_associated_func_returning_smth() -> u32 {}
900 fn non_public_associated_func_returning_unit() {}
901 pub fn public_associated_func_returning_smth() -> MyProgram {}
902 pub fn public_associated_func_returning_unit() {}
903 fn non_public_method_returning_smth(&self) -> u32 {}
904 fn non_public_method_returning_unit(&self) {}
905 pub fn public_method_returning_smth(&self) -> u32 {}
906 pub fn public_method_returning_smth_with_other_params(&self, p1: u32) -> u32 {}
907 pub fn public_methos_returning_smth_and_consuming_self(self) -> u32 {}
908 }
909 ))
910 .unwrap();
911
912 let sails_path = &sails_paths::sails_path_or_default(None);
913 let discovered_services =
914 shared::discover_invocation_targets(&program_impl, service_ctor_predicate, sails_path)
915 .iter()
916 .map(|fn_builder| fn_builder.ident.to_string())
917 .collect::<Vec<_>>();
918
919 assert_eq!(discovered_services.len(), 1);
920 assert!(discovered_services.contains(&String::from("public_method_returning_smth")));
921 }
922
923 #[test]
924 fn is_mut_service_ctor_detects_only_public_mut_self_factories() {
925 let program_impl: ItemImpl = syn::parse2(quote!(
926 impl MyProgram {
927 pub fn mut_factory(&mut self) -> MyService {}
928 pub fn shared_factory(&self) -> MyService {}
929 fn non_public_mut_factory(&mut self) -> MyService {}
930 pub fn mut_factory_returning_unit(&mut self) {}
931 pub fn mut_factory_with_other_params(&mut self, p1: u32) -> MyService {}
932 }
933 ))
934 .unwrap();
935
936 let detected = program_impl
937 .items
938 .iter()
939 .filter_map(|item| match item {
940 ImplItem::Fn(fn_item) if is_mut_service_ctor(fn_item) => {
941 Some(fn_item.sig.ident.to_string())
942 }
943 _ => None,
944 })
945 .collect::<Vec<_>>();
946
947 assert_eq!(detected, vec![String::from("mut_factory")]);
948 }
949}