use crate::{
export, sails_paths,
shared::{self, FnBuilder},
};
use args::ProgramArgs;
use proc_macro_error::abort;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use std::{
collections::BTreeMap,
env,
ops::{Deref, DerefMut},
};
use syn::{
Generics, Ident, ImplItem, ImplItemFn, ItemImpl, Path, PathArguments, Receiver, ReturnType,
Type, TypePath, Visibility, WhereClause, parse_quote, spanned::Spanned,
};
mod args;
#[cfg(feature = "ethexe")]
mod ethexe;
static mut PROGRAM_SPANS: BTreeMap<String, Span> = BTreeMap::new();
pub fn gprogram(args: TokenStream2, program_impl_tokens: TokenStream2) -> TokenStream2 {
let program_impl = parse_gprogram_impl(program_impl_tokens);
ensure_single_gprogram(&program_impl);
let args = parse_args(args);
gen_gprogram_impl(program_impl, args)
}
#[doc(hidden)]
pub fn __gprogram_internal(args: TokenStream2, program_impl_tokens: TokenStream2) -> TokenStream2 {
let program_impl = parse_gprogram_impl(program_impl_tokens);
let args = parse_args(args);
gen_gprogram_impl(program_impl, args)
}
fn parse_args(args: TokenStream2) -> ProgramArgs {
syn::parse2(args).unwrap_or_else(|err| {
abort!(
err.span(),
"failed to parse `program` attribute arguments: {}",
err
)
})
}
fn parse_gprogram_impl(program_impl_tokens: TokenStream2) -> ItemImpl {
syn::parse2(program_impl_tokens).unwrap_or_else(|err| {
abort!(
err.span(),
"`program` attribute can be applied to impls only: {}",
err
)
})
}
#[allow(static_mut_refs)]
fn ensure_single_gprogram(program_impl: &ItemImpl) {
let crate_name = env::var("CARGO_CRATE_NAME").unwrap_or("crate".to_string());
if unsafe { PROGRAM_SPANS.get(&crate_name) }.is_some() {
abort!(
program_impl,
"multiple `program` attributes are not allowed"
)
}
unsafe { PROGRAM_SPANS.insert(crate_name, program_impl.span()) };
}
struct ProgramBuilder {
program_impl: ItemImpl,
program_args: ProgramArgs,
type_constraints: Option<WhereClause>,
has_default_ctor_only: bool,
}
impl ProgramBuilder {
fn new(program_impl: ItemImpl, program_args: ProgramArgs) -> Self {
let mut program_impl = program_impl;
let type_constraints = program_impl.generics.where_clause.take();
let has_default_ctor_only = ensure_default_program_ctor(&mut program_impl);
Self {
program_impl,
program_args,
type_constraints,
has_default_ctor_only,
}
}
fn sails_path(&self) -> &Path {
self.program_args.sails_path()
}
fn impl_type(&self) -> (&TypePath, &PathArguments, &Ident) {
shared::impl_type_refs(self.program_impl.self_ty.as_ref())
}
fn impl_constraints(&self) -> (&Generics, Option<&WhereClause>) {
(&self.program_impl.generics, self.type_constraints.as_ref())
}
fn program_ctors(&self) -> Vec<FnBuilder<'_>> {
discover_program_ctors(&self.program_impl, self.sails_path())
}
fn handle_reply_fn(&mut self) -> Option<&mut ImplItemFn> {
let mut fn_iter = self.program_impl.items.iter_mut().filter_map(|item| {
if let ImplItem::Fn(fn_item) = item
&& has_handle_reply_attr(fn_item) {
fn_item
.attrs
.retain(|attr| !attr.path().is_ident("handle_reply"));
if handle_reply_predicate(fn_item) {
return Some(fn_item);
} else {
abort!(
fn_item,
"`handle_reply` function must be private and have a single `&self` argument and no return type"
);
}
}
None
});
let handle_reply_fn = fn_iter.next();
if let Some(duplicate) = fn_iter.next() {
abort!(duplicate, "only one `handle_reply` function is allowed");
}
handle_reply_fn
}
#[cfg(feature = "ethexe")]
fn service_ctors(&self) -> Vec<FnBuilder<'_>> {
shared::discover_invocation_targets(self, service_ctor_predicate, self.sails_path())
}
}
impl ProgramBuilder {
fn wire_up_service_exposure(
&mut self,
program_ident: &Ident,
) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) {
let mut services_meta = Vec::new();
let mut meta_asyncness = Vec::new();
let mut routes = BTreeMap::new();
#[allow(unused_mut)]
let mut solidity_dispatchers: Vec<TokenStream2> = Vec::new();
let has_async_ctor = self
.program_ctors()
.iter()
.any(|fn_builder| fn_builder.is_async());
if has_async_ctor {
meta_asyncness.push(quote!(true));
}
let mut modifications = Vec::new();
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() {
if let ImplItem::Fn(fn_item) = impl_item {
if is_mut_service_ctor(fn_item) {
abort!(
fn_item,
"service constructor must take `&self`, not `&mut self`: a `&mut self` \
factory can leak an exclusive borrow of program state across an `.await`"
);
}
if !service_ctor_predicate(fn_item) {
continue;
}
let mut invocation_export = shared::invocation_export_or_default(fn_item);
let entry_id = routes.len() as u16;
#[cfg(feature = "ethexe")]
{
use convert_case::{Case, Casing};
let camel_case_route = invocation_export.route.to_case(Case::Camel);
shared::validation::validate_identifier(
&camel_case_route,
fn_item.sig.ident.span(),
"Exposed Service",
);
}
if let Some(duplicate) = routes.insert(
invocation_export.route.clone(),
fn_item.sig.ident.to_string(),
) {
abort!(
invocation_export.span,
"`export` attribute conflicts with one already assigned to '{}'",
duplicate
);
}
invocation_export.export = true;
let fn_builder =
FnBuilder::new(invocation_export, entry_id, fn_item, self.sails_path());
let original_service_ctor_fn = fn_builder.original_service_ctor_fn();
let wrapping_service_ctor_fn =
fn_builder.wrapping_service_ctor_fn(&original_service_ctor_fn.sig.ident);
services_meta.push(fn_builder.service_meta());
if !has_async_ctor {
meta_asyncness.push(fn_builder.service_meta_asyncness());
}
#[cfg(feature = "ethexe")]
if fn_builder.has_ethabi_codec() {
solidity_dispatchers.push(fn_builder.sol_service_invocation());
}
let service_type = fn_builder.result_type.clone();
let service_ctor_ident = fn_builder.ident.clone();
services_count_data.push(service_type.clone());
services_ids_data.push(service_type.clone());
if fn_builder.has_scale_codec() {
route_dispatch_data.push((
fn_builder.service_route_idx(),
service_ctor_ident,
service_type,
));
}
modifications.push((idx, original_service_ctor_fn, wrapping_service_ctor_fn));
}
}
for (idx, original_service_ctor_fn, wrapping_service_ctor_fn) in modifications {
self.program_impl.items[idx] = ImplItem::Fn(original_service_ctor_fn);
self.program_impl
.items
.push(ImplItem::Fn(wrapping_service_ctor_fn));
}
if meta_asyncness.is_empty() {
meta_asyncness.push(quote!(false));
}
let handle_reply_fn = self.handle_reply_fn().map(|item_fn| {
let handle_reply_fn_ident = &item_fn.sig.ident;
quote! {
let program_ref = unsafe { #program_ident.as_ref() }.expect("Program not initialized");
program_ref.#handle_reply_fn_ident();
}
})
.unwrap_or_default();
let handle_signal_fn = self
.program_args
.handle_signal()
.map(|handle_signal_path| quote!( #handle_signal_path ();))
.unwrap_or_default();
let sails_path = self.sails_path();
let (program_type_path, _program_type_args, _) = self.impl_type();
let (generics, program_type_constraints) = self.impl_constraints();
let program_meta_impl = quote! {
impl #generics #sails_path::meta::ProgramMeta for #program_type_path #program_type_constraints {
type ConstructorsMeta = meta_in_program::ConstructorsMeta;
const SERVICES: &'static [(&'static str, #sails_path::meta::AnyServiceMeta)] = &[
#(#services_meta),*
];
const ASYNC: bool = #( #meta_asyncness )||*;
}
};
let services_count_expr = {
let count_exprs = services_count_data.iter().map(|service_type| {
quote! {
+ #sails_path::meta::count_base_services::<#service_type>()
}
});
let base_count = services_count_data.len();
quote! {
const SERVICES_COUNT: usize = #base_count #(#count_exprs)*;
}
};
let services_ids_expr = {
let ids_exprs = services_ids_data.iter().map(|service_type| {
quote! {
#sails_path::meta::BaseServiceMeta::new::<#service_type>(""),
}
});
quote! {
const INTERFACE_IDS: &'static [(#sails_path::meta::InterfaceId, u8)] =
&#sails_path::meta::interface_ids::<SERVICES_COUNT>(&[
#(#ids_exprs)*
]);
}
};
let route_dispatches = route_dispatch_data
.iter()
.map(|(route_idx, service_ctor_ident, service_type)| {
quote! {
#route_idx => {
let svc = program_ref.#service_ctor_ident();
#sails_path::service_route_dispatch!(
svc: #service_type,
interface_id = interface_id,
entry_id = entry_id,
input = &input[header_len..],
);
}
}
})
.collect::<Vec<_>>();
let solidity_main = self.sol_main(solidity_dispatchers.as_slice());
let payable = self.program_args.payable().then(|| {
quote! {
if gstd::msg::value() > 0 && gstd::msg::size() == 0 {
return;
}
}
});
let main_fn = quote!(
#services_count_expr
#services_ids_expr
#[unsafe(no_mangle)]
extern "C" fn handle() {
#payable
let mut input = gstd::msg::load_bytes().expect("Failed to read input");
let program_ref = unsafe { #program_ident.as_ref() }.expect("Program not initialized");
#solidity_main
if let Ok(header) = <#sails_path::meta::SailsMessageHeader as #sails_path::Decode>::decode(&mut input.as_slice()) {
let header_len = header.hlen().inner() as usize;
let (interface_id, route_id, entry_id) = header
.try_match_interfaces(INTERFACE_IDS)
.expect("Failed to find matching service")
.into_inner();
match route_id {
#(#route_dispatches)*
_ => gstd::unknown_input_panic("Unknown route_id", &[route_id])
}
}
}
);
let handle_reply_fn = quote! {
#[unsafe(no_mangle)]
extern "C" fn handle_reply() {
use #sails_path::meta::ProgramMeta;
if #program_type_path::ASYNC {
gstd::handle_reply_with_hook();
}
#handle_reply_fn
}
};
#[cfg(not(feature = "ethexe"))]
let handle_signal_fn = quote! {
#[unsafe(no_mangle)]
extern "C" fn handle_signal() {
use #sails_path::meta::ProgramMeta;
if #program_type_path::ASYNC {
gstd::handle_signal();
}
#handle_signal_fn
}
};
(
program_meta_impl,
main_fn,
handle_reply_fn,
handle_signal_fn,
)
}
fn generate_init(&self, program_ident: &Ident) -> (TokenStream2, TokenStream2) {
let sails_path = self.sails_path();
let type_info_path = sails_paths::type_info_path(sails_path);
let (program_type_path, ..) = self.impl_type();
let input_ident = Ident::new("input", Span::call_site());
let program_ctors = self.program_ctors();
let mut ctor_dispatches = Vec::with_capacity(program_ctors.len() + 1);
let mut ctor_params_structs = Vec::with_capacity(program_ctors.len());
let mut ctor_meta_variants = Vec::with_capacity(program_ctors.len());
for fn_builder in &program_ctors {
if fn_builder.has_scale_codec() {
ctor_dispatches.push(fn_builder.ctor_branch_impl(
program_type_path,
&input_ident,
program_ident,
));
}
ctor_params_structs.push(fn_builder.ctor_params_struct());
ctor_meta_variants.push(fn_builder.ctor_meta_variant());
}
let solidity_init = self.sol_init(&input_ident);
let sails_init = quote! {
if let Ok(header) = <#sails_path::meta::SailsMessageHeader as #sails_path::Decode>::decode(&mut #input_ident) {
if header.interface_id() != #sails_path::meta::InterfaceId::zero() {
#sails_path::gstd::unknown_input_panic("Non zero ctor interface_id", header.to_bytes().as_slice());
}
match header.entry_id() {
#(#ctor_dispatches)*
_ => #sails_path::gstd::unknown_input_panic("Unexpected ctor entry_id", input),
}
}
};
let empty_input_guard = if self.has_default_ctor_only {
let fn_builder = program_ctors
.first()
.expect("default ctor must exist when has_default_ctor_only is true");
let ctor_ident = fn_builder.ident;
let params_struct_ident = &fn_builder.params_struct_ident;
quote! {
if #input_ident.is_empty() {
#sails_path::program_ctor!(
#program_ident = #program_type_path :: #ctor_ident (),
params_struct = meta_in_program::#params_struct_ident
);
return;
}
}
} else {
quote!()
};
let init_fn = quote! {
#[unsafe(no_mangle)]
extern "C" fn init() {
let mut #input_ident: &[u8] = &gstd::msg::load_bytes().expect("Failed to read input");
#empty_input_guard
#solidity_init
#sails_init
}
};
let meta_in_program = quote! {
mod meta_in_program {
use super::*;
#( #ctor_params_structs )*
#[derive(#sails_path::TypeInfo)]
#[type_info(crate = #type_info_path)]
pub enum ConstructorsMeta {
#( #ctor_meta_variants ),*
}
}
};
(meta_in_program, init_fn)
}
}
#[cfg(not(feature = "ethexe"))]
impl ProgramBuilder {
fn program_signature_impl(&self) -> TokenStream2 {
quote!()
}
fn match_ctor_impl(&self, _program_ident: &Ident) -> TokenStream2 {
quote!()
}
fn program_const(&self) -> TokenStream2 {
quote!()
}
fn sol_init(&self, _input_ident: &Ident) -> TokenStream2 {
quote!()
}
fn sol_main(&self, _solidity_dispatchers: &[TokenStream2]) -> TokenStream2 {
quote!()
}
}
impl Deref for ProgramBuilder {
type Target = ItemImpl;
fn deref(&self) -> &Self::Target {
&self.program_impl
}
}
impl DerefMut for ProgramBuilder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.program_impl
}
}
fn gen_gprogram_impl(program_impl: ItemImpl, program_args: ProgramArgs) -> TokenStream2 {
let mut program_builder = ProgramBuilder::new(program_impl, program_args);
let sails_path = program_builder.sails_path().clone();
let program_ident = Ident::new("PROGRAM", Span::call_site());
let program_signature_impl = program_builder.program_signature_impl();
let match_ctor_impl = program_builder.match_ctor_impl(&program_ident);
let program_const = program_builder.program_const();
let (program_meta_impl, main_fn, handle_reply_fn, handle_signal_fn) =
program_builder.wire_up_service_exposure(&program_ident);
let (meta_in_program, init_fn) = program_builder.generate_init(&program_ident);
let (program_type_path, ..) = program_builder.impl_type();
let program_impl = program_builder.deref();
quote!(
#program_impl
#program_meta_impl
#meta_in_program
#program_signature_impl
#program_const
#[cfg(target_arch = "wasm32")]
pub mod wasm {
use super::*;
use #sails_path::{gstd, hex, prelude::*};
static mut #program_ident: Option<#program_type_path> = None;
#init_fn
#match_ctor_impl
#main_fn
#handle_reply_fn
#handle_signal_fn
}
)
}
fn ensure_default_program_ctor(program_impl: &mut ItemImpl) -> bool {
let sails_path = &sails_paths::sails_path_or_default(None);
if discover_program_ctors(program_impl, sails_path).is_empty() {
program_impl.items.push(ImplItem::Fn(parse_quote!(
pub fn create() -> Self {
Default::default()
}
)));
return true;
}
false
}
fn discover_program_ctors<'a>(
program_impl: &'a ItemImpl,
sails_path: &'a Path,
) -> Vec<FnBuilder<'a>> {
let self_type_path: TypePath = parse_quote!(Self);
let (program_type_path, _, _) = shared::impl_type_refs(program_impl.self_ty.as_ref());
let mut vec = shared::discover_invocation_targets(
program_impl,
|fn_item| program_ctor_predicate(fn_item, &self_type_path, program_type_path),
sails_path,
);
vec.sort_by_key(|f| f.route.to_lowercase());
vec.iter_mut()
.enumerate()
.for_each(|(idx, f)| f.entry_id = idx as u16);
#[cfg(feature = "ethexe")]
{
for ctor in &vec {
shared::validation::validate_identifier(
&ctor.route_camel_case(),
ctor.ident.span(),
"Program constructor",
);
}
}
vec
}
fn program_ctor_predicate(
fn_item: &ImplItemFn,
self_type_path: &TypePath,
program_type_path: &TypePath,
) -> bool {
if matches!(fn_item.vis, Visibility::Public(_))
&& fn_item.sig.receiver().is_none()
&& let ReturnType::Type(_, output_type) = &fn_item.sig.output
&& let Type::Path(output_type_path) = output_type.as_ref()
{
if output_type_path == self_type_path || output_type_path == program_type_path {
return true;
}
if let Some((Type::Path(output_type_path), _)) =
shared::extract_result_types(output_type_path)
&& (output_type_path == self_type_path || output_type_path == program_type_path)
{
return true;
}
}
false
}
fn service_ctor_receiver(fn_item: &ImplItemFn) -> Option<&Receiver> {
fn_item.sig.receiver().filter(|receiver| {
matches!(fn_item.vis, Visibility::Public(_))
&& fn_item.sig.inputs.len() == 1
&& !matches!(fn_item.sig.output, ReturnType::Default)
&& receiver.reference.is_some()
})
}
fn service_ctor_predicate(fn_item: &ImplItemFn) -> bool {
service_ctor_receiver(fn_item).is_some_and(|receiver| receiver.mutability.is_none())
}
fn is_mut_service_ctor(fn_item: &ImplItemFn) -> bool {
service_ctor_receiver(fn_item).is_some_and(|receiver| receiver.mutability.is_some())
}
fn has_handle_reply_attr(fn_item: &ImplItemFn) -> bool {
fn_item
.attrs
.iter()
.any(|attr| attr.path().is_ident("handle_reply"))
}
fn handle_reply_predicate(fn_item: &ImplItemFn) -> bool {
matches!(fn_item.vis, Visibility::Inherited)
&& matches!(
fn_item.sig.receiver(),
Some(Receiver {
mutability: None,
reference: Some(_),
..
})
)
&& fn_item.sig.inputs.len() == 1
&& matches!(fn_item.sig.output, ReturnType::Default)
}
impl FnBuilder<'_> {
fn service_route_idx(&self) -> u8 {
if self.entry_id >= u8::MAX as u16 {
abort!(self.ident, "too many services; maximum is 255");
}
(self.entry_id + 1) as u8
}
fn service_meta(&self) -> TokenStream2 {
let sails_path = self.sails_path;
let route = &self.route;
let service_type = &self.result_type;
quote!(
( #route , < #service_type as #sails_path::meta::ServiceMeta >::META )
)
}
fn service_meta_asyncness(&self) -> TokenStream2 {
let sails_path = self.sails_path;
let service_type = &self.result_type;
quote!(<#service_type as #sails_path::meta::ServiceMeta>::ASYNC )
}
fn original_service_ctor_fn(&self) -> ImplItemFn {
let mut original_service_ctor_fn = self.impl_fn.clone();
let original_service_ctor_fn_ident = Ident::new(
&format!("__{}", original_service_ctor_fn.sig.ident),
original_service_ctor_fn.sig.ident.span(),
);
original_service_ctor_fn.attrs.clear();
original_service_ctor_fn.vis = Visibility::Inherited;
original_service_ctor_fn.sig.ident = original_service_ctor_fn_ident;
original_service_ctor_fn
}
fn wrapping_service_ctor_fn(&self, original_service_ctor_fn_ident: &Ident) -> ImplItemFn {
let sails_path = self.sails_path;
let service_type = &self.result_type;
let route_idx = self.service_route_idx();
let unwrap_token = self.error_type.is_some().then(|| quote!(.unwrap()));
let mut wrapping_service_ctor_fn = self.impl_fn.clone();
wrapping_service_ctor_fn
.attrs
.retain(|attr| export::parse_attr(attr).is_none());
wrapping_service_ctor_fn.sig.output = parse_quote!(
-> < #service_type as #sails_path::gstd::services::Service>::Exposure
);
wrapping_service_ctor_fn.block = parse_quote!({
let service = self. #original_service_ctor_fn_ident () #unwrap_token;
let exposure = < #service_type as #sails_path::gstd::services::Service>::expose(
service,
#route_idx,
);
exposure
});
wrapping_service_ctor_fn
}
fn ctor_branch_impl(
&self,
program_type_path: &TypePath,
input_ident: &Ident,
program_ident: &Ident,
) -> TokenStream2 {
let sails_path = self.sails_path;
let handler_ident = self.ident;
let entry_id = self.entry_id;
let handler_args = self.params_idents();
let handler_types = self.params_types();
let payable_check = {
#[cfg(feature = "ethexe")]
{
self.payable_check()
}
#[cfg(not(feature = "ethexe"))]
{
quote!()
}
};
let await_token = self.is_async().then(|| quote!(.await));
let unwrap_token = self.error_type.is_some().then(|| quote!(.unwrap()));
let raw_call = quote! { #program_type_path :: #handler_ident (#(#handler_args),*) #await_token #unwrap_token };
let params_struct_ident = &self.params_struct_ident;
let ctor_call_impl = quote! {
#sails_path::program_ctor!(
#program_ident = #raw_call,
params_struct = meta_in_program::#params_struct_ident
)
};
quote!(
#entry_id => {
let (#(#handler_args),*): (#(#handler_types),*) = #sails_path::Decode::decode(&mut #input_ident)
.unwrap_or_else(|_| #sails_path::gstd::unknown_input_panic("Unknown request", #input_ident));
#payable_check
#ctor_call_impl
}
)
}
fn ctor_params_struct(&self) -> TokenStream2 {
let sails_path = self.sails_path;
let params_struct_ident = &self.params_struct_ident;
let params_struct_members = self.params().map(|(ident, ty)| quote!(#ident: #ty));
let entry_id = &self.entry_id;
let decode_disabled = (!self.has_scale_codec()).then(|| quote!(decode = false,));
quote! {
#sails_path::invocation_io!(
pub struct #params_struct_ident {
#(pub(super) #params_struct_members,)*
},
entry_id = #entry_id,
#decode_disabled
);
}
}
fn ctor_meta_variant(&self) -> TokenStream2 {
let ctor_route = Ident::new(self.route.as_str(), Span::call_site());
let ctor_docs_attrs = self
.impl_fn
.attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"));
let params_struct_ident = &self.params_struct_ident;
#[cfg(feature = "ethexe")]
let payable_ann = self.payable.then(|| quote!(#[annotate(payable)]));
#[cfg(not(feature = "ethexe"))]
let payable_ann: Option<TokenStream2> = None;
#[cfg(feature = "ethexe")]
let codec_ann: Option<TokenStream2> =
match (self.has_scale_codec(), self.has_ethabi_codec()) {
(true, false) => Some(quote!(#[annotate(codec = "scale")])),
(false, true) => Some(quote!(#[annotate(codec = "ethabi")])),
_ => None,
};
#[cfg(not(feature = "ethexe"))]
let codec_ann: Option<TokenStream2> = None;
if let Some(err_ty) = &self.error_type {
let err_ty = shared::replace_any_lifetime_with_static(err_ty.clone());
quote! {
#( #ctor_docs_attrs )*
#payable_ann
#codec_ann
#ctor_route(#params_struct_ident, #err_ty)
}
} else {
quote! {
#( #ctor_docs_attrs )*
#payable_ann
#codec_ann
#ctor_route(#params_struct_ident)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn gprogram_discovers_public_associated_functions_returning_self_or_the_type_as_ctors() {
let program_impl = syn::parse2(quote!(
impl MyProgram {
fn non_public_associated_func_returning_self() -> Self {}
fn non_public_associated_func_returning_type() -> MyProgram {}
fn non_public_associated_func_returning_smth() -> u32 {}
pub fn public_associated_func_returning_self() -> Self {}
pub fn public_associated_func_returning_type() -> MyProgram {}
pub fn public_associated_func_returning_smth() -> u32 {}
fn non_public_method_returning_self(&self) -> Self {}
fn non_public_method_returning_type(&self) -> MyProgram {}
fn non_public_method_returning_smth(&self) -> u32 {}
pub fn public_method_returning_self(&self) -> Self {}
pub fn public_method_returning_type(&self) -> MyProgram {}
pub fn public_method_returning_smth(&self) -> u32 {}
}
))
.unwrap();
let sails_path = &sails_paths::sails_path_or_default(None);
let discovered_ctors = discover_program_ctors(&program_impl, sails_path)
.iter()
.map(|fn_builder| fn_builder.ident.to_string())
.collect::<Vec<_>>();
assert_eq!(discovered_ctors.len(), 2);
assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_self")));
assert!(discovered_ctors.contains(&String::from("public_associated_func_returning_type")));
}
#[test]
fn gprogram_discovers_public_methods_with_self_ref_only_and_some_return_as_service_funcs() {
let program_impl = syn::parse2(quote!(
impl MyProgram {
fn non_public_associated_func_returning_smth() -> u32 {}
fn non_public_associated_func_returning_unit() {}
pub fn public_associated_func_returning_smth() -> MyProgram {}
pub fn public_associated_func_returning_unit() {}
fn non_public_method_returning_smth(&self) -> u32 {}
fn non_public_method_returning_unit(&self) {}
pub fn public_method_returning_smth(&self) -> u32 {}
pub fn public_method_returning_smth_with_other_params(&self, p1: u32) -> u32 {}
pub fn public_methos_returning_smth_and_consuming_self(self) -> u32 {}
}
))
.unwrap();
let sails_path = &sails_paths::sails_path_or_default(None);
let discovered_services =
shared::discover_invocation_targets(&program_impl, service_ctor_predicate, sails_path)
.iter()
.map(|fn_builder| fn_builder.ident.to_string())
.collect::<Vec<_>>();
assert_eq!(discovered_services.len(), 1);
assert!(discovered_services.contains(&String::from("public_method_returning_smth")));
}
#[test]
fn is_mut_service_ctor_detects_only_public_mut_self_factories() {
let program_impl: ItemImpl = syn::parse2(quote!(
impl MyProgram {
pub fn mut_factory(&mut self) -> MyService {}
pub fn shared_factory(&self) -> MyService {}
fn non_public_mut_factory(&mut self) -> MyService {}
pub fn mut_factory_returning_unit(&mut self) {}
pub fn mut_factory_with_other_params(&mut self, p1: u32) -> MyService {}
}
))
.unwrap();
let detected = program_impl
.items
.iter()
.filter_map(|item| match item {
ImplItem::Fn(fn_item) if is_mut_service_ctor(fn_item) => {
Some(fn_item.sig.ident.to_string())
}
_ => None,
})
.collect::<Vec<_>>();
assert_eq!(detected, vec![String::from("mut_factory")]);
}
}