sails_macros_core/program/
mod.rs

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
23/// Static Spans of Program `impl` block
24static 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}
77
78impl ProgramBuilder {
79    fn new(program_impl: ItemImpl, program_args: ProgramArgs) -> Self {
80        let mut program_impl = program_impl;
81        let type_constraints = program_impl.generics.where_clause.take();
82        ensure_default_program_ctor(&mut program_impl);
83
84        Self {
85            program_impl,
86            program_args,
87            type_constraints,
88        }
89    }
90
91    fn sails_path(&self) -> &Path {
92        self.program_args.sails_path()
93    }
94
95    fn impl_type(&self) -> (&TypePath, &PathArguments, &Ident) {
96        shared::impl_type_refs(self.program_impl.self_ty.as_ref())
97    }
98
99    fn impl_constraints(&self) -> (&Generics, Option<&WhereClause>) {
100        (&self.program_impl.generics, self.type_constraints.as_ref())
101    }
102
103    fn program_ctors(&self) -> Vec<FnBuilder<'_>> {
104        discover_program_ctors(&self.program_impl, self.sails_path())
105    }
106
107    fn handle_reply_fn(&mut self) -> Option<&mut ImplItemFn> {
108        let mut fn_iter = self.program_impl.items.iter_mut().filter_map(|item| {
109            if let ImplItem::Fn(fn_item) = item
110                && has_handle_reply_attr(fn_item) {
111                    fn_item
112                        .attrs
113                        .retain(|attr| !attr.path().is_ident("handle_reply"));
114                    if handle_reply_predicate(fn_item) {
115                        return Some(fn_item);
116                    } else {
117                        abort!(
118                            fn_item,
119                            "`handle_reply` function must have a single `&self` argument and no return type"
120                        );
121                    }
122                }
123            None
124        });
125        let handle_reply_fn = fn_iter.next();
126        if let Some(duplicate) = fn_iter.next() {
127            abort!(duplicate, "only one `handle_reply` function is allowed");
128        }
129        handle_reply_fn
130    }
131
132    #[cfg(feature = "ethexe")]
133    fn service_ctors(&self) -> Vec<FnBuilder<'_>> {
134        shared::discover_invocation_targets(self, service_ctor_predicate, self.sails_path())
135    }
136}
137
138impl ProgramBuilder {
139    fn wire_up_service_exposure(
140        &mut self,
141        program_ident: &Ident,
142    ) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) {
143        let mut services_route = Vec::new();
144        let mut services_meta = Vec::new();
145        let mut meta_asyncness = Vec::new();
146        let mut invocation_dispatches = Vec::new();
147        let mut routes = BTreeMap::new();
148        // only used for ethexe
149        #[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 item_impl = self
162            .program_impl
163            .items
164            .iter()
165            .enumerate()
166            .filter_map(|(idx, impl_item)| {
167                if let ImplItem::Fn(fn_item) = impl_item
168                    && service_ctor_predicate(fn_item)
169                {
170                    let (span, route, unwrap_result, _) =
171                        shared::invocation_export_or_default(fn_item);
172                    if let Some(duplicate) =
173                        routes.insert(route.clone(), fn_item.sig.ident.to_string())
174                    {
175                        abort!(
176                            span,
177                            "`export` attribute conflicts with one already assigned to '{}'",
178                            duplicate
179                        );
180                    }
181                    return Some((idx, route, fn_item, unwrap_result));
182                }
183                None
184            })
185            .map(|(idx, route, fn_item, unwrap_result)| {
186                let fn_builder =
187                    FnBuilder::from(route, true, fn_item, unwrap_result, self.sails_path());
188                let original_service_ctor_fn = fn_builder.original_service_ctor_fn();
189                let wrapping_service_ctor_fn =
190                    fn_builder.wrapping_service_ctor_fn(&original_service_ctor_fn.sig.ident);
191
192                services_route.push(fn_builder.service_const_route());
193                services_meta.push(fn_builder.service_meta());
194
195                if !has_async_ctor {
196                    // If there are no async constructors, we can't push the asyncness as false,
197                    // as there could be async handlers in services.
198                    meta_asyncness.push(fn_builder.service_meta_asyncness());
199                }
200                invocation_dispatches.push(fn_builder.service_invocation());
201                #[cfg(feature = "ethexe")]
202                solidity_dispatchers.push(fn_builder.sol_service_invocation());
203
204                (idx, original_service_ctor_fn, wrapping_service_ctor_fn)
205            })
206            .collect::<Vec<_>>();
207
208        if meta_asyncness.is_empty() {
209            // In case non of constructors is async and there are no services exposed.
210            meta_asyncness.push(quote!(false));
211        }
212
213        // replace service ctor fn impls
214        for (idx, original_service_ctor_fn, wrapping_service_ctor_fn, ..) in item_impl {
215            self.program_impl.items[idx] = ImplItem::Fn(original_service_ctor_fn);
216            self.program_impl
217                .items
218                .push(ImplItem::Fn(wrapping_service_ctor_fn));
219        }
220
221        let handle_reply_fn = self.handle_reply_fn().map(|item_fn| {
222            let handle_reply_fn_ident = &item_fn.sig.ident;
223            quote! {
224                let program_ref = unsafe { #program_ident.as_mut() }.expect("Program not initialized");
225                program_ref.#handle_reply_fn_ident();
226            }
227        })
228        .unwrap_or_default();
229
230        let handle_signal_fn = self
231            .program_args
232            .handle_signal()
233            .map(|handle_signal_path| quote!( #handle_signal_path ();))
234            .unwrap_or_default();
235
236        let sails_path = self.sails_path();
237        let (program_type_path, _program_type_args, _) = self.impl_type();
238        let (generics, program_type_constraints) = self.impl_constraints();
239
240        let program_meta_impl = quote! {
241            #(#services_route)*
242
243            impl #generics #sails_path::meta::ProgramMeta for #program_type_path #program_type_constraints {
244                type ConstructorsMeta = meta_in_program::ConstructorsMeta;
245
246                const SERVICES: &'static [(&'static str, #sails_path::meta::AnyServiceMetaFn)] = &[
247                    #(#services_meta),*
248                ];
249                const ASYNC: bool = #( #meta_asyncness )||*;
250            }
251        };
252
253        invocation_dispatches.push(quote! {
254            { gstd::unknown_input_panic("Unexpected service", &input) }
255        });
256
257        let solidity_main = self.sol_main(solidity_dispatchers.as_slice());
258
259        let payable = self.program_args.payable().then(|| {
260            quote! {
261                if gstd::msg::value() > 0 && gstd::msg::size() == 0 {
262                    return;
263                }
264            }
265        });
266
267        let main_fn = quote!(
268            #[unsafe(no_mangle)]
269            extern "C" fn handle() {
270                #payable
271
272                let mut input = gstd::msg::load_bytes().expect("Failed to read input");
273                let program_ref = unsafe { #program_ident.as_mut() }.expect("Program not initialized");
274
275                #solidity_main
276
277                #(#invocation_dispatches)else*;
278            }
279
280        );
281
282        let handle_reply_fn = quote! {
283            #[unsafe(no_mangle)]
284            extern "C" fn handle_reply() {
285                use #sails_path::meta::ProgramMeta;
286
287                if #program_type_path::ASYNC {
288                    gstd::handle_reply_with_hook();
289                }
290
291                #handle_reply_fn
292            }
293        };
294
295        #[cfg(not(feature = "ethexe"))]
296        let handle_signal_fn = quote! {
297            #[unsafe(no_mangle)]
298            extern "C" fn handle_signal() {
299                use #sails_path::meta::ProgramMeta;
300
301                if #program_type_path::ASYNC {
302                    gstd::handle_signal();
303                }
304
305                #handle_signal_fn
306            }
307        };
308
309        (
310            program_meta_impl,
311            main_fn,
312            handle_reply_fn,
313            handle_signal_fn,
314        )
315    }
316
317    fn generate_init(&self, program_ident: &Ident) -> (TokenStream2, TokenStream2) {
318        let sails_path = self.sails_path();
319        let scale_codec_path = sails_paths::scale_codec_path(sails_path);
320        let scale_info_path = sails_paths::scale_info_path(sails_path);
321
322        let (program_type_path, ..) = self.impl_type();
323        let input_ident = Ident::new("input", Span::call_site());
324
325        let program_ctors = self.program_ctors();
326
327        let mut ctor_dispatches = Vec::with_capacity(program_ctors.len() + 1);
328        let mut ctor_params_structs = Vec::with_capacity(program_ctors.len());
329        let mut ctor_meta_variants = Vec::with_capacity(program_ctors.len());
330
331        for fn_builder in program_ctors {
332            ctor_dispatches.push(fn_builder.ctor_branch_impl(
333                program_type_path,
334                &input_ident,
335                program_ident,
336            ));
337            ctor_params_structs
338                .push(fn_builder.ctor_params_struct(&scale_codec_path, &scale_info_path));
339            ctor_meta_variants.push(fn_builder.ctor_meta_variant());
340        }
341
342        ctor_dispatches.push(quote! {
343            { gstd::unknown_input_panic("Unexpected ctor", input) }
344        });
345
346        let solidity_init = self.sol_init(&input_ident);
347
348        let init_fn = quote! {
349            #[unsafe(no_mangle)]
350            extern "C" fn init() {
351                use gstd::InvocationIo;
352
353                let mut #input_ident: &[u8] = &gstd::msg::load_bytes().expect("Failed to read input");
354
355                #solidity_init
356
357                #(#ctor_dispatches)else*;
358            }
359        };
360
361        let meta_in_program = quote! {
362            mod meta_in_program {
363                use super::*;
364                use #sails_path::gstd::InvocationIo;
365
366                #( #ctor_params_structs )*
367
368                #[derive(#sails_path ::TypeInfo)]
369                #[scale_info(crate = #scale_info_path)]
370                pub enum ConstructorsMeta {
371                    #( #ctor_meta_variants ),*
372                }
373            }
374        };
375        (meta_in_program, init_fn)
376    }
377}
378
379// Empty ProgramBuilder Implementations without `ethexe` feature
380#[cfg(not(feature = "ethexe"))]
381impl ProgramBuilder {
382    fn program_signature_impl(&self) -> TokenStream2 {
383        quote!()
384    }
385
386    fn match_ctor_impl(&self, _program_ident: &Ident) -> TokenStream2 {
387        quote!()
388    }
389
390    fn program_const(&self) -> TokenStream2 {
391        quote!()
392    }
393
394    fn sol_init(&self, _input_ident: &Ident) -> TokenStream2 {
395        quote!()
396    }
397
398    fn sol_main(&self, _solidity_dispatchers: &[TokenStream2]) -> TokenStream2 {
399        quote!()
400    }
401}
402
403impl Deref for ProgramBuilder {
404    type Target = ItemImpl;
405
406    fn deref(&self) -> &Self::Target {
407        &self.program_impl
408    }
409}
410
411impl DerefMut for ProgramBuilder {
412    fn deref_mut(&mut self) -> &mut Self::Target {
413        &mut self.program_impl
414    }
415}
416
417fn gen_gprogram_impl(program_impl: ItemImpl, program_args: ProgramArgs) -> TokenStream2 {
418    let mut program_builder = ProgramBuilder::new(program_impl, program_args);
419
420    let sails_path = program_builder.sails_path().clone();
421
422    let program_ident = Ident::new("PROGRAM", Span::call_site());
423
424    // Call this before `wire_up_service_exposure`
425    let program_signature_impl = program_builder.program_signature_impl();
426    let match_ctor_impl = program_builder.match_ctor_impl(&program_ident);
427    let program_const = program_builder.program_const();
428
429    let (program_meta_impl, main_fn, handle_reply_fn, handle_signal_fn) =
430        program_builder.wire_up_service_exposure(&program_ident);
431    let (meta_in_program, init_fn) = program_builder.generate_init(&program_ident);
432
433    let (program_type_path, ..) = program_builder.impl_type();
434
435    let program_impl = program_builder.deref();
436
437    quote!(
438        #program_impl
439
440        #program_meta_impl
441
442        #meta_in_program
443
444        #program_signature_impl
445
446        #program_const
447
448        #[cfg(target_arch = "wasm32")]
449        pub mod wasm {
450            use super::*;
451            use #sails_path::{gstd, hex, prelude::*};
452
453            static mut #program_ident: Option<#program_type_path> = None;
454
455            #init_fn
456
457            #match_ctor_impl
458
459            #main_fn
460
461            #handle_reply_fn
462
463            #handle_signal_fn
464        }
465    )
466}
467
468fn ensure_default_program_ctor(program_impl: &mut ItemImpl) {
469    let sails_path = &sails_paths::sails_path_or_default(None);
470    if discover_program_ctors(program_impl, sails_path).is_empty() {
471        program_impl.items.push(ImplItem::Fn(parse_quote!(
472            pub fn create() -> Self {
473                Default::default()
474            }
475        )));
476    }
477}
478
479fn discover_program_ctors<'a>(
480    program_impl: &'a ItemImpl,
481    sails_path: &'a Path,
482) -> Vec<FnBuilder<'a>> {
483    let self_type_path: TypePath = parse_quote!(Self);
484    let (program_type_path, _, _) = shared::impl_type_refs(program_impl.self_ty.as_ref());
485    shared::discover_invocation_targets(
486        program_impl,
487        |fn_item| program_ctor_predicate(fn_item, &self_type_path, program_type_path),
488        sails_path,
489    )
490}
491
492fn program_ctor_predicate(
493    fn_item: &ImplItemFn,
494    self_type_path: &TypePath,
495    program_type_path: &TypePath,
496) -> bool {
497    if matches!(fn_item.vis, Visibility::Public(_))
498        && fn_item.sig.receiver().is_none()
499        && let ReturnType::Type(_, output_type) = &fn_item.sig.output
500        && let Type::Path(output_type_path) = output_type.as_ref()
501    {
502        if output_type_path == self_type_path || output_type_path == program_type_path {
503            return true;
504        }
505        if let Some(Type::Path(output_type_path)) = shared::extract_result_type(output_type_path)
506            && (output_type_path == self_type_path || output_type_path == program_type_path)
507        {
508            return true;
509        }
510    }
511    false
512}
513
514fn service_ctor_predicate(fn_item: &ImplItemFn) -> bool {
515    matches!(fn_item.vis, Visibility::Public(_))
516        && matches!(
517            fn_item.sig.receiver(),
518            Some(Receiver {
519                reference: Some(_),
520                ..
521            })
522        )
523        && fn_item.sig.inputs.len() == 1
524        && !matches!(fn_item.sig.output, ReturnType::Default)
525}
526
527fn has_handle_reply_attr(fn_item: &ImplItemFn) -> bool {
528    fn_item
529        .attrs
530        .iter()
531        .any(|attr| attr.path().is_ident("handle_reply"))
532}
533
534fn handle_reply_predicate(fn_item: &ImplItemFn) -> bool {
535    matches!(fn_item.vis, Visibility::Inherited)
536        && matches!(
537            fn_item.sig.receiver(),
538            Some(Receiver {
539                mutability: None,
540                reference: Some(_),
541                ..
542            })
543        )
544        && fn_item.sig.inputs.len() == 1
545        && matches!(fn_item.sig.output, ReturnType::Default)
546}
547
548impl FnBuilder<'_> {
549    fn route_ident(&self) -> Ident {
550        Ident::new(
551            &format!("__ROUTE_{}", self.route.to_ascii_uppercase()),
552            Span::call_site(),
553        )
554    }
555
556    fn service_meta(&self) -> TokenStream2 {
557        let sails_path = self.sails_path;
558        let route = &self.route;
559        let service_type = &self.result_type;
560        quote!(
561            ( #route , #sails_path::meta::AnyServiceMeta::new::< #service_type >)
562        )
563    }
564
565    fn service_meta_asyncness(&self) -> TokenStream2 {
566        let sails_path = self.sails_path;
567        let service_type = &self.result_type;
568        quote!(< #service_type as  #sails_path::meta::ServiceMeta>::ASYNC )
569    }
570
571    fn service_const_route(&self) -> TokenStream2 {
572        let route_ident = &self.route_ident();
573        let ctor_route_bytes = self.encoded_route.as_slice();
574        let ctor_route_len = ctor_route_bytes.len();
575        quote!(
576            const #route_ident: [u8; #ctor_route_len] = [ #(#ctor_route_bytes),* ];
577        )
578    }
579
580    fn service_invocation(&self) -> TokenStream2 {
581        let route_ident = &self.route_ident();
582        let service_ctor_ident = self.ident;
583        quote! {
584            if input.starts_with(& #route_ident) {
585                let mut service = program_ref.#service_ctor_ident();
586                let is_async = service
587                    .check_asyncness(&input[#route_ident .len()..])
588                    .unwrap_or_else(|| {
589                        gstd::unknown_input_panic("Unknown call", &input[#route_ident .len()..])
590                    });
591                if is_async {
592                    gstd::message_loop(async move {
593                        service
594                            .try_handle_async(&input[#route_ident .len()..], |encoded_result, value| {
595                                gstd::msg::reply_bytes(encoded_result, value)
596                                    .expect("Failed to send output");
597                            })
598                            .await
599                            .unwrap_or_else(|| {
600                                gstd::unknown_input_panic("Unknown request", &input)
601                            });
602                    });
603                } else {
604                    service
605                        .try_handle(&input[#route_ident .len()..], |encoded_result, value| {
606                            gstd::msg::reply_bytes(encoded_result, value)
607                                .expect("Failed to send output");
608                        })
609                        .unwrap_or_else(|| gstd::unknown_input_panic("Unknown request", &input));
610                }
611            }
612        }
613    }
614
615    fn original_service_ctor_fn(&self) -> ImplItemFn {
616        let mut original_service_ctor_fn = self.impl_fn.clone();
617        let original_service_ctor_fn_ident = Ident::new(
618            &format!("__{}", original_service_ctor_fn.sig.ident),
619            original_service_ctor_fn.sig.ident.span(),
620        );
621        original_service_ctor_fn.attrs.clear();
622        original_service_ctor_fn.vis = Visibility::Inherited;
623        original_service_ctor_fn.sig.ident = original_service_ctor_fn_ident;
624        original_service_ctor_fn
625    }
626
627    fn wrapping_service_ctor_fn(&self, original_service_ctor_fn_ident: &Ident) -> ImplItemFn {
628        let sails_path = self.sails_path;
629        let service_type = &self.result_type;
630        let route_ident = &self.route_ident();
631        let unwrap_token = self.unwrap_result.then(|| quote!(.unwrap()));
632
633        let mut wrapping_service_ctor_fn = self.impl_fn.clone();
634        // Filter out `export  attribute
635        wrapping_service_ctor_fn
636            .attrs
637            .retain(|attr| export::parse_attr(attr).is_none());
638        wrapping_service_ctor_fn.sig.output = parse_quote!(
639            -> < #service_type as #sails_path::gstd::services::Service>::Exposure
640        );
641        wrapping_service_ctor_fn.block = parse_quote!({
642            let service = self. #original_service_ctor_fn_ident () #unwrap_token;
643            let exposure = < #service_type as #sails_path::gstd::services::Service>::expose(
644                service,
645                #route_ident .as_ref(),
646            );
647            exposure
648        });
649        wrapping_service_ctor_fn
650    }
651
652    fn ctor_branch_impl(
653        &self,
654        program_type_path: &TypePath,
655        input_ident: &Ident,
656        program_ident: &Ident,
657    ) -> TokenStream2 {
658        let handler_ident = self.ident;
659        let unwrap_token = self.unwrap_result.then(|| quote!(.unwrap()));
660        let handler_args = self
661            .params_idents()
662            .iter()
663            .map(|ident| quote!(request.#ident));
664        let params_struct_ident = &self.params_struct_ident;
665        let ctor_call_impl = if self.is_async() {
666            quote! {
667                gstd::message_loop(async move {
668                    let program = #program_type_path :: #handler_ident (#(#handler_args),*) .await #unwrap_token ;
669
670                    unsafe {
671                        #program_ident = Some(program);
672                    }
673                });
674            }
675        } else {
676            quote! {
677                let program = #program_type_path :: #handler_ident (#(#handler_args),*) #unwrap_token;
678                unsafe {
679                    #program_ident = Some(program);
680                }
681            }
682        };
683
684        quote!(
685            if let Ok(request) = meta_in_program::#params_struct_ident::decode_params( #input_ident) {
686                #ctor_call_impl
687            }
688        )
689    }
690
691    fn ctor_params_struct(&self, scale_codec_path: &Path, scale_info_path: &Path) -> TokenStream2 {
692        let sails_path = self.sails_path;
693        let params_struct_ident = &self.params_struct_ident;
694        let params_struct_members = self.params().map(|(ident, ty)| quote!(#ident: #ty));
695        let ctor_route_bytes = self.encoded_route.as_slice();
696        let is_async = self.is_async();
697
698        quote! {
699            #[derive(#sails_path ::Decode, #sails_path ::TypeInfo)]
700            #[codec(crate = #scale_codec_path )]
701            #[scale_info(crate = #scale_info_path )]
702            pub struct #params_struct_ident {
703                #(pub(super) #params_struct_members,)*
704            }
705
706            impl InvocationIo for #params_struct_ident {
707                const ROUTE: &'static [u8] = &[ #(#ctor_route_bytes),* ];
708                type Params = Self;
709                const ASYNC: bool = #is_async;
710            }
711        }
712    }
713
714    fn ctor_meta_variant(&self) -> TokenStream2 {
715        let ctor_route = Ident::new(self.route.as_str(), Span::call_site());
716        let ctor_docs_attrs = self
717            .impl_fn
718            .attrs
719            .iter()
720            .filter(|attr| attr.path().is_ident("doc"));
721        let params_struct_ident = &self.params_struct_ident;
722
723        quote! {
724            #( #ctor_docs_attrs )*
725            #ctor_route(#params_struct_ident)
726        }
727    }
728}
729
730#[cfg(test)]
731mod tests {
732    use super::*;
733    use quote::quote;
734
735    #[test]
736    fn gprogram_discovers_public_associated_functions_returning_self_or_the_type_as_ctors() {
737        let program_impl = syn::parse2(quote!(
738            impl MyProgram {
739                fn non_public_associated_func_returning_self() -> Self {}
740                fn non_public_associated_func_returning_type() -> MyProgram {}
741                fn non_public_associated_func_returning_smth() -> u32 {}
742                pub fn public_associated_func_returning_self() -> Self {}
743                pub fn public_associated_func_returning_type() -> MyProgram {}
744                pub fn public_associated_func_returning_smth() -> u32 {}
745                fn non_public_method_returning_self(&self) -> Self {}
746                fn non_public_method_returning_type(&self) -> MyProgram {}
747                fn non_public_method_returning_smth(&self) -> u32 {}
748                pub fn public_method_returning_self(&self) -> Self {}
749                pub fn public_method_returning_type(&self) -> MyProgram {}
750                pub fn public_method_returning_smth(&self) -> u32 {}
751            }
752        ))
753        .unwrap();
754
755        let sails_path = &sails_paths::sails_path_or_default(None);
756        let discovered_ctors = discover_program_ctors(&program_impl, sails_path)
757            .iter()
758            .map(|fn_builder| fn_builder.ident.to_string())
759            .collect::<Vec<_>>();
760
761        assert_eq!(discovered_ctors.len(), 2);
762        assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_self")));
763        assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_type")));
764    }
765
766    #[test]
767    fn gprogram_discovers_public_methods_with_self_ref_only_and_some_return_as_service_funcs() {
768        let program_impl = syn::parse2(quote!(
769            impl MyProgram {
770                fn non_public_associated_func_returning_smth() -> u32 {}
771                fn non_public_associated_func_returning_unit() {}
772                pub fn public_associated_func_returning_smth() -> MyProgram {}
773                pub fn public_associated_func_returning_unit() {}
774                fn non_public_method_returning_smth(&self) -> u32 {}
775                fn non_public_method_returning_unit(&self) {}
776                pub fn public_method_returning_smth(&self) -> u32 {}
777                pub fn public_method_returning_smth_with_other_params(&self, p1: u32) -> u32 {}
778                pub fn public_methos_returning_smth_and_consuming_self(self) -> u32 {}
779            }
780        ))
781        .unwrap();
782
783        let sails_path = &sails_paths::sails_path_or_default(None);
784        let discovered_services =
785            shared::discover_invocation_targets(&program_impl, service_ctor_predicate, sails_path)
786                .iter()
787                .map(|fn_builder| fn_builder.ident.to_string())
788                .collect::<Vec<_>>();
789
790        assert_eq!(discovered_services.len(), 1);
791        assert!(discovered_services.contains(&String::from("public_method_returning_smth")));
792    }
793}