Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(macros): introduce generic ServiceExposure (not generated) #672

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 161 additions & 57 deletions rs/macros/core/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Supporting functions and structures for the `gservice` macro.

#![allow(unused_variables)] // temporary
use crate::{
sails_paths,
shared::{self, Func},
Expand All @@ -27,10 +27,10 @@ use convert_case::{Case, Casing};
use parity_scale_codec::Encode;
use proc_macro2::{Span, TokenStream};
use proc_macro_error::abort;
use quote::quote;
use quote::{format_ident, quote};
use std::collections::BTreeMap;
use syn::{
parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Ident, ImplItemFn,
parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Ident, ImplItemFn, Index,
ItemImpl, Lifetime, Path, Type, Visibility,
};

Expand Down Expand Up @@ -305,77 +305,153 @@ fn generate_gservice(args: TokenStream, service_impl: ItemImpl) -> TokenStream {
.iter()
.filter(|attr| matches!(attr.path().get_ident(), Some(ident) if ident == "allow"));

quote!(
#service_impl
// V2
let trait_ident = format_ident!("{}ImplTrait", service_ident);

pub struct #exposure_type_path<#exposure_generic_args> {
#message_id_ident : #sails_path::MessageId,
#route_ident : &'static [u8],
#[cfg(not(target_arch = "wasm32"))]
#inner_ident : Box<T>, // Ensure service is not movable
#[cfg(not(target_arch = "wasm32"))]
#inner_ptr_ident : *const T, // Prevent exposure being Send + Sync
#[cfg(target_arch = "wasm32")]
#inner_ident : T,
#( #base_exposures_members )*
}
let mut trait_funcs = Vec::with_capacity(service_handlers.len());
let mut trait_funcs_impl = Vec::with_capacity(service_handlers.len());
let mut invocation_dispatches = Vec::with_capacity(service_handlers.len());
for (handler_route, (handler_fn, ..)) in &service_handlers {
let handler_allow_attrs = handler_fn
.attrs
.iter()
.filter(|attr| attr.path().is_ident("allow"));
let handler_docs_attrs = handler_fn
.attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"));

#exposure_drop_code
let handler_sig = &handler_fn.sig;
let handler_func = Func::from(handler_sig);

#( #exposure_allow_attrs )*
impl #generics #exposure_type_path< #exposure_args > #service_type_constraints {
#( #exposure_funcs )*
trait_funcs.push(quote! {
#handler_sig;
});

#( #base_exposure_accessors )*
let handler_ident = handler_func.ident();
let handler_params = handler_func.params().iter().map(|item| item.0);
let handler_await_token = handler_func.is_async().then(|| quote!(.await));
trait_funcs_impl.push({
quote!(
#( #handler_allow_attrs )*
#handler_sig {
let exposure_scope = #sails_path::gstd::services::ExposureCallScope::new2(self);
self. #inner_ident . #handler_ident (#(#handler_params),*) #handler_await_token
}
)
});

pub async fn handle(&mut self, #input_ident: &[u8]) -> (Vec<u8>, u128) {
self.try_handle( #input_ident ).await.unwrap_or_else(|| {
#unexpected_route_panic
})
}
let handler_generator = HandlerGenerator::from(handler_func.clone());
let invocation_func = handler_generator.invocation_func_v2(&meta_module_ident, &sails_path);

pub async fn try_handle(&mut self, #input_ident : &[u8]) -> Option<(Vec<u8>, u128)> {
#( #invocation_dispatches )*
#( #base_exposures_invocations )*
None
}
invocation_dispatches.push({
quote!(
#handler_route => {
#invocation_func
Some((#sails_path::Encode::encode(&(#handler_route, &result)), value))
}
)
});
}

let mut exposure_lifetimes: Punctuated<Lifetime, Comma> = Punctuated::new();
for lt in lifetimes.iter().map(|lt| {
let lt = format!("'{lt}");
Lifetime::new(&lt, Span::call_site())
}) {
exposure_lifetimes.push(lt);
}
let trait_lifetimes = if !exposure_lifetimes.is_empty() {
quote! { < #exposure_lifetimes> }
} else {
quote! {}
};

// base v2
let single_base_type = service_args.base_types().len() == 1;

let mut base_expo_types = Vec::with_capacity(service_args.base_types().len());
let mut base_types_funcs = Vec::with_capacity(service_args.base_types().len());
let mut base_types_impl = Vec::with_capacity(service_args.base_types().len());
let mut base_exposure_instantiation = Vec::with_capacity(service_args.base_types().len());

service_args.base_types().iter()
.enumerate()
.for_each(|(idx, base_type)| {
let as_base_ident = format_ident!("as_base_{}", idx);
let base_expo_type = quote! { #sails_path::gstd::services::ServiceExposure< #base_type, () > };

base_expo_types.push(quote! {
#base_expo_type
});

base_types_funcs.push(quote!{
fn #as_base_ident (&self) -> & #base_expo_type;
});

let extend_ref = if single_base_type {
quote! { &self.extend }
} else {
let base_idx = Index::from(idx);
quote! { &self.extend.#base_idx }
};

base_types_impl.push(quote!{
fn #as_base_ident (&self) -> & #base_expo_type {
#extend_ref
}
});

base_exposure_instantiation.push(quote!(
< #base_type as Clone>::clone(AsRef::< #base_type >::as_ref( &self )).expose( #message_id_ident , #route_ident )
));
});

let base_type = if single_base_type {
let single_type = &base_expo_types[0];
quote! { #single_type }
} else {
quote! { ( #( #base_expo_types ),* ) }
};
let base_inst: TokenStream = quote! { ( #( #base_exposure_instantiation ),* ) };

#( #invocation_funcs )*
let expo_type =
quote! { #sails_path::gstd::services::ServiceExposure< #service_type_path, #base_type > };

#exposure_set_event_listener_code
quote!(
#service_impl

#[allow(async_fn_in_trait)]
pub trait #trait_ident #trait_lifetimes {
#( #trait_funcs )*

#( #base_types_funcs )*
}

impl #generics #sails_path::gstd::services::Exposure for #exposure_type_path< #exposure_args > #service_type_constraints {
fn message_id(&self) -> #sails_path::MessageId {
self. #message_id_ident
}
impl #generics #trait_ident #trait_lifetimes for #expo_type #service_type_constraints {
#( #trait_funcs_impl )*

#( #base_types_impl )*
}

fn route(&self) -> &'static [u8] {
self. #route_ident
impl #generics #sails_path::gstd::services::ServiceHandle for #service_type_path #service_type_constraints {
async fn try_handle(&mut self, #input_ident : &[u8]) -> Option<(Vec<u8>, u128)> {
let mut __input = #input_ident;
let route: String = #sails_path::Decode::decode(&mut __input).ok()?;
match route.as_str() {
#( #invocation_dispatches )*
_ => None,
}
}
}

impl #generics #sails_path::gstd::services::Service for #service_type_path #service_type_constraints {
type Exposure = #exposure_type_path< #exposure_args >;
type Exposure = #expo_type;
type Extend = #base_type;

fn expose(self, #message_id_ident : #sails_path::MessageId, #route_ident : &'static [u8]) -> Self::Exposure {
#[cfg(not(target_arch = "wasm32"))]
let inner_box = Box::new(self);
#[cfg(not(target_arch = "wasm32"))]
let #inner_ident = inner_box.as_ref();
#[cfg(target_arch = "wasm32")]
let #inner_ident = &self;
Self::Exposure {
#message_id_ident ,
#route_ident ,
#( #base_exposures_instantiations )*
#[cfg(not(target_arch = "wasm32"))]
#inner_ptr_ident : inner_box.as_ref() as *const Self,
#[cfg(not(target_arch = "wasm32"))]
#inner_ident : inner_box ,
#[cfg(target_arch = "wasm32")]
#inner_ident : self,
}
let extend = #base_inst;
Self::Exposure::new(#message_id_ident, #route_ident, self, extend)
}
}

Expand Down Expand Up @@ -588,4 +664,32 @@ impl<'a> HandlerGenerator<'a> {
}
)
}

fn invocation_func_v2(&self, meta_module_ident: &Ident, sails_path: &Path) -> TokenStream {
let params_struct_ident = self.params_struct_ident();
let handler_func_ident = self.handler_func_ident();
let handler_func_params = self.handler.params().iter().map(|item| {
let param_ident = item.0;
quote!(request.#param_ident)
});

let result_type = self.result_type();
let await_token = self.handler.is_async().then(|| quote!(.await));
let handle_token = if self.reply_with_value() {
quote! {
let command_reply: CommandReply<#result_type> = self.#handler_func_ident(#(#handler_func_params),*)#await_token.into();
let (result, value) = command_reply.to_tuple();
}
} else {
quote! {
let result = self.#handler_func_ident(#(#handler_func_params),*)#await_token;
let value = 0u128;
}
};

quote!(
let request: #meta_module_ident::#params_struct_ident = #sails_path::Decode::decode(&mut __input).expect("Failed to decode request");
#handle_token
)
}
}
Loading